NetBSD Problem Report #60052
From www@netbsd.org Tue Mar 3 15:03:17 2026
Return-Path: <www@netbsd.org>
Received: from mail.netbsd.org (mail.netbsd.org [199.233.217.200])
(using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)
key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256
client-signature RSA-PSS (2048 bits) client-digest SHA256)
(Client CN "mail.netbsd.org", Issuer "R13" (verified OK))
by mollari.NetBSD.org (Postfix) with ESMTPS id 27AE21A9239
for <gnats-bugs@gnats.NetBSD.org>; Tue, 3 Mar 2026 15:03:17 +0000 (UTC)
Message-Id: <20260303150315.F3C001A923C@mollari.NetBSD.org>
Date: Tue, 3 Mar 2026 15:03:15 +0000 (UTC)
From: kris@tranception.com
Reply-To: kris@tranception.com
To: gnats-bugs@NetBSD.org
Subject: libedit: history_load() off by one memory access bug
X-Send-Pr-Version: www-1.0
X-From4GNATS: "kris@tranception.com via gnats" <gnats-admin@NetBSD.org>
>Number: 60052
>Category: lib
>Synopsis: libedit: history_load() off by one memory access bug
>Confidential: no
>Severity: serious
>Priority: medium
>Responsible: lib-bug-people
>State: open
>Class: sw-bug
>Submitter-Id: net
>Arrival-Date: Tue Mar 03 15:05:00 +0000 2026
>Last-Modified: Tue Mar 03 15:10:02 +0000 2026
>Originator: Kristofer Peterson
>Release: NetBSD-current
>Organization:
Tranception Limited
>Environment:
>Description:
history_load() reads in encoded historical command lines from the history file line by line, allocating a buffer to hold the contents as decoded by strunvis().
strunvis() requires a decoding buffer at least equal in size to the encoded source string, and history_load() grows this buffer if necessary by checking the number of characters read in by getline().
As getline() returns the number of characters read but does not include the terminate null character. As such history_load() can fail to allocate a buffer large enough to accomodate the terminating null character.
In particular, when the encoded input line consists of 1,024 unescaped characters followed by a newline, a 1,025 byte buffer is required however history_load will not increase the size of the initial 1,024 decoded buffer.
>How-To-Repeat:
Append a line of 1,024 'X' and a newline to ~/.sh_history and run interactive shell.
$ sh -c 'X=ABCDEFGH;X=$X$X;X=$X$X$X$X;X=$X$X$X$X;X=$X$X$X$X;echo $X >>~/.sh_history'
$ valgrind --leak-check=full --show-leak-kinds=all /bin/sh -i /dev/null
# valgrind output
...
==95054== Invalid write of size 1
==95054== at 0x498F226: strnunvisx (in /home/dev/box/obj3/home/dev/box/src3/amd64.amd64/tmp/lib/libc.so.7)
==95054== by 0x48ACB23: history (in /home/dev/box/obj3/home/dev/box/src3/amd64.amd64/tmp/lib/libedit.so.8)
==95054== by 0x40152E2: histload (src3/bin/sh/histedit.c:137)
==95054== by 0x401AE19: main (src3/bin/sh/main.c:158)
==95054== Address 0x5822d10 is 0 bytes after a block of size 1,024 alloc'd
==95054== at 0x487C224: malloc (vg_replace_malloc.c:451)
==95054== by 0x48ACA79: history (in /home/dev/box/obj3/home/dev/box/src3/amd64.amd64/tmp/lib/libedit.so.8)
==95054== by 0x40152E2: histload (src3/bin/sh/histedit.c:137)
==95054== by 0x401AE19: main (src3/bin/sh/main.c:158)
==95054==
==95054== Invalid read of size 1
==95054== at 0x4883339: strlen (vg_replace_strmem.c:520)
==95054== by 0x4AAC66E: strdup (in /home/dev/box/obj3/home/dev/box/src3/amd64.amd64/tmp/lib/libc.so.7)
==95054== by 0x48ABC04: ??? (in /home/dev/box/obj3/home/dev/box/src3/amd64.amd64/tmp/lib/libedit.so.8)
==95054== by 0x48ACB31: history (in /home/dev/box/obj3/home/dev/box/src3/amd64.amd64/tmp/lib/libedit.so.8)
==95054== by 0x40152E2: histload (src3/bin/sh/histedit.c:137)
==95054== by 0x401AE19: main (src3/bin/sh/main.c:158)
==95054== Address 0x5822d10 is 0 bytes after a block of size 1,024 alloc'd
==95054== at 0x487C224: malloc (vg_replace_malloc.c:451)
==95054== by 0x48ACA79: history (in /home/dev/box/obj3/home/dev/box/src3/amd64.amd64/tmp/lib/libedit.so.8)
==95054== by 0x40152E2: histload (src3/bin/sh/histedit.c:137)
==95054== by 0x401AE19: main (src3/bin/sh/main.c:158)
==95054==
==95054== Invalid read of size 1
==95054== at 0x4884A00: memcpy (vg_replace_strmem.c:1168)
==95054== by 0x48ABC04: ??? (in /home/dev/box/obj3/home/dev/box/src3/amd64.amd64/tmp/lib/libedit.so.8)
==95054== by 0x48ACB31: history (in /home/dev/box/obj3/home/dev/box/src3/amd64.amd64/tmp/lib/libedit.so.8)
==95054== by 0x40152E2: histload (src3/bin/sh/histedit.c:137)
==95054== by 0x401AE19: main (src3/bin/sh/main.c:158)
==95054== Address 0x5822d10 is 0 bytes after a block of size 1,024 alloc'd
==95054== at 0x487C224: malloc (vg_replace_malloc.c:451)
==95054== by 0x48ACA79: history (in /home/dev/box/obj3/home/dev/box/src3/amd64.amd64/tmp/lib/libedit.so.8)
==95054== by 0x40152E2: histload (src3/bin/sh/histedit.c:137)
==95054== by 0x401AE19: main (src3/bin/sh/main.c:158)
...
>Fix:
diff --git a/contrib/libedit/history.c b/contrib/libedit/history.c
index 8395b329784d..b49c59bf523c 100644
--- a/contrib/libedit/history.c
+++ b/contrib/libedit/history.c
@@ -800,7 +800,7 @@ history_load(TYPE(History) *h, const char *fname)
for (i = 0; (sz = getline(&line, &llen, fp)) != -1; i++) {
if (sz > 0 && line[sz - 1] == '\n')
line[--sz] = '\0';
- if (max_size < (size_t)sz) {
+ if (max_size <= (size_t)sz) {
char *nptr;
max_size = ((size_t)sz + 1024) & (size_t)~1023;
nptr = h_realloc(ptr, max_size * sizeof(*ptr));
>Audit-Trail:
From: "Christos Zoulas" <christos@netbsd.org>
To: gnats-bugs@gnats.NetBSD.org
Cc:
Subject: PR/60052 CVS commit: src/lib/libedit
Date: Tue, 3 Mar 2026 10:06:35 -0500
Module Name: src
Committed By: christos
Date: Tue Mar 3 15:06:35 UTC 2026
Modified Files:
src/lib/libedit: history.c
Log Message:
PR/60052: Kristofer Peterson: history_load() off by one memory access bug
To generate a diff of this commit:
cvs rdiff -u -r1.64 -r1.65 src/lib/libedit/history.c
Please note that diffs are not public domain; they are subject to the
copyright notices on the relevant files.
(Contact us)
$NetBSD: query-full-pr,v 1.51 2026/08/10 02:28:17 riastradh Exp $
$NetBSD: gnats_config.sh,v 1.10 2026/05/13 22:00:09 riastradh Exp $
Copyright © 1994-2026
The NetBSD Foundation, Inc. ALL RIGHTS RESERVED.