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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy