#1036302 free(): double free detected in tcache 2 during history search

Package:
bash
Source:
bash
Description:
GNU Bourne Again SHell
Submitter:
Date:
2025-03-30 21:39:02 UTC
Severity:
normal
#1036302#5
Date:
2023-05-18 23:33:51 UTC
From:
To:
Dear Maintainer,

Using history-search-backward and -forward can cause bash to die with
an error:

	free(): double free detected in tcache 2
	Aborted (core dumped)

This is easily replicated by binding keys to run the history-search-
functions. Start up a fresh bash shell and type the following.

	bind '"\C-p": history-search-backward'
	bind '"\C-n": history-search-forward'
	^P^U^P^U^N^J

Bash will immediately crash.

Note ^ means hold down the control key while pressing the next letter.
So, in the above example, you'd by hitting:

	Control-P		# history-search-backward
	Control-U		# unix-line-discard
	Control-P		# history-search-backward
	Control-U		# unix-line-discard
	Control-N		# history-search-forward
	Control-J		# accept-line

#1036302#10
Date:
2023-06-02 10:01:35 UTC
From:
To:
Hello,
below is the top of a valgrind run
with dbgsym package installed.

Kind regards,
Bernhard



benutzer@debian:~$ valgrind bash
==1114== Memcheck, a memory error detector
==1114== Copyright (C) 2002-2022, and GNU GPL'd, by Julian Seward et al.
==1114== Using Valgrind-3.19.0 and LibVEX; rerun with -h for copyright info
==1114== Command: bash
==1114==
benutzer@debian:~$ bind '"\C-p": history-search-backward'
benutzer@debian:~$ bind '"\C-n": history-search-forward'
benutzer@debian:~$

	Control-P		# history-search-backward
	Control-U		# unix-line-discard
	Control-P		# history-search-backward
	Control-U		# unix-line-discard
	Control-N		# history-search-forward
	Control-J		# accept-line

==1114== Invalid read of size 4
==1114==    at 0x1E9D04: rl_do_undo (undo.c:186)
==1114==    by 0x1EA0B4: rl_revert_line (undo.c:337)
==1114==    by 0x1CD86C: readline_internal_teardown (readline.c:498)
==1114==    by 0x1CEC7B: readline_internal (readline.c:734)
==1114==    by 0x1CEC7B: readline (readline.c:387)
==1114==    by 0x13AFE1: yy_readline_get (parse.y:1528)
==1114==    by 0x13DAE0: yy_getc (parse.y:1462)
==1114==    by 0x13DAE0: shell_getc (parse.y:2393)
==1114==    by 0x13FF5A: read_token.constprop.0 (parse.y:3402)
==1114==    by 0x1440BA: yylex (parse.y:2890)
==1114==    by 0x1440BA: yyparse (y.tab.c:1854)
==1114==    by 0x13A5B5: parse_command (eval.c:348)
==1114==    by 0x13A743: read_command (eval.c:392)
==1114==    by 0x13A8F5: reader_loop (eval.c:139)
==1114==    by 0x1393D8: main (shell.c:833)
==1114==  Address 0x53ba808 is 24 bytes inside a block of size 32 free'd
==1114==    at 0x484317B: free (vg_replace_malloc.c:872)
==1114==    by 0x1E9ABA: _rl_free_undo_list (undo.c:111)
==1114==    by 0x1F03DF: _rl_free_saved_history_line (misc.c:396)
==1114==    by 0x1D4286: rl_history_search_forward (search.c:647)
==1114==    by 0x1CDD96: _rl_dispatch_subseq (readline.c:916)
==1114==    by 0x1CE371: _rl_dispatch (readline.c:860)
==1114==    by 0x1CE371: readline_internal_char (readline.c:675)
==1114==    by 0x1CEC64: readline_internal_charloop (readline.c:721)
==1114==    by 0x1CEC64: readline_internal (readline.c:733)
==1114==    by 0x1CEC64: readline (readline.c:387)
==1114==    by 0x13AFE1: yy_readline_get (parse.y:1528)
==1114==    by 0x13DAE0: yy_getc (parse.y:1462)
==1114==    by 0x13DAE0: shell_getc (parse.y:2393)
==1114==    by 0x13FF5A: read_token.constprop.0 (parse.y:3402)
==1114==    by 0x1440BA: yylex (parse.y:2890)
==1114==    by 0x1440BA: yyparse (y.tab.c:1854)
==1114==    by 0x13A5B5: parse_command (eval.c:348)
==1114==  Block was alloc'd at
==1114==    at 0x48407B4: malloc (vg_replace_malloc.c:381)
==1114==    by 0x1A2C8D: xmalloc (xmalloc.c:114)
==1114==    by 0x1E9A4E: alloc_undo_entry (undo.c:75)
==1114==    by 0x1E9A4E: rl_add_undo (undo.c:92)
==1114==    by 0x1EDB41: rl_delete_text (text.c:152)
==1114==    by 0x1E8E4C: rl_kill_text (kill.c:177)
==1114==    by 0x1E9466: rl_unix_line_discard (kill.c:412)
==1114==    by 0x1CDD96: _rl_dispatch_subseq (readline.c:916)
==1114==    by 0x1CE371: _rl_dispatch (readline.c:860)
==1114==    by 0x1CE371: readline_internal_char (readline.c:675)
==1114==    by 0x1CEC64: readline_internal_charloop (readline.c:721)
==1114==    by 0x1CEC64: readline_internal (readline.c:733)
==1114==    by 0x1CEC64: readline (readline.c:387)
==1114==    by 0x13AFE1: yy_readline_get (parse.y:1528)
==1114==    by 0x13DAE0: yy_getc (parse.y:1462)
==1114==    by 0x13DAE0: shell_getc (parse.y:2393)
==1114==    by 0x13FF5A: read_token.constprop.0 (parse.y:3402)
==1114==
==1114== Invalid read of size 4
...
==1114==
benutzer@debian:~$ exit
...
==1114== ERROR SUMMARY: 85 errors from 13 contexts (suppressed: 0 from 0)
benutzer@debian:~$

#1036302#15
Date:
2023-08-09 02:56:21 UTC
From:
To:
Thank you for narrowing it down, Bernhard. I’ve further isolated the
problem to libreadline. I have attached a program based on the sample code
from the libreadline documentation which exhibits the same behaviour.

Example run:

$ make
cc     rlgets.c  -lreadline -o rlgets

$ make test
bash -c "echo $'foo\nbar\n\cp\cu\cp\cu\cn\cj' | ./rlgets"
prompt> foo
prompt> bar
prompt>
free(): double free detected in tcache 2
bash: line 1: 556449 Done                    echo 'foo
bar

'
     556450 Aborted                 (core dumped) | ./rlgets
make: *** [Makefile:5: test] Error 134

Ben Wong
rlgets.c

#include <readline/readline.h>    /* readline() */#include
<readline/history.h>    /* add_history() */#include <stdlib.h>
/* free() */
/* A static variable for holding the line. */static char *line_read =
(char *)NULL;
/* Read a string, and return a pointer to it.
   Returns NULL on EOF. */char *
rl_gets (char *prompt)
{
  /* If the buffer has already been allocated,
     return the memory to the free pool. */
  if (line_read)
    {
      free (line_read);
      line_read = (char *)NULL;
    }

  /* Get a line from the user. */
  line_read = readline (prompt);

  /* If the line has any text in it,
     save it on the history. */
  if (line_read && *line_read)
    add_history (line_read);

  return (line_read);
}
int main(int argc, char *argv[]) {
  char *buf;

  rl_bind_key ('p'-'a'+1, rl_history_search_backward);
  rl_bind_key ('n'-'a'+1, rl_history_search_forward);
  read_history("history_file");

  while (1) {
    buf = rl_gets("prompt> ");
    if (!buf) { break; }
    if ( !strncmp(buf, "exit", 4) || !strncmp(buf, "quit", 4) ) { break; }
  }

  write_history ("history_file");
  return 0;
}

Makefile

LDLIBS+=-lreadline
rlgets:    rlgets.c

test:    rlgets
    bash -c "echo $$'foo\nbar\n\cp\cu\cp\cu\cn\cj' | ./rlgets"

clean:
    rm rlgets history_file core *~ 2>/dev/null || true

On Fri, Jun 2, 2023 at 3:01 AM Bernhard Übelacker bernhardu@mailbox.org <http://mailto:bernhardu@mailbox.org> wrote:

Hello,

#1036302#20
Date:
2025-03-30 21:29:49 UTC
From:
To:
Despite Bernhard's efforts, this is still not fixed. Plus there's a
bunch of similar sounding reports, a few on Debian's BTS alone, even if
unclear whether it's the exact same issue. This one, probably connected
to readline and for what I believe ancient, persists at any rate. I'm
seeing it apparently randomly about once per week (then usually my xterm
just disappears, session history lost) and it's getting hard to bear.
Also hard to believe something so basic and still popular as
bash/readline could be so buggy for so long.

Do other people/devs just never run into it? Are we others doing
something wrong? I read in some places, perhaps in connection with
similar bugs, that it seems to be related to "hard" bash or history
usage; when you're working fast that is, and I think I can about confirm
as much. It apparently only happens when I'm busy, but certainly never
in the background or when I'm doing nothing. Still, bash should be able
to handle this. Or else maybe not allow us to bind history search to
keys to begin with, and this is a serious sugggestion. Or at least warn
us against possible consequences. It has to be fixed in some way, any
way. As it is however it's not good.


Oliver