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)

NetBSD Home
NetBSD PR Database Search

(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.