Skip to content

Commit f381cfe

Browse files
zoobalarryhastings
authored andcommitted
[3.5] bpo-33001: Prevent buffer overrun in os.symlink (GH-5989) (#5991)
* bpo-33001: Minimal fix to prevent buffer overrun in os.symlink * Remove invalid test
1 parent 937ac1f commit f381cfe

File tree

3 files changed

+97
-38
lines changed

3 files changed

+97
-38
lines changed

Lib/test/test_os.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2117,6 +2117,46 @@ def test_12084(self):
21172117
os.remove(file1)
21182118
shutil.rmtree(level1)
21192119

2120+
def test_buffer_overflow(self):
2121+
# Older versions would have a buffer overflow when detecting
2122+
# whether a link source was a directory. This test ensures we
2123+
# no longer crash, but does not otherwise validate the behavior
2124+
segment = 'X' * 27
2125+
path = os.path.join(*[segment] * 10)
2126+
test_cases = [
2127+
# overflow with absolute src
2128+
('\\' + path, segment),
2129+
# overflow dest with relative src
2130+
(segment, path),
2131+
# overflow when joining src
2132+
(path[:180], path[:180]),
2133+
]
2134+
for src, dest in test_cases:
2135+
try:
2136+
os.symlink(src, dest)
2137+
except FileNotFoundError:
2138+
pass
2139+
else:
2140+
try:
2141+
os.remove(dest)
2142+
except OSError:
2143+
pass
2144+
# Also test with bytes, since that is a separate code path.
2145+
# However, because of the stack layout, it is not possible
2146+
# to exploit the overflow on Python 3.5 using bytes
2147+
try:
2148+
os.symlink(os.fsencode(src), os.fsencode(dest))
2149+
except ValueError:
2150+
# Conversion function checks for len(arg) >= 260
2151+
pass
2152+
except FileNotFoundError:
2153+
pass
2154+
else:
2155+
try:
2156+
os.remove(dest)
2157+
except OSError:
2158+
pass
2159+
21202160

21212161
@unittest.skipUnless(sys.platform == "win32", "Win32 specific tests")
21222162
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: 56 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -7259,90 +7259,98 @@ check_CreateSymbolicLink(void)
72597259
return (Py_CreateSymbolicLinkW && Py_CreateSymbolicLinkA);
72607260
}
72617261

7262-
/* Remove the last portion of the path */
7263-
static void
7262+
/* Remove the last portion of the path - return 0 on success */
7263+
static int
72647264
_dirnameW(WCHAR *path)
72657265
{
72667266
WCHAR *ptr;
7267+
size_t length = wcsnlen_s(path, MAX_PATH);
7268+
if (length == MAX_PATH) {
7269+
return -1;
7270+
}
72677271

72687272
/* walk the path from the end until a backslash is encountered */
7269-
for(ptr = path + wcslen(path); ptr != path; ptr--) {
7273+
for(ptr = path + length; ptr != path; ptr--) {
72707274
if (*ptr == L'\\' || *ptr == L'/')
72717275
break;
72727276
}
72737277
*ptr = 0;
7278+
return 0;
72747279
}
72757280

7276-
/* Remove the last portion of the path */
7277-
static void
7281+
/* Remove the last portion of the path - return 0 on success */
7282+
static int
72787283
_dirnameA(char *path)
72797284
{
72807285
char *ptr;
7286+
size_t length = strnlen_s(path, MAX_PATH);
7287+
if (length == MAX_PATH) {
7288+
return -1;
7289+
}
72817290

72827291
/* walk the path from the end until a backslash is encountered */
7283-
for(ptr = path + strlen(path); ptr != path; ptr--) {
7292+
for(ptr = path + length; ptr != path; ptr--) {
72847293
if (*ptr == '\\' || *ptr == '/')
72857294
break;
72867295
}
72877296
*ptr = 0;
7297+
return 0;
72887298
}
72897299

72907300
/* Is this path absolute? */
72917301
static int
72927302
_is_absW(const WCHAR *path)
72937303
{
7294-
return path[0] == L'\\' || path[0] == L'/' || path[1] == L':';
7304+
return path[0] == L'\\' || path[0] == L'/' ||
7305+
(path[0] && path[1] == L':');
72957306

72967307
}
72977308

72987309
/* Is this path absolute? */
72997310
static int
73007311
_is_absA(const char *path)
73017312
{
7302-
return path[0] == '\\' || path[0] == '/' || path[1] == ':';
7313+
return path[0] == '\\' || path[0] == '/' ||
7314+
(path[0] && path[1] == ':');
73037315

73047316
}
73057317

7306-
/* join root and rest with a backslash */
7307-
static void
7318+
/* join root and rest with a backslash - return 0 on success */
7319+
static int
73087320
_joinW(WCHAR *dest_path, const WCHAR *root, const WCHAR *rest)
73097321
{
7310-
size_t root_len;
7311-
73127322
if (_is_absW(rest)) {
7313-
wcscpy(dest_path, rest);
7314-
return;
7323+
return wcscpy_s(dest_path, MAX_PATH, rest);
73157324
}
73167325

7317-
root_len = wcslen(root);
7326+
if (wcscpy_s(dest_path, MAX_PATH, root)) {
7327+
return -1;
7328+
}
73187329

7319-
wcscpy(dest_path, root);
7320-
if(root_len) {
7321-
dest_path[root_len] = L'\\';
7322-
root_len++;
7330+
if (dest_path[0] && wcscat_s(dest_path, MAX_PATH, L"\\")) {
7331+
return -1;
73237332
}
7324-
wcscpy(dest_path+root_len, rest);
7333+
7334+
return wcscat_s(dest_path, MAX_PATH, rest);
73257335
}
73267336

7327-
/* join root and rest with a backslash */
7328-
static void
7337+
/* join root and rest with a backslash - return 0 on success */
7338+
static int
73297339
_joinA(char *dest_path, const char *root, const char *rest)
73307340
{
7331-
size_t root_len;
7332-
73337341
if (_is_absA(rest)) {
7334-
strcpy(dest_path, rest);
7335-
return;
7342+
return strcpy_s(dest_path, MAX_PATH, rest);
73367343
}
73377344

7338-
root_len = strlen(root);
7345+
if (strcpy_s(dest_path, MAX_PATH, root)) {
7346+
return -1;
7347+
}
73397348

7340-
strcpy(dest_path, root);
7341-
if(root_len) {
7342-
dest_path[root_len] = '\\';
7343-
root_len++;
7349+
if (dest_path[0] && strcat_s(dest_path, MAX_PATH, "\\")) {
7350+
return -1;
73447351
}
7345-
strcpy(dest_path+root_len, rest);
7352+
7353+
return strcat_s(dest_path, MAX_PATH, rest);
73467354
}
73477355

73487356
/* Return True if the path at src relative to dest is a directory */
@@ -7354,10 +7362,14 @@ _check_dirW(WCHAR *src, WCHAR *dest)
73547362
WCHAR src_resolved[MAX_PATH] = L"";
73557363

73567364
/* dest_parent = os.path.dirname(dest) */
7357-
wcscpy(dest_parent, dest);
7358-
_dirnameW(dest_parent);
7365+
if (wcscpy_s(dest_parent, MAX_PATH, dest) ||
7366+
_dirnameW(dest_parent)) {
7367+
return 0;
7368+
}
73597369
/* src_resolved = os.path.join(dest_parent, src) */
7360-
_joinW(src_resolved, dest_parent, src);
7370+
if (_joinW(src_resolved, dest_parent, src)) {
7371+
return 0;
7372+
}
73617373
return (
73627374
GetFileAttributesExW(src_resolved, GetFileExInfoStandard, &src_info)
73637375
&& src_info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY
@@ -7373,10 +7385,14 @@ _check_dirA(char *src, char *dest)
73737385
char src_resolved[MAX_PATH] = "";
73747386

73757387
/* dest_parent = os.path.dirname(dest) */
7376-
strcpy(dest_parent, dest);
7377-
_dirnameA(dest_parent);
7388+
if (strcpy_s(dest_parent, MAX_PATH, dest) ||
7389+
_dirnameA(dest_parent)) {
7390+
return 0;
7391+
}
73787392
/* src_resolved = os.path.join(dest_parent, src) */
7379-
_joinA(src_resolved, dest_parent, src);
7393+
if (_joinA(src_resolved, dest_parent, src)) {
7394+
return 0;
7395+
}
73807396
return (
73817397
GetFileAttributesExA(src_resolved, GetFileExInfoStandard, &src_info)
73827398
&& src_info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY
@@ -7441,6 +7457,7 @@ os_symlink_impl(PyObject *module, path_t *src, path_t *dst,
74417457
#ifdef MS_WINDOWS
74427458

74437459
Py_BEGIN_ALLOW_THREADS
7460+
_Py_BEGIN_SUPPRESS_IPH
74447461
if (dst->wide) {
74457462
/* if src is a directory, ensure target_is_directory==1 */
74467463
target_is_directory |= _check_dirW(src->wide, dst->wide);
@@ -7453,6 +7470,7 @@ os_symlink_impl(PyObject *module, path_t *src, path_t *dst,
74537470
result = Py_CreateSymbolicLinkA(dst->narrow, src->narrow,
74547471
target_is_directory);
74557472
}
7473+
_Py_END_SUPPRESS_IPH
74567474
Py_END_ALLOW_THREADS
74577475

74587476
if (!result)

0 commit comments

Comments
 (0)