Skip to content

Commit 6921e73

Browse files
authored
bpo-33001: Prevent buffer overrun in os.symlink (GH-5989)
1 parent 4c19b95 commit 6921e73

File tree

3 files changed

+73
-28
lines changed

3 files changed

+73
-28
lines changed

Lib/test/test_os.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2164,6 +2164,40 @@ def test_29248(self):
21642164
target = os.readlink(r'C:\Users\All Users')
21652165
self.assertTrue(os.path.samefile(target, r'C:\ProgramData'))
21662166

2167+
def test_buffer_overflow(self):
2168+
# Older versions would have a buffer overflow when detecting
2169+
# whether a link source was a directory. This test ensures we
2170+
# no longer crash, but does not otherwise validate the behavior
2171+
segment = 'X' * 27
2172+
path = os.path.join(*[segment] * 10)
2173+
test_cases = [
2174+
# overflow with absolute src
2175+
('\\' + path, segment),
2176+
# overflow dest with relative src
2177+
(segment, path),
2178+
# overflow when joining src
2179+
(path[:180], path[:180]),
2180+
]
2181+
for src, dest in test_cases:
2182+
try:
2183+
os.symlink(src, dest)
2184+
except FileNotFoundError:
2185+
pass
2186+
else:
2187+
try:
2188+
os.remove(dest)
2189+
except OSError:
2190+
pass
2191+
# Also test with bytes, since that is a separate code path.
2192+
try:
2193+
os.symlink(os.fsencode(src), os.fsencode(dest))
2194+
except FileNotFoundError:
2195+
pass
2196+
else:
2197+
try:
2198+
os.remove(dest)
2199+
except OSError:
2200+
pass
21672201

21682202
@unittest.skipUnless(sys.platform == "win32", "Win32 specific tests")
21692203
class Win32JunctionTests(unittest.TestCase):
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Minimal fix to prevent buffer overrun in os.symlink on Windows

Modules/posixmodule.c

Lines changed: 38 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -7471,7 +7471,7 @@ win_readlink(PyObject *self, PyObject *args, PyObject *kwargs)
74717471
#if defined(MS_WINDOWS)
74727472

74737473
/* Grab CreateSymbolicLinkW dynamically from kernel32 */
7474-
static DWORD (CALLBACK *Py_CreateSymbolicLinkW)(LPCWSTR, LPCWSTR, DWORD) = NULL;
7474+
static BOOLEAN (CALLBACK *Py_CreateSymbolicLinkW)(LPCWSTR, LPCWSTR, DWORD) = NULL;
74757475

74767476
static int
74777477
check_CreateSymbolicLink(void)
@@ -7486,47 +7486,51 @@ check_CreateSymbolicLink(void)
74867486
return Py_CreateSymbolicLinkW != NULL;
74877487
}
74887488

7489-
/* Remove the last portion of the path */
7490-
static void
7489+
/* Remove the last portion of the path - return 0 on success */
7490+
static int
74917491
_dirnameW(WCHAR *path)
74927492
{
74937493
WCHAR *ptr;
7494+
size_t length = wcsnlen_s(path, MAX_PATH);
7495+
if (length == MAX_PATH) {
7496+
return -1;
7497+
}
74947498

74957499
/* walk the path from the end until a backslash is encountered */
7496-
for(ptr = path + wcslen(path); ptr != path; ptr--) {
7497-
if (*ptr == L'\\' || *ptr == L'/')
7500+
for(ptr = path + length; ptr != path; ptr--) {
7501+
if (*ptr == L'\\' || *ptr == L'/') {
74987502
break;
7503+
}
74997504
}
75007505
*ptr = 0;
7506+
return 0;
75017507
}
75027508

75037509
/* Is this path absolute? */
75047510
static int
75057511
_is_absW(const WCHAR *path)
75067512
{
7507-
return path[0] == L'\\' || path[0] == L'/' || path[1] == L':';
7508-
7513+
return path[0] == L'\\' || path[0] == L'/' ||
7514+
(path[0] && path[1] == L':');
75097515
}
75107516

7511-
/* join root and rest with a backslash */
7512-
static void
7517+
/* join root and rest with a backslash - return 0 on success */
7518+
static int
75137519
_joinW(WCHAR *dest_path, const WCHAR *root, const WCHAR *rest)
75147520
{
7515-
size_t root_len;
7516-
75177521
if (_is_absW(rest)) {
7518-
wcscpy(dest_path, rest);
7519-
return;
7522+
return wcscpy_s(dest_path, MAX_PATH, rest);
75207523
}
75217524

7522-
root_len = wcslen(root);
7525+
if (wcscpy_s(dest_path, MAX_PATH, root)) {
7526+
return -1;
7527+
}
75237528

7524-
wcscpy(dest_path, root);
7525-
if(root_len) {
7526-
dest_path[root_len] = L'\\';
7527-
root_len++;
7529+
if (dest_path[0] && wcscat_s(dest_path, MAX_PATH, L"\\")) {
7530+
return -1;
75287531
}
7529-
wcscpy(dest_path+root_len, rest);
7532+
7533+
return wcscat_s(dest_path, MAX_PATH, rest);
75307534
}
75317535

75327536
/* Return True if the path at src relative to dest is a directory */
@@ -7538,10 +7542,14 @@ _check_dirW(LPCWSTR src, LPCWSTR dest)
75387542
WCHAR src_resolved[MAX_PATH] = L"";
75397543

75407544
/* dest_parent = os.path.dirname(dest) */
7541-
wcscpy(dest_parent, dest);
7542-
_dirnameW(dest_parent);
7545+
if (wcscpy_s(dest_parent, MAX_PATH, dest) ||
7546+
_dirnameW(dest_parent)) {
7547+
return 0;
7548+
}
75437549
/* src_resolved = os.path.join(dest_parent, src) */
7544-
_joinW(src_resolved, dest_parent, src);
7550+
if (_joinW(src_resolved, dest_parent, src)) {
7551+
return 0;
7552+
}
75457553
return (
75467554
GetFileAttributesExW(src_resolved, GetFileExInfoStandard, &src_info)
75477555
&& src_info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY
@@ -7597,26 +7605,28 @@ os_symlink_impl(PyObject *module, path_t *src, path_t *dst,
75977605
}
75987606
#endif
75997607

7600-
if ((src->narrow && dst->wide) || (src->wide && dst->narrow)) {
7601-
PyErr_SetString(PyExc_ValueError,
7602-
"symlink: src and dst must be the same type");
7603-
return NULL;
7604-
}
7605-
76067608
#ifdef MS_WINDOWS
76077609

76087610
Py_BEGIN_ALLOW_THREADS
7611+
_Py_BEGIN_SUPPRESS_IPH
76097612
/* if src is a directory, ensure target_is_directory==1 */
76107613
target_is_directory |= _check_dirW(src->wide, dst->wide);
76117614
result = Py_CreateSymbolicLinkW(dst->wide, src->wide,
76127615
target_is_directory);
7616+
_Py_END_SUPPRESS_IPH
76137617
Py_END_ALLOW_THREADS
76147618

76157619
if (!result)
76167620
return path_error2(src, dst);
76177621

76187622
#else
76197623

7624+
if ((src->narrow && dst->wide) || (src->wide && dst->narrow)) {
7625+
PyErr_SetString(PyExc_ValueError,
7626+
"symlink: src and dst must be the same type");
7627+
return NULL;
7628+
}
7629+
76207630
Py_BEGIN_ALLOW_THREADS
76217631
#if HAVE_SYMLINKAT
76227632
if (dir_fd != DEFAULT_DIR_FD)

0 commit comments

Comments
 (0)