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.

Reply via email to