NetBSD Problem Report #60384
From storchaka@gmail.com Mon Jun 29 21:31:40 2026
Return-Path: <storchaka@gmail.com>
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" (not verified))
by mollari.NetBSD.org (Postfix) with ESMTPS id 81E551A923A
for <gnats-bugs@gnats.NetBSD.org>; Mon, 29 Jun 2026 21:31:40 +0000 (UTC)
Message-Id: <0b5ed3f6-78b2-4c13-9d48-f15c118aed05@gmail.com>
Date: Tue, 30 Jun 2026 00:31:33 +0300
From: Serhiy Storchaka <storchaka@gmail.com>
Reply-To:
To: gnats-bugs@netbsd.org
Subject: lib/curses: delscreen() double-frees the soft-label window
(use-after-free)
X-From4GNATS: "Serhiy Storchaka via gnats" <gnats-admin@NetBSD.org>
>Number: 60384
>Category: lib
>Synopsis: lib/curses: delscreen() double-frees the soft-label window
>Confidential: no
>Severity: serious
>Priority: medium
>Responsible: lib-bug-people
>State: open
>Class: sw-bug
>Submitter-Id: net
>Arrival-Date: Mon Jun 29 21:35:00 +0000 2026
>Last-Modified: Tue Jun 30 04:52:43 +0000 2026
>Originator: Serhiy Storchaka <storchaka@gmail.com>
>Release:
>Organization:
>Environment:
netbsd-curses (portable), commit 51d179d; same code in NetBSD base
src/lib/libcurses. libcurses built with HAVE_WCHAR.
>Description:
A program that uses soft-label keys (slk_init()) and later calls
delscreen() double-frees the soft-label window, corrupting the heap.
slk_init() arranges for the soft-label line to be created by __newwin(),
which links the window into screen->winlistp; __slk_ripoffline() also
stores it in screen->slk_window. delscreen() then frees it twice
(screen.c):
__delscreen(screen); /* walks winlistp, delwin()s EVERY window */
...
__slk_free(screen); /* delwin()s screen->slk_window AGAIN */
The second delwin() operates on already-freed memory; with HAVE_WCHAR it
reaches __cursesi_win_free_nsp(), which reads win->alines[] of the freed
window.
A screen without soft labels is unaffected: screen->slk_window is NULL,
so __slk_free() is a no-op and every window is freed exactly once.
(Found via CPython's curses module, which calls delscreen() when a screen
object is collected; any program mixing slk_init() with delscreen() is
affected.)
>How-To-Repeat:
/* slk_dblfree.c */
#include <curses.h>
#include <stdio.h>
int main(void)
{
FILE *out = fopen("/dev/null", "w");
FILE *in = fopen("/dev/null", "r");
slk_init(0); /* before newterm() */
SCREEN *sp = newterm("xterm", out, in);
if (sp == NULL) {
fprintf(stderr, "newterm() failed\n");
return 1;
}
delscreen(sp); /* double-frees slk window */
return 0;
}
$ cc slk_dblfree.c -lcurses -o slk_dblfree
$ valgrind ./slk_dblfree
valgrind reports invalid reads in __cursesi_win_free_nsp() reached from
__slk_free() <- delscreen(). Removing the slk_init(0) line makes it
report 0 errors. A bare run may or may not SIGSEGV depending on heap
reuse; under a real workload it crashes reliably.
>Fix:
delwin() unlinks a window from screen->winlistp, so whichever of
__delscreen() and __slk_free() runs first removes the soft-label window
from the list and the other no longer frees it. Calling __slk_free()
before __delscreen() in delscreen() (and clearing screen->slk_window so a
repeat call is a no-op) frees the window exactly once. __slk_free() must
keep its delwin(), since it is also called standalone from __slk_init()
to release a previous soft-label window on re-init.
delscreen(SCREEN *screen)
{
- __delscreen(screen);
+ __slk_free(screen);
_cursesi_free_keymap(screen->base_keymap);
- __slk_free(screen);
+ __delscreen(screen);
...
}
__slk_free(SCREEN *screen)
{
if (screen->slk_window != NULL)
delwin(screen->slk_window);
+ screen->slk_window = NULL;
...
}
With this change the reproducer reports 0 errors under valgrind.
>Release-Note:
>Audit-Trail:
Responsible-Changed-From-To: gnats-admin->lib-bug-people
Responsible-Changed-By: dholland@NetBSD.org
Responsible-Changed-When: Tue, 30 Jun 2026 03:27:43 +0000
Responsible-Changed-Why:
sort out broken PR
>Unformatted:
(use-after-free)
(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.