Skip to content

Fix heap-buffer-overflow READ in LightPcapNg parse_by_block_type - #2160

Open
trailfork wants to merge 2 commits into
seladb:devfrom
trailfork:fix-lightpcapng-oob-read
Open

Fix heap-buffer-overflow READ in LightPcapNg parse_by_block_type#2160
trailfork wants to merge 2 commits into
seladb:devfrom
trailfork:fix-lightpcapng-oob-read

Conversation

@trailfork

Copy link
Copy Markdown

Summary

parse_by_block_type in the bundled LightPcapNg
(3rdParty/LightPcapNg/LightPcapNg/src/light_pcapng.c) reads each block's
mandatory fixed header with no minimum-size check. light_read_record
(light_pcapng.c:353) sizes the block body from the attacker-controlled
block_total_length and allocates exactly that many bytes:

const uint32_t bytesToRead = current->block_total_length - 2 * sizeof(blockSize) - sizeof(blockType);
uint32_t *local_data = calloc(bytesToRead, 1);

parse_by_block_type then reads each block type's fixed header out of that
buffer without verifying it is large enough. With block_total_length == 16,
bytesToRead == 4, so the second 4-byte field read runs one word past the
allocation — e.g. the Section Header Block version field
(light_pcapng.c:115, after byteorder_magic) or the Interface Description
Block snapshot_length field (light_pcapng.c:140, after
link_type/reserved). A crafted .pcapng / .zstd / .zst opened via
PcapNgFileReaderDevice (the standard IFileReaderDevice::getReader(path)->open()
path) triggers a heap-buffer-overflow READ.

Impact

Attack surface: file parsing only — not network-reachable. The faulting
fields (SHB version, IDB snapshot_length, block_total_length) are pcapng
container metadata emitted by the capture writer, not captured wire data.
Reachable through the library's default consumption pattern:
IFileReaderDevice::getReader dispatches on file extension, so any
.pcapng/.zstd/.zst path is routed to the vulnerable reader, and at least
eight shipped examples use getReader(path)->open() (PcapPrinter, PcapSplitter,
PcapSearch, HttpAnalyzer, TcpReassembly, SSLAnalyzer, TLSFingerprinting,
IPFragUtil/IPDefragUtil).

Trigger: the victim opens or scans a crafted capture file. PcapPrinter opens a
single CLI-named file (crash during open(), matching poc.pcapng);
PcapSearch recursively sweeps every capture under a -d directory with no
per-file interaction (the unattended-ingest case).

No disclosure: the over-read is read-only with no write primitive. The over-read
value is consumed internally — it becomes the parsed SHB version / IDB
snapshot_length, surfaced to callers only as opaque 64-bit numbers — and is
never returned to the attacker on an observable channel. There is no demonstrated
information-disclosure primitive and no path to memory-corruption RCE.

Affected versions: present at master HEAD (commit af83983b) and at the
originally reported commit 12cdbd5b. The vendored LightPcapNg sources under
3rdParty/ are the only copy on the read path; the seladb LightPcapNg fork they
derive from has had no upstream maintainer since 2017, so the fix is proposed
here, against PcapPlusPlus.

Severity: Medium — denial of service (crash on an unmapped page; deterministic
abort under ASan / hardened builds). Read-only, no write, no disclosure.

  • Typical (local file the victim opens):
    CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:N/I:N/A:H (5.5)
  • Theoretical unattended consumer ingesting untrusted files with no per-file
    interaction (e.g. a recursive directory sweep like the PcapSearch example):
    CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H (7.5)

Reproduction

Two minimal inputs exercise the two crash paths (base64 below):

  1. poc.pcapng (16 bytes) — undersized Section Header Block; crashes during
    open() -> light_pcapng_open_read -> light_read_record ->
    parse_by_block_type (SHB, line 115).
  2. evidence_bundle.bin (44 bytes) — valid SHB followed by an undersized
    Interface Description Block; crashes later via
    getNextPackets -> getNextPacketInternal -> light_read_record ->
    parse_by_block_type (IDB, line 140).

Build the pcapng fuzz target with ASan and run each input:

git clone https://github.com/seladb/PcapPlusPlus /tmp/pcapplusplus-src
cd /tmp/pcapplusplus-src

cmake -S . -B build \
  -DPCAPPP_BUILD_FUZZERS=ON -DPCAPPP_BUILD_TESTS=OFF -DPCAPPP_BUILD_EXAMPLES=OFF \
  -DCMAKE_C_FLAGS="-g -O1 -fno-omit-frame-pointer -fsanitize=address,undefined" \
  -DCMAKE_CXX_FLAGS="-g -O1 -fno-omit-frame-pointer -fsanitize=address,undefined" \
  -DCMAKE_EXE_LINKER_FLAGS="-fsanitize=address,undefined"

cmake --build build -j --target FuzzTargetNg
./build/Tests/Fuzzers/FuzzTargetNg poc.pcapng          # SHB path
./build/Tests/Fuzzers/FuzzTargetNg evidence_bundle.bin # IDB path

The fuzz target links libFuzzer (-fsanitize=fuzzer), which ships with the
default clang on Linux. On macOS, Apple's clang lacks libFuzzer: install
Homebrew LLVM (brew install llvm) and add
-DCMAKE_C_COMPILER="$(brew --prefix llvm)/bin/clang" -DCMAKE_CXX_COMPILER="$(brew --prefix llvm)/bin/clang++"
to the cmake invocation above.

Expected: heap-buffer-overflow ... READ of size 4, 0 bytes after 4-byte region, allocated in light_read_record.

To recreate the input files:

# poc.pcapng
base64 -d > poc.pcapng <<'EOF'
Cg0NChAAAABNPCsaEAAAAA==
EOF

# evidence_bundle.bin
base64 -d > evidence_bundle.bin <<'EOF'
Cg0NChwAAABNPCsaAQAAAP//////////HAAAAAEAAAAQAAAAAQD//xAAAAA=
EOF

Fix

Reject blocks whose body is too small to hold the mandatory fixed fields, per
block type, before parsing them. The patch derives body_size = block_total_length - 12 (mirroring light_read_record's bytesToRead) at the
top of parse_by_block_type and validates it against each block type's
fixed-header size (16 for SHB, 8 for IDB, 20 for EPB, 4 for SPB, 12 for CUSTOM)
before reading any field. On an insufficient body the case allocates a
zero-initialised header struct (and, where applicable, an empty option) and
returns, so downstream consumers read a well-formed but empty record instead of
dereferencing NULL. After the fix, both inputs are rejected gracefully
("Couldn't read the first packet in the file") with no ASan report. See the
commit in this PR.

Credit

Found by Claude during an automated review conducted by Anthropic; manually
validated and patched by Trail of Bits. Reference: ANT-2026-01245.

Signed-off-by: trailfork <tyler.rolling@trailofbits.com>
@trailfork
trailfork requested a review from seladb as a code owner June 5, 2026 15:25
@Dimi1010
Dimi1010 changed the base branch from master to dev June 23, 2026 08:03
@Dimi1010

Copy link
Copy Markdown
Collaborator

Closing and reopening to run CI on dev.

@Dimi1010 Dimi1010 closed this Jun 23, 2026
@Dimi1010 Dimi1010 reopened this Jun 23, 2026
@codecov

codecov Bot commented Jun 23, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 13.04348% with 20 lines in your changes missing coverage. Please review.
✅ Project coverage is 82.69%. Comparing base (edd69fb) to head (a958360).
⚠️ Report is 52 commits behind head on dev.

Files with missing lines Patch % Lines
...rdParty/LightPcapNg/LightPcapNg/src/light_pcapng.c 13.04% 17 Missing and 3 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##              dev    #2160      +/-   ##
==========================================
- Coverage   82.72%   82.69%   -0.03%     
==========================================
  Files         332      332              
  Lines       59807    59823      +16     
  Branches    12593    12595       +2     
==========================================
- Hits        49475    49472       -3     
- Misses       8944     8946       +2     
- Partials     1388     1405      +17     
Flag Coverage Δ
23.11.6 7.27% <0.00%> (-0.02%) ⬇️
24.11.5 7.28% <0.00%> (+0.01%) ⬆️
25.11.1 7.28% <0.00%> (-0.04%) ⬇️
alpine320 76.81% <10.00%> (-0.05%) ⬇️
fedora42 76.41% <10.00%> (-0.05%) ⬇️
macos-14 82.23% <9.09%> (-0.04%) ⬇️
macos-15 82.22% <9.09%> (-0.03%) ⬇️
mingw32 71.11% <ø> (ø)
mingw64 71.08% <ø> (+0.06%) ⬆️
npcap ?
rhel94 76.21% <10.00%> (-0.05%) ⬇️
ubuntu2204 76.24% <10.00%> (-0.03%) ⬇️
ubuntu2204-icpx 59.40% <ø> (+0.02%) ⬆️
ubuntu2404 76.53% <10.00%> (-0.05%) ⬇️
ubuntu2404-arm64 76.52% <10.00%> (-0.03%) ⬇️
ubuntu2604 76.42% <10.00%> (-0.04%) ⬇️
unittest 82.69% <13.04%> (-0.03%) ⬇️
windows-2022 85.79% <ø> (+0.10%) ⬆️
windows-2025 85.53% <ø> (+0.12%) ⬆️
winpcap 85.83% <ø> (+0.20%) ⬆️
xdp 53.11% <0.00%> (-0.04%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@seladb seladb left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@trailfork thank you for providing this fix! Can you add one or more examples in Tests/Fuzzers/RegressionTests/regression_samples that would crash/fail without this fix?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants