DPDK ip_frag security analyis
A message that is not in the archive (sent off-list or not yet received): [email protected]
Quoted 29 lines
Subject: Security analysis of
lib/ip_frag(fragmentation & reassembly)Hi,
Below is a security-focused review of the DPDK
ip_fraglibrary coveringip_frag_common.h,ip_frag_internal.c,ip_reassembly.h,rte_ip_frag.h,rte_ip_frag_common.c,rte_ipv4_fragmentation.c,rte_ipv4_reassembly.c,rte_ipv6_fragmentation.c, andrte_ipv6_reassembly.c.Findings are grouped by severity.
ERRORS
======1.
TAILQ_FOREACHmodification during iteration in
rte_ip_frag_table_del_expired_entries()File:
rte_ip_frag_common.c, lines 142-151
TAILQ_FOREACH(fp, &tbl->lru, lru)iterates the LRU list whileip_frag_tbl_del()callsTAILQ_REMOVE()on the current element inside the loop body.TAILQ_FOREACHreadsfp->lru.tqe_nextin the loop-advance step *after* the body executes. AfterTAILQ_REMOVE, the removed element'stqe_nextis stale.
Quoted 7 lines
On glibc/BSD this happens to work because
TAILQ_REMOVEdoes not zerotqe_next, but this violates the TAILQ API contract. If the implementation ever changes, or if a debug TAILQ that poisons removed entries is used, this becomes a use-after-remove bug.Fix: use
TAILQ_FOREACH_SAFE, or saveTAILQ_NEXT(fp, lru)before callingip_frag_tbl_del().
ACK, that looks like a valid one to me.
Quoted 11 lines
2. No overlapping fragment detection (teardrop-class attacks, RFC 5722 violation for IPv6)
File:
ip_frag_internal.c,ip_frag_process()
ip_frag_process()does not check whether a newly arrived fragment overlaps with any previously stored fragment. It only detects duplicate first/last fragments. For middle fragments, it blindly incrementslast_idxand stores the fragment.
Quoted 4 lines
The reassembly functions happen to fail on overlapping fragments because the offset-matching chain walk cannot find a match, so the hole-detection fires. The mitigation is accidental rather than intentional.
Nope, it is not accidental, it was intentional design choice. Checking for each incoming fragment that there is no overlaps might be quite expensive. So, we postpone that check till we have all fragments arrive and start re-assemble process.
It is sort of trade-off speed vs keeping mbuf with overlapping fragment for a bit longer.
Looks like a false positive to me.
Quoted 31 lines
For IPv6, RFC 5722 mandates that overlapping fragments MUST cause the entire datagram to be silently dropped. The current code does eventually drop them, but only after accumulating fragments and burning table slots until timeout. An attacker can exhaust fragment table entries by sending overlapping fragments.
Fix: check new fragment offset/length against existing entries in frags[] before storing. For IPv6, drop immediately per RFC 5722.
3. Death row buffer overflow --
IP_FRAG_MBUF2DRhas no bounds checkFile:
ip_frag_common.h, line 38The
IP_FRAG_MBUF2DR(dr, mb)macro writes to dr->row[dr->cnt++] with no bounds check. Similarly,ip_frag_free()adds up toIP_MAX_FRAG_NUM(8) entries without checking capacity.The death row is sized for
RTE_IP_FRAG_DEATH_ROW_LEN* (MAX_FRAG+ 1) = 288 entries. Thedel_expired_entries()function does check capacity, but the hot-path reassembly functions callip_frag_find()->ip_frag_tbl_del()andip_frag_process()->ip_frag_free()+IP_FRAG_MBUF2DR()without any capacity check.If an application calls reassembly in a tight loop without draining the death row between calls,
dr->cntcan exceedRTE_IP_FRAG_DEATH_ROW_MBUF_LEN, causing an out-of-bounds write. The death row is typically stack-allocated, so this is a potential stack buffer overflow.
Quoted 4 lines
ip_frag_find()can evict a stale entry AND an LRU entry in a single call (up to 16 mbufs), thenip_frag_process()can add 9 more on error.A burst of 32 bad fragments hitting different stale entries could overflow.
That seems not entirely correct, as we just remove stale entry from the table and the LRU list, so max IP_MAX_FRAG_NUM (8) can be deleted in one go.
But overall, yes it is a potential problem, as we assume that user will always operate in bursts of 32 packes, and call free_death_row() after each such burst is processed.
Which might not be the always the case.
Needs to be addressed.
Quoted 19 lines
Fix: add a bounds check in
IP_FRAG_MBUF2DRandip_frag_free(), or document the maximum number of reassembly calls permitted between death row drains.WARNINGS
========4. IPv6
move_lenunderflow inipv6_frag_reassemble()File:
rte_ipv6_reassembly.c, line 104move_len = m->l2_len + m->l3_len - sizeof(*frag_hdr);
move_lenisuint32_t. If a buggy caller setsl2_len+l3_len< 8,move_lenwraps to ~4 billion. The subsequentip_frag_memmove()would attempt to copy gigabytes of data, causing a segfault or memory corruption.
Quoted 6 lines
The API documents that callers must set l2_len/l3_len correctly, and for a valid IPv6 fragment
l3_lenalone should be >= 48 bytes. But there is no defensive validation.Fix: add a sanity check that
l2_len+l3_len>=sizeof(*frag_hdr)before computingmove_len.
I don't think we need to do anything here.
It should be safe to assume that input mbuf fields are valid, specially if it is already documented.
Quoted 12 lines
5.
rte_ipv6_frag_get_ipv6_fragment_header()only inspects the first extension headerFile:
rte_ip_frag.h, line 143This function checks only
hdr->proto==IPPROTO_FRAGMENT. If any other extension header (hop-by-hop, routing, destination options) precedes the fragment header, this function returns NULL and the packet is treated as non-fragmented.
Quoted 6 lines
An attacker can evade reassembly by inserting a preceding extension header, causing fragments to be forwarded as-is. This can bypass firewall rules that rely on seeing reassembled traffic.
The limitation is documented but is a security-relevant design gap for applications using this library.
Yep, it is a well-known and long-standing limitation that needs to be addressed. I think it was even attempt to fix it, but for whatever reason it wasn't accepted.
Quoted 9 lines
Fix: walk the extension header chain to find the fragment header, or clearly document the security implications in the API header.
INFORMATIONAL
=============6. Hash collision DoS via fixed seed
Quoted 8 lines
Both
ipv4_frag_hash()andipv6_frag_hash()use CRC32 (x86/ARM) or jhash with a fixed, publicly known prime seed (0xeaad8405). An attacker who can send crafted IP fragments can precompute hash collisions, causing all fragments to land in the same bucket. Afterbucket_entriesconcurrent flows collide, new flows are dropped.Fix: randomize the hash seed at table creation time.
ACK, seems valid - needs to be fixed.
Quoted 9 lines
7.
max_cycles+fp->startwraparoundThe timeout comparison (
max_cycles+fp->start< tms) at multiple locations usesuint64_tarithmetic. If the sum wraps past 2^64, entries appear timed out when they are not. With rdtsc-based timestamps this requires running for hundreds of years, so it is not practically exploitable. Worth noting for alternative time sources with faster-incrementing values.
False positive.
Quoted 10 lines
8. No total reassembled size limit before table insertion
The library accepts and stores fragments up to
IP_MAX_FRAG_NUMper datagram, but there is no explicit limit on total reassembled size. An attacker can claimtotal_size= 65535 via a crafted last fragment, then send middle fragments that consume table entries and death row capacity during the attack window, even though reassembly will ultimately fail whenfrag_sizeexceedstotal_size.
We do have max number of frags perf packet. Honestly I don't think there is a need for such extra check.
Quoted 4 lines · On Thu, 9 Apr 2026 13:04:52 +0000 Konstantin Ananyev…
Quoted 2 lines
Fix: use
TAILQ_FOREACH_SAFE, or saveTAILQ_NEXT(fp, lru)before callingip_frag_tbl_del().ACK, that looks like a valid one to me.
I sent patch for that one:
https://patchwork.dpdk.org/project/dpdk/patch/[email protected]/
Quoted 9 lines
Quoted 9 lines
6. Hash collision DoS via fixed seed
Both
ipv4_frag_hash()andipv6_frag_hash()use CRC32 (x86/ARM) or jhash with a fixed, publicly known prime seed (0xeaad8405). An attacker who can send crafted IP fragments can precompute hash collisions, causing all fragments to land in the same bucket. Afterbucket_entriesconcurrent flows collide, new flows are dropped.
Quoted 3 lines
Quoted 1 line
Fix: randomize the hash seed at table creation time.
ACK, seems valid - needs to be fixed.
Sent patch for that one:
https://patchwork.dpdk.org/project/dpdk/patch/[email protected]/
Probably should go to a better hash function to be really paranoid. Linux and BSD switched over to siphash because of this.