[3.14] gh-144759: Fix undefined behavior from NULL pointer arithmetic in lexer (GH-144788) (#144834)

gh-144759: Fix undefined behavior from NULL pointer arithmetic in lexer (GH-144788)

Guard against NULL pointer arithmetic in `_PyLexer_remember_fstring_buffers`
and `_PyLexer_restore_fstring_buffers`. When `start` or `multi_line_start`
are NULL (uninitialized in tok_mode_stack[0]), performing `NULL - tok->buf`
is undefined behavior. Add explicit NULL checks to store -1 as sentinel
and restore NULL accordingly.

Add test_lexer_buffer_realloc_with_null_start to test_repl.py that
exercises the code path where the lexer buffer is reallocated while
tok_mode_stack[0] has NULL start/multi_line_start pointers. This
triggers _PyLexer_remember_fstring_buffers and verifies the NULL
checks prevent undefined behavior.
(cherry picked from commit e6110efd03)

Co-authored-by: Ramin Farajpour Cami <ramin.blackhat@gmail.com>
This commit is contained in:
Miss Islington (bot)
2026-02-15 16:10:15 +01:00
committed by GitHub
parent 5b0c1f780f
commit 70ecd56113
3 changed files with 24 additions and 4 deletions
+16
View File
@@ -143,6 +143,22 @@ class TestInteractiveInterpreter(unittest.TestCase):
output = kill_python(p)
self.assertEqual(p.returncode, 0)
@cpython_only
def test_lexer_buffer_realloc_with_null_start(self):
# gh-144759: NULL pointer arithmetic in the lexer when start and
# multi_line_start are NULL (uninitialized in tok_mode_stack[0])
# and the lexer buffer is reallocated while parsing long input.
long_value = "a" * 2000
user_input = dedent(f"""\
x = f'{{{long_value!r}}}'
print(x)
""")
p = spawn_repl()
p.stdin.write(user_input)
output = kill_python(p)
self.assertEqual(p.returncode, 0)
self.assertIn(long_value, output)
def test_close_stdin(self):
user_input = dedent('''
import os
@@ -0,0 +1,4 @@
Fix undefined behavior in the lexer when ``start`` and ``multi_line_start``
pointers are ``NULL`` in ``_PyLexer_remember_fstring_buffers()`` and
``_PyLexer_restore_fstring_buffers()``. The ``NULL`` pointer arithmetic
(``NULL - valid_pointer``) is now guarded with explicit ``NULL`` checks.
+4 -4
View File
@@ -13,8 +13,8 @@ _PyLexer_remember_fstring_buffers(struct tok_state *tok)
for (index = tok->tok_mode_stack_index; index >= 0; --index) {
mode = &(tok->tok_mode_stack[index]);
mode->start_offset = mode->start - tok->buf;
mode->multi_line_start_offset = mode->multi_line_start - tok->buf;
mode->start_offset = mode->start == NULL ? -1 : mode->start - tok->buf;
mode->multi_line_start_offset = mode->multi_line_start == NULL ? -1 : mode->multi_line_start - tok->buf;
}
}
@@ -27,8 +27,8 @@ _PyLexer_restore_fstring_buffers(struct tok_state *tok)
for (index = tok->tok_mode_stack_index; index >= 0; --index) {
mode = &(tok->tok_mode_stack[index]);
mode->start = tok->buf + mode->start_offset;
mode->multi_line_start = tok->buf + mode->multi_line_start_offset;
mode->start = mode->start_offset < 0 ? NULL : tok->buf + mode->start_offset;
mode->multi_line_start = mode->multi_line_start_offset < 0 ? NULL : tok->buf + mode->multi_line_start_offset;
}
}