From 3beafd2a016f8817a9c3d91b4ea8577e43a5e393 Mon Sep 17 00:00:00 2001 From: Clinton Thomas <1033162+KernelClint@users.noreply.github.com> Date: Wed, 30 Sep 2026 09:32:35 -0400 Subject: [PATCH 1/3] libpcap: size pcap_pkthdr's timestamp from the platform's time_t 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 --- scapy/libs/winpcapy.py | 37 +++++++++++++++++++++++++++++--- test/regression.uts | 48 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 82 insertions(+), 3 deletions(-) diff --git a/scapy/libs/winpcapy.py b/scapy/libs/winpcapy.py index 42bfa850921..4fde37273d3 100644 --- a/scapy/libs/winpcapy.py +++ b/scapy/libs/winpcapy.py @@ -11,10 +11,12 @@ from ctypes import * from ctypes.util import find_library +import ctypes import os +import time from scapy.libs.structures import bpf_program -from scapy.consts import WINDOWS, BSD +from scapy.consts import WINDOWS, BSD, DARWIN, LINUX, NETBSD, OPENBSD if WINDOWS: # Try to load Npcap, or Winpcap @@ -60,9 +62,38 @@ class bpf_version(Structure): ("bv_minor", c_ushort)] +# The timestamp in struct pcap_pkthdr. Windows uses two longs, and OpenBSD its +# own struct bpf_timeval of two u_int32_t. Elsewhere its seconds are a time_t, +# which is 64-bit on NetBSD, musl and 64-bit-time glibc even where a long is +# 32-bit. Getting its size wrong shifts caplen and len, which follow it. This +# assumes Python and libpcap were built for the same time_t, as a +# distribution's packages are. +if WINDOWS: + _time_t = _timeval_usec_t = c_long +elif OPENBSD: + _time_t = _timeval_usec_t = c_uint32 +else: + try: + _time_t = ctypes.c_time_t # Python 3.12+ + except AttributeError: + # Older Pythons: ask whether this build's time_t can hold 2**31 + try: + time.gmtime(2 ** 31) + _time_t = c_int64 + except (OverflowError, OSError, ValueError): + _time_t = c_int32 + if NETBSD or DARWIN: + _timeval_usec_t = c_int + elif LINUX: + # As wide as tv_sec in glibc and musl, including glibc's 64-bit time + _timeval_usec_t = _time_t + else: + _timeval_usec_t = c_long + + class timeval(Structure): - _fields_ = [('tv_sec', c_long), - ('tv_usec', c_long)] + _fields_ = [('tv_sec', _time_t), + ('tv_usec', _timeval_usec_t)] # sockaddr is used by pcap_addr. diff --git a/test/regression.uts b/test/regression.uts index 2b84ecfcb90..dd8468e3306 100644 --- a/test/regression.uts +++ b/test/regression.uts @@ -312,6 +312,54 @@ finally: # Anything past caplen is whatever the buffer held before, not this packet. assert pkt == b"\x01\x02\x03\x04" += Native libpcap capture reads the header as libpcap lays it out +~ libpcap + +# pcap_pkthdr starts with a struct timeval whose size varies by platform. Read +# a real file through libpcap, so a wrong size shows up as a wrong caplen. The +# microseconds are 1 so that, if they are misread as caplen, the read is short +# and the test fails instead of crashing. +import struct +from ctypes import POINTER, c_ubyte, create_string_buffer +from scapy.arch import libpcap as _libpcap +from scapy.libs.winpcapy import ( + PCAP_ERRBUF_SIZE, pcap_close, pcap_open_offline, pcap_pkthdr +) + +# Turning libpcap on turns BPF off on BSD and macOS, so restore both. +_was_use_pcap, _was_use_bpf = conf.use_pcap, conf.use_bpf +conf.use_pcap = True +try: + if conf.use_pcap: # False when no libpcap could be loaded + fname = get_temp_file() + with open(fname, "wb") as fd: + fd.write(struct.pack(" Date: Thu, 1 Oct 2026 18:57:22 +0200 Subject: [PATCH 2/3] Cleanup code AI-Assisted: no --- scapy/libs/winpcapy.py | 68 +++++++++++++++++++++--------------------- 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/scapy/libs/winpcapy.py b/scapy/libs/winpcapy.py index 4fde37273d3..17a0474a7fc 100644 --- a/scapy/libs/winpcapy.py +++ b/scapy/libs/winpcapy.py @@ -11,7 +11,6 @@ from ctypes import * from ctypes.util import find_library -import ctypes import os import time @@ -56,49 +55,50 @@ FILE = c_void_p STRING = c_char_p +try: + c_time_t # Python 3.12+ +except NameError: + # Older Pythons: ask whether this build's time_t can hold 2**31 + try: + time.gmtime(2 ** 31) + c_time_t = c_int64 + except (OverflowError, OSError, ValueError): + c_time_t = c_int32 + class bpf_version(Structure): _fields_ = [("bv_major", c_ushort), ("bv_minor", c_ushort)] -# The timestamp in struct pcap_pkthdr. Windows uses two longs, and OpenBSD its -# own struct bpf_timeval of two u_int32_t. Elsewhere its seconds are a time_t, -# which is 64-bit on NetBSD, musl and 64-bit-time glibc even where a long is -# 32-bit. Getting its size wrong shifts caplen and len, which follow it. This -# assumes Python and libpcap were built for the same time_t, as a -# distribution's packages are. +# timeval has a different structure depending on the OS +# - Windows uses two longs +# - OpenBSD its own struct bpf_timeval of two u_int32_t +# - Elsewhere its seconds are a time_t if WINDOWS: - _time_t = _timeval_usec_t = c_long + # https://learn.microsoft.com/fr-fr/windows/win32/api/winsock/ns-winsock-timeval + class timeval(Structure): + _fields_ = [('tv_sec', c_long), + ('tv_usec', c_long)] elif OPENBSD: - _time_t = _timeval_usec_t = c_uint32 + # https://github.com/openbsd/src/blob/a5d3ee8e660b7269f17fcd507a49e1e10ce52d58/sys/net/bpf.h#L143 + class timeval(Structure): + _fields_ = [('tv_sec', c_uint32), + ('tv_usec', c_uint32)] +elif NETBSD or DARWIN: + class timeval(Structure): + _fields_ = [('tv_sec', c_time_t), + ('tv_usec', c_int)] +elif LINUX: + # As wide as tv_sec in glibc and musl, including glibc's 64-bit time + class timeval(Structure): + _fields_ = [('tv_sec', c_time_t), + ('tv_usec', c_time_t)] else: - try: - _time_t = ctypes.c_time_t # Python 3.12+ - except AttributeError: - # Older Pythons: ask whether this build's time_t can hold 2**31 - try: - time.gmtime(2 ** 31) - _time_t = c_int64 - except (OverflowError, OSError, ValueError): - _time_t = c_int32 - if NETBSD or DARWIN: - _timeval_usec_t = c_int - elif LINUX: - # As wide as tv_sec in glibc and musl, including glibc's 64-bit time - _timeval_usec_t = _time_t - else: - _timeval_usec_t = c_long - - -class timeval(Structure): - _fields_ = [('tv_sec', _time_t), - ('tv_usec', _timeval_usec_t)] - + class timeval(Structure): + _fields_ = [('tv_sec', c_time_t), + ('tv_usec', c_long)] -# sockaddr is used by pcap_addr. -# For example if sa_family==socket.AF_INET then we need cast -# with sockaddr_in # sockaddr has a different structure depending on the OS if BSD: From 32e60df80adcfcdf5a2860692c1baf487af85f43 Mon Sep 17 00:00:00 2001 From: Gabriel <10530980+gpotter2@users.noreply.github.com> Date: Thu, 1 Oct 2026 19:11:39 +0200 Subject: [PATCH 3/3] Update test/regression.uts AI-Assisted: no --- test/regression.uts | 48 --------------------------------------------- 1 file changed, 48 deletions(-) diff --git a/test/regression.uts b/test/regression.uts index dd8468e3306..2b84ecfcb90 100644 --- a/test/regression.uts +++ b/test/regression.uts @@ -312,54 +312,6 @@ finally: # Anything past caplen is whatever the buffer held before, not this packet. assert pkt == b"\x01\x02\x03\x04" -= Native libpcap capture reads the header as libpcap lays it out -~ libpcap - -# pcap_pkthdr starts with a struct timeval whose size varies by platform. Read -# a real file through libpcap, so a wrong size shows up as a wrong caplen. The -# microseconds are 1 so that, if they are misread as caplen, the read is short -# and the test fails instead of crashing. -import struct -from ctypes import POINTER, c_ubyte, create_string_buffer -from scapy.arch import libpcap as _libpcap -from scapy.libs.winpcapy import ( - PCAP_ERRBUF_SIZE, pcap_close, pcap_open_offline, pcap_pkthdr -) - -# Turning libpcap on turns BPF off on BSD and macOS, so restore both. -_was_use_pcap, _was_use_bpf = conf.use_pcap, conf.use_bpf -conf.use_pcap = True -try: - if conf.use_pcap: # False when no libpcap could be loaded - fname = get_temp_file() - with open(fname, "wb") as fd: - fd.write(struct.pack("