Skip to content

Stack-buffer-overflow in mz_zip_reader_entry_get_hash #1023

Description

@cryptcrack

Summary

mz_zip_reader_entry_get_hash() validates a hash extra-field record's digest_size field against MZ_HASH_MAX_SIZE (256), but then uses a different, unvalidated value—the caller-supplied digest_size parameter—as the copy length when reading into a fixed 256-byte stack buffer.

By crafting a ZIP entry with two MZ_ZIP_EXTENSION_HASH (0x1a51) extra-field records—one oversized to poison reader->hash_digest_size and another with a digest_size small enough to pass validation—an attacker can trigger an attacker-controlled write of up to 65,535 bytes into the 256-byte stack buffer.

This stack-based buffer overflow can corrupt adjacent stack memory, causing a denial of service through process crashes and potentially enabling arbitrary code execution depending on the runtime environment and compiler protections.

Root cause

mz_zip_reader_entry_get_first_hash() reads the 16-bit digest_size field from the first MZ_ZIP_EXTENSION_HASH (0x1a51) extra-field record of a ZIP entry. Because this value is fully attacker-controlled and is not validated, it is stored directly in reader->hash_digest_size.

Later, mz_zip_reader_entry_close() calls:

mz_zip_reader_entry_get_hash(reader, reader->hash_algorithm,
                              expected_hash /* uint8_t[256] on the stack */,
                              reader->hash_digest_size /* attacker value */);

Inside mz_zip_reader_entry_get_hash(), the extra field is scanned again, and each hash record's own cur_digest_size is validated against MZ_HASH_MAX_SIZE (256). However, the subsequent mz_stream_read() call does not use the freshly validated cur_digest_size. Instead, it uses the caller-supplied digest_size parameter—the original, unchecked value:

if ((cur_algorithm == algorithm) && (cur_digest_size <= digest_size) &&
    (cur_digest_size <= MZ_HASH_MAX_SIZE)) {
    if (mz_stream_read(file_extra_stream, digest, digest_size) == cur_digest_size)
        return_err = MZ_OK;

Reproduction

Create a ZIP archive with a single entry whose central directory extra field contains two consecutive MZ_ZIP_EXTENSION_HASH (0x1a51) records:

  1. The first record should specify algorithm=SHA1 (20) and digest_size=2000, followed by 2000 bytes of filler data. This value is stored in reader->hash_digest_size.
  2. The second record should specify the same algorithm=SHA1 and digest_size=20, followed by 20 bytes of filler data. This value passes the size validation check.
  3. Append approximately 2000 bytes of additional padding after the second record to ensure enough data is available for the oversized read.

Extract the archive using minizip -x.

Log

=================================================================
==4579==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7b6b81f00260 at pc 0x60cbf8c7e518 bp 0x7ffe5c219a80 sp 0x7ffe5c219240
WRITE of size 2000 at 0x7b6b81f00260 thread T0
    #0 0x60cbf8c7e517 in __asan_memcpy (/root/minizip+0xc8517) (BuildId: 406e7b0b535e229af3760f62a3c7ce8cdc63f17a)
    #1 0x60cbf8cfaf62 in mz_stream_mem_read /root/minizip-ng/mz_strm_mem.c:107:5
    #2 0x60cbf8cf4445 in mz_stream_read /root/minizip-ng/mz_strm.c:40:12
    #3 0x60cbf8cdc4d2 in mz_zip_reader_entry_get_hash /root/minizip-ng/mz_zip_rw.c:515:17
    #4 0x60cbf8cdb737 in mz_zip_reader_entry_close /root/minizip-ng/mz_zip_rw.c:458:13
    #5 0x60cbf8cdc775 in mz_zip_reader_entry_save_process /root/minizip-ng/mz_zip_rw.c:602:15
    #6 0x60cbf8cdc9de in mz_zip_reader_entry_save /root/minizip-ng/mz_zip_rw.c:639:19
    #7 0x60cbf8cdd99a in mz_zip_reader_entry_save_file /root/minizip-ng/mz_zip_rw.c:779:15
    #8 0x60cbf8cde983 in mz_zip_reader_save_all /root/minizip-ng/mz_zip_rw.c:926:15
    #9 0x60cbf8cc1186 in minizip_extract /root/minizip-ng/minizip.c:404:15
    #10 0x60cbf8cc2955 in main /root/minizip-ng/minizip.c:712:15
    #11 0x7b6b83b3b1c9  (/lib/x86_64-linux-gnu/libc.so.6+0x2a1c9) (BuildId: 8e9fd827446c24067541ac5390e6f527fb5947bb)
    #12 0x7b6b83b3b28a in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2a28a) (BuildId: 8e9fd827446c24067541ac5390e6f527fb5947bb)
    #13 0x60cbf8be5784 in _start (/root/minizip+0x2f784) (BuildId: 406e7b0b535e229af3760f62a3c7ce8cdc63f17a)

Address 0x7b6b81f00260 is located in stack of thread T0 at offset 608 in frame
    #0 0x60cbf8cdb3cf in mz_zip_reader_entry_close /root/minizip-ng/mz_zip_rw.c:440

  This frame has 2 object(s):
    [32, 288) 'computed_hash' (line 446)
    [352, 608) 'expected_hash' (line 447) <== Memory access at offset 608 overflows this variable
HINT: this may be a false positive if your program uses some custom stack unwind mechanism, swapcontext or vfork
      (longjmp and C++ exceptions *are* supported)
SUMMARY: AddressSanitizer: stack-buffer-overflow (/root/minizip+0xc8517) (BuildId: 406e7b0b535e229af3760f62a3c7ce8cdc63f17a) in __asan_memcpy
Shadow bytes around the buggy address:
  0x7b6b81efff80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x7b6b81f00000: f1 f1 f1 f1 00 00 00 00 00 00 00 00 00 00 00 00
  0x7b6b81f00080: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x7b6b81f00100: 00 00 00 00 f2 f2 f2 f2 f2 f2 f2 f2 00 00 00 00
  0x7b6b81f00180: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x7b6b81f00200: 00 00 00 00 00 00 00 00 00 00 00 00[f3]f3 f3 f3
  0x7b6b81f00280: f3 f3 f3 f3 00 00 00 00 00 00 00 00 00 00 00 00
  0x7b6b81f00300: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x7b6b81f00380: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x7b6b81f00400: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x7b6b81f00480: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
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
==4579==ABORTING
$ gdb ./minizip
$ r -x poc.zip
Program received signal SIGSEGV, Segmentation fault.
0x0000414141414141 in ?? ()
(gdb) info registers 
rax            0x0                 0
rbx            0x4141414141414141  4702111234474983745
rcx            0x4                 4
rdx            0x6055c4a39968      105921487542632
rsi            0x6053c19f80b0      105912847007920
rdi            0x6053c19f5ba8      105912846998440
rbp            0x4141414141414141  0x4141414141414141
rsp            0x7ffe2139a2b0      0x7ffe2139a2b0
r8             0x6053c19d4010      105912846860304
r9             0x7                 7
r10            0x6053c19f80c0      105912847007936
r11            0x92d84bd62fc2558   661330669237708120
r12            0x6053c03f7850      105912823937104
r13            0x0                 0
r14            0x4141414141414141  4702111234474983745
r15            0x6053c19d4751      105912846862161
rip            0x414141414141      0x414141414141
eflags         0x10206             [ PF IF RF ]
cs             0x33                51
ss             0x2b                43
ds             0x0                 0
es             0x0                 0
fs             0x0                 0
gs             0x0                 0
k0             0x2002221           33563169
k1             0xffff              65535
k2             0x100ffff           16842751
k3             0x0                 0
k4             0x0                 0
k5             0x0                 0
k6             0x0                 0
k7             0x0                 0
fs_base        0x77af173d3080      131593892868224
gs_base        0x0                 0
(gdb) 

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions