https://github.com/python/cpython/commit/2b1eed460d598a30a5eab63f5f1f4d8ac8e43468
commit: 2b1eed460d598a30a5eab63f5f1f4d8ac8e43468
branch: 3.15
author: Miss Islington (bot) <[email protected]>
committer: pablogsal <[email protected]>
date: 2026-05-09T00:28:21Z
summary:

[3.15] gh-149474: use `Py_fopen` in `Binary{Reader,Writer}` for audit hook and 
path-like support (GH-149524) (#149586)

gh-149474: use `Py_fopen` in `Binary{Reader,Writer}` for audit hook and 
path-like support (GH-149524)
(cherry picked from commit 354ef336e4cd48cf0c02bc9a0c642adf5d543184)

Co-authored-by: Maurycy Pawłowski-Wieroński <[email protected]>

files:
A Misc/NEWS.d/next/Security/2026-05-08-02-18-54.gh-issue-149474.ujQ-mu.rst
M Lib/test/audit-tests.py
M Lib/test/test_profiling/test_sampling_profiler/test_binary_format.py
M Modules/_remote_debugging/binary_io.h
M Modules/_remote_debugging/binary_io_reader.c
M Modules/_remote_debugging/binary_io_writer.c
M Modules/_remote_debugging/clinic/module.c.h
M Modules/_remote_debugging/module.c

diff --git a/Lib/test/audit-tests.py b/Lib/test/audit-tests.py
index a893932169a089..8be5bf8aa4f546 100644
--- a/Lib/test/audit-tests.py
+++ b/Lib/test/audit-tests.py
@@ -208,6 +208,16 @@ def rl(name):
         else:
             return None
 
+    try:
+        import _remote_debugging
+    except ImportError:
+        _remote_debugging = None
+
+    def rd(name):
+        if _remote_debugging:
+            return getattr(_remote_debugging, name, None)
+        return None
+
     # Try a range of "open" functions.
     # All of them should fail
     with TestHook(raise_on_events={"open"}) as hook:
@@ -225,6 +235,8 @@ def rl(name):
             (rl("append_history_file"), 0, None),
             (rl("read_init_file"), testfn),
             (rl("read_init_file"), None),
+            (rd("BinaryWriter"), testfn, 1000, 0),
+            (rd("BinaryReader"), testfn),
         ]:
             if not fn:
                 continue
@@ -258,6 +270,8 @@ def rl(name):
                 ("~/.history", "a") if rl("append_history_file") else None,
                 (testfn, "r") if readline else None,
                 ("<readline_init_file>", "r") if readline else None,
+                (testfn, "wb") if rd("BinaryWriter") else None,
+                (testfn, "rb") if rd("BinaryReader") else None,
             ]
             if i is not None
         ],
diff --git 
a/Lib/test/test_profiling/test_sampling_profiler/test_binary_format.py 
b/Lib/test/test_profiling/test_sampling_profiler/test_binary_format.py
index 9cf706aa2dafee..1fbb4e2d6c6fbb 100644
--- a/Lib/test/test_profiling/test_sampling_profiler/test_binary_format.py
+++ b/Lib/test/test_profiling/test_sampling_profiler/test_binary_format.py
@@ -2,6 +2,7 @@
 
 import json
 import os
+import pathlib
 import random
 import struct
 import tempfile
@@ -814,6 +815,35 @@ def test_invalid_file_path(self):
             with BinaryReader("/nonexistent/path/file.bin") as reader:
                 reader.replay_samples(RawCollector())
 
+    def test_path_arguments_round_trip(self):
+        """Reader and writer accept str, bytes or os.PathLike."""
+        with tempfile.NamedTemporaryFile(suffix=".bin", delete=False) as f:
+            filename = f.name
+        self.temp_files.append(filename)
+
+        for path_arg in (filename, os.fsencode(filename), 
pathlib.Path(filename)):
+            with self.subTest(path_type=type(path_arg).__name__):
+                writer = _remote_debugging.BinaryWriter(path_arg, 1000, 0)
+                writer.finalize()
+                reader = _remote_debugging.BinaryReader(path_arg)
+                info = reader.get_info()
+                reader.close()
+                self.assertEqual(info["sample_count"], 0)
+
+    def test_rejects_non_pathlike(self):
+        """Reader and writer raise TypeError on non-path-like filenames."""
+        with self.assertRaises(TypeError):
+            _remote_debugging.BinaryWriter(123, 1000, 0)
+        with self.assertRaises(TypeError):
+            _remote_debugging.BinaryReader(123)
+
+    def test_invalid_path_error_preserves_pathlib(self):
+        """Missing path: OSError carries the original path object, not a 
string."""
+        missing = pathlib.Path("/i/do/not/exist")
+        with self.assertRaises(FileNotFoundError) as cm:
+            _remote_debugging.BinaryReader(missing)
+        self.assertEqual(os.fspath(cm.exception.filename), os.fspath(missing))
+
     def test_writer_handles_empty_stack_first_sample(self):
         """BinaryWriter.write_sample tolerates an empty stack on a fresh 
thread.
 
diff --git 
a/Misc/NEWS.d/next/Security/2026-05-08-02-18-54.gh-issue-149474.ujQ-mu.rst 
b/Misc/NEWS.d/next/Security/2026-05-08-02-18-54.gh-issue-149474.ujQ-mu.rst
new file mode 100644
index 00000000000000..48e718b95ebe3a
--- /dev/null
+++ b/Misc/NEWS.d/next/Security/2026-05-08-02-18-54.gh-issue-149474.ujQ-mu.rst
@@ -0,0 +1,3 @@
+Fix the binary writer in :mod:`profiling.sampling` not firing the audit
+(:pep:`578`) when creating the output file. The writer and the reader now
+accept any path-like object. Patch by Maurycy Pawłowski-Wieroński.
diff --git a/Modules/_remote_debugging/binary_io.h 
b/Modules/_remote_debugging/binary_io.h
index 87a54371c774f1..d4188335c0b6d0 100644
--- a/Modules/_remote_debugging/binary_io.h
+++ b/Modules/_remote_debugging/binary_io.h
@@ -253,7 +253,6 @@ typedef struct {
 /* Main binary writer structure */
 typedef struct {
     FILE *fp;
-    char *filename;
 
     /* Write buffer for batched I/O */
     uint8_t *write_buffer;
@@ -311,10 +310,7 @@ typedef struct {
 
 /* Main binary reader structure */
 typedef struct {
-    char *filename;
-
 #if USE_MMAP
-    int fd;
     uint8_t *mapped_data;
     size_t mapped_size;
 #else
@@ -522,7 +518,7 @@ grow_array_inplace(void **ptr_addr, size_t count, size_t 
*capacity, size_t elem_
  * Create a new binary writer.
  *
  * Arguments:
- *   filename: Path to output file
+ *   path: Path to output file
  *   sample_interval_us: Sampling interval in microseconds
  *   compression_type: COMPRESSION_NONE or COMPRESSION_ZSTD
  *   start_time_us: Start timestamp in microseconds (from time.monotonic() * 
1e6)
@@ -531,7 +527,7 @@ grow_array_inplace(void **ptr_addr, size_t count, size_t 
*capacity, size_t elem_
  *   New BinaryWriter* on success, NULL on failure (PyErr set)
  */
 BinaryWriter *binary_writer_create(
-    const char *filename,
+    PyObject *path,
     uint64_t sample_interval_us,
     int compression_type,
     uint64_t start_time_us
@@ -583,12 +579,12 @@ void binary_writer_destroy(BinaryWriter *writer);
  * Open a binary file for reading.
  *
  * Arguments:
- *   filename: Path to input file
+ *   path: Path to input file
  *
  * Returns:
  *   New BinaryReader* on success, NULL on failure (PyErr set)
  */
-BinaryReader *binary_reader_open(const char *filename);
+BinaryReader *binary_reader_open(PyObject *path);
 
 /*
  * Replay samples from binary file through a collector.
diff --git a/Modules/_remote_debugging/binary_io_reader.c 
b/Modules/_remote_debugging/binary_io_reader.c
index 551530b519952c..972b197cfbad86 100644
--- a/Modules/_remote_debugging/binary_io_reader.c
+++ b/Modules/_remote_debugging/binary_io_reader.c
@@ -358,7 +358,7 @@ reader_parse_frame_table(BinaryReader *reader, const 
uint8_t *data, size_t file_
 }
 
 BinaryReader *
-binary_reader_open(const char *filename)
+binary_reader_open(PyObject *path)
 {
     BinaryReader *reader = PyMem_Calloc(1, sizeof(BinaryReader));
     if (!reader) {
@@ -366,29 +366,18 @@ binary_reader_open(const char *filename)
         return NULL;
     }
 
-#if USE_MMAP
-    reader->fd = -1;  /* Explicit initialization for cleanup safety */
-#endif
-
-    reader->filename = PyMem_Malloc(strlen(filename) + 1);
-    if (!reader->filename) {
-        PyMem_Free(reader);
-        PyErr_NoMemory();
-        return NULL;
-    }
-    strcpy(reader->filename, filename);
-
 #if USE_MMAP
     /* Open with mmap on Unix */
-    reader->fd = open(filename, O_RDONLY);
-    if (reader->fd < 0) {
-        PyErr_SetFromErrnoWithFilename(PyExc_IOError, filename);
+    FILE *fp = Py_fopen(path, "rb");
+    if (!fp) {
         goto error;
     }
+    int fd = fileno(fp);
 
     struct stat st;
-    if (fstat(reader->fd, &st) < 0) {
+    if (fstat(fd, &st) < 0) {
         PyErr_SetFromErrno(PyExc_IOError);
+        Py_fclose(fp);
         goto error;
     }
     reader->mapped_size = st.st_size;
@@ -400,14 +389,15 @@ binary_reader_open(const char *filename)
      */
 #ifdef __linux__
     reader->mapped_data = mmap(NULL, reader->mapped_size, PROT_READ,
-                               MAP_PRIVATE | MAP_POPULATE, reader->fd, 0);
+                               MAP_PRIVATE | MAP_POPULATE, fd, 0);
 #else
     reader->mapped_data = mmap(NULL, reader->mapped_size, PROT_READ,
-                               MAP_PRIVATE, reader->fd, 0);
+                               MAP_PRIVATE, fd, 0);
 #endif
     if (reader->mapped_data == MAP_FAILED) {
         reader->mapped_data = NULL;
         PyErr_SetFromErrno(PyExc_IOError);
+        Py_fclose(fp);
         goto error;
     }
 
@@ -428,19 +418,20 @@ binary_reader_open(const char *filename)
 
     /* Add file descriptor-level hints for better kernel I/O scheduling */
 #if defined(__linux__) && defined(POSIX_FADV_SEQUENTIAL)
-    (void)posix_fadvise(reader->fd, 0, 0, POSIX_FADV_SEQUENTIAL);
+    (void)posix_fadvise(fd, 0, 0, POSIX_FADV_SEQUENTIAL);
     if (reader->mapped_size > (64 * 1024 * 1024)) {
-        (void)posix_fadvise(reader->fd, 0, 0, POSIX_FADV_WILLNEED);
+        (void)posix_fadvise(fd, 0, 0, POSIX_FADV_WILLNEED);
     }
 #endif
 
+    (void)Py_fclose(fp);
+
     uint8_t *data = reader->mapped_data;
     size_t file_size = reader->mapped_size;
 #else
     /* Use stdio on Windows */
-    reader->fp = fopen(filename, "rb");
+    reader->fp = Py_fopen(path, "rb");
     if (!reader->fp) {
-        PyErr_SetFromErrnoWithFilename(PyExc_IOError, filename);
         goto error;
     }
 
@@ -1263,8 +1254,6 @@ binary_reader_close(BinaryReader *reader)
         return;
     }
 
-    PyMem_Free(reader->filename);
-
 #if USE_MMAP
     if (reader->mapped_data) {
         munmap(reader->mapped_data, reader->mapped_size);
@@ -1274,13 +1263,9 @@ binary_reader_close(BinaryReader *reader)
     /* Clear sample_data which may point into the now-unmapped region */
     reader->sample_data = NULL;
     reader->sample_data_size = 0;
-    if (reader->fd >= 0) {
-        close(reader->fd);
-        reader->fd = -1;  /* Mark as closed */
-    }
 #else
     if (reader->fp) {
-        fclose(reader->fp);
+        Py_fclose(reader->fp);
         reader->fp = NULL;
     }
     if (reader->file_data) {
diff --git a/Modules/_remote_debugging/binary_io_writer.c 
b/Modules/_remote_debugging/binary_io_writer.c
index 4cfed7300ac5ab..c31ed7d746466f 100644
--- a/Modules/_remote_debugging/binary_io_writer.c
+++ b/Modules/_remote_debugging/binary_io_writer.c
@@ -717,7 +717,7 @@ write_sample_with_encoding(BinaryWriter *writer, 
ThreadEntry *entry,
 }
 
 BinaryWriter *
-binary_writer_create(const char *filename, uint64_t sample_interval_us, int 
compression_type,
+binary_writer_create(PyObject *path, uint64_t sample_interval_us, int 
compression_type,
                      uint64_t start_time_us)
 {
     BinaryWriter *writer = PyMem_Calloc(1, sizeof(BinaryWriter));
@@ -726,14 +726,6 @@ binary_writer_create(const char *filename, uint64_t 
sample_interval_us, int comp
         return NULL;
     }
 
-    writer->filename = PyMem_Malloc(strlen(filename) + 1);
-    if (!writer->filename) {
-        PyMem_Free(writer);
-        PyErr_NoMemory();
-        return NULL;
-    }
-    strcpy(writer->filename, filename);
-
     writer->start_time_us = start_time_us;
     writer->sample_interval_us = sample_interval_us;
     writer->compression_type = compression_type;
@@ -799,9 +791,8 @@ binary_writer_create(const char *filename, uint64_t 
sample_interval_us, int comp
         }
     }
 
-    writer->fp = fopen(filename, "wb");
+    writer->fp = Py_fopen(path, "wb");
     if (!writer->fp) {
-        PyErr_SetFromErrnoWithFilename(PyExc_IOError, filename);
         goto error;
     }
 
@@ -1193,7 +1184,7 @@ binary_writer_finalize(BinaryWriter *writer)
         return -1;
     }
 
-    if (fclose(writer->fp) != 0) {
+    if (Py_fclose(writer->fp) != 0) {
         writer->fp = NULL;
         PyErr_SetFromErrno(PyExc_IOError);
         return -1;
@@ -1211,10 +1202,9 @@ binary_writer_destroy(BinaryWriter *writer)
     }
 
     if (writer->fp) {
-        fclose(writer->fp);
+        Py_fclose(writer->fp);
     }
 
-    PyMem_Free(writer->filename);
     PyMem_Free(writer->write_buffer);
 
 #ifdef HAVE_ZSTD
diff --git a/Modules/_remote_debugging/clinic/module.c.h 
b/Modules/_remote_debugging/clinic/module.c.h
index 1133db808efaec..d56622fb82ab56 100644
--- a/Modules/_remote_debugging/clinic/module.c.h
+++ b/Modules/_remote_debugging/clinic/module.c.h
@@ -688,7 +688,7 @@ PyDoc_STRVAR(_remote_debugging_BinaryWriter___init____doc__,
 
 static int
 _remote_debugging_BinaryWriter___init___impl(BinaryWriterObject *self,
-                                             const char *filename,
+                                             PyObject *filename,
                                              unsigned long long 
sample_interval_us,
                                              unsigned long long start_time_us,
                                              int compression);
@@ -728,7 +728,7 @@ _remote_debugging_BinaryWriter___init__(PyObject *self, 
PyObject *args, PyObject
     PyObject * const *fastargs;
     Py_ssize_t nargs = PyTuple_GET_SIZE(args);
     Py_ssize_t noptargs = nargs + (kwargs ? PyDict_GET_SIZE(kwargs) : 0) - 3;
-    const char *filename;
+    PyObject *filename;
     unsigned long long sample_interval_us;
     unsigned long long start_time_us;
     int compression = 0;
@@ -738,19 +738,7 @@ _remote_debugging_BinaryWriter___init__(PyObject *self, 
PyObject *args, PyObject
     if (!fastargs) {
         goto exit;
     }
-    if (!PyUnicode_Check(fastargs[0])) {
-        _PyArg_BadArgument("BinaryWriter", "argument 'filename'", "str", 
fastargs[0]);
-        goto exit;
-    }
-    Py_ssize_t filename_length;
-    filename = PyUnicode_AsUTF8AndSize(fastargs[0], &filename_length);
-    if (filename == NULL) {
-        goto exit;
-    }
-    if (strlen(filename) != (size_t)filename_length) {
-        PyErr_SetString(PyExc_ValueError, "embedded null character");
-        goto exit;
-    }
+    filename = fastargs[0];
     if (!_PyLong_UnsignedLongLong_Converter(fastargs[1], &sample_interval_us)) 
{
         goto exit;
     }
@@ -1009,7 +997,7 @@ 
PyDoc_STRVAR(_remote_debugging_BinaryReader___init____doc__,
 
 static int
 _remote_debugging_BinaryReader___init___impl(BinaryReaderObject *self,
-                                             const char *filename);
+                                             PyObject *filename);
 
 static int
 _remote_debugging_BinaryReader___init__(PyObject *self, PyObject *args, 
PyObject *kwargs)
@@ -1045,26 +1033,14 @@ _remote_debugging_BinaryReader___init__(PyObject *self, 
PyObject *args, PyObject
     PyObject *argsbuf[1];
     PyObject * const *fastargs;
     Py_ssize_t nargs = PyTuple_GET_SIZE(args);
-    const char *filename;
+    PyObject *filename;
 
     fastargs = _PyArg_UnpackKeywords(_PyTuple_CAST(args)->ob_item, nargs, 
kwargs, NULL, &_parser,
             /*minpos*/ 1, /*maxpos*/ 1, /*minkw*/ 0, /*varpos*/ 0, argsbuf);
     if (!fastargs) {
         goto exit;
     }
-    if (!PyUnicode_Check(fastargs[0])) {
-        _PyArg_BadArgument("BinaryReader", "argument 'filename'", "str", 
fastargs[0]);
-        goto exit;
-    }
-    Py_ssize_t filename_length;
-    filename = PyUnicode_AsUTF8AndSize(fastargs[0], &filename_length);
-    if (filename == NULL) {
-        goto exit;
-    }
-    if (strlen(filename) != (size_t)filename_length) {
-        PyErr_SetString(PyExc_ValueError, "embedded null character");
-        goto exit;
-    }
+    filename = fastargs[0];
     return_value = 
_remote_debugging_BinaryReader___init___impl((BinaryReaderObject *)self, 
filename);
 
 exit:
@@ -1564,4 +1540,4 @@ _remote_debugging_get_gc_stats(PyObject *module, PyObject 
*const *args, Py_ssize
 exit:
     return return_value;
 }
-/*[clinic end generated code: output=36674f4cb8a653f3 input=a9049054013a1b77]*/
+/*[clinic end generated code: output=5e2a29746a0c5d65 input=a9049054013a1b77]*/
diff --git a/Modules/_remote_debugging/module.c 
b/Modules/_remote_debugging/module.c
index 172f8711a2a2a0..efdd2e1a2d7b7a 100644
--- a/Modules/_remote_debugging/module.c
+++ b/Modules/_remote_debugging/module.c
@@ -1476,7 +1476,7 @@ class _remote_debugging.BinaryWriter "BinaryWriterObject 
*" "&PyBinaryWriter_Typ
 /*[clinic input]
 @permit_long_docstring_body
 _remote_debugging.BinaryWriter.__init__
-    filename: str
+    filename: object
     sample_interval_us: unsigned_long_long
     start_time_us: unsigned_long_long
     *
@@ -1495,11 +1495,11 @@ Use as a context manager or call finalize() when done.
 
 static int
 _remote_debugging_BinaryWriter___init___impl(BinaryWriterObject *self,
-                                             const char *filename,
+                                             PyObject *filename,
                                              unsigned long long 
sample_interval_us,
                                              unsigned long long start_time_us,
                                              int compression)
-/*[clinic end generated code: output=014c0306f1bacf4b input=3bdf01c1cc2f5a1d]*/
+/*[clinic end generated code: output=00446656ea2e5986 input=b92f0c77ba4cd274]*/
 {
     if (self->writer) {
         binary_writer_destroy(self->writer);
@@ -1742,7 +1742,7 @@ class _remote_debugging.BinaryReader "BinaryReaderObject 
*" "&PyBinaryReader_Typ
 
 /*[clinic input]
 _remote_debugging.BinaryReader.__init__
-    filename: str
+    filename: object
 
 High-performance binary reader for profiling data.
 
@@ -1754,8 +1754,8 @@ Use as a context manager or call close() when done.
 
 static int
 _remote_debugging_BinaryReader___init___impl(BinaryReaderObject *self,
-                                             const char *filename)
-/*[clinic end generated code: output=9699226f7ae052bb input=4201f9cc500ef2f6]*/
+                                             PyObject *filename)
+/*[clinic end generated code: output=f04b33ee5c5e6dbf input=9d7cbe8b4f1a97c9]*/
 {
     if (self->reader) {
         binary_reader_close(self->reader);

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to