Title: Segfault when readline history is more then 2 * history size
Type: crash Stage: patch review
Components: Extension Modules Versions: Python 3.7, Python 3.6, Python 3.5, Python 2.7
Status: open Resolution:
Dependencies: Superseder:
Assigned To: Nosy List: martin.panter, nirs, serhiy.storchaka, twouters
Priority: normal Keywords:

Created on 2017-03-19 22:15 by nirs, last changed 2017-03-23 11:57 by martin.panter.

Pull Requests
URL Status Linked Edit
PR 728 open python-dev, 2017-03-19 22:27
Messages (4)
msg289865 - (view) Author: Nir Soffer (nirs) * Date: 2017-03-19 22:15
GNU readline let the user select limit the history size by setting:

$ cat ~/.inputrc 
set history-size 1000

So I cooked this test script:

$ cat 
from __future__ import print_function
import readline


print("current_history_length", readline.get_current_history_length())
print("history_length", readline.get_history_length())
print("history_get_item(1)", readline.get_history_item(1))
print("history_get_item(1000)", readline.get_history_item(1000))



And this history file generator:

$ cat make-history 
for i in range(2000):
    print("%04d" % i)

Generating .history file with 2000 entries:
$ python3 make-history > .history

Finally running the test script:

$ python3 
current_history_length 1000
history_length -1
history_get_item(1) None
history_get_item(1000) None
please crash
Segmentation fault (core dumped)

So we have few issues here:
- segfault
- history_get_item returns None for both 1 and 1000
  although we have 1000 items in history
- history_length is always wrong (-1), instead of
  the expected value (1000), set in .inputrc

Running with gdb we see:

$ gdb python3
GNU gdb (GDB) Fedora 7.12.1-46.fc25
Copyright (C) 2017 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-redhat-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
Find the GDB manual and other documentation resources online at:
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from python3...Reading symbols from /usr/lib/debug/usr/libexec/system-python.debug...done.

(gdb) run
Starting program: /usr/bin/python3
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib64/".
current_history_length 1000
history_length -1
history_get_item(1) None
history_get_item(1000) None

Program received signal SIGSEGV, Segmentation fault.
0x00007fffeff60fab in call_readline (sys_stdin=<optimized out>, sys_stdout=<optimized out>, prompt=<optimized out>) at /usr/src/debug/Python-3.5.2/Modules/readline.c:1281
1281	            line = (const char *)history_get(length)->line;

(gdb) list
1276	            if (using_libedit_emulation) {
1277	                /* handle older 0-based or newer 1-based indexing */
1278	                line = (const char *)history_get(length + libedit_history_start - 1)->line;
1279	            } else
1280	#endif /* __APPLE__ */
1281	            line = (const char *)history_get(length)->line;
1282	        else
1283	            line = "";
1284	        if (strcmp(p, line))
1285	            add_history(p);

So we assume that history_get(length) returns non-null
when length > 0, but this assumption is not correct.

In 2 other usages in Modules/readline.c, we validate
that history_get() return value is not null before
using it.

If we change the .history contents to 1999 lines, we get:

$ python3 make-history | head -1999 > .history

$ python3
current_history_length 1000
history_length -1
history_get_item(1) None
history_get_item(1000) 0999

$ wc -l .history
1000 .history

$ head -1 .history
$ tail -1 .history

So now it does not crash, but item 1 is still None.

Trying again with history file with 1000 entries:

$ python3 make-history | head -1000 > .history

$ python3
current_history_length 1000
history_length -1
history_get_item(1) 0000
history_get_item(1000) 0999
looks fine!

$ wc -l .history
1000 .history

$ head -1 history
head: cannot open 'history' for reading: No such file or directory

$ head -1 .history

$ tail -1 .history
looks fine!

Finally trying with 1001 items:

$ python3 make-history | head -1001 > .history

$ python3
current_history_length 1000
history_length -1
history_get_item(1) None
history_get_item(1000) 0999

And item 1 is wrong.

I got same results with python 2.7, 3.5 and master
on fedora 25.

The root cause seems to be a readline bug when history
file is bigger than the history-size in .inputrc,
but I could not find yet readline library documentation,
so  I don't know if the issues is incorrect usage of the
readline apis, or bug in readline.
msg289881 - (view) Author: Serhiy Storchaka (serhiy.storchaka) * (Python committer) Date: 2017-03-20 09:33
The fix LGTM. Any chance to write a test? And please add an entry in Misc/NEWS.
msg289883 - (view) Author: Nir Soffer (nirs) * Date: 2017-03-20 11:26
Sure, I'll add news entry and tests.
msg290044 - (view) Author: Martin Panter (martin.panter) * (Python committer) Date: 2017-03-23 11:57
Gnu Readline comes includes its own documentation (e.g. /usr/share/info/ on my computer). It is also at <>.

Perhaps the history_base value is relevant; see some of the comments starting at <>.

It would be interesting to see if Apple Editline is affected. According to the comment in the get_history_item function, history_get might crash before returning the null pointer. Is there some other workaround that avoids calling history_get?
Date User Action Args
2017-03-23 11:57:57martin.pantersetnosy: + martin.panter
messages: + msg290044
2017-03-20 11:26:30nirssetmessages: + msg289883
2017-03-20 09:33:28serhiy.storchakasetversions: + Python 3.6
nosy: + serhiy.storchaka, twouters

messages: + msg289881

type: crash
stage: patch review
2017-03-19 22:27:36python-devsetpull_requests: + pull_request643
2017-03-19 22:15:24nirscreate