Bug 15326

Summary: Buffer overflow with _FORTIFY_SOURCE=3 in fixdep
Product: Busybox Reporter: Sam James <sam>
Component: OtherAssignee: unassigned
Status: RESOLVED FIXED    
Severity: normal CC: busybox-cvs
Priority: P5    
Version: 1.35.x   
Target Milestone: ---   
Hardware: All   
OS: Linux   
Host: x86_64-pc-linux-gnu Target:
Build: x86_64-pc-linux-gnu
Attachments: /tmp/kxgettext.o.d

Description Sam James 2023-02-10 02:19:42 UTC
I reported this downstream first in Gentoo at https://bugs.gentoo.org/893776.

Every few attempts (not every time) to build busybox recently, I get:
```
make -j32 -l32 -j1 -s allyesconfig SKIP_SELINUX=y
*** buffer overflow detected ***: terminated
Aborted (core dumped)
make[1]: *** [scripts/Makefile.host:104: scripts/basic/docproc] Error 134
make: *** [Makefile:357: scripts_basic] Error 2
```

Isolating the command which crashes:
```
# gdb --args scripts/basic/fixdep scripts/kconfig/.kxgettext.o.d scripts/kconfig/kxgettext.o $'x86_64-pc-linux-gnu-gcc -Wp,-MD,scripts/kconfig/.kxgettext.o.d -O2 -ggdb3       -c -o scripts/kconfig/kxgettext.o scripts/kconfig/kxgettext.c'
[...]
cmd_scripts/kconfig/kxgettext.o := x86_64-pc-linux-gnu-gcc -Wp,-MD,scripts/kconfig/.kxgettext.o.d -O2 -ggdb3       -c -o scripts/kconfig/kxgettext.o scripts/kconfig/kxgettext.c

deps_scripts/kconfig/kxgettext.o := \
  scripts/kconfig/kxgettext.c \
  /usr/include/gentoo/fortify.h \
  /usr/include/stdlib.h \
  /usr/include/bits/libc-header-start.h \
  /usr/include/features.h \
  /usr/include/features-time64.h \
  /usr/include/bits/wordsize.h \
  /usr/include/bits/timesize.h \
  /usr/include/stdc-predef.h \
  /usr/include/sys/cdefs.h \
  /usr/include/bits/long-double.h \
  /usr/include/gnu/stubs.h \
  /usr/include/gnu/stubs-64.h \
  /usr/lib/llvm/16/bin/../../../../lib/clang/16/include/stddef.h \
  /usr/include/bits/waitflags.h \
  /usr/include/bits/waitstatus.h \
  /usr/include/bits/floatn.h \
  /usr/include/bits/floatn-common.h \
  /usr/include/sys/types.h \
  /usr/include/bits/types.h \
  /usr/include/bits/typesizes.h \
  /usr/include/bits/time64.h \
  /usr/include/bits/types/clock_t.h \
  /usr/include/bits/types/clockid_t.h \
  /usr/include/bits/types/time_t.h \
  /usr/include/bits/types/timer_t.h \
  /usr/include/bits/stdint-intn.h \
  /usr/include/endian.h \
  /usr/include/bits/endian.h \
  /usr/include/bits/endianness.h \
  /usr/include/bits/byteswap.h \
  /usr/include/bits/uintn-identity.h \
  /usr/include/sys/select.h \
  /usr/include/bits/select.h \
  /usr/include/bits/types/sigset_t.h \
  /usr/include/bits/types/__sigset_t.h \
  /usr/include/bits/types/struct_timeval.h \
  /usr/include/bits/types/struct_timespec.h \
  /usr/include/bits/select2.h \
  /usr/include/bits/pthreadtypes.h \
  /usr/include/bits/thread-shared-types.h \
  /usr/include/bits/pthreadtypes-arch.h \
  /usr/include/bits/atomic_wide_counter.h \
  /usr/include/bits/struct_mutex.h \
  /usr/include/bits/struct_rwlock.h \
  /usr/include/alloca.h \
  /usr/include/bits/stdlib-bsearch.h \
  /usr/include/bits/stdlib-float.h \
  /usr/include/bits/stdlib.h \
  /usr/include/string.h \
  /usr/include/bits/types/locale_t.h \
  /usr/include/bits/types/__locale_t.h \
  /usr/include/strings.h \
  /usr/include/bits/strings_fortified.h \
  /usr/include/bits/string_fortified.h \
  scripts/kconfig/lkc.h \
  scripts/kconfig/expr.h \
  /usr/include/stdio.h \
  /usr/lib/llvm/16/bin/../../../../lib/clang/16/include/stdarg.h \
  /usr/include/bits/types/__fpos_t.h \
  /usr/include/bits/types/__mbstate_t.h \
  /usr/include/bits/types/__fpos64_t.h \
  /usr/include/bits/types/__FILE.h \
  /usr/include/bits/types/FILE.h \
  /usr/include/bits/types/struct_FILE.h \
  /usr/include/bits/stdio_lim.h \
  /usr/include/bits/stdio2-decl.h \
  /usr/include/bits/stdio.h \
  /usr/include/bits/stdio2.h \
  /usr/lib/llvm/16/bin/../../../../lib/clang/16/include/stdbool.h \
  /usr/include/libintl.h \
  /usr/include/locale.h \
  /usr/include/bits/locale.h \
  scripts/kconfig/lkc_proto.h \
*** buffer overflow detected ***: terminated

Program received signal SIGABRT, Aborted.
0x00007ffff7e40f0c in ?? () from /usr/lib64/libc.so.6
(gdb) bt
#0  0x00007ffff7e40f0c in ?? () from /usr/lib64/libc.so.6
#1  0x00007ffff7defae6 in raise () from /usr/lib64/libc.so.6
#2  0x00007ffff7dd8877 in abort () from /usr/lib64/libc.so.6
#3  0x00007ffff7dd97b8 in ?? () from /usr/lib64/libc.so.6
#4  0x00007ffff7ed415b in __fortify_fail () from /usr/lib64/libc.so.6
#5  0x00007ffff7ed28c6 in __chk_fail () from /usr/lib64/libc.so.6
#6  0x0000555555555b92 in memcpy (__len=18446744073709551614, __src=<optimized out>, __dest=0x7fffffffd5f0) at /usr/include/bits/string_fortified.h:29
#7  parse_dep_file (map=map@entry=0x7ffff7fc3000, len=<optimized out>) at scripts/basic/fixdep.c:341
#8  0x0000555555555dd9 in print_deps () at scripts/basic/fixdep.c:379
#9  0x00005555555552d8 in main (argc=<optimized out>, argv=<optimized out>) at scripts/basic/fixdep.c:411
(gdb)
```

Note the large length in memcpy:
```
(gdb) b /usr/include/bits/string_fortified.h:29 if __len >= 100
warning: failed to validate condition at location 1, disabling:
  No symbol "__len" in current context.
Breakpoint 1 at 0x555555555587: /usr/include/bits/string_fortified.h:29. (4 locations)

(gdb) r
Breakpoint 1.4, memcpy (__len=18446744073709551614, __src=0x7ffff7fc3a24, __dest=0x7fffffffd5f0) at /usr/include/bits/string_fortified.h:29
29        return __builtin___memcpy_chk (__dest, __src, __len,
(gdb) bt full

#0  memcpy (__len=18446744073709551614, __src=0x7ffff7fc3a24, __dest=0x7fffffffd5f0) at /usr/include/bits/string_fortified.h:29
No locals.
#1  parse_dep_file (map=map@entry=0x7ffff7fc3000, len=<optimized out>) at scripts/basic/fixdep.c:341
        m = 0x7ffff7fc3a24 ""
        end = 0x7ffff7fc3a24 ""
        p = 0x7ffff7fc3a22 "\n\n"
        s = 0x7fffffffd5f0 "scripts/kconfig/lkc_proto.h"
#2  0x0000555555555dd9 in print_deps () at scripts/basic/fixdep.c:379
        st = {st_dev = 64512, st_ino = 1074419, st_nlink = 1, st_mode = 33188, st_uid = 250, st_gid = 250, __pad0 = 0, st_rdev = 0, st_size = 2596, st_blksize = 4096, st_blocks = 8, st_atim = {
            tv_sec = 1675994249, tv_nsec = 352732056}, st_mtim = {tv_sec = 1675994249, tv_nsec = 352732056}, st_ctim = {tv_sec = 1675994249, tv_nsec = 352732056}, __glibc_reserved = {0, 0, 0}}
        fd = 3
        map = 0x7ffff7fc3000
#3  0x00005555555552d8 in main (argc=<optimized out>, argv=<optimized out>) at scripts/basic/fixdep.c:411
No locals.
(gdb)
```

I've uploaded a copy of the unpacked source tarball with the needed dep files at https://dev.gentoo.org/~sam/bugs/busybox/busybox-1.35.0-bug893776-fortify-source.tar.xz.
Comment 1 Sam James 2023-02-10 02:25:54 UTC
I can reproduce in a fresh tarball of 1.36.0 after untarring with:
1. make HOSTCCFLAGS="-O2 -ggdb3 -D_FORTIFY_SOURCE=3" scripts_basic
2. cp /tmp/kxgettext.o.d scripts/kconfig/
3. scripts/basic/fixdep scripts/kconfig/.kxgettext.o.d scripts/kconfig/kxgettext.o $'x86_64-pc-linux-gnu-gcc -Wp,-MD,scripts/kconfig/.kxgettext.o.d -O2 -ggdb3       -c -o scripts/kconfig/kxgettext.o scripts/kconfig/kxgettext.c'
Comment 2 Sam James 2023-02-10 02:29:36 UTC
Created attachment 9501 [details]
/tmp/kxgettext.o.d
Comment 3 Sam James 2023-02-10 02:34:29 UTC
fwiw I can reproduce with:
- gcc 13.0.1 20230205 (unreleased)
- gcc 12.2.1 20230204
- clang 15.0.7
Comment 4 Sam James 2023-02-10 02:35:07 UTC
... and without FORTIFY_SOURCE, it just corrupts:
```
Program received signal SIGSEGV, Segmentation fault.
0x0000682e6f746f72 in ?? ()
(gdb) bt
#0  0x0000682e6f746f72 in ?? ()
#1  0x0000000000000000 in ?? ()
``
Comment 5 Sam James 2023-02-10 02:52:13 UTC
huh, I can get it to crash with scripts/kconfig/.kxgettext.o.d as:
```
/tmp/garbage: \
  /usr/include/stdlib.h \
```

```
# scripts/basic/fixdep scripts/kconfig/.kxgettext.o.d scripts/kconfig/kxgettext.o $'clang-15 -Wp,-MD,scripts/kconfig/.kxgettext.o.d -O2 -ggdb3 -c -o scripts/kconfig/kxqgettext.o scripts/kconfig/kxgettext.c'
cmd_scripts/kconfig/kxgettext.o := clang-15 -Wp,-MD,scripts/kconfig/.kxgettext.o.d -O2 -ggdb3 -c -o scripts/kconfig/kxqgettext.o scripts/kconfig/kxgettext.c

deps_scripts/kconfig/kxgettext.o := \
  /usr/include/stdlib.h \
Segmentation fault (core dumped)
```
Comment 6 Denys Vlasenko 2023-02-16 11:37:07 UTC
(In reply to Sam James from comment #0)


        p = memchr(m, ':', len);
        if (!p) {
                fprintf(stderr, "fixdep: parse error\n");
                exit(1);
        }
        memcpy(s, m, p-m); s[p-m] = 0;


> Note the large length in memcpy:
> Breakpoint 1.4, memcpy (__len=18446744073709551614, __src=0x7ffff7fc3a24, __dest=0x7fffffffd5f0) at /usr/include/bits/string_fortified.h:29
29        return __builtin___memcpy_chk (__dest, __src, __len,

So, the __len is -2.

> #1  parse_dep_file (map=map@entry=0x7ffff7fc3000, len=<optimized out>) at scripts/basic/fixdep.c:341
>        m = 0x7ffff7fc3a24 ""
>        end = 0x7ffff7fc3a24 ""
>        p = 0x7ffff7fc3a22 "\n\n"
>        s = 0x7fffffffd5f0 "scripts/kconfig/lkc_proto.h"


Well... looks like memchr(m, ':', len) returned a pointer two bytes BEFORE the start of the search string (!!!), so (p-m) evaluated to -2, which code rightly does not expect.

Can you investigate further why memchr() does this? E.g. is it getting bogus "len" parameter? Is it just buggy?
Comment 7 Denys Vlasenko 2023-02-27 12:19:28 UTC
Fixed in git.