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.