Skip to content

Commit 1424245

Browse files
committed
gh-42400: Fix buffer overflow in _Py_wrealpath for long paths
1 parent 209eaff commit 1424245

3 files changed

Lines changed: 92 additions & 3 deletions

File tree

Lib/test/test_fileutils.py

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
import os
44
import os.path
55
import unittest
6+
import tempfile
7+
import shutil
68
from test.support import import_helper
79

810
# Skip this test if the _testcapi module isn't available.
@@ -26,5 +28,88 @@ def test_capi_normalize_path(self):
2628
msg=f'input: {filename!r} expected output: {expected!r}')
2729

2830

31+
class RealpathTests(unittest.TestCase):
32+
"""Tests for _Py_wrealpath used by os.path.realpath"""
33+
34+
def test_realpath_long_path(self):
35+
"""Test that realpath handles paths longer than MAXPATHLEN (4096)"""
36+
if os.name == 'nt':
37+
raise unittest.SkipTest('POSIX-specific test')
38+
39+
base = tempfile.mkdtemp()
40+
original_cwd = os.getcwd()
41+
try:
42+
os.chdir(base)
43+
44+
for i in range(85):
45+
dirname = f"d{i:03d}_" + "x" * 44
46+
os.mkdir(dirname)
47+
os.chdir(dirname)
48+
49+
full_path = os.getcwd()
50+
51+
self.assertGreater(len(full_path), 4096,
52+
f"Path should exceed MAXPATHLEN, got {len(full_path)}")
53+
54+
# Main test: realpath should not crash on long paths
55+
result = os.path.realpath(full_path)
56+
57+
self.assertTrue(os.path.isabs(result))
58+
self.assertGreater(len(result), 4096)
59+
# Note: os.path.exists() may fail on very long paths
60+
# The important thing is realpath() doesn't crash
61+
62+
finally:
63+
os.chdir(original_cwd)
64+
shutil.rmtree(base, ignore_errors=True)
65+
66+
def test_realpath_nonexistent_with_strict(self):
67+
"""Test that realpath with strict=True raises for nonexistent paths"""
68+
if os.name == 'nt':
69+
raise unittest.SkipTest('POSIX-specific test')
70+
71+
base = tempfile.mkdtemp()
72+
try:
73+
nonexistent = os.path.join(base, "does_not_exist", "subdir")
74+
75+
# Without strict, should return the path
76+
result = os.path.realpath(nonexistent, strict=False)
77+
self.assertIsNotNone(result)
78+
79+
# With strict=True, should raise an error
80+
with self.assertRaises(OSError):
81+
os.path.realpath(nonexistent, strict=True)
82+
83+
finally:
84+
shutil.rmtree(base, ignore_errors=True)
85+
86+
def test_realpath_symlink_long_path(self):
87+
"""Test realpath with symlinks in long paths"""
88+
if os.name == 'nt':
89+
raise unittest.SkipTest('POSIX-specific test')
90+
91+
base = tempfile.mkdtemp()
92+
try:
93+
# Create a long path
94+
current = base
95+
for i in range(30):
96+
dirname = f"d{i:03d}_" + "x" * 44
97+
current = os.path.join(current, dirname)
98+
os.mkdir(current)
99+
100+
# Create a symlink pointing to the long path
101+
symlink = os.path.join(base, "link")
102+
os.symlink(current, symlink)
103+
104+
# Resolve the symlink
105+
result = os.path.realpath(symlink)
106+
107+
self.assertEqual(os.path.normpath(result), os.path.normpath(current))
108+
self.assertGreater(len(result), 1500)
109+
110+
finally:
111+
shutil.rmtree(base, ignore_errors=True)
112+
113+
29114
if __name__ == "__main__":
30115
unittest.main()
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Fix buffer overflow in :c:func:`!_Py_wrealpath` for paths exceeding
2+
:c:macro:`MAXPATHLEN` by using dynamic memory allocation. Patch by Shamil
3+
Abdulaev.

Python/fileutils.c

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2118,7 +2118,6 @@ _Py_wrealpath(const wchar_t *path,
21182118
wchar_t *resolved_path, size_t resolved_path_len)
21192119
{
21202120
char *cpath;
2121-
char cresolved_path[MAXPATHLEN];
21222121
wchar_t *wresolved_path;
21232122
char *res;
21242123
size_t r;
@@ -2127,12 +2126,14 @@ _Py_wrealpath(const wchar_t *path,
21272126
errno = EINVAL;
21282127
return NULL;
21292128
}
2130-
res = realpath(cpath, cresolved_path);
2129+
res = realpath(cpath, NULL);
21312130
PyMem_RawFree(cpath);
21322131
if (res == NULL)
21332132
return NULL;
21342133

2135-
wresolved_path = Py_DecodeLocale(cresolved_path, &r);
2134+
wresolved_path = Py_DecodeLocale(res, &r);
2135+
free(res);
2136+
21362137
if (wresolved_path == NULL) {
21372138
errno = EINVAL;
21382139
return NULL;

0 commit comments

Comments
 (0)