Skip to content

libpcap: fix timeval on multiple platforms leading to a crash - #5209

Merged
gpotter2 merged 3 commits into
secdev:masterfrom
KernelClint:fix/libpcap-timeval-size
Oct 1, 2026
Merged

gpotter2 merged 3 commits into
secdev:masterfrom
KernelClint:fix/libpcap-timeval-size

Conversation

@KernelClint

@KernelClint KernelClint commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

Description

Fixes the segfault @evverx reported in #5122 on 32-bit NetBSD after the GHSA-c547-xwrv-q9jm fix.

struct pcap_pkthdr starts with a struct timeval, and Scapy declared it as two C longs (scapy/libs/winpcapy.py:63 on master). Where time_t is 64-bit but long is 32-bit, the real timestamp is bigger than that. So caplen and len, which come right after it, were read from the wrong place:

  • NetBSD i386: Scapy's len was the real caplen, and its caplen was the timestamp's microseconds. Before the GHSA fix the packet bytes happened to be right. After it, each packet is copied with a length between 0 and 999,999, which crashes sniff.
  • musl and 64-bit-time glibc on 32-bit ARM or x86: both of Scapy's fields fall inside the microseconds. Before the GHSA fix every packet came back empty; after it, the same crash.

This PR declares the timestamp with the platform's real sizes (scapy/libs/winpcapy.py:65-96):

  • seconds: ctypes.c_time_t on Python 3.12+. On older Pythons, time.gmtime(2 ** 31) tells us whether this build's time_t is 64-bit;
  • microseconds: int on NetBSD and macOS, as wide as the seconds on Linux, and long elsewhere;
  • Windows keeps two longs, and OpenBSD uses two u_int32_t: its libpcap declares its own struct bpf_timeval (lib/libpcap/pcap.h:93, sys/net/bpf.h:143).

This assumes Python and libpcap use the same time_t, as a distribution's packages do.

Test

Native libpcap capture reads the header as libpcap lays it out (test/regression.uts:315) reads a pcap file through libpcap itself, so a wrong layout shows up as a wrong caplen. The existing test above it builds the header with Scapy's own struct, so it passes whatever the layout is. The new test uses a 1 µs timestamp so a misread fails cleanly rather than crashing. It is tagged libpcap only, so the BSD configs run it too.

Checked with real libpcap, before and after this PR:

Platform master this PR
NetBSD 10.1 i386 (VM) wrong lengths, up to 734,512 bytes read from a 60-byte packet pass
Alpine i386 and armv7 (musl) segfault pass
Debian trixie armhf (64-bit time) wrong lengths pass
OpenBSD 7.9 i386 (VM) pass (a long is 32 bits there) pass
Debian trixie i386 and x86_64, macOS arm64 pass pass

On Python 3.7 to 3.13 (official python images, glibc and musl, amd64, i386 and armv7), the fallback and c_time_t picked the right size in all 35 combinations.

The new test fails on master on the NetBSD, musl and armhf targets above, and passes on all of them with this PR. Scapy's CI is 64-bit only, so it cannot fail there.

Not changed here

On macOS, Apple's pcap_pkthdr has an extra char comment[256] after len (280 bytes, where Scapy's is 24). Capture reads the header through pcap_next_ex, which is unaffected, and nothing in Scapy calls pcap_next or pcap_dump, the two functions where it would matter. I've left it out so this stays a small fix; happy to add it.

@KernelClint KernelClint mentioned this pull request Sep 30, 2026
@codecov

codecov Bot commented Sep 30, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 81.14%. Comparing base (90623e7) to head (32e60df).
⚠️ Report is 4 commits behind head on master.

Additional details and impacted files
@@            Coverage Diff             @@
##           master    #5209      +/-   ##
==========================================
- Coverage   81.16%   81.14%   -0.02%     
==========================================
  Files         393      393              
  Lines       98120    98316     +196     
==========================================
+ Hits        79637    79779     +142     
- Misses      18483    18537      +54     

see 22 files with indirect coverage changes

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

@evverx

evverx commented Sep 30, 2026

Copy link
Copy Markdown
Contributor

Scapy's CI is 64-bit only, so it cannot fail there

scapy can manually trigger Fedora builds on various architectures including 32-bit i386 and big-endian s390x machines. That being said it seems it started failing when 59f9a0e was merged (https://download.copr.fedorainfracloud.org/results/packit/evverx-scapy-2/fedora-rawhide-x86_64/11055396-scapy/builder-live.log.gz). I'll try to take a look tomorrow. I'm far from the keyboard where I can look into that properly.

@KernelClint
KernelClint force-pushed the fix/libpcap-timeval-size branch from 62cc2bf to ed0df76 Compare September 30, 2026 16:41
@KernelClint

Copy link
Copy Markdown
Contributor Author

Pushed an update:

  • OpenBSD's libpcap doesn't use struct timeval in pcap_pkthdr. It has its own struct bpf_timeval of two u_int32_t, so the first version got OpenBSD wrong. Checked on an OpenBSD 7.9 i386 VM. Master was right there only because a long is 32 bits on i386; on amd64 it would read the wrong offsets.
  • The new test left conf.use_bpf off on macOS, which broke the send tests later in the macOS job. It now restores it.

The IPv6ExtHdrSegmentRouting failure in every job comes from #5207 on master, not from this PR.

@evverx thanks, I didn't know about the Fedora builds. A 32-bit i386 run would exercise the new test.

The timestamp at the start of struct pcap_pkthdr was declared as two
C longs. Where time_t is 64-bit but long is 32-bit (NetBSD, musl,
64-bit-time glibc), caplen and len were read from the wrong offsets,
so reading caplen copied up to 999,999 bytes per packet. OpenBSD's
libpcap uses its own struct bpf_timeval of two u_int32_t instead.

AI-Assisted: yes
@gpotter2

Copy link
Copy Markdown
Member

Not a huge fan of the code, so I made an alternative (much simpler) version: #5211
I would prefer to have very simple if/else, and only add stuff if we have a confirmation that it breaks.

@KernelClint

Copy link
Copy Markdown
Contributor Author

Tested #5211 and replied there: #5211 (comment). I'll close this one once it's merged.

@gpotter2

Copy link
Copy Markdown
Member

You're right, my code just doesn't work.. I missed the musl case which kind of makes my approach wrong. I've removed the parts that were supposed to fix this issue to just keep the cleanups, let's keep this open.

Comment thread scapy/libs/winpcapy.py Outdated
if WINDOWS:
_time_t = _timeval_usec_t = c_long
elif OPENBSD:
_time_t = _timeval_usec_t = c_uint32

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

It isn't obvious from the commit message but this change makes scapy actually work in libpcap mode on 64-bit OpenBSD machines.

I'm still trying to wrap my head around the code but in the meantime I tested it on 32/64-bit NetBSD, 32/64-bit FreeBSD, 64-bit OpenBSD and it seems to work (I'm not sure what happens on illumos yet though). I also ran the testsuite with this commit included in libpcap mode with netaccess enabled on Fedora on i386, s390x,aarch64, ppc64le and arm64 and it passed there (apart from some failures caused by the firewall blocking whois things and things like that on Packit).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

illumos comes with time_t and long so it should work there too (I tested it on OmniOS). It would be cool if it was possible to detect possible time_t python/libpcap mismatches (other than segfaults at runtime) but I can't come up with anything.

@gpotter2 gpotter2 Oct 1, 2026 •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks @evverx. I agree with you that the code was hard to read. I tried to refactor it, but I hope I haven't broken anything.

Regarding the test, I think it's already covered by

scapy/test/regression.uts

Lines 283 to 309 in ac0ab2e

= libpcap - test open_pcap
~ libpcap
_old_usepcap = conf.use_pcap
conf.use_pcap = True
from scapy.arch.libpcap import open_pcap
try:
# 0. Create
fname = get_temp_file()
pkt = Ether() / IP()
pkt.wirelen = len(pkt) + 1000
wrpcap(fname, [pkt])
# 1. Open with libpcap
reader = open_pcap(fname, offline=True)
# 2. Read packet
reader.next()
# 3. Check the header lengths
assert reader.header.contents.len == 1034
assert reader.header.contents.caplen == 34
finally:
try:
reader.close()
except Exception:
pass
conf.use_pcap = _old_usepcap
so I'd remove it

AI-Assisted: no
@gpotter2
gpotter2 force-pushed the fix/libpcap-timeval-size branch from 34e42ee to db38571 Compare October 1, 2026 17:04
Comment thread test/regression.uts Outdated
@gpotter2
gpotter2 force-pushed the fix/libpcap-timeval-size branch from 402e07a to 32e60df Compare October 1, 2026 17:17
@gpotter2 gpotter2 changed the title libpcap: size pcap_pkthdr's timestamp from the platform's time_t libpcap: fix timeval on multiple platforms leading to a crash Oct 1, 2026
@gpotter2 gpotter2 added this to the 2.8.0 milestone Oct 1, 2026
@gpotter2

gpotter2 commented Oct 1, 2026

Copy link
Copy Markdown
Member

/packit build

@gpotter2
gpotter2 merged commit 9ef9871 into secdev:master Oct 1, 2026
49 of 53 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants