Describe the bug
A malformed MP4 triggers undefined behavior inside the mp4v2 library during track setup:
- Null pointer to
strcmp — MP4File::GenerateTracks (src/mp4file.cpp:472) calls strcmp(handlerType, MP4_HINT_TRACK_TYPE) without checking for NULL. When a trak/mdia/hdlr is malformed such that the handler type string is null, strcmp(NULL, ...) is UB. UBSan reports null pointer passed as argument 1, which is declared to never be null.
- Misaligned load —
STRTOINT32 (src/mp4util.cpp:339) dereferences *(uint32_t*)s when MP4V2_INTSTRING_ALIGNMENT is not defined; atom-type bytes reached through ATOMID() in MP4Atom::ReadChildAtoms are not guaranteed 4-byte aligned, a misaligned-load UB.
Under a trap-mode build (-fno-sanitize-recover=all) the first UB aborts the process (SIGILL / exit 132); with a recoverable UBSan build the null pointer runtime error is printed.
To Reproduce
-
Build mp4v2 with ASan + UBSan (trap mode):
git clone https://github.com/TechSmith/mp4v2.git
cd mp4v2
git checkout 6727d3c5faaf8b9db9214127f6e6d9e5e8cf95c1
mkdir -p build && cd build
cmake -DMP4V2_BUILD_SHARED=OFF \
-DCMAKE_C_FLAGS="-fsanitize=address,undefined -fno-sanitize-recover=all -fno-omit-frame-pointer -g" \
-DCMAKE_CXX_FLAGS="-fsanitize=address,undefined -fno-sanitize-recover=all -fno-omit-frame-pointer -g" \
..
make -j"$(nproc)" mp4v2
-
Use the same memory-backed harness as issue #XXX (save as fuzz.cpp — feeds the MP4 bytes through MP4FileProvider into MP4Read):
#include <mp4v2/mp4v2.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct MemFile { const uint8_t* data; int64_t size; int64_t pos; };
static const uint8_t* g_data; static size_t g_size;
static void* provider_open(const char* name, MP4FileMode mode) { (void)name;(void)mode;
MemFile* f = (MemFile*)malloc(sizeof(MemFile)); f->data = g_data; f->size = (int64_t)g_size; f->pos = 0; return f; }
static int provider_seek(void* h, int64_t pos) { ((MemFile*)h)->pos = pos < 0 ? 0 : pos; return 0; }
static int provider_read(void* h, void* buf, int64_t size, int64_t* nin, int64_t max) {
(void)max; MemFile* f = (MemFile*)h; *nin = 0;
if (size <= 0 || f->pos >= f->size) return 0;
int64_t n = f->size - f->pos; if (size < n) n = size;
memcpy(buf, f->data + f->pos, (size_t)n); f->pos += n; *nin = n; return 0; }
static int provider_write(void* h, const void* b, int64_t s, int64_t* n, int64_t m) { (void)h;(void)b;(void)m; *n = s; return 0; }
static int provider_close(void* h) { free(h); return 0; }
static int64_t provider_size(void* h) { return ((MemFile*)h)->size; }
int main(int argc, char** argv) {
if (argc < 2) return 1;
FILE* f = fopen(argv[1], "rb"); if (!f) return 1;
fseek(f, 0, SEEK_END); long sz = ftell(f); fseek(f, 0, SEEK_SET);
uint8_t* buf = (uint8_t*)malloc((size_t)sz);
fread(buf, 1, (size_t)sz, f); fclose(f);
g_data = buf; g_size = (size_t)sz;
MP4FileProvider p; memset(&p, 0, sizeof(p));
p.open = provider_open; p.seek = provider_seek; p.read = provider_read;
p.write = provider_write; p.close = provider_close; p.size = provider_size;
MP4FileHandle h = MP4ReadProvider("fuzz.m4a", &p);
if (h != MP4_INVALID_FILE_HANDLE) { MP4Dump(h, false); MP4Close(h, 0); }
free(buf);
return 0;
}
-
Compile and write the exact 575-byte crash input (a malformed MP4 with a soun track whose hdlr/track setup drives GenerateTracks into strcmp(NULL, ...)). The base64 string below round-trips to the byte-identical crash input:
clang++ -std=c++11 -O1 -g -fsanitize=address,undefined -fno-sanitize-recover=all -fno-omit-frame-pointer \
-I include fuzz.cpp libmp4v2.a -o mp4v2_read_fuzzer -lpthread -lm
printf 'AAAAFGZ0eXBpc29tAAACAGlzb20AAAIBbW9vdgAAAGxtdmhkAAAAAAAAAAAAAAAAAAAD6AAAAAAA4wAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAY10cmFrAAAAXHRraGQAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAEAAAAAAAAAAAD////hAAABAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAEpbWRpYQAAACBtZGhkAAAAAAAAAAAAAAAAAAAD6AAAAABVxAAAAAAADGhkbHIAAAAAAAAAAHNvdW4AAAAAAAAAAAAAAAAAAAAA4G1pbmYAAAAUdm1oZAAAAAEAAAAAAAAAAAAAACRkaW5mAAAAHGRyZWYAAAAAAAAAAQAAAAx1cmwgAAAAAQAAAKBzdGJsAAAANHN0c2QAAAAAAAAAAQAAACR0d29zAAAAAAAAAAEAAAAAAAAAAAACABAAAAAAAACsRAAAABhzdHRzAAAAAAAAAAEAAAACAAAEAAAAABxzdHN6AAAAAAAAAAAAAAACAAAABAAAAAQAAAAcc3RzYwAAAAAAAAABAAAAAQAAAAIAAAABAAAAFHN0YwABbwAAAAAAAAABAAACHQAAAChtZGF0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAABkAAAAAAAAAAA=' | base64 -d > poc.m4a
-
Run it:
./mp4v2_read_fuzzer poc.m4a
UBSan output (trap mode aborts with SIGILL, exit 132; recoverable mode prints):
/src/workspace/src/mp4file.cpp:472:28: runtime error: null pointer passed as argument 1, which is declared to never be null
SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior ... mp4file.cpp:472:28
In our build the process exits with code 1 (UBSan trap).
Expected behavior
GenerateTracks should check the handler type for NULL before strcmp, and STRTOINT32 should load the 32-bit value byte-by-byte (or via memcpy) instead of dereferencing a potentially misaligned pointer. Malformed MP4s should be rejected with an error rather than triggering undefined behavior.
Screenshots
Please complete the following information:
- OS: Linux x86-64 (Ubuntu 22.04)
- mp4v2 version: 2.0.0 (commit
6727d3c5faaf8b9db9214127f6e6d9e5e8cf95c1)
Additional context
- Deterministic: yes — the same 575-byte input always triggers the UBSan report (exit 1 in our trap-mode build).
- Root cause locations:
strcmp in MP4File::GenerateTracks (src/mp4file.cpp:472) and misaligned uint32_t load in STRTOINT32 (src/mp4util.cpp:339, reached via ATOMID() in MP4Atom::ReadChildAtoms).
- Reachability: any MP4 with a track whose handler setup reaches
GenerateTracks through the public MP4Read API.
- Severity: medium. Null-pointer UB and misaligned-load UB are undefined behavior; a crash is deterministic under sanitizers, and the misaligned load may also misbehave on strict-alignment architectures.
- CWE-476 (NULL Pointer Dereference).
Describe the bug
A malformed MP4 triggers undefined behavior inside the mp4v2 library during track setup:
strcmp—MP4File::GenerateTracks(src/mp4file.cpp:472) callsstrcmp(handlerType, MP4_HINT_TRACK_TYPE)without checking for NULL. When atrak/mdia/hdlris malformed such that the handler type string is null,strcmp(NULL, ...)is UB. UBSan reportsnull pointer passed as argument 1, which is declared to never be null.STRTOINT32(src/mp4util.cpp:339) dereferences*(uint32_t*)swhenMP4V2_INTSTRING_ALIGNMENTis not defined; atom-type bytes reached throughATOMID()inMP4Atom::ReadChildAtomsare not guaranteed 4-byte aligned, a misaligned-load UB.Under a trap-mode build (
-fno-sanitize-recover=all) the first UB aborts the process (SIGILL / exit 132); with a recoverable UBSan build thenull pointerruntime error is printed.To Reproduce
Build mp4v2 with ASan + UBSan (trap mode):
Use the same memory-backed harness as issue #XXX (save as
fuzz.cpp— feeds the MP4 bytes throughMP4FileProviderintoMP4Read):Compile and write the exact 575-byte crash input (a malformed MP4 with a
sountrack whosehdlr/track setup drivesGenerateTracksintostrcmp(NULL, ...)). The base64 string below round-trips to the byte-identical crash input:clang++ -std=c++11 -O1 -g -fsanitize=address,undefined -fno-sanitize-recover=all -fno-omit-frame-pointer \ -I include fuzz.cpp libmp4v2.a -o mp4v2_read_fuzzer -lpthread -lm printf 'AAAAFGZ0eXBpc29tAAACAGlzb20AAAIBbW9vdgAAAGxtdmhkAAAAAAAAAAAAAAAAAAAD6AAAAAAA4wAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAY10cmFrAAAAXHRraGQAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAEAAAAAAAAAAAD////hAAABAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAEpbWRpYQAAACBtZGhkAAAAAAAAAAAAAAAAAAAD6AAAAABVxAAAAAAADGhkbHIAAAAAAAAAAHNvdW4AAAAAAAAAAAAAAAAAAAAA4G1pbmYAAAAUdm1oZAAAAAEAAAAAAAAAAAAAACRkaW5mAAAAHGRyZWYAAAAAAAAAAQAAAAx1cmwgAAAAAQAAAKBzdGJsAAAANHN0c2QAAAAAAAAAAQAAACR0d29zAAAAAAAAAAEAAAAAAAAAAAACABAAAAAAAACsRAAAABhzdHRzAAAAAAAAAAEAAAACAAAEAAAAABxzdHN6AAAAAAAAAAAAAAACAAAABAAAAAQAAAAcc3RzYwAAAAAAAAABAAAAAQAAAAIAAAABAAAAFHN0YwABbwAAAAAAAAABAAACHQAAAChtZGF0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAABkAAAAAAAAAAA=' | base64 -d > poc.m4aRun it:
UBSan output (trap mode aborts with SIGILL, exit 132; recoverable mode prints):
In our build the process exits with code 1 (UBSan trap).
Expected behavior
GenerateTracksshould check the handler type for NULL beforestrcmp, andSTRTOINT32should load the 32-bit value byte-by-byte (or viamemcpy) instead of dereferencing a potentially misaligned pointer. Malformed MP4s should be rejected with an error rather than triggering undefined behavior.Screenshots
Please complete the following information:
6727d3c5faaf8b9db9214127f6e6d9e5e8cf95c1)Additional context
strcmpinMP4File::GenerateTracks(src/mp4file.cpp:472) and misaligneduint32_tload inSTRTOINT32(src/mp4util.cpp:339, reached viaATOMID()inMP4Atom::ReadChildAtoms).GenerateTracksthrough the publicMP4ReadAPI.