Skip to content

Commit fae8f4a

Browse files
nirsberkerpeksag
authored andcommitted
bpo-29854: Fix segfault in call_readline() (GH-728)
If history-length is set in .inputrc, and the history file is double the history size (or more), history_get(N) returns NULL, and python segfaults. Fix that by checking for NULL return value. It seems that the root cause is incorrect handling of bigger history in readline, but Python should not segfault even if readline returns unexpected value. This issue affects only GNU readline. When using libedit emulation system history size option does not work.
1 parent 25a4206 commit fae8f4a

File tree

3 files changed

+48
-7
lines changed

3 files changed

+48
-7
lines changed

Lib/test/test_readline.py

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
import sys
1010
import tempfile
1111
import unittest
12-
from test.support import import_module, unlink, TESTFN
12+
from test.support import import_module, unlink, temp_dir, TESTFN
1313
from test.support.script_helper import assert_python_ok
1414

1515
# Skip tests if there is no readline module
@@ -210,13 +210,50 @@ def display(substitution, matches, longest_match_length):
210210
self.assertIn(b"result " + expected + b"\r\n", output)
211211
self.assertIn(b"history " + expected + b"\r\n", output)
212212

213+
@unittest.skipIf(is_editline,
214+
"editline history size configuration is broken")
215+
def test_history_size(self):
216+
history_size = 10
217+
with temp_dir() as test_dir:
218+
inputrc = os.path.join(test_dir, "inputrc")
219+
with open(inputrc, "wb") as f:
220+
f.write(b"set history-size %d\n" % history_size)
221+
222+
history_file = os.path.join(test_dir, "history")
223+
with open(history_file, "wb") as f:
224+
# history_size * 2 items crashes readline
225+
data = b"".join(b"item %d\n" % i
226+
for i in range(history_size * 2))
227+
f.write(data)
228+
229+
script = """
230+
import os
231+
import readline
232+
233+
history_file = os.environ["HISTORY_FILE"]
234+
readline.read_history_file(history_file)
235+
input()
236+
readline.write_history_file(history_file)
237+
"""
238+
239+
env = dict(os.environ)
240+
env["INPUTRC"] = inputrc
241+
env["HISTORY_FILE"] = history_file
242+
243+
run_pty(script, input=b"last input\r", env=env)
244+
245+
with open(history_file, "rb") as f:
246+
lines = f.readlines()
247+
self.assertEqual(len(lines), history_size)
248+
self.assertEqual(lines[-1].strip(), b"last input")
249+
213250

214-
def run_pty(script, input=b"dummy input\r"):
251+
def run_pty(script, input=b"dummy input\r", env=None):
215252
pty = import_module('pty')
216253
output = bytearray()
217254
[master, slave] = pty.openpty()
218255
args = (sys.executable, '-c', script)
219-
proc = subprocess.Popen(args, stdin=slave, stdout=slave, stderr=slave)
256+
proc = subprocess.Popen(args, stdin=slave, stdout=slave, stderr=slave, env=env)
220257
os.close(slave)
221258
with ExitStack() as cleanup:
222259
cleanup.enter_context(proc)
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix segfault in readline when using readline's history-size option. Patch
2+
by Nir Soffer.

Modules/readline.c

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1347,15 +1347,17 @@ call_readline(FILE *sys_stdin, FILE *sys_stdout, const char *prompt)
13471347
if (should_auto_add_history && n > 0) {
13481348
const char *line;
13491349
int length = _py_get_history_length();
1350-
if (length > 0)
1350+
if (length > 0) {
1351+
HIST_ENTRY *hist_ent;
13511352
#ifdef __APPLE__
13521353
if (using_libedit_emulation) {
13531354
/* handle older 0-based or newer 1-based indexing */
1354-
line = (const char *)history_get(length + libedit_history_start - 1)->line;
1355+
hist_ent = history_get(length + libedit_history_start - 1);
13551356
} else
13561357
#endif /* __APPLE__ */
1357-
line = (const char *)history_get(length)->line;
1358-
else
1358+
hist_ent = history_get(length);
1359+
line = hist_ent ? hist_ent->line : "";
1360+
} else
13591361
line = "";
13601362
if (strcmp(p, line))
13611363
add_history(p);

0 commit comments

Comments
 (0)