Package: libncurses6
Version: 6.4-4
Severity: normal
Tags: patch

Dear Maintainer,

Found by valgrind:
  $ valgrind ./urlview text text.uv README
  ...
  ==1999505== Invalid read of size 1
  ==1999505==    at 0x4872850: waddnstr (in 
/usr/lib/x86_64-linux-gnu/libncursesw.so.6.4)
  ==1999505==    by 0x10C26E: main (urlview.c:620)
  ==1999505==  Address 0x4c78fbc is 0 bytes after a block of size 1,052 alloc'd
  ==1999505==    at 0x484582F: realloc (in 
/usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
  ==1999505==    by 0x10BD0D: main (urlview.c:610)
this corresponds to
  
https://git.sr.ht/~nabijaczleweli/urlview-ng/tree/44240e3b8ed0694f14f40f4b1169359abe452b4b/item/urlview.c#L566-621
which is a big chunk, sorry
(execstatus allocated in EXECAPPENDONE(),
 waddnstr() invoked via mvaddnstr() at the end).
but the code amounts to:
  #include <curses.h>
  #include <string.h>
  #include <stdlib.h>
  int main() {
        char * new = malloc(4);
        memcpy(new, "dupa", 4);
        initscr();
        addnstr(new, 4);
        endwin();
  }
with
  $ cc n.c $(pkgconf --cflags --libs ncursesw)
  $ valgrind ./a.out > /dev/pts/5
  ==2022105== Memcheck, a memory error detector
  ==2022105== Copyright (C) 2002-2022, and GNU GPL'd, by Julian Seward et al.
  ==2022105== Using Valgrind-3.19.0 and LibVEX; rerun with -h for copyright info
  ==2022105== Command: ./a.out
  ==2022105==
  ==2022105== Invalid read of size 1
  ==2022105==    at 0x4872850: waddnstr (in 
/usr/lib/x86_64-linux-gnu/libncursesw.so.6.4)
  ==2022105==    by 0x1091AE: main (in /home/nabijaczleweli/uwu/a.out)
  ==2022105==  Address 0x4ab9044 is 0 bytes after a block of size 4 alloc'd
  ==2022105==    at 0x48407B4: malloc (in 
/usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
  ==2022105==    by 0x109181: main (in /home/nabijaczleweli/uwu/a.out)
  ==2022105==
  ==2022105==
  ==2022105== HEAP SUMMARY:
  ==2022105==     in use at exit: 813,473 bytes in 228 blocks
  ==2022105==   total heap usage: 238 allocs, 10 frees, 821,440 bytes allocated
  ==2022105==
  ==2022105== LEAK SUMMARY:
  ==2022105==    definitely lost: 4 bytes in 1 blocks
  ==2022105==    indirectly lost: 0 bytes in 0 blocks
  ==2022105==      possibly lost: 0 bytes in 0 blocks
  ==2022105==    still reachable: 813,469 bytes in 227 blocks
  ==2022105==         suppressed: 0 bytes in 0 blocks
  ==2022105== Rerun with --leak-check=full to see details of leaked memory
  ==2022105==
  ==2022105== For lists of detected and suppressed errors, rerun with: -s
  ==2022105== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)

I hope this sample is simple enough it's obvious it's correct.

Looking at ncurses-6.4+20230625/ncurses/base/lib_addstr.c:
  NCURSES_EXPORT(int)
  waddnstr(WINDOW *win, const char *astr, int n)
  {
      const char *str = astr;
      int code = ERR;
  
      T((T_CALLED("waddnstr(%p,%s,%d)"), (void *) win, _nc_visbufn(astr, n), 
n));
  
      if (win && (str != 0)) {
          TR(TRACE_VIRTPUT | TRACE_ATTRS,
             ("... current %s", _traceattr(WINDOW_ATTRS(win))));
          code = OK;
  
          TR(TRACE_VIRTPUT, ("str is not null, length = %d",
                             ((n > 0) ? n : (int) strlen(str))));
          if (n < 0)
              n = INT_MAX;
          while ((*str != '\0') && (n-- > 0)) {
              NCURSES_CH_T ch;
              TR(TRACE_VIRTPUT, ("*str = %#o", UChar(*str)));
              SetChar(ch, UChar(*str++), A_NORMAL);
              if (_nc_waddch_nosync(win, ch) == ERR) {
                  code = ERR;
                  break;
              }
          }
          _nc_synchook(win);
      }
      TR(TRACE_VIRTPUT, ("waddnstr returns %d", code));
      returnCode(code);
  }
I see "while ((*str != '\0') && (n-- > 0)) {" is in the wrong order,
and ought to be "(n-- > 0) && (*str != '\0')" ‒ only reading from str
when it's not past the end.

A cursory glance with \bn\b at the rest of the funxions in that file
reveals:
  waddchnstr has "for (i = 0; i < n && ChCharOf(astr[i]) != '\0'; ++i) {"
             which is correct,
  waddnwstr  has "while ((*str != L('\0')) && (n-- > 0)) {"
             which should also be "while ((n-- > 0) && (*str != L('\0'))) {".

Rebuilding 6.4+20230625-2 with the changes described (obvious diff attached)
fixes the issue
  $ 
LD_LIBRARY_PATH=~/uwu/ncurses-6.4+20230625/obj-wide/usr/lib/x86_64-linux-gnu/ 
valgrind ./a.out > /dev/pts/5
  ==2162311== Memcheck, a memory error detector
  ==2162311== Copyright (C) 2002-2022, and GNU GPL'd, by Julian Seward et al.
  ==2162311== Using Valgrind-3.19.0 and LibVEX; rerun with -h for copyright info
  ==2162311== Command: ./a.out
  ==2162311==
  ==2162311==
  ==2162311== HEAP SUMMARY:
  ==2162311==     in use at exit: 813,473 bytes in 228 blocks
  ==2162311==   total heap usage: 239 allocs, 11 frees, 821,468 bytes allocated
  ==2162311==
  ==2162311== LEAK SUMMARY:
  ==2162311==    definitely lost: 4 bytes in 1 blocks
  ==2162311==    indirectly lost: 0 bytes in 0 blocks
  ==2162311==      possibly lost: 0 bytes in 0 blocks
  ==2162311==    still reachable: 813,469 bytes in 227 blocks
  ==2162311==         suppressed: 0 bytes in 0 blocks
  ==2162311== Rerun with --leak-check=full to see details of leaked memory
  ==2162311==
  ==2162311== For lists of detected and suppressed errors, rerun with: -s
  ==2162311== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
but there are probably other funxions that take string_view^W(char*, int)s
and need to be evaluated, and I don't know what those would be.

Best,
наб

-- System Information:
Debian Release: 12.2
  APT prefers stable-updates
  APT policy: (500, 'stable-updates'), (500, 'stable-security'), (500, 
'stable-debug'), (500, 'stable')
Architecture: amd64 (x86_64)
Foreign Architectures: i386

Kernel: Linux 6.1.0-9-amd64 (SMP w/24 CPU threads; PREEMPT)
Kernel taint flags: TAINT_PROPRIETARY_MODULE, TAINT_FIRMWARE_WORKAROUND, 
TAINT_OOT_MODULE, TAINT_UNSIGNED_MODULE
Locale: LANG=en_GB.UTF-8, LC_CTYPE=en_GB.UTF-8 (charmap=UTF-8), 
LANGUAGE=en_GB:en
Shell: /bin/sh linked to /usr/bin/dash
Init: systemd (via /run/systemd/system)
LSM: AppArmor: enabled

Versions of packages libncurses6 depends on:
ii  libc6      2.36-9+deb12u3
ii  libtinfo6  6.4-4

Versions of packages libncurses6 recommends:
ii  libgpm2  1.20.7-10+b1

libncurses6 suggests no packages.

-- no debconf information
--- ncurses-6.4+20230625.orig/ncurses/base/lib_addstr.c
+++ ncurses-6.4+20230625/ncurses/base/lib_addstr.c
@@ -64,7 +64,7 @@ waddnstr(WINDOW *win, const char *astr,
                           ((n > 0) ? n : (int) strlen(str))));
        if (n < 0)
            n = INT_MAX;
-       while ((*str != '\0') && (n-- > 0)) {
+       while ((n-- > 0) && (*str != '\0')) {
            NCURSES_CH_T ch;
            TR(TRACE_VIRTPUT, ("*str = %#o", UChar(*str)));
            SetChar(ch, UChar(*str++), A_NORMAL);
@@ -237,7 +237,7 @@ waddnwstr(WINDOW *win, const wchar_t *st
                           ((n > 0) ? n : (int) wcslen(str))));
        if (n < 0)
            n = INT_MAX;
-       while ((*str != L('\0')) && (n-- > 0)) {
+       while ((n-- > 0) && (*str != L('\0'))) {
            NCURSES_CH_T ch;
            TR(TRACE_VIRTPUT, ("*str[0] = %#lx", (unsigned long) *str));
            SetChar(ch, *str++, A_NORMAL);

Attachment: signature.asc
Description: PGP signature

Reply via email to