Skip to content

Commit 04f77d4

Browse files
nirsberkerpeksag
authored andcommitted
[3.6] 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 03e0df6 commit 04f77d4

File tree

3 files changed

+55
-7
lines changed

3 files changed

+55
-7
lines changed

Lib/test/test_readline.py

Lines changed: 47 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,57 @@ 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+
# We have 2 reasons to skip this test:
214+
# - readline: history size was added in 6.0
215+
# See https://cnswww.cns.cwru.edu/php/chet/readline/CHANGES
216+
# - editline: history size is broken on OS X 10.11.6.
217+
# Newer versions were not tested yet.
218+
@unittest.skipIf(readline._READLINE_VERSION < 0x600,
219+
"this readline version does not support history-size")
220+
@unittest.skipIf(is_editline,
221+
"editline history size configuration is broken")
222+
def test_history_size(self):
223+
history_size = 10
224+
with temp_dir() as test_dir:
225+
inputrc = os.path.join(test_dir, "inputrc")
226+
with open(inputrc, "wb") as f:
227+
f.write(b"set history-size %d\n" % history_size)
228+
229+
history_file = os.path.join(test_dir, "history")
230+
with open(history_file, "wb") as f:
231+
# history_size * 2 items crashes readline
232+
data = b"".join(b"item %d\n" % i
233+
for i in range(history_size * 2))
234+
f.write(data)
235+
236+
script = """
237+
import os
238+
import readline
239+
240+
history_file = os.environ["HISTORY_FILE"]
241+
readline.read_history_file(history_file)
242+
input()
243+
readline.write_history_file(history_file)
244+
"""
245+
246+
env = dict(os.environ)
247+
env["INPUTRC"] = inputrc
248+
env["HISTORY_FILE"] = history_file
249+
250+
run_pty(script, input=b"last input\r", env=env)
251+
252+
with open(history_file, "rb") as f:
253+
lines = f.readlines()
254+
self.assertEqual(len(lines), history_size)
255+
self.assertEqual(lines[-1].strip(), b"last input")
256+
213257

214-
def run_pty(script, input=b"dummy input\r"):
258+
def run_pty(script, input=b"dummy input\r", env=None):
215259
pty = import_module('pty')
216260
output = bytearray()
217261
[master, slave] = pty.openpty()
218262
args = (sys.executable, '-c', script)
219-
proc = subprocess.Popen(args, stdin=slave, stdout=slave, stderr=slave)
263+
proc = subprocess.Popen(args, stdin=slave, stdout=slave, stderr=slave, env=env)
220264
os.close(slave)
221265
with ExitStack() as cleanup:
222266
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)