Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
gh-42400: Fix buffer overflow in _Py_wrealpath for long paths
  • Loading branch information
ashm-dev committed Nov 13, 2025
commit 3255a3dc6c677cf8333a69c82486731188a68f2a
85 changes: 85 additions & 0 deletions Lib/test/test_fileutils.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import os
import os.path
import unittest
import tempfile
import shutil
from test.support import import_helper

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


class RealpathTests(unittest.TestCase):
"""Tests for _Py_wrealpath used by os.path.realpath"""

def test_realpath_long_path(self):
"""Test that realpath handles paths longer than MAXPATHLEN (4096)"""
if os.name == 'nt':
raise unittest.SkipTest('POSIX-specific test')

base = tempfile.mkdtemp()
original_cwd = os.getcwd()
try:
os.chdir(base)

for i in range(85):
dirname = f"d{i:03d}_" + "x" * 44
os.mkdir(dirname)
os.chdir(dirname)

full_path = os.getcwd()

self.assertGreater(len(full_path), 4096,
f"Path should exceed MAXPATHLEN, got {len(full_path)}")

# Main test: realpath should not crash on long paths
result = os.path.realpath(full_path)

self.assertTrue(os.path.isabs(result))
self.assertGreater(len(result), 4096)
# Note: os.path.exists() may fail on very long paths
# The important thing is realpath() doesn't crash

finally:
os.chdir(original_cwd)
shutil.rmtree(base, ignore_errors=True)

def test_realpath_nonexistent_with_strict(self):
"""Test that realpath with strict=True raises for nonexistent paths"""
if os.name == 'nt':
raise unittest.SkipTest('POSIX-specific test')

base = tempfile.mkdtemp()
try:
nonexistent = os.path.join(base, "does_not_exist", "subdir")

# Without strict, should return the path
result = os.path.realpath(nonexistent, strict=False)
self.assertIsNotNone(result)

# With strict=True, should raise an error
with self.assertRaises(OSError):
os.path.realpath(nonexistent, strict=True)

finally:
shutil.rmtree(base, ignore_errors=True)

def test_realpath_symlink_long_path(self):
"""Test realpath with symlinks in long paths"""
if os.name == 'nt':
raise unittest.SkipTest('POSIX-specific test')

base = tempfile.mkdtemp()
try:
# Create a long path
current = base
for i in range(30):
dirname = f"d{i:03d}_" + "x" * 44
current = os.path.join(current, dirname)
os.mkdir(current)

# Create a symlink pointing to the long path
symlink = os.path.join(base, "link")
os.symlink(current, symlink)

# Resolve the symlink
result = os.path.realpath(symlink)

self.assertEqual(os.path.normpath(result), os.path.normpath(current))
self.assertGreater(len(result), 1500)

finally:
shutil.rmtree(base, ignore_errors=True)


if __name__ == "__main__":
unittest.main()
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Fix buffer overflow in ``_Py_wrealpath()`` for paths exceeding 4096 bytes
Comment thread
ashm-dev marked this conversation as resolved.
Outdated
by using dynamic memory allocation instead of fixed-size buffer.
Patch by Shamil Abdulaev.
7 changes: 4 additions & 3 deletions Python/fileutils.c
Original file line number Diff line number Diff line change
Expand Up @@ -2118,7 +2118,6 @@ _Py_wrealpath(const wchar_t *path,
wchar_t *resolved_path, size_t resolved_path_len)
{
char *cpath;
char cresolved_path[MAXPATHLEN];
wchar_t *wresolved_path;
char *res;
size_t r;
Expand All @@ -2127,12 +2126,14 @@ _Py_wrealpath(const wchar_t *path,
errno = EINVAL;
return NULL;
}
res = realpath(cpath, cresolved_path);
res = realpath(cpath, NULL);
PyMem_RawFree(cpath);
if (res == NULL)
return NULL;

wresolved_path = Py_DecodeLocale(cresolved_path, &r);
wresolved_path = Py_DecodeLocale(res, &r);
free(res);

if (wresolved_path == NULL) {
errno = EINVAL;
return NULL;
Expand Down
Loading