From 0463fb78fca1012d44678ed71f5d173b9a5489b8 Mon Sep 17 00:00:00 2001 From: Shamil Abdulaev Date: Sun, 2 Nov 2025 16:23:27 +0300 Subject: [PATCH 01/13] Fix heap-buffer-overflow when reading null bytes --- .../Security/2025-11-02-16-23-17.gh-issue-140594.YIWUpl.rst | 2 ++ Parser/myreadline.c | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Security/2025-11-02-16-23-17.gh-issue-140594.YIWUpl.rst diff --git a/Misc/NEWS.d/next/Security/2025-11-02-16-23-17.gh-issue-140594.YIWUpl.rst b/Misc/NEWS.d/next/Security/2025-11-02-16-23-17.gh-issue-140594.YIWUpl.rst new file mode 100644 index 00000000000000..6edea74d2e1a3d --- /dev/null +++ b/Misc/NEWS.d/next/Security/2025-11-02-16-23-17.gh-issue-140594.YIWUpl.rst @@ -0,0 +1,2 @@ +Fixed heap-buffer-overflow in PyOS_StdioReadline when encountering null +bytes in interactive input. Patch by Shamil Abdulaev. diff --git a/Parser/myreadline.c b/Parser/myreadline.c index 64e8f5383f0602..3a80f476b78bf0 100644 --- a/Parser/myreadline.c +++ b/Parser/myreadline.c @@ -344,7 +344,7 @@ PyOS_StdioReadline(FILE *sys_stdin, FILE *sys_stdout, const char *prompt) break; } n += strlen(p + n); - } while (p[n-1] != '\n'); + } while (n > 0 && p[n-1] != '\n'); pr = (char *)PyMem_RawRealloc(p, n+1); if (pr == NULL) { From 71d79cad8a55d8a136a6856a4309d7016701f897 Mon Sep 17 00:00:00 2001 From: Shamil Abdulaev Date: Sun, 2 Nov 2025 19:45:15 +0300 Subject: [PATCH 02/13] Add test for NUL byte in interactive mode --- Lib/test/test_cmd_line.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/Lib/test/test_cmd_line.py b/Lib/test/test_cmd_line.py index 3ed7a360d64e3c..952b57794c04c6 100644 --- a/Lib/test/test_cmd_line.py +++ b/Lib/test/test_cmd_line.py @@ -201,6 +201,27 @@ def test_run_module_bug1764407(self): self.assertTrue(data.find(b'1 loop') != -1) self.assertTrue(data.find(b'__main__.Timer') != -1) + @support.cpython_only + def test_null_byte_in_interactive_mode(self): + # gh-140594: heap-buffer-underflow в PyOS_StdioReadline при \0 в интерактивном вводе + env = os.environ.copy() + env.pop('PYTHONSTARTUP', None) + args = [sys.executable, '-I', '-S', '-q', '-i'] + p = subprocess.Popen( + args, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + ) + out, _ = p.communicate(b'\x00', timeout=10) + self.assertEqual( + p.returncode, 0, + msg=f"Interpreter aborted on NUL input, output:\n{out[:500]!r}" + ) + if out: + self.assertNotIn(b'AddressSanitizer', out) + self.assertNotIn(b'ERROR:', out) + def test_relativedir_bug46421(self): # Test `python -m unittest` with a relative directory beginning with ./ # Note: We have to switch to the project's top module's directory, as per From 42fdcfb84ff9765873089aaf02ce138e7b6fb25e Mon Sep 17 00:00:00 2001 From: Shamil Abdulaev Date: Sun, 2 Nov 2025 20:07:35 +0300 Subject: [PATCH 03/13] test: refactor null byte interactive mode test to use EnvironmentVarGuard --- Lib/test/test_cmd_line.py | 34 ++++++++++++++++------------------ 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/Lib/test/test_cmd_line.py b/Lib/test/test_cmd_line.py index 952b57794c04c6..6572443b372090 100644 --- a/Lib/test/test_cmd_line.py +++ b/Lib/test/test_cmd_line.py @@ -203,24 +203,22 @@ def test_run_module_bug1764407(self): @support.cpython_only def test_null_byte_in_interactive_mode(self): - # gh-140594: heap-buffer-underflow в PyOS_StdioReadline при \0 в интерактивном вводе - env = os.environ.copy() - env.pop('PYTHONSTARTUP', None) - args = [sys.executable, '-I', '-S', '-q', '-i'] - p = subprocess.Popen( - args, - stdin=subprocess.PIPE, - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, - ) - out, _ = p.communicate(b'\x00', timeout=10) - self.assertEqual( - p.returncode, 0, - msg=f"Interpreter aborted on NUL input, output:\n{out[:500]!r}" - ) - if out: - self.assertNotIn(b'AddressSanitizer', out) - self.assertNotIn(b'ERROR:', out) + # gh-140594: heap-buffer-underflow in PyOS_StdioReadline when a NUL (\0) is present in interactive input + with os_helper.EnvironmentVarGuard() as env: + env.unset('PYTHONSTARTUP') + args = [sys.executable, '-I', '-S', '-q', '-i'] + p = subprocess.Popen( + args, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + env=env.copy(), + ) + out, _ = p.communicate(b'\x00', timeout=10) + self.assertEqual( + p.returncode, 0, + msg=f"Interpreter aborted on NUL input, output:\n{out[:500]!r}" + ) def test_relativedir_bug46421(self): # Test `python -m unittest` with a relative directory beginning with ./ From b09a4c80efbeebb8faf351920484cd087612b81e Mon Sep 17 00:00:00 2001 From: Shamil Abdulaev Date: Sun, 2 Nov 2025 20:11:29 +0300 Subject: [PATCH 04/13] docs: improve security advisory for PyOS_StdioReadline fix --- .../Security/2025-11-02-16-23-17.gh-issue-140594.YIWUpl.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Misc/NEWS.d/next/Security/2025-11-02-16-23-17.gh-issue-140594.YIWUpl.rst b/Misc/NEWS.d/next/Security/2025-11-02-16-23-17.gh-issue-140594.YIWUpl.rst index 6edea74d2e1a3d..54b3127426ca4d 100644 --- a/Misc/NEWS.d/next/Security/2025-11-02-16-23-17.gh-issue-140594.YIWUpl.rst +++ b/Misc/NEWS.d/next/Security/2025-11-02-16-23-17.gh-issue-140594.YIWUpl.rst @@ -1,2 +1,2 @@ -Fixed heap-buffer-overflow in PyOS_StdioReadline when encountering null -bytes in interactive input. Patch by Shamil Abdulaev. +Fix a buffer overflow in :c:func:`!PyOS_StdioReadline` when a single NULL character is read from the standard input. +Patch by Shamil Abdulaev. From 976dedb772980226bd898c67a74122b1edefd033 Mon Sep 17 00:00:00 2001 From: Shamil Abdulaev Date: Sun, 2 Nov 2025 20:16:16 +0300 Subject: [PATCH 05/13] test: remove unnecessary -S and -q flags from null byte test --- Lib/test/test_cmd_line.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_cmd_line.py b/Lib/test/test_cmd_line.py index 6572443b372090..cce790e21fbe4d 100644 --- a/Lib/test/test_cmd_line.py +++ b/Lib/test/test_cmd_line.py @@ -206,7 +206,9 @@ def test_null_byte_in_interactive_mode(self): # gh-140594: heap-buffer-underflow in PyOS_StdioReadline when a NUL (\0) is present in interactive input with os_helper.EnvironmentVarGuard() as env: env.unset('PYTHONSTARTUP') - args = [sys.executable, '-I', '-S', '-q', '-i'] + # -I: isolated mode (ignore env vars, no user site-packages) + # -i: interactive mode (required to trigger the bug) + args = [sys.executable, '-I', '-i'] p = subprocess.Popen( args, stdin=subprocess.PIPE, From 3f4a6be66cae4cfd8fc7f21789e79e02e0620b04 Mon Sep 17 00:00:00 2001 From: Shamil Date: Sun, 2 Nov 2025 21:03:54 +0300 Subject: [PATCH 06/13] Update Misc/NEWS.d/next/Security/2025-11-02-16-23-17.gh-issue-140594.YIWUpl.rst MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com> --- .../Security/2025-11-02-16-23-17.gh-issue-140594.YIWUpl.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/Security/2025-11-02-16-23-17.gh-issue-140594.YIWUpl.rst b/Misc/NEWS.d/next/Security/2025-11-02-16-23-17.gh-issue-140594.YIWUpl.rst index 54b3127426ca4d..b263335a3d45fe 100644 --- a/Misc/NEWS.d/next/Security/2025-11-02-16-23-17.gh-issue-140594.YIWUpl.rst +++ b/Misc/NEWS.d/next/Security/2025-11-02-16-23-17.gh-issue-140594.YIWUpl.rst @@ -1,2 +1,2 @@ -Fix a buffer overflow in :c:func:`!PyOS_StdioReadline` when a single NULL character is read from the standard input. +Fix a buffer overflow when a single NULL character is read from the standard input. Patch by Shamil Abdulaev. From 2392f76bf1001dd9816335a727a6e68a009398d8 Mon Sep 17 00:00:00 2001 From: Shamil Abdulaev Date: Sun, 2 Nov 2025 21:16:26 +0300 Subject: [PATCH 07/13] test: simplify null byte interactive mode test to use spawn_python --- Lib/test/test_cmd_line.py | 27 +++++++++------------------ 1 file changed, 9 insertions(+), 18 deletions(-) diff --git a/Lib/test/test_cmd_line.py b/Lib/test/test_cmd_line.py index cce790e21fbe4d..89b4a5d51880e6 100644 --- a/Lib/test/test_cmd_line.py +++ b/Lib/test/test_cmd_line.py @@ -203,24 +203,15 @@ def test_run_module_bug1764407(self): @support.cpython_only def test_null_byte_in_interactive_mode(self): - # gh-140594: heap-buffer-underflow in PyOS_StdioReadline when a NUL (\0) is present in interactive input - with os_helper.EnvironmentVarGuard() as env: - env.unset('PYTHONSTARTUP') - # -I: isolated mode (ignore env vars, no user site-packages) - # -i: interactive mode (required to trigger the bug) - args = [sys.executable, '-I', '-i'] - p = subprocess.Popen( - args, - stdin=subprocess.PIPE, - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, - env=env.copy(), - ) - out, _ = p.communicate(b'\x00', timeout=10) - self.assertEqual( - p.returncode, 0, - msg=f"Interpreter aborted on NUL input, output:\n{out[:500]!r}" - ) + # gh-140594: heap-buffer-underflow in PyOS_StdioReadline when a NUL (\0) + # is present in interactive input. The test ensures that feeding a null + # byte to the interactive prompt does not crash the interpreter. + proc = spawn_python('-I', '-i') + out, _ = proc.communicate(b'\x00', timeout=10) + self.assertEqual( + proc.returncode, 0, + msg=f"Interpreter aborted on NUL input, output:\n{out[:500]!r}" + ) def test_relativedir_bug46421(self): # Test `python -m unittest` with a relative directory beginning with ./ From cfd56e63ef5b48fea4de159462ca23b20fa6061a Mon Sep 17 00:00:00 2001 From: Shamil Date: Sun, 2 Nov 2025 21:28:42 +0300 Subject: [PATCH 08/13] Update Lib/test/test_cmd_line.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com> --- Lib/test/test_cmd_line.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/Lib/test/test_cmd_line.py b/Lib/test/test_cmd_line.py index 89b4a5d51880e6..76309120c3068b 100644 --- a/Lib/test/test_cmd_line.py +++ b/Lib/test/test_cmd_line.py @@ -208,10 +208,7 @@ def test_null_byte_in_interactive_mode(self): # byte to the interactive prompt does not crash the interpreter. proc = spawn_python('-I', '-i') out, _ = proc.communicate(b'\x00', timeout=10) - self.assertEqual( - proc.returncode, 0, - msg=f"Interpreter aborted on NUL input, output:\n{out[:500]!r}" - ) + self.assertEqual(proc.returncode, 0) def test_relativedir_bug46421(self): # Test `python -m unittest` with a relative directory beginning with ./ From 2647f029b6a4c149eb1376e631220a31805c6c23 Mon Sep 17 00:00:00 2001 From: Shamil Abdulaev Date: Sun, 2 Nov 2025 21:33:01 +0300 Subject: [PATCH 09/13] test: simplify null byte interactive mode test by removing unnecessary flags --- Lib/test/test_cmd_line.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_cmd_line.py b/Lib/test/test_cmd_line.py index 76309120c3068b..f9c86acaa01184 100644 --- a/Lib/test/test_cmd_line.py +++ b/Lib/test/test_cmd_line.py @@ -206,8 +206,8 @@ def test_null_byte_in_interactive_mode(self): # gh-140594: heap-buffer-underflow in PyOS_StdioReadline when a NUL (\0) # is present in interactive input. The test ensures that feeding a null # byte to the interactive prompt does not crash the interpreter. - proc = spawn_python('-I', '-i') - out, _ = proc.communicate(b'\x00', timeout=10) + proc = spawn_python('-i') + proc.communicate(b'\x00', timeout=10) self.assertEqual(proc.returncode, 0) def test_relativedir_bug46421(self): From 9dbeba973f1cc16c6fae6cf7864475039e5866cd Mon Sep 17 00:00:00 2001 From: Shamil Abdulaev Date: Thu, 20 Nov 2025 19:08:36 +0300 Subject: [PATCH 10/13] fix --- Parser/myreadline.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Parser/myreadline.c b/Parser/myreadline.c index 3a80f476b78bf0..ee77479ba7bdcc 100644 --- a/Parser/myreadline.c +++ b/Parser/myreadline.c @@ -344,7 +344,7 @@ PyOS_StdioReadline(FILE *sys_stdin, FILE *sys_stdout, const char *prompt) break; } n += strlen(p + n); - } while (n > 0 && p[n-1] != '\n'); + } while (n == 0 || p[n-1] != '\n'); pr = (char *)PyMem_RawRealloc(p, n+1); if (pr == NULL) { From a9827370dcaebfe578ee596d71580650845b5e98 Mon Sep 17 00:00:00 2001 From: Shamil Abdulaev Date: Thu, 20 Nov 2025 19:26:22 +0300 Subject: [PATCH 11/13] fix comment --- Lib/test/test_cmd_line.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_cmd_line.py b/Lib/test/test_cmd_line.py index f9c86acaa01184..e6c009be53cb71 100644 --- a/Lib/test/test_cmd_line.py +++ b/Lib/test/test_cmd_line.py @@ -203,7 +203,7 @@ def test_run_module_bug1764407(self): @support.cpython_only def test_null_byte_in_interactive_mode(self): - # gh-140594: heap-buffer-underflow in PyOS_StdioReadline when a NUL (\0) + # gh-140594: heap-buffer-overflow in PyOS_StdioReadline when a NULL # is present in interactive input. The test ensures that feeding a null # byte to the interactive prompt does not crash the interpreter. proc = spawn_python('-i') From 6f83dc8fd930254388885545ca55a4ee0f705930 Mon Sep 17 00:00:00 2001 From: Shamil Abdulaev Date: Fri, 21 Nov 2025 11:10:23 +0300 Subject: [PATCH 12/13] fix test docstring --- Lib/test/test_cmd_line.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Lib/test/test_cmd_line.py b/Lib/test/test_cmd_line.py index e6c009be53cb71..6e907f8575cb83 100644 --- a/Lib/test/test_cmd_line.py +++ b/Lib/test/test_cmd_line.py @@ -203,9 +203,10 @@ def test_run_module_bug1764407(self): @support.cpython_only def test_null_byte_in_interactive_mode(self): - # gh-140594: heap-buffer-overflow in PyOS_StdioReadline when a NULL - # is present in interactive input. The test ensures that feeding a null - # byte to the interactive prompt does not crash the interpreter. + # gh-140594: Fix a buffer overflow when a single NULL character is read + # from standard input in interactive mode. The test ensures that + # feeding a null byte to the interactive prompt does not crash + # the interpreter. proc = spawn_python('-i') proc.communicate(b'\x00', timeout=10) self.assertEqual(proc.returncode, 0) From 6ffaace8afcdc004578dbeac4ece887477aa69fb Mon Sep 17 00:00:00 2001 From: Shamil Abdulaev Date: Fri, 21 Nov 2025 11:13:53 +0300 Subject: [PATCH 13/13] fix news --- .../2025-11-02-16-23-17.gh-issue-140594.YIWUpl.rst | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename Misc/NEWS.d/next/{Security => Core_and_Builtins}/2025-11-02-16-23-17.gh-issue-140594.YIWUpl.rst (100%) diff --git a/Misc/NEWS.d/next/Security/2025-11-02-16-23-17.gh-issue-140594.YIWUpl.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-11-02-16-23-17.gh-issue-140594.YIWUpl.rst similarity index 100% rename from Misc/NEWS.d/next/Security/2025-11-02-16-23-17.gh-issue-140594.YIWUpl.rst rename to Misc/NEWS.d/next/Core_and_Builtins/2025-11-02-16-23-17.gh-issue-140594.YIWUpl.rst