Installed screen again, hit a segfault, reportbug said I have this open already.
Repro on bookworm/amd64 and sid/x32.
This time with some more interesting gubbins, hopefully,
because strace on bookworm ends with
-- >8 --
3234294 ioctl(1, TCGETS, 0x7fff4f414180) = -1 ENOTTY (Inappropriate ioctl for device)
3234294 access("/etc/terminfo/s/st-256color", R_OK) = 0
3234294 getuid() = 1000
3234294 setfsuid(1000) = 1000
3234294 getgid() = 100
3234294 setfsgid(100) = 100
3234294 openat(AT_FDCWD, "/etc/terminfo/s/st-256color", O_RDONLY) = 5
3234294 geteuid() = 1000
3234294 setfsuid(1000) = 1000
3234294 getegid() = 100
3234294 setfsgid(100) = 100
3234294 newfstatat(5, "", {st_mode=S_IFREG|0644, st_size=2565, ...}, AT_EMPTY_PATH) = 0
3234294 read(5, "\32\1(\0\35\0\17\0i\1W\6st-256color| simplet"..., 30720) = 2565
3234294 read(5, "", 27648) = 0
3234294 close(5) = 0
3234294 ioctl(2, TCGETS, 0x7fff4f414180) = -1 ENOTTY (Inappropriate ioctl for device)
3234294 ioctl(2, TCGETS, 0x7fff4f414110) = -1 ENOTTY (Inappropriate ioctl for device)
3234294 writev(2, [{iov_base="corrupted size vs. prev_size", iov_len=28}, {iov_base="\n", iov_len=1}], 2) = 29
3234294 mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7feaabb5c000
3234294 rt_sigprocmask(SIG_UNBLOCK, [ABRT], NULL, 8) = 0
3234294 gettid() = 3234294
3234294 getpid() = 3234294
3234294 tgkill(3234294, 3234294, SIGABRT) = 0
3234294 --- SIGABRT {si_signo=SIGABRT, si_code=SI_TKILL, si_pid=3234294, si_uid=1000} ---
3234294 +++ killed by SIGABRT +++
-- >8 --
for the child and then the top-level process hangs
(well, it loops in pause() with ^C ignored; ^Z/^\ work normally).
Pretty sure this is a malloc sanity-check abort message?
On sid I just get "[screen caught signal 11. (core dumped)]" and no coredump.
No valgrind for x32 so whatever.
On bookworm for valgrind screen ls I get:
-- >8 --
==3274260== Memcheck, a memory error detector
==3274260== Copyright (C) 2002-2022, and GNU GPL'd, by Julian Seward et al.
==3274260== Using Valgrind-3.19.0 and LibVEX; rerun with -h for copyright info
==3274260== Command: screen ls
==3274260==
==3274433== Invalid write of size 1
==3274433== at 0x48468E4: strcpy (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==3274433== by 0x487E600: UnknownInlinedFun (string_fortified.h:79)
==3274433== by 0x487E600: tgetstr_sp (lib_termcap.c:380)
==3274433== by 0x135FDA: e_tgetstr (termcap.c:1505)
==3274433== by 0x135FDA: InitTermcap (termcap.c:157)
==3274433== by 0x112B28: main (screen.c:1390)
==3274433== Address 0x4b23b66 is 0 bytes after a block of size 1,030 alloc'd
==3274433== at 0x48407B4: malloc (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==3274433== by 0x135E29: InitTermcap (termcap.c:136)
==3274433== by 0x112B28: main (screen.c:1390)
==3274433==
==3274433== Invalid write of size 1
==3274433== at 0x48468F6: strcpy (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==3274433== by 0x487E600: UnknownInlinedFun (string_fortified.h:79)
==3274433== by 0x487E600: tgetstr_sp (lib_termcap.c:380)
==3274433== by 0x135FDA: e_tgetstr (termcap.c:1505)
==3274433== by 0x135FDA: InitTermcap (termcap.c:157)
==3274433== by 0x112B28: main (screen.c:1390)
==3274433== Address 0x4b23b68 is 2 bytes after a block of size 1,030 alloc'd
==3274433== at 0x48407B4: malloc (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==3274433== by 0x135E29: InitTermcap (termcap.c:136)
==3274433== by 0x112B28: main (screen.c:1390)
==3274433==
==3274433== Invalid read of size 1
==3274433== at 0x48467F4: __strlen_sse2 (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==3274433== by 0x487E60C: tgetstr_sp (lib_termcap.c:382)
==3274433== by 0x135FDA: e_tgetstr (termcap.c:1505)
==3274433== by 0x135FDA: InitTermcap (termcap.c:157)
==3274433== by 0x112B28: main (screen.c:1390)
==3274433== Address 0x4b23b66 is 0 bytes after a block of size 1,030 alloc'd
==3274433== at 0x48407B4: malloc (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==3274433== by 0x135E29: InitTermcap (termcap.c:136)
==3274433== by 0x112B28: main (screen.c:1390)
==3274433==
==3274433== Invalid read of size 1
==3274433== at 0x48467E2: __strlen_sse2 (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==3274433== by 0x487E60C: tgetstr_sp (lib_termcap.c:382)
==3274433== by 0x135FDA: e_tgetstr (termcap.c:1505)
==3274433== by 0x135FDA: InitTermcap (termcap.c:157)
==3274433== by 0x112B28: main (screen.c:1390)
==3274433== Address 0x4b23b69 is 3 bytes after a block of size 1,030 alloc'd
==3274433== at 0x48407B4: malloc (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==3274433== by 0x135E29: InitTermcap (termcap.c:136)
==3274433== by 0x112B28: main (screen.c:1390)
==3274433==
==3274433== Invalid read of size 1
==3274433== at 0x135E9F: InitTermcap (termcap.c:159)
==3274433== by 0x112B28: main (screen.c:1390)
==3274433== Address 0x4b23b69 is 3 bytes after a block of size 1,030 alloc'd
==3274433== at 0x48407B4: malloc (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==3274433== by 0x135E29: InitTermcap (termcap.c:136)
==3274433== by 0x112B28: main (screen.c:1390)
==3274433==
==3274433== Invalid read of size 1
==3274433== at 0x136912: InitTermcap (termcap.c:469)
==3274433== by 0x112B28: main (screen.c:1390)
==3274433== Address 0x4b23ba5 is 11 bytes before a block of size 7,952 alloc'd
==3274433== at 0x48455EF: calloc (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==3274433== by 0x4879967: _nc_build_names (comp_captab.c:3285)
==3274433== by 0x487A10A: _nc_find_type_entry (comp_hash.c:104)
==3274433== by 0x487E453: tgetnum_sp (lib_termcap.c:300)
==3274433== by 0x135FFF: e_tgetnum (termcap.c:1541)
==3274433== by 0x135FFF: InitTermcap (termcap.c:154)
==3274433== by 0x112B28: main (screen.c:1390)
==3274433==
==3274433== Invalid read of size 1
==3274433== at 0x48467F4: __strlen_sse2 (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==3274433== by 0x133A18: remap (termcap.c:528)
==3274433== by 0x13695E: InitTermcap (termcap.c:478)
==3274433== by 0x112B28: main (screen.c:1390)
==3274433== Address 0x4b23b66 is 0 bytes after a block of size 1,030 alloc'd
==3274433== at 0x48407B4: malloc (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==3274433== by 0x135E29: InitTermcap (termcap.c:136)
==3274433== by 0x112B28: main (screen.c:1390)
==3274433==
==3274433== Invalid read of size 1
==3274433== at 0x13313C: findseq_ge (termcap.c:632)
==3274433== by 0x133CE2: addmapseq (termcap.c:687)
==3274433== by 0x133CE2: remap (termcap.c:562)
==3274433== by 0x13695E: InitTermcap (termcap.c:478)
==3274433== by 0x112B28: main (screen.c:1390)
==3274433== Address 0x4b23b66 is 0 bytes after a block of size 1,030 alloc'd
==3274433== at 0x48407B4: malloc (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==3274433== by 0x135E29: InitTermcap (termcap.c:136)
==3274433== by 0x112B28: main (screen.c:1390)
==3274433==
==3274433== Invalid read of size 1
==3274433== at 0x4847A1D: memcpy@GLIBC_2.2.5 (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==3274433== by 0x133D94: UnknownInlinedFun (strings_fortified.h:25)
==3274433== by 0x133D94: addmapseq (termcap.c:710)
==3274433== by 0x133D94: remap (termcap.c:562)
==3274433== by 0x13695E: InitTermcap (termcap.c:478)
==3274433== by 0x112B28: main (screen.c:1390)
==3274433== Address 0x4b23b66 is 0 bytes after a block of size 1,030 alloc'd
==3274433== at 0x48407B4: malloc (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==3274433== by 0x135E29: InitTermcap (termcap.c:136)
==3274433== by 0x112B28: main (screen.c:1390)
==3274433==
==3274433== Invalid read of size 1
==3274433== at 0x4847A10: memcpy@GLIBC_2.2.5 (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==3274433== by 0x133D94: UnknownInlinedFun (strings_fortified.h:25)
==3274433== by 0x133D94: addmapseq (termcap.c:710)
==3274433== by 0x133D94: remap (termcap.c:562)
==3274433== by 0x13695E: InitTermcap (termcap.c:478)
==3274433== by 0x112B28: main (screen.c:1390)
==3274433== Address 0x4b23b67 is 1 bytes after a block of size 1,030 alloc'd
==3274433== at 0x48407B4: malloc (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==3274433== by 0x135E29: InitTermcap (termcap.c:136)
==3274433== by 0x112B28: main (screen.c:1390)
==3274433==
==3274433== Invalid read of size 1
==3274433== at 0x133ED9: addmapseq (termcap.c:733)
==3274433== by 0x133ED9: remap (termcap.c:562)
==3274433== by 0x13695E: InitTermcap (termcap.c:478)
==3274433== by 0x112B28: main (screen.c:1390)
==3274433== Address 0x4b23b66 is 0 bytes after a block of size 1,030 alloc'd
==3274433== at 0x48407B4: malloc (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==3274433== by 0x135E29: InitTermcap (termcap.c:136)
==3274433== by 0x112B28: main (screen.c:1390)
==3274433==
==3274433== Invalid read of size 1
==3274433== at 0x48467E2: __strlen_sse2 (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==3274433== by 0x133A18: remap (termcap.c:528)
==3274433== by 0x13695E: InitTermcap (termcap.c:478)
==3274433== by 0x112B28: main (screen.c:1390)
==3274433== Address 0x4b23b69 is 3 bytes after a block of size 1,030 alloc'd
==3274433== at 0x48407B4: malloc (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==3274433== by 0x135E29: InitTermcap (termcap.c:136)
==3274433== by 0x112B28: main (screen.c:1390)
==3274433==
valgrind: m_mallocfree.c:303 (get_bszB_as_is): Assertion 'bszB_lo == bszB_hi' failed.
valgrind: Heap block lo/hi size mismatch: lo = 1104, hi = 9093149294164390459.
This is probably caused by your program erroneously writing past the
end of a heap block and corrupting heap metadata. If you fix any
invalid writes reported by Memcheck, this assertion failure will
probably go away. Please try that before reporting this as a bug.
host stacktrace:
==3274433== at 0x580429AA: ??? (in /usr/libexec/valgrind/memcheck-amd64-linux)
==3274433== by 0x58042AC7: ??? (in /usr/libexec/valgrind/memcheck-amd64-linux)
==3274433== by 0x58042C57: ??? (in /usr/libexec/valgrind/memcheck-amd64-linux)
==3274433== by 0x5804C708: ??? (in /usr/libexec/valgrind/memcheck-amd64-linux)
==3274433== by 0x5803B31A: ??? (in /usr/libexec/valgrind/memcheck-amd64-linux)
==3274433== by 0x58039A1E: ??? (in /usr/libexec/valgrind/memcheck-amd64-linux)
==3274433== by 0x5803E1F5: ??? (in /usr/libexec/valgrind/memcheck-amd64-linux)
==3274433== by 0x58038CF8: ??? (in /usr/libexec/valgrind/memcheck-amd64-linux)
==3274433== by 0x5800F063: ??? (in /usr/libexec/valgrind/memcheck-amd64-linux)
==3274433== by 0x1002F02807: ???
==3274433== by 0x1002DB5F2F: ???
==3274433== by 0x1002DB5F17: ???
==3274433== by 0x1002DB5F2F: ???
==3274433== by 0x1C0F: ???
==3274433== by 0x100200839F: ???
sched status:
running_tid=1
Thread 1: status = VgTs_Runnable (lwpid 3274433)
==3274433== at 0x48478F1: memcpy@GLIBC_2.2.5 (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==3274433== by 0x133D94: UnknownInlinedFun (strings_fortified.h:25)
==3274433== by 0x133D94: addmapseq (termcap.c:710)
==3274433== by 0x133D94: remap (termcap.c:562)
==3274433== by 0x13695E: InitTermcap (termcap.c:478)
==3274433== by 0x112B28: main (screen.c:1390)
client stack range: [0x1FFEFE5000 0x1FFF000FFF] client SP: 0x1FFEFFEA08
valgrind stack range: [0x1002CB6000 0x1002DB5FFF] top usage: 19208 of 1048576
-- >8 --
which confirms memory corruption.
Despite the suggestive name, termcap.c appears to be from screen:
136 if ((D_tentry = (char *)malloc(TERMCAP_BUFSIZE + (extra_incap ? strlen(extra_incap) + 1 : 0))) == 0)
...
145 tp = D_tentry;
146 for (i = 0; i < T_N; i++) {
148 switch(term[i].type) {
156 case T_STR:
157 D_tcs[i].str = e_tgetstr(term[i].tcname, &tp);
1497 static char * e_tgetstr(cap, tepp) char *cap; char **tepp; {
1502 char *tep;
1503 if ((tep = findcap(cap, tepp, 0)))
1504 return (*tep == '@') ? 0 : tep;
1505 return tgetstr(cap, tepp);
tgetstr(3) doesn't seem to say what the size /should/ be.
lib_termcap.c
342 NCURSES_SP_NAME(tgetstr) (NCURSES_SP_DCLx const char *id, char **area)
378 if (area != 0
379 && *area != 0) {
380 _nc_STRCPY(*area, result, 1024);
381 result = *area;
382 *area += strlen(*area) + 1;
Which seems a little undercooked (TERMCAP_BUFSIZE is 1023), and GDBing to 380
(gdb) p result
$4 = 0x5555555e3f12 "\033[%i%p1%d;%p2%dH"
for "cm", then a few more, then the last call is for "la", and that crashes.
For "la" the result seems to be NULL, so afterward tp is "".
Rebuilding screen with asan, I see
-- >8 --
$ strace -vs9999 -foss ./screen
^\Quit
$ eval "printf %b $(grep 'write(2' ss | cut -c18- | sed 's/, [0-9]*)[ ]*=[ ]*[0-9]*$//' | tr '\n' ' ')"
=================================================================
==3391667==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x519000000e86 at pc 0x559f9edd719a bp 0x7ffd78561dd0 sp 0x7ffd78561588
WRITE of size 7 at 0x519000000e86 thread T0
#0 0x559f9edd7199 in strcpy (/home/nabijaczleweli/uwu/iselect/debian/screen-4.9.1/build/screen+0xde199) (BuildId: c61c99670bf01c54ae47da9eae7a493e6e27987e)
#1 0x7f7bd4c17600 /usr/include/x86_64-linux-gnu/bits/string_fortified.h:79:10
#2 0x7f7bd4c17600 in tgetstr_sp obj-wide/ncurses/../../ncurses/tinfo/lib_termcap.c:380:7
#3 0x559f9ee95a77 in e_tgetstr build/../termcap.c:1505:10
#4 0x559f9ee95a77 in InitTermcap build/../termcap.c:157:19
#5 0x559f9ee32ab3 in main build/../screen.c:1390:9
#6 0x7f7bd48e2249 in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58:16
#7 0x7f7bd48e2304 in __libc_start_main csu/../csu/libc-start.c:360:3
#8 0x559f9ed50960 in _start (/home/nabijaczleweli/uwu/iselect/debian/screen-4.9.1/build/screen+0x57960) (BuildId: c61c99670bf01c54ae47da9eae7a493e6e27987e)
0x519000000e86 is located 0 bytes after 1030-byte region [0x519000000a80,0x519000000e86)
allocated by thread T0 here:
#0 0x559f9edef75f in malloc (/home/nabijaczleweli/uwu/iselect/debian/screen-4.9.1/build/screen+0xf675f) (BuildId: c61c99670bf01c54ae47da9eae7a493e6e27987e)
#1 0x559f9ee957ea in InitTermcap build/../termcap.c:136:27
#2 0x559f9ee32ab3 in main build/../screen.c:1390:9
#3 0x7f7bd48e2249 in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58:16
SUMMARY: AddressSanitizer: heap-buffer-overflow (/home/nabijaczleweli/uwu/iselect/debian/screen-4.9.1/build/screen+0xde199) (BuildId: c61c99670bf01c54ae47da9eae7a493e6e27987e) in strcpy
Shadow bytes around the buggy address:
0x519000000c00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x519000000c80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x519000000d00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x519000000d80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x519000000e00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x519000000e80:[06]fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x519000000f00: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x519000000f80: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x519000001000: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x519000001080: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x519000001100: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
Shadow byte legend (one shadow byte represents 8 application bytes):
Addressable: 00
Partially addressable: 01 02 03 04 05 06 07
Heap left redzone: fa
Freed heap region: fd
Stack left redzone: f1
Stack mid redzone: f2
Stack right redzone: f3
Stack after return: f5
Stack use after scope: f8
Global redzone: f9
Global init order: f6
Poisoned by user: f7
Container overflow: fc
Array cookie: ac
Intra object redzone: bb
ASan internal: fe
Left alloca redzone: ca
Right alloca redzone: cb
==3391667==ABORTING
-- >8 --
so they definitely agree here.
This looks like a classic buffer overflow: since
382 *area += strlen(*area) + 1;
and with ltrace -l '*' -foll screen I can see the Area
3413285 screen->malloc(1030) = 0x561401f95100
and the bumps
3413285 <... tgetstr_sp resumed> ) = 0x561401f95100
3413285 <... tgetstr resumed> , "") = "\033[%i%p1%d;%p2%dH"
(this has length 16, so we'd expect the next one to be +17, and whaddaya know)
3413285 <... tgetstr_sp resumed> ) = 0x561401f95111
3413285 <... tgetstr resumed> , "") = "\033[H"
and the last non-NULL result is
3413285 <... tgetstr_sp resumed> ) = 0x561401f95556
3413285 <... tgetstr resumed> , "") = "\033OD"
which is hex(561401f95556) - hex(561401f95100) = 1110 (+ sizeof("\e0D")),
which obviously blows the 1030.
turning line 1505 into return tgetstr(cap, NULL);
(since per tgetstr(3) under ncurses the buffer is auxiliary)
kinda works (after disabling asan, else use-after-free, as expected).
Turning line 136 into ...TERMCAP_BUFSIZE * 10... fully works, even under asan.
So:
1. where-ever TERMCAP_BUFSIZE=1023 came from, and what-ever it's good for,
using it for this allocation is obviously wrong:
ncurses does strlcpy(..., 1024) to it,
so it must be 1024 for /every/ call to tgetstr() at /least/
2. this is presumably triggered by st-256color because it has more
overall capability string volume than other teletypes?
I'm not an expert on terminfo, but this is it:
https://git.suckless.org/st/file/st.info.html
3. the easiest fix is probably to just allocate 10k,
and to hell with it?
4. a more convoluted but a more correct fix is to tgetstr() to a stack 1k buffer,
then just strdup the result, and free it later
I have implemented 4., and a patch (accd'g to dpkg-source --commit vs 4.9.1-1)
is included below. It passes asan and valgrind.
It doesn't pass msan, but this is unrelated to the patch since it also
happens with the original implementation regardless of $TERM.
Someone who understands screen (not me) should probably try running it
with msan and fix the use-after-free :)
Best,