Skip to content

Commit 68c3724

Browse files
nirsberkerpeksag
authored andcommitted
[3.5] 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 3bbdf99 commit 68c3724

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
@@ -195,13 +195,57 @@ def display(substitution, matches, longest_match_length):
195195
self.assertIn(b"result " + expected + b"\r\n", output)
196196
self.assertIn(b"history " + expected + b"\r\n", output)
197197

198+
# We have 2 reasons to skip this test:
199+
# - readline: history size was added in 6.0
200+
# See https://cnswww.cns.cwru.edu/php/chet/readline/CHANGES
201+
# - editline: history size is broken on OS X 10.11.6.
202+
# Newer versions were not tested yet.
203+
@unittest.skipIf(readline._READLINE_VERSION < 0x600,
204+
"this readline version does not support history-size")
205+
@unittest.skipIf(is_editline,
206+
"editline history size configuration is broken")
207+
def test_history_size(self):
208+
history_size = 10
209+
with temp_dir() as test_dir:
210+
inputrc = os.path.join(test_dir, "inputrc")
211+
with open(inputrc, "wb") as f:
212+
f.write(b"set history-size %d\n" % history_size)
213+
214+
history_file = os.path.join(test_dir, "history")
215+
with open(history_file, "wb") as f:
216+
# history_size * 2 items crashes readline
217+
data = b"".join(b"item %d\n" % i
218+
for i in range(history_size * 2))
219+
f.write(data)
220+
221+
script = """
222+
import os
223+
import readline
224+
225+
history_file = os.environ["HISTORY_FILE"]
226+
readline.read_history_file(history_file)
227+
input()
228+
readline.write_history_file(history_file)
229+
"""
230+
231+
env = dict(os.environ)
232+
env["INPUTRC"] = inputrc
233+
env["HISTORY_FILE"] = history_file
234+
235+
run_pty(script, input=b"last input\r", env=env)
236+
237+
with open(history_file, "rb") as f:
238+
lines = f.readlines()
239+
self.assertEqual(len(lines), history_size)
240+
self.assertEqual(lines[-1].strip(), b"last input")
241+
198242

199-
def run_pty(script, input=b"dummy input\r"):
243+
def run_pty(script, input=b"dummy input\r", env=None):
200244
pty = import_module('pty')
201245
output = bytearray()
202246
[master, slave] = pty.openpty()
203247
args = (sys.executable, '-c', script)
204-
proc = subprocess.Popen(args, stdin=slave, stdout=slave, stderr=slave)
248+
proc = subprocess.Popen(args, stdin=slave, stdout=slave, stderr=slave, env=env)
205249
os.close(slave)
206250
with ExitStack() as cleanup:
207251
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
@@ -1330,15 +1330,17 @@ call_readline(FILE *sys_stdin, FILE *sys_stdout, const char *prompt)
13301330
if (n > 0) {
13311331
const char *line;
13321332
int length = _py_get_history_length();
1333-
if (length > 0)
1333+
if (length > 0) {
1334+
HIST_ENTRY *hist_ent;
13341335
#ifdef __APPLE__
13351336
if (using_libedit_emulation) {
13361337
/* handle older 0-based or newer 1-based indexing */
1337-
line = (const char *)history_get(length + libedit_history_start - 1)->line;
1338+
hist_ent = history_get(length + libedit_history_start - 1);
13381339
} else
13391340
#endif /* __APPLE__ */
1340-
line = (const char *)history_get(length)->line;
1341-
else
1341+
hist_ent = history_get(length);
1342+
line = hist_ent ? hist_ent->line : "";
1343+
} else
13421344
line = "";
13431345
if (strcmp(p, line))
13441346
add_history(p);

0 commit comments

Comments
 (0)