CVE-Class: Integer Overflow in BPF Map Index Computation — xdp_sample.bpf.c
Summary
A silent integer overflow in the BPF map array index formula causes all XDP redirect error statistics to be corrupted by default (when nr_cpus = 0). Additionally, with crafted nr_cpus values an attacker controlling program load parameters can force arbitrary error-type aliasing, permanently masking or fabricating security-relevant packet drop events.
Affected File
samples/bpf/xdp_sample.bpf.c
Vulnerability Details
Primary: Default-Zero Collapse (Always Triggered)
Location: xdp_redirect_collect_stat(), line 78
// xdp_sample.bpf.c:68-86
static __always_inline int xdp_redirect_collect_stat(int from, int err)
{
u32 cpu = bpf_get_smp_processor_id();
u32 key = XDP_REDIRECT_ERROR;
struct datarec *rec;
u32 idx;
if (!IN_SET(from_match, from))
return 0;
key = xdp_get_err_key(err); // key ∈ {0,1,2,3,4,5,6}
idx = key * nr_cpus + cpu; // ← VULNERABLE
rec = bpf_map_lookup_elem(&redir_err_cnt, &idx);
Root cause:
nr_cpus is declared as const volatile int nr_cpus = 0 (line 23). Its default value is zero. Because it is a const volatile global meant to be patched before load, the BPF verifier cannot constant-fold it away once loaded with a non-zero value, but it also does not validate it is sensible.
When nr_cpus == 0 (the default, and the state when the program is loaded without explicit configuration):
idx = key * 0 + cpu = cpu (for every value of key)
All seven distinct error buckets (key = 0–6 representing SUCCESS, generic error, EINVAL, ENETDOWN, EMSGSIZE, EOPNOTSUPP, ENOSPC) collapse to the same per-CPU datarec entry. Every call to xdp_redirect_collect_stat increments either rec->dropped or rec->processed in slot [cpu], regardless of error type.
Consequence: success packets and every error class overwrite each other’s counters. Monitoring tools that rely on these statistics — including the companion xdp_redirect sample — silently receive completely wrong data.
Secondary: Integer Overflow for Bucket Aliasing
When a user loads the program with a crafted nr_cpus, the 32-bit multiplication can be made to overflow, causing two different error key values to map to the same idx:
key=6 (ENOSPC), nr_cpus = 715827883:
6 × 715827883 = 4,294,967,298 → wraps to 2 (mod 2³²)
idx = 2 + cpu
key=0 (SUCCESS), nr_cpus = 715827883:
0 × 715827883 = 0
idx = 0 + cpu
With a suitable nr_cpus, ENOSPC errors alias to the same bucket as another error class (or as SUCCESS), so dropped packets are counted as successfully processed — a security monitoring bypass.
Tertiary: Same Pattern in tp_xdp_cpumap_enqueue (line 138)
idx = to_cpu * nr_cpus + cpu;
rec = bpf_map_lookup_elem(&cpumap_enqueue_cnt, &idx);
to_cpu is a signed int parameter received from the tracepoint. If a crafted tracepoint fires with a negative to_cpu, the expression to_cpu * nr_cpus is negative, and when stored into u32 idx it wraps to a large value or, after modular arithmetic with nr_cpus, aliases another valid slot.
Impact
| Scenario | Effect |
|---|---|
Default load (nr_cpus=0) |
All redirect stats silently mixed; monitoring useless |
Crafted nr_cpus at load time |
Error types alias each other; dropped packets appear as successes |
Negative to_cpu + crafted nr_cpus |
cpumap enqueue stats corrupted; overload detection bypassed |
In a production XDP deployment, these corrupted statistics feed into userspace tools that may alert on packet drops, redirect errors, and overload conditions. Silent corruption means real denial-of-service events (e.g., ENOSPC on a cpumap queue) are invisible to operators.
Root Cause (CWE)
- CWE-190 — Integer Overflow or Wraparound
- CWE-682 — Incorrect Calculation
- CWE-369 — Divide By Zero (degenerate case: multiply by zero)
Proof of Concept
With nr_cpus left at the default (0), load the BPF program and trigger XDP redirects. Read redir_err_cnt map entries 0–6 × ncpus: all entries for keys 1–6 will be zero while entries at key=0 accumulate all events — drops and successes merged into a single bucket per CPU.
Fix
Validate nr_cpus is positive and non-zero before use, and add a bounds check on the computed index:
if (nr_cpus <= 0)
return 0; // reject invalid configuration
idx = key * (u32)nr_cpus + cpu;
/* Ensure index stays within allocated map bounds */
if (idx >= (u32)(XDP_REDIRECT_ERROR + 1) * (u32)nr_cpus)
return 0;
rec = bpf_map_lookup_elem(&redir_err_cnt, &idx);
Apply the same fix to tp_xdp_cpumap_enqueue (line 138) and reject negative to_cpu values before the multiply.