Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
29 changes: 29 additions & 0 deletions Lib/test/test_bytes.py
Original file line number Diff line number Diff line change
Expand Up @@ -1637,6 +1637,35 @@ def test_take_bytes_optimization(self):
bytes_header_size = sys.getsizeof(b'')
self.assertEqual(ba.__alloc__(), 499 + bytes_header_size)

def test_take_bytes_reentrant_resize(self):
# gh-153570: n.__index__() can resize the bytearray, so take_bytes()
# must re-read the size afterwards. It cached the size before the
# call and used it for the bounds check and the buffer reads, so a
# reentrant clear() returned freed memory (a use-after-free read).
def take(target, resize, n):
class Evil:
def __index__(self):
resize(target)
return n
return target.take_bytes(Evil())

# clear() during __index__: nothing is left to take.
ba = bytearray(b'abcdefgh')
with self.assertRaises(IndexError):
take(ba, lambda b: b.clear(), 8)
self.assertEqual(ba, b'')

# shrink during __index__: n past the new size is out of range.
ba = bytearray(b'abcdefgh')
with self.assertRaises(IndexError):
take(ba, lambda b: b.__delitem__(slice(4, None)), 8)
self.assertEqual(ba, b'abcd')

# grow during __index__: the take runs against the new, larger size.
ba = bytearray(b'abcd')
self.assertEqual(take(ba, lambda b: b.extend(b'efgh'), 8), b'abcdefgh')
self.assertEqual(ba, b'')

def test_setitem(self):
def setitem_as_mapping(b, i, val):
b[i] = val
Expand Down
5 changes: 4 additions & 1 deletion Lib/test/test_curses.py
Original file line number Diff line number Diff line change
Expand Up @@ -3133,7 +3133,10 @@ def test_set_wide(self):
except UnicodeEncodeError:
self.skipTest('the locale cannot encode %r' % label)
curses.slk_set(1, label, 0)
self.assertEqual(curses.slk_label(1), label)
# The label can be truncated to fit the soft label width, e.g. in the
# EUC-JP locale, where "Å" and "ö" are double-width JIS X 0212
# characters, so the 8-column label only fits "Ångstr".
self.assertIn(curses.slk_label(1), (label, 'Ångstr'))

def test_set_bad_justify(self):
self.make_slk_screen()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Fix a use-after-free in :meth:`bytearray.take_bytes` when the argument's
:meth:`~object.__index__` method resizes the bytearray. Patch by tonghuaroot.
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Modernize fileutils removing GetFileInformationByHandle API calls and allow
build for Universal Windows Platform.
64 changes: 33 additions & 31 deletions Modules/posixmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -829,9 +829,9 @@ PyOS_AfterFork(void)
#ifdef MS_WINDOWS
/* defined in fileutils.c */
void _Py_time_t_to_FILE_TIME(time_t, int, FILETIME *);
void _Py_attribute_data_to_stat(BY_HANDLE_FILE_INFORMATION *, ULONG,
FILE_BASIC_INFO *, FILE_ID_INFO *,
struct _Py_stat_struct *);
void _Py_attribute_data_to_stat(FILE_STANDARD_INFO*, ULONG,
FILE_BASIC_INFO*, FILE_ID_INFO*,
struct _Py_stat_struct*);
void _Py_stat_basic_info_to_stat(FILE_STAT_BASIC_INFORMATION *,
struct _Py_stat_struct *);
#endif
Expand Down Expand Up @@ -2025,25 +2025,31 @@ win32_wchdir(LPCWSTR path)

static void
find_data_to_file_info(WIN32_FIND_DATAW *pFileData,
BY_HANDLE_FILE_INFORMATION *info,
FILE_BASIC_INFO* basic_info,
FILE_STANDARD_INFO* standard_info,
ULONG *reparse_tag)
{
memset(info, 0, sizeof(*info));
info->dwFileAttributes = pFileData->dwFileAttributes;
info->ftCreationTime = pFileData->ftCreationTime;
info->ftLastAccessTime = pFileData->ftLastAccessTime;
info->ftLastWriteTime = pFileData->ftLastWriteTime;
info->nFileSizeHigh = pFileData->nFileSizeHigh;
info->nFileSizeLow = pFileData->nFileSizeLow;
/* info->nNumberOfLinks = 1; */
memset(basic_info, 0, sizeof(*basic_info));
memset(standard_info, 0, sizeof(*standard_info));

basic_info->FileAttributes = pFileData->dwFileAttributes;
basic_info->CreationTime.HighPart = pFileData->ftCreationTime.dwHighDateTime;
basic_info->CreationTime.LowPart = pFileData->ftCreationTime.dwLowDateTime;
basic_info->LastAccessTime.HighPart = pFileData->ftLastAccessTime.dwHighDateTime;
basic_info->LastAccessTime.LowPart = pFileData->ftLastAccessTime.dwLowDateTime;
basic_info->LastWriteTime.HighPart = pFileData->ftLastWriteTime.dwHighDateTime;
basic_info->LastWriteTime.LowPart = pFileData->ftLastWriteTime.dwLowDateTime;
standard_info->EndOfFile.HighPart = pFileData->nFileSizeHigh;
standard_info->EndOfFile.LowPart = pFileData->nFileSizeLow;

if (pFileData->dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)
*reparse_tag = pFileData->dwReserved0;
else
*reparse_tag = 0;
}

static BOOL
attributes_from_dir(LPCWSTR pszFile, BY_HANDLE_FILE_INFORMATION *info, ULONG *reparse_tag)
attributes_from_dir(LPCWSTR pszFile, FILE_BASIC_INFO* basic_info, FILE_STANDARD_INFO* standard_info, ULONG* reparse_tag)
{
HANDLE hFindFile;
WIN32_FIND_DATAW FileData;
Expand Down Expand Up @@ -2074,7 +2080,7 @@ attributes_from_dir(LPCWSTR pszFile, BY_HANDLE_FILE_INFORMATION *info, ULONG *re
return FALSE;
}
FindClose(hFindFile);
find_data_to_file_info(&FileData, info, reparse_tag);
find_data_to_file_info(&FileData, basic_info, standard_info, reparse_tag);
return TRUE;
}

Expand Down Expand Up @@ -2108,10 +2114,9 @@ win32_xstat_slow_impl(const wchar_t *path, struct _Py_stat_struct *result,
BOOL traverse)
{
HANDLE hFile;
BY_HANDLE_FILE_INFORMATION fileInfo;
FILE_BASIC_INFO basicInfo;
FILE_BASIC_INFO *pBasicInfo = NULL;
FILE_ID_INFO idInfo;
FILE_STANDARD_INFO standardInfo = {0};
FILE_BASIC_INFO basicInfo = {0};
FILE_ID_INFO idInfo = {0};
FILE_ID_INFO *pIdInfo = NULL;
FILE_ATTRIBUTE_TAG_INFO tagInfo = { 0 };
DWORD fileType, error;
Expand All @@ -2132,7 +2137,7 @@ win32_xstat_slow_impl(const wchar_t *path, struct _Py_stat_struct *result,
case ERROR_ACCESS_DENIED: /* Cannot sync or read attributes. */
case ERROR_SHARING_VIOLATION: /* It's a paging file. */
/* Try reading the parent directory. */
if (!attributes_from_dir(path, &fileInfo, &tagInfo.ReparseTag)) {
if (!attributes_from_dir(path, &basicInfo, &standardInfo, &tagInfo.ReparseTag)) {
/* Cannot read the parent directory. */
switch (GetLastError()) {
case ERROR_FILE_NOT_FOUND: /* File cannot be found */
Expand All @@ -2147,7 +2152,7 @@ win32_xstat_slow_impl(const wchar_t *path, struct _Py_stat_struct *result,

return -1;
}
if (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) {
if (basicInfo.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) {
if (traverse ||
!IsReparseTagNameSurrogate(tagInfo.ReparseTag)) {
/* The stat call has to traverse but cannot, so fail. */
Expand Down Expand Up @@ -2247,9 +2252,8 @@ win32_xstat_slow_impl(const wchar_t *path, struct _Py_stat_struct *result,
}
}

if (!GetFileInformationByHandle(hFile, &fileInfo) ||
!GetFileInformationByHandleEx(hFile, FileBasicInfo,
&basicInfo, sizeof(basicInfo))) {
if (!GetFileInformationByHandleEx(hFile, FileStandardInfo, &standardInfo, sizeof(standardInfo)) ||
!GetFileInformationByHandleEx(hFile, FileBasicInfo, &basicInfo, sizeof(basicInfo))) {
switch (GetLastError()) {
case ERROR_INVALID_PARAMETER:
case ERROR_INVALID_FUNCTION:
Expand All @@ -2264,17 +2268,14 @@ win32_xstat_slow_impl(const wchar_t *path, struct _Py_stat_struct *result,
goto cleanup;
}

/* Successfully got FileBasicInfo, so we'll pass it along */
pBasicInfo = &basicInfo;

if (GetFileInformationByHandleEx(hFile, FileIdInfo, &idInfo, sizeof(idInfo))) {
/* Successfully got FileIdInfo, so pass it along */
pIdInfo = &idInfo;
}
}

_Py_attribute_data_to_stat(&fileInfo, tagInfo.ReparseTag, pBasicInfo, pIdInfo, result);
update_st_mode_from_path(path, fileInfo.dwFileAttributes, result);
_Py_attribute_data_to_stat(&standardInfo, tagInfo.ReparseTag, &basicInfo, pIdInfo, result);
update_st_mode_from_path(path, basicInfo.FileAttributes, result);

cleanup:
if (hFile != INVALID_HANDLE_VALUE) {
Expand Down Expand Up @@ -16702,7 +16703,8 @@ static PyObject *
DirEntry_from_find_data(PyObject *module, path_t *path, WIN32_FIND_DATAW *dataW)
{
DirEntry *entry;
BY_HANDLE_FILE_INFORMATION file_info;
FILE_BASIC_INFO basic_info;
FILE_STANDARD_INFO standard_info;
ULONG reparse_tag;
wchar_t *joined_path;

Expand Down Expand Up @@ -16740,8 +16742,8 @@ DirEntry_from_find_data(PyObject *module, path_t *path, WIN32_FIND_DATAW *dataW)
goto error;
}

find_data_to_file_info(dataW, &file_info, &reparse_tag);
_Py_attribute_data_to_stat(&file_info, reparse_tag, NULL, NULL, &entry->win32_lstat);
find_data_to_file_info(dataW, &basic_info, &standard_info, &reparse_tag);
_Py_attribute_data_to_stat(&standard_info, reparse_tag, &basic_info, NULL, &entry->win32_lstat);

/* ctime is only deprecated from 3.12, so we copy birthtime across */
entry->win32_lstat.st_ctime = entry->win32_lstat.st_birthtime;
Expand Down
2 changes: 2 additions & 0 deletions Objects/bytearrayobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -1555,6 +1555,8 @@ bytearray_take_bytes_impl(PyByteArrayObject *self, PyObject *n)
if (to_take == -1 && PyErr_Occurred()) {
return NULL;
}
// n.__index__() may have resized self; use the current size.
size = Py_SIZE(self);
if (to_take < 0) {
to_take += size;
}
Expand Down
47 changes: 19 additions & 28 deletions Python/fileutils.c
Original file line number Diff line number Diff line change
Expand Up @@ -1103,50 +1103,41 @@ typedef union {


void
_Py_attribute_data_to_stat(BY_HANDLE_FILE_INFORMATION *info, ULONG reparse_tag,
_Py_attribute_data_to_stat(FILE_STANDARD_INFO* standard_info, ULONG reparse_tag,
FILE_BASIC_INFO *basic_info, FILE_ID_INFO *id_info,
struct _Py_stat_struct *result)
{
memset(result, 0, sizeof(*result));
result->st_mode = attributes_to_mode(info->dwFileAttributes);
result->st_size = (((__int64)info->nFileSizeHigh)<<32) + info->nFileSizeLow;
result->st_dev = id_info ? id_info->VolumeSerialNumber : info->dwVolumeSerialNumber;
result->st_rdev = 0;

result->st_size = standard_info->EndOfFile.QuadPart;
result->st_nlink = standard_info->NumberOfLinks;

/* st_ctime is deprecated, but we preserve the legacy value in our caller, not here */
if (basic_info) {
LARGE_INTEGER_to_time_t_nsec(&basic_info->CreationTime, &result->st_birthtime, &result->st_birthtime_nsec);
LARGE_INTEGER_to_time_t_nsec(&basic_info->ChangeTime, &result->st_ctime, &result->st_ctime_nsec);
LARGE_INTEGER_to_time_t_nsec(&basic_info->LastWriteTime, &result->st_mtime, &result->st_mtime_nsec);
LARGE_INTEGER_to_time_t_nsec(&basic_info->LastAccessTime, &result->st_atime, &result->st_atime_nsec);
} else {
FILE_TIME_to_time_t_nsec(&info->ftCreationTime, &result->st_birthtime, &result->st_birthtime_nsec);
FILE_TIME_to_time_t_nsec(&info->ftLastWriteTime, &result->st_mtime, &result->st_mtime_nsec);
FILE_TIME_to_time_t_nsec(&info->ftLastAccessTime, &result->st_atime, &result->st_atime_nsec);
}
result->st_nlink = info->nNumberOfLinks;
LARGE_INTEGER_to_time_t_nsec(&basic_info->CreationTime, &result->st_birthtime, &result->st_birthtime_nsec);
LARGE_INTEGER_to_time_t_nsec(&basic_info->ChangeTime, &result->st_ctime, &result->st_ctime_nsec);
LARGE_INTEGER_to_time_t_nsec(&basic_info->LastWriteTime, &result->st_mtime, &result->st_mtime_nsec);
LARGE_INTEGER_to_time_t_nsec(&basic_info->LastAccessTime, &result->st_atime, &result->st_atime_nsec);

if (id_info) {
result->st_dev = id_info->VolumeSerialNumber;
id_128_to_ino file_id;
file_id.id = id_info->FileId;
result->st_ino = file_id.st_ino;
result->st_ino_high = file_id.st_ino_high;
}
if (!result->st_ino && !result->st_ino_high) {
/* should only occur for DirEntry_from_find_data, in which case the
index is likely to be zero anyway. */
result->st_ino = (((uint64_t)info->nFileIndexHigh) << 32) + info->nFileIndexLow;
}

result->st_file_attributes = basic_info->FileAttributes;
result->st_mode = attributes_to_mode(result->st_file_attributes);

/* bpo-37834: Only actual symlinks set the S_IFLNK flag. But lstat() will
open other name surrogate reparse points without traversing them. To
detect/handle these, check st_file_attributes and st_reparse_tag. */
result->st_reparse_tag = reparse_tag;
if (info->dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT &&
if (result->st_file_attributes & FILE_ATTRIBUTE_REPARSE_POINT &&
reparse_tag == IO_REPARSE_TAG_SYMLINK) {
/* set the bits that make this a symlink */
result->st_mode = (result->st_mode & ~S_IFMT) | S_IFLNK;
}
result->st_file_attributes = info->dwFileAttributes;
}

void
Expand Down Expand Up @@ -1231,9 +1222,9 @@ int
_Py_fstat_noraise(int fd, struct _Py_stat_struct *status)
{
#ifdef MS_WINDOWS
BY_HANDLE_FILE_INFORMATION info;
FILE_BASIC_INFO basicInfo;
FILE_ID_INFO idInfo;
FILE_STANDARD_INFO standardInfo = {0};
FILE_BASIC_INFO basicInfo = {0};
FILE_ID_INFO idInfo = {0};
FILE_ID_INFO *pIdInfo = &idInfo;
HANDLE h;
int type;
Expand Down Expand Up @@ -1266,7 +1257,7 @@ _Py_fstat_noraise(int fd, struct _Py_stat_struct *status)
return 0;
}

if (!GetFileInformationByHandle(h, &info) ||
if (!GetFileInformationByHandleEx(h,FileStandardInfo, &standardInfo, sizeof(standardInfo)) ||
!GetFileInformationByHandleEx(h, FileBasicInfo, &basicInfo, sizeof(basicInfo))) {
/* The Win32 error is already set, but we also set errno for
callers who expect it */
Expand All @@ -1279,7 +1270,7 @@ _Py_fstat_noraise(int fd, struct _Py_stat_struct *status)
pIdInfo = NULL;
}

_Py_attribute_data_to_stat(&info, 0, &basicInfo, pIdInfo, status);
_Py_attribute_data_to_stat(&standardInfo, 0, &basicInfo, pIdInfo, status);
return 0;
#else
return fstat(fd, status);
Expand Down
Loading