Skip to content

Commit fdb332e

Browse files
committed
[Windows] disk_partitions: use wide-char APIs to fix UnicodeDecodeError (#1959)
ANSI GetLogicalDriveStrings/FindFirstVolumeMountPoint/GetVolumeInformation return bytes in the system ANSI code page; feeding them to Py_BuildValue "(ssss)" decodes as strict UTF-8 and crashes on non-UTF8 systems (e.g. cp1251). Switch to the W family + PyUnicode_FromWideChar, and make psutil_QueryDosDevice wide for the same reason. Signed-off-by: Alex Chen <l46983284@gmail.com>
1 parent 51aa68e commit fdb332e

1 file changed

Lines changed: 89 additions & 45 deletions

File tree

psutil/arch/windows/disk.c

Lines changed: 89 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66

77
#include <Python.h>
88
#include <windows.h>
9-
#include <tchar.h>
109

1110
#include "../../arch/all/init.h"
1211

@@ -221,21 +220,25 @@ psutil_disk_io_counters(PyObject *self, PyObject *args) {
221220

222221
PyObject *
223222
psutil_disk_partitions(PyObject *self, PyObject *args) {
224-
DWORD num_bytes;
225-
char drive_strings[255];
226-
char *drive_letter = drive_strings;
227-
char mp_buf[MAX_PATH];
228-
char mp_path[MAX_PATH];
223+
DWORD num_chars;
224+
wchar_t drive_strings[255];
225+
wchar_t *drive_letter = drive_strings;
226+
wchar_t mp_buf[MAX_PATH];
227+
wchar_t mp_path[MAX_PATH];
229228
int all = 0;
230229
int type;
231230
int ret;
232231
unsigned int old_mode = 0;
233232
char opts[50];
234233
HANDLE mp_h;
235234
BOOL mp_flag = TRUE;
236-
char fs_type[MAX_PATH + 1] = {0};
235+
wchar_t fs_type[MAX_PATH + 1] = {0};
237236
DWORD pflags = 0;
238237
DWORD lpMaximumComponentLength = 0; // max file name
238+
PyObject *py_drive = NULL;
239+
PyObject *py_mount = NULL;
240+
PyObject *py_fs = NULL;
241+
PyObject *py_opts = NULL;
239242
PyObject *py_retlist = PyList_New(0);
240243

241244
if (py_retlist == NULL) {
@@ -250,11 +253,11 @@ psutil_disk_partitions(PyObject *self, PyObject *args) {
250253
goto error;
251254

252255
Py_BEGIN_ALLOW_THREADS
253-
num_bytes = GetLogicalDriveStrings(254, drive_letter);
256+
num_chars = GetLogicalDriveStringsW(254, drive_letter);
254257
Py_END_ALLOW_THREADS
255258

256-
if (num_bytes == 0) {
257-
psutil_oserror();
259+
if (num_chars == 0) {
260+
psutil_oserror_wsyscall("GetLogicalDriveStringsW");
258261
goto error;
259262
}
260263

@@ -263,7 +266,7 @@ psutil_disk_partitions(PyObject *self, PyObject *args) {
263266
fs_type[0] = 0;
264267

265268
Py_BEGIN_ALLOW_THREADS
266-
type = GetDriveType(drive_letter);
269+
type = GetDriveTypeW(drive_letter);
267270
Py_END_ALLOW_THREADS
268271

269272
// by default we only show hard drives and cd-roms
@@ -276,7 +279,7 @@ psutil_disk_partitions(PyObject *self, PyObject *args) {
276279
// floppy disk: skip it by default as it introduces a
277280
// considerable slowdown.
278281
if ((type == DRIVE_REMOVABLE)
279-
&& (strcmp(drive_letter, "A:\\") == 0))
282+
&& (wcscmp(drive_letter, L"A:\\") == 0))
280283
{
281284
goto next;
282285
}
@@ -285,8 +288,8 @@ psutil_disk_partitions(PyObject *self, PyObject *args) {
285288
// May spin up a removable drive or go over the wire for a
286289
// network one, so do it without the GIL.
287290
Py_BEGIN_ALLOW_THREADS
288-
ret = GetVolumeInformation(
289-
(LPCTSTR)drive_letter,
291+
ret = GetVolumeInformationW(
292+
drive_letter,
290293
NULL, // we don't want the volume name
291294
0,
292295
NULL,
@@ -320,40 +323,64 @@ psutil_disk_partitions(PyObject *self, PyObject *args) {
320323
str_append(opts, sizeof(opts), ",");
321324
str_append(opts, sizeof(opts), psutil_get_drive_type(type));
322325

326+
// Convert the strings which will go into the result tuples.
327+
py_drive = PyUnicode_FromWideChar(drive_letter, wcslen(drive_letter));
328+
if (py_drive == NULL)
329+
goto error;
330+
py_fs = PyUnicode_FromWideChar(fs_type, wcslen(fs_type));
331+
if (py_fs == NULL)
332+
goto error;
333+
// opts holds pure ASCII, so plain UTF-8 decoding is safe.
334+
py_opts = PyUnicode_FromString(opts);
335+
if (py_opts == NULL)
336+
goto error;
337+
323338
// Check for mount points on this volume and add/get info
324339
// (checks first to know if we can even have mount points)
325340
if ((ret != 0) && (pflags & FILE_SUPPORTS_REPARSE_POINTS)) {
326341
Py_BEGIN_ALLOW_THREADS
327-
mp_h = FindFirstVolumeMountPoint(drive_letter, mp_buf, MAX_PATH);
342+
mp_h = FindFirstVolumeMountPointW(drive_letter, mp_buf, MAX_PATH);
328343
Py_END_ALLOW_THREADS
329344

330345
if (mp_h != INVALID_HANDLE_VALUE) {
331346
mp_flag = TRUE;
332347
while (mp_flag) {
333-
// Append full mount path with drive letter
334-
str_copy(
335-
mp_path, sizeof(mp_path), drive_letter
336-
); // initialize
337-
str_append(
338-
mp_path, sizeof(mp_path), mp_buf
339-
); // append mount point
348+
// Append full mount path with drive letter.
349+
mp_path[0] = L'\0';
350+
wcsncat(mp_path, drive_letter, _ARRAYSIZE(mp_path) - 1);
351+
wcsncat(
352+
mp_path,
353+
mp_buf,
354+
_ARRAYSIZE(mp_path) - wcslen(mp_path) - 1
355+
);
356+
357+
py_mount = PyUnicode_FromWideChar(
358+
mp_path, wcslen(mp_path)
359+
);
360+
if (py_mount == NULL) {
361+
FindVolumeMountPointClose(mp_h);
362+
goto error;
363+
}
340364

341365
if (!pylist_append_fmt(
342366
py_retlist,
343-
"(ssss)",
344-
drive_letter,
345-
mp_path,
346-
fs_type, // typically "NTFS"
347-
opts
367+
"(OOOO)",
368+
py_drive,
369+
py_mount,
370+
py_fs, // typically "NTFS"
371+
py_opts
348372
))
349373
{
350374
FindVolumeMountPointClose(mp_h);
351375
goto error;
352376
}
377+
Py_CLEAR(py_mount);
353378

354379
// Continue looking for more mount points
355380
Py_BEGIN_ALLOW_THREADS
356-
mp_flag = FindNextVolumeMountPoint(mp_h, mp_buf, MAX_PATH);
381+
mp_flag = FindNextVolumeMountPointW(
382+
mp_h, mp_buf, MAX_PATH
383+
);
357384
Py_END_ALLOW_THREADS
358385
}
359386
FindVolumeMountPointClose(mp_h);
@@ -362,25 +389,33 @@ psutil_disk_partitions(PyObject *self, PyObject *args) {
362389

363390
if (!pylist_append_fmt(
364391
py_retlist,
365-
"(ssss)",
366-
drive_letter,
367-
drive_letter,
368-
fs_type, // either FAT, FAT32, NTFS, HPFS, CDFS, UDF or NWFS
369-
opts
392+
"(OOOO)",
393+
py_drive,
394+
py_drive,
395+
py_fs, // either FAT, FAT32, NTFS, HPFS, CDFS, UDF or NWFS
396+
py_opts
370397
))
371398
{
372399
goto error;
373400
}
374401
goto next;
375402

376403
next:
377-
drive_letter = strchr(drive_letter, 0) + 1;
404+
Py_CLEAR(py_opts);
405+
Py_CLEAR(py_fs);
406+
Py_CLEAR(py_mount);
407+
Py_CLEAR(py_drive);
408+
drive_letter = wcschr(drive_letter, 0) + 1;
378409
}
379410

380411
SetErrorMode(old_mode);
381412
return py_retlist;
382413

383414
error:
415+
Py_XDECREF(py_opts);
416+
Py_XDECREF(py_fs);
417+
Py_XDECREF(py_mount);
418+
Py_XDECREF(py_drive);
384419
SetErrorMode(old_mode);
385420
Py_DECREF(py_retlist);
386421
return NULL;
@@ -392,23 +427,32 @@ psutil_disk_partitions(PyObject *self, PyObject *args) {
392427
// If no match is found return an empty string.
393428
PyObject *
394429
psutil_QueryDosDevice(PyObject *self, PyObject *args) {
395-
LPCTSTR lpDevicePath;
396-
TCHAR d = TEXT('A');
397-
TCHAR szBuff[5];
430+
PyObject *py_device_path;
431+
PyObject *py_ret = NULL;
432+
wchar_t *device_path;
433+
wchar_t d = L'A';
398434

399-
if (!PyArg_ParseTuple(args, "s", &lpDevicePath))
435+
if (!PyArg_ParseTuple(args, "U", &py_device_path))
400436
return NULL;
401437

402-
while (d <= TEXT('Z')) {
403-
TCHAR szDeviceName[3] = {d, TEXT(':'), TEXT('\0')};
404-
TCHAR szTarget[512] = {0};
405-
if (QueryDosDevice(szDeviceName, szTarget, 511) != 0) {
406-
if (_tcscmp(lpDevicePath, szTarget) == 0) {
407-
_stprintf_s(szBuff, _countof(szBuff), TEXT("%c:"), d);
408-
return PyUnicode_FromString(szBuff);
438+
device_path = PyUnicode_AsWideCharString(py_device_path, NULL);
439+
if (device_path == NULL)
440+
return NULL;
441+
442+
while (d <= L'Z') {
443+
wchar_t szDeviceName[3] = {d, L':', L'\0'};
444+
wchar_t szTarget[512] = {0};
445+
if (QueryDosDeviceW(szDeviceName, szTarget, 511) != 0) {
446+
if (wcscmp(device_path, szTarget) == 0) {
447+
py_ret = PyUnicode_FromWideChar(
448+
szDeviceName, wcslen(szDeviceName)
449+
);
450+
PyMem_Free(device_path);
451+
return py_ret;
409452
}
410453
}
411454
d++;
412455
}
456+
PyMem_Free(device_path);
413457
return PyUnicode_FromString("");
414458
}

0 commit comments

Comments
 (0)