https://sourceware.org/bugzilla/show_bug.cgi?id=34899
Bug ID: 34899
Summary: zstd SHF_COMPRESSED section shorter than ch_size is
accepted, leaving the rest of the buffer uninitialised
(bfd decompress_contents, readelf)
Product: binutils
Version: 2.48 (HEAD)
Status: UNCONFIRMED
Severity: normal
Priority: P2
Component: binutils
Assignee: unassigned at sourceware dot org
Reporter: martin.mirchev at u dot nus.edu
Target Milestone: ---
In bfd/compress.c, decompress_contents() returns success for zstd when
ZSTD_decompress() did not report an error:
533 size_t ret = ZSTD_decompress (uncompressed_buffer, uncompressed_size,
534 compressed_buffer, compressed_size);
535 return !ZSTD_isError (ret);
It never checks ret against uncompressed_size (ch_size). The zlib path does
(:555 requires both sizes to reach 0). A valid zstd frame that produces fewer
bytes than ch_size therefore "succeeds", and the tail of the malloc'd buffer
(compress.c:809) goes uninitialised to every consumer. readelf has the same
missing check in its own decompressor (binutils/readelf.c:16632-16635).
Tested on master 1ec1cee48577c7b1960ddce5a6054b3f9fab6ef5 (--with-zstd).
Release: Ubuntu 22.04 gcc 11 -g -O2. ASan: -O1 -g -fsanitize=address,undefined.
Attached (all files in bfd-zstd-short-frame.tar.gz): short.o, an x86-64 ET_REL
with a SHF_COMPRESSED .debug_zs whose
Chdr is ch_type=ELFCOMPRESS_ZSTD, ch_size=80, followed by a valid zstd frame
(one raw block, Frame_Content_Size 16) holding 16 x 'A'. Also full.o (the same
with an 80-byte frame, control) and mkobj.py, which generates both.
$ objdump -Z -s -j .debug_zs short.o (ASan build: fill byte 0xbe)
0000 41414141 41414141 41414141 41414141 AAAAAAAAAAAAAAAA
0010 bebebebe bebebebe bebebebe bebebebe ................
... (rows 0x20-0x40 the same)
$ MALLOC_PERTURB_=170 objdump -Z -s -j .debug_zs short.o (release)
0010 55555555 ... (64 bytes of allocator fill)
$ MALLOC_PERTURB_=170 objcopy --decompress-debug-sections short.o out.o
$ readelf -x .debug_zs out.o
0x00000010 55555555 55555555 ... (the uninitialised tail is now in out.o)
ASan does not flag uninitialised reads. Valgrind (release,
--track-origins=yes):
objdump -Z -s: Use of uninitialised value of size 8 ... dump_section
(objdump.c:5507)
Uninitialised value was created by a heap allocation
at malloc <- bfd_malloc (libbfd.c:291)
<- bfd_get_full_section_contents (compress.c:809)
<- dump_section (objdump.c:5453)
readelf -z -x: same kind of reports, origin xmalloc
<- uncompress_section_contents (readelf.c:16627)
objcopy --decompress-debug-sections:
Syscall param write(buf) points to uninitialised byte(s)
... origin bfd_get_full_section_contents (compress.c:809)
<- copy_section (objcopy.c:4670)
Controls: full.o prints 80 x 'A' and is valgrind-clean. The same short payload
as ELFCOMPRESS_ZLIB is rejected ("Reading section .debug_zs failed because:
bad value").
Both zstd paths should treat a decompressed size different from ch_size as an
error, the way the zlib path already does.
This issue was found by an LLM-Enhanced Static Analysis Engine and verified
manually.
--
You are receiving this mail because:
You are on the CC list for the bug.