Skip to content

Commit baa4507

Browse files
authored
[3.6] bpo-33001: Prevent buffer overrun in os.symlink (GH-5989) (GH-5990)
1 parent 6935a51 commit baa4507

File tree

3 files changed

+74
-28
lines changed

3 files changed

+74
-28
lines changed

Lib/test/test_os.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2170,6 +2170,41 @@ def test_29248(self):
21702170
target = os.readlink(r'C:\Users\All Users')
21712171
self.assertTrue(os.path.samefile(target, r'C:\ProgramData'))
21722172

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

21742209
@unittest.skipUnless(sys.platform == "win32", "Win32 specific tests")
21752210
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
@@ -7144,7 +7144,7 @@ win_readlink(PyObject *self, PyObject *args, PyObject *kwargs)
71447144
#if defined(MS_WINDOWS)
71457145

71467146
/* Grab CreateSymbolicLinkW dynamically from kernel32 */
7147-
static DWORD (CALLBACK *Py_CreateSymbolicLinkW)(LPCWSTR, LPCWSTR, DWORD) = NULL;
7147+
static BOOLEAN (CALLBACK *Py_CreateSymbolicLinkW)(LPCWSTR, LPCWSTR, DWORD) = NULL;
71487148

71497149
static int
71507150
check_CreateSymbolicLink(void)
@@ -7159,47 +7159,51 @@ check_CreateSymbolicLink(void)
71597159
return Py_CreateSymbolicLinkW != NULL;
71607160
}
71617161

7162-
/* Remove the last portion of the path */
7163-
static void
7162+
/* Remove the last portion of the path - return 0 on success */
7163+
static int
71647164
_dirnameW(WCHAR *path)
71657165
{
71667166
WCHAR *ptr;
7167+
size_t length = wcsnlen_s(path, MAX_PATH);
7168+
if (length == MAX_PATH) {
7169+
return -1;
7170+
}
71677171

71687172
/* walk the path from the end until a backslash is encountered */
7169-
for(ptr = path + wcslen(path); ptr != path; ptr--) {
7170-
if (*ptr == L'\\' || *ptr == L'/')
7173+
for(ptr = path + length; ptr != path; ptr--) {
7174+
if (*ptr == L'\\' || *ptr == L'/') {
71717175
break;
7176+
}
71727177
}
71737178
*ptr = 0;
7179+
return 0;
71747180
}
71757181

71767182
/* Is this path absolute? */
71777183
static int
71787184
_is_absW(const WCHAR *path)
71797185
{
7180-
return path[0] == L'\\' || path[0] == L'/' || path[1] == L':';
7181-
7186+
return path[0] == L'\\' || path[0] == L'/' ||
7187+
(path[0] && path[1] == L':');
71827188
}
71837189

7184-
/* join root and rest with a backslash */
7185-
static void
7190+
/* join root and rest with a backslash - return 0 on success */
7191+
static int
71867192
_joinW(WCHAR *dest_path, const WCHAR *root, const WCHAR *rest)
71877193
{
7188-
size_t root_len;
7189-
71907194
if (_is_absW(rest)) {
7191-
wcscpy(dest_path, rest);
7192-
return;
7195+
return wcscpy_s(dest_path, MAX_PATH, rest);
71937196
}
71947197

7195-
root_len = wcslen(root);
7198+
if (wcscpy_s(dest_path, MAX_PATH, root)) {
7199+
return -1;
7200+
}
71967201

7197-
wcscpy(dest_path, root);
7198-
if(root_len) {
7199-
dest_path[root_len] = L'\\';
7200-
root_len++;
7202+
if (dest_path[0] && wcscat_s(dest_path, MAX_PATH, L"\\")) {
7203+
return -1;
72017204
}
7202-
wcscpy(dest_path+root_len, rest);
7205+
7206+
return wcscat_s(dest_path, MAX_PATH, rest);
72037207
}
72047208

72057209
/* Return True if the path at src relative to dest is a directory */
@@ -7211,10 +7215,14 @@ _check_dirW(LPCWSTR src, LPCWSTR dest)
72117215
WCHAR src_resolved[MAX_PATH] = L"";
72127216

72137217
/* dest_parent = os.path.dirname(dest) */
7214-
wcscpy(dest_parent, dest);
7215-
_dirnameW(dest_parent);
7218+
if (wcscpy_s(dest_parent, MAX_PATH, dest) ||
7219+
_dirnameW(dest_parent)) {
7220+
return 0;
7221+
}
72167222
/* src_resolved = os.path.join(dest_parent, src) */
7217-
_joinW(src_resolved, dest_parent, src);
7223+
if (_joinW(src_resolved, dest_parent, src)) {
7224+
return 0;
7225+
}
72187226
return (
72197227
GetFileAttributesExW(src_resolved, GetFileExInfoStandard, &src_info)
72207228
&& src_info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY
@@ -7270,26 +7278,28 @@ os_symlink_impl(PyObject *module, path_t *src, path_t *dst,
72707278
}
72717279
#endif
72727280

7273-
if ((src->narrow && dst->wide) || (src->wide && dst->narrow)) {
7274-
PyErr_SetString(PyExc_ValueError,
7275-
"symlink: src and dst must be the same type");
7276-
return NULL;
7277-
}
7278-
72797281
#ifdef MS_WINDOWS
72807282

72817283
Py_BEGIN_ALLOW_THREADS
7284+
_Py_BEGIN_SUPPRESS_IPH
72827285
/* if src is a directory, ensure target_is_directory==1 */
72837286
target_is_directory |= _check_dirW(src->wide, dst->wide);
72847287
result = Py_CreateSymbolicLinkW(dst->wide, src->wide,
72857288
target_is_directory);
7289+
_Py_END_SUPPRESS_IPH
72867290
Py_END_ALLOW_THREADS
72877291

72887292
if (!result)
72897293
return path_error2(src, dst);
72907294

72917295
#else
72927296

7297+
if ((src->narrow && dst->wide) || (src->wide && dst->narrow)) {
7298+
PyErr_SetString(PyExc_ValueError,
7299+
"symlink: src and dst must be the same type");
7300+
return NULL;
7301+
}
7302+
72937303
Py_BEGIN_ALLOW_THREADS
72947304
#if HAVE_SYMLINKAT
72957305
if (dir_fd != DEFAULT_DIR_FD)

0 commit comments

Comments
 (0)