DPDK ipsec security analysis
Konstantin Ananyev
konstantin.ananyev at huawei.com
Mon Apr 13 17:25:07 CEST 2026
> I am being proactive and checking all the DPDK libraries with Claude AI
> for security issues; better that we find them than security "researchers".
> 2
> Today's AI harvest of issues was:
>
> 1. Buffer Overflow Risk (MEDIUM) - ipsec/sa.c:363
> - Tunnel header copying without bounds validation
> - Could overflow if called directly without validation
>
> 2. Integer Overflow (MEDIUM) - ipsec/sa.c:99-158
> - Replay window size calculations could overflow
> - Could lead to under-allocation vulnerabilities
>
> 3. Race Condition (MEDIUM) - ipsec/ipsec_sad.c:103-112
> - Non-atomic counter updates in concurrent scenarios
> - Could lead to reference count inconsistencies
>
> 4. Null Pointer Dereference (MEDIUM) - ipsec/sa.c:50-82
> - Logic error in crypto transform validation
> - Could crash with invalid input
>
>
> More detail was:
>
> # DPDK IPsec Library Security Vulnerability Report
>
> **Report Date:** April 8, 2026
> **Analysis Scope:** DPDK IPsec Library Implementation
> **Location:** ~/DPDK/security/lib/ipsec/
> **Analyzed Files:** sa.c, ipsec_sad.c, esp_inb.c, esp_outb.c, sa.h
>
> ---
>
> ## EXECUTIVE SUMMARY
>
> This report details security vulnerabilities discovered in the DPDK IPsec library
> implementation. The IPsec library provides core functionality for ESP
> (Encapsulating Security Payload) protocol processing, Security Association (SA)
> management, and Security Association Database (SAD) operations.
>
> **Key Findings:**
> - **7 distinct vulnerabilities** identified
> - **2 MEDIUM severity** issues requiring immediate attention
> - **3 MEDIUM severity** issues requiring priority fixes
> - **2 LOW severity** issues for hardening
> - Primary concerns: Buffer overflow risks, integer overflow, race conditions, and
> null pointer dereferences
>
> ---
>
> ## TABLE OF CONTENTS
>
> 1. [Vulnerability Summary](#vulnerability-summary)
> 2. [Critical Path Analysis](#critical-path-analysis)
> 3. [Detailed Vulnerability Reports](#detailed-vulnerability-reports)
> 4. [Attack Scenarios](#attack-scenarios)
> 5. [Remediation Recommendations](#remediation-recommendations)
> 6. [Security Hardening Suggestions](#security-hardening-suggestions)
>
> ---
>
> ## VULNERABILITY SUMMARY
>
> | ID | Vulnerability Type | Severity | File | Lines | CVSS Est. |
> |----|-------------------|----------|------|-------|-----------|
> | IPSEC-001 | Buffer Overflow - Tunnel Header | MEDIUM | sa.c | 363-368 | 6.5 |
> | IPSEC-002 | Integer Overflow - Size Calculation | MEDIUM | sa.c | 105, 155, 157
> | 7.0 |
> | IPSEC-003 | Null Pointer Dereference | MEDIUM | sa.c | 50-56 | 5.5 |
> | IPSEC-004 | Race Condition - Counter Update | MEDIUM | ipsec_sad.c | 110,
> 112 | 4.0 |
> | IPSEC-005 | Non-Atomic Statistics Update | MEDIUM | esp_*.c, sa.c | Multiple |
> 3.0 |
> | IPSEC-006 | Timing Attack - SAD Lookup | LOW | ipsec_sad.c | 77-78 | 3.5 |
> | IPSEC-007 | Non-Constant Time Comparison | LOW | ipsec_sad.c | 361-362 |
> 2.0 |
IPSEC-003 and IPSEC-005 looks like a real ones to me.
Rest ones I believe are false-positives.
> ---
>
> ## CRITICAL PATH ANALYSIS
>
> ### Data Flow Through IPsec Library
>
> ```
That's schema below is just wrong - it mixes both CP and DP paths together.
> Packet Input
> ↓
> SA Initialization (sa.c)
> ├─→ Tunnel Mode: esp_outb_tun_init() [VULN: IPSEC-001, IPSEC-002]
> └─→ Transport Mode: esp_outb_init()
> ↓
> SAD Operations (ipsec_sad.c)
> ├─→ SAD Add/Delete [VULN: IPSEC-004]
> └─→ SAD Lookup [VULN: IPSEC-006]
> ↓
> ESP Processing (esp_inb.c, esp_outb.c)
> └─→ Statistics Update [VULN: IPSEC-005]
> ↓
> Packet Output
> ```
>
> **Attack Surface:**
> - Control plane: SA initialization and SAD management
> - Data plane: ESP packet processing and statistics
> - Concurrent access: Multi-threaded SA operations
>
> ---
>
> ## DETAILED VULNERABILITY REPORTS
>
> ### IPSEC-001: Buffer Overflow in Tunnel Header Copy
>
> **Severity:** MEDIUM
> **CWE:** CWE-120 (Buffer Copy without Checking Size of Input)
> **File:** `sa.c`
> **Function:** `esp_outb_tun_init()`
> **Lines:** 363-368
>
> #### Vulnerable Code
>
> ```c
> static void
> esp_outb_tun_init(struct rte_ipsec_sa *sa, const struct rte_ipsec_sa_prm *prm)
> {
> sa->proto = prm->tun.next_proto;
> sa->hdr_len = prm->tun.hdr_len;
> sa->hdr_l3_off = prm->tun.hdr_l3_off;
>
> memcpy(sa->hdr, prm->tun.hdr, prm->tun.hdr_len); // LINE 363: UNCHECKED
> COPY
>
> /* insert UDP header if UDP encapsulation is enabled */
> if (sa->type & RTE_IPSEC_SATP_NATT_ENABLE) {
> struct rte_udp_hdr *udph = (struct rte_udp_hdr *)
> &sa->hdr[prm->tun.hdr_len]; // LINE 368: UNCHECKED OFFSET
> sa->hdr_len += sizeof(struct rte_udp_hdr);
> udph->src_port = rte_cpu_to_be_16(prm->ipsec_xform.udp.sport);
> udph->dst_port = rte_cpu_to_be_16(prm->ipsec_xform.udp.dport);
> udph->dgram_cksum = 0;
> }
> }
> ```
>
> #### Analysis
>
> **Primary Issue (Line 363):**
> - `memcpy()` copies `prm->tun.hdr_len` bytes from `prm->tun.hdr` to `sa->hdr`
> - Destination buffer `sa->hdr` is defined as `uint8_t hdr[IPSEC_MAX_HDR_SIZE]`
> (64 bytes)
> - No bounds check before the copy operation
> - If `prm->tun.hdr_len > IPSEC_MAX_HDR_SIZE`, buffer overflow occurs
>
> **Secondary Issue (Line 368):**
> - Pointer arithmetic: `&sa->hdr[prm->tun.hdr_len]`
> - Writes UDP header at offset `prm->tun.hdr_len`
> - After adding UDP header, total size becomes: `prm->tun.hdr_len + sizeof(struct
> rte_udp_hdr)` (hdr_len + 8 bytes)
> - No validation that `prm->tun.hdr_len + 8 <= IPSEC_MAX_HDR_SIZE`
> - Could write beyond `sa->hdr` buffer bounds
>
> **Mitigating Factor:**
> The function `rte_ipsec_sa_init()` (line 586) performs a bounds check:
> ```c
> if (prm->ipsec_xform.mode == RTE_SECURITY_IPSEC_SA_MODE_TUNNEL) {
> uint32_t hlen = prm->tun.hdr_len;
> if (sa->type & RTE_IPSEC_SATP_NATT_ENABLE)
> hlen += sizeof(struct rte_udp_hdr);
> if (hlen > sizeof(sa->hdr)) // LINE 586: BOUNDS CHECK
> return -EINVAL;
> }
> ```
>
> However, this protection only applies if `rte_ipsec_sa_init()` is the sole caller of
> `esp_outb_tun_init()`.
Yes, rte_ipsec_sa_init() is the *only* caller of esp_outb_tun_init() .
That's why there no need to extra bounds checks in esp_outb_tun_init() -
all checks already supposed to be done at higher level.
False positive.
>
> #### Exploitability
>
> **Attack Vector:**
> 1. Attacker controls SA initialization parameters through control plane API
> 2. Provides `prm->tun.hdr_len` > 64 bytes
> 3. If `esp_outb_tun_init()` is called directly or validation is bypassed, buffer
> overflow occurs
>
> **Impact:**
> - Stack-based buffer overflow (struct rte_ipsec_sa is cache-aligned)
> - Overwrite adjacent structure members:
> - `sqn` union (sequence number state)
> - `statistics` structure
> - Potential code execution if function pointers or return addresses are
> overwritten
> - Memory corruption leading to crashes or undefined behavior
>
> **CVSS v3.1 Score:** 6.5 (MEDIUM)
> - Attack Vector: Local (requires control plane access)
> - Attack Complexity: Low
> - Privileges Required: Low
> - Impact: High (memory corruption, potential code execution)
>
> #### Proof of Concept
>
> ```c
> // Malicious SA parameters
> struct rte_ipsec_sa_prm prm;
> prm.tun.hdr_len = 128; // Exceeds IPSEC_MAX_HDR_SIZE (64)
> prm.type = RTE_IPSEC_SATP_NATT_ENABLE;
>
> // If validation is bypassed:
> esp_outb_tun_init(sa, &prm);
> // Result: Overwrite 64+ bytes beyond sa->hdr buffer
> ```
>
> #### Recommended Fix
>
> **Option 1: Add explicit bounds check in esp_outb_tun_init()**
> ```c
> static int
> esp_outb_tun_init(struct rte_ipsec_sa *sa, const struct rte_ipsec_sa_prm *prm)
> {
> uint32_t hlen = prm->tun.hdr_len;
>
> /* Calculate total header length including UDP if needed */
> if (prm->type & RTE_IPSEC_SATP_NATT_ENABLE)
> hlen += sizeof(struct rte_udp_hdr);
>
> /* Validate total size before copy */
> if (hlen > sizeof(sa->hdr))
> return -EINVAL;
>
> sa->proto = prm->tun.next_proto;
> sa->hdr_len = prm->tun.hdr_len;
> sa->hdr_l3_off = prm->tun.hdr_l3_off;
>
> memcpy(sa->hdr, prm->tun.hdr, prm->tun.hdr_len);
>
> if (prm->type & RTE_IPSEC_SATP_NATT_ENABLE) {
> struct rte_udp_hdr *udph = (struct rte_udp_hdr *)
> &sa->hdr[prm->tun.hdr_len];
> sa->hdr_len += sizeof(struct rte_udp_hdr);
> udph->src_port = rte_cpu_to_be_16(prm->ipsec_xform.udp.sport);
> udph->dst_port = rte_cpu_to_be_16(prm->ipsec_xform.udp.dport);
> udph->dgram_cksum = 0;
> }
>
> return 0;
> }
> ```
>
> **Option 2: Use safe copy function**
> ```c
> /* Use memcpy_s or manual bounds-checked copy */
> if (prm->tun.hdr_len > sizeof(sa->hdr))
> return -EINVAL;
>
> size_t copy_len = RTE_MIN(prm->tun.hdr_len, sizeof(sa->hdr));
> memcpy(sa->hdr, prm->tun.hdr, copy_len);
> ```
>
> ---
>
> ### IPSEC-002: Integer Overflow in Replay Window Size Calculation
>
> **Severity:** MEDIUM
> **CWE:** CWE-190 (Integer Overflow or Wraparound)
> **File:** `sa.c`
> **Functions:** `rsn_size()`, `ipsec_sa_size()`
> **Lines:** 105, 155, 157
>
> #### Vulnerable Code
>
> ```c
> static size_t
> rsn_size(uint32_t nb_bucket)
> {
> size_t sz;
> struct replay_sqn *rsn;
>
> sz = sizeof(*rsn) + nb_bucket * sizeof(rsn->window[0]); // LINE 105: MULTIPLY
> OVERFLOW
> sz = RTE_ALIGN_CEIL(sz, RTE_CACHE_LINE_SIZE);
> return sz;
> }
>
> static int32_t
> ipsec_sa_size(uint64_t type, uint32_t *wnd_sz, uint32_t *nb_bucket)
> {
> uint32_t n, sz, wsz;
>
> wsz = *wnd_sz;
> n = 0;
>
> if ((type & RTE_IPSEC_SATP_DIR_MASK) == RTE_IPSEC_SATP_DIR_IB) {
> wsz = ((type & RTE_IPSEC_SATP_ESN_MASK) ==
> RTE_IPSEC_SATP_ESN_DISABLE) ?
> wsz : RTE_MAX(wsz, (uint32_t) lib/ipsec/sa.h);
> if (wsz != 0)
> n = replay_num_bucket(wsz);
> }
>
> if (n > WINDOW_BUCKET_MAX) // LINE 147: BOUNDS CHECK
> return -EINVAL;
>
> *wnd_sz = wsz;
> *nb_bucket = n;
>
> sz = rsn_size(n);
> if ((type & RTE_IPSEC_SATP_SQN_MASK) == RTE_IPSEC_SATP_SQN_ATOM)
> sz *= REPLAY_SQN_NUM; // LINE 155: MULTIPLY OVERFLOW
>
> sz += sizeof(struct rte_ipsec_sa); // LINE 157: ADDITION OVERFLOW
> return sz;
WINDOW_BUCKET_MAX == 2^15
REPLAY_SQN_NUM == 2
sizeof(*rsn) == 24
sizeof(rsn->window[0]) == 8
nb_bucket <= WINDOW_BUCKET_MAX
So:
MAX(sizeof(*rsn) + nb_bucket * sizeof(rsn->window[0])) == 262168
262168*2 == 524336
That's much less then max of size_t, even if we assume that sizeoif(size_t) == 4
False positive.
> }
> ```
>
> #### Analysis
>
> **Issue 1 (Line 105): Multiplication Overflow**
> - `nb_bucket * sizeof(rsn->window[0])` can overflow
> - `sizeof(rsn->window[0])` is typically 8 bytes (uint64_t)
> - If `nb_bucket` is large (near UINT32_MAX), multiplication overflows
> - Overflow wraps to small value, leading to under-allocation
>
> **Issue 2 (Line 155): Multiplication by REPLAY_SQN_NUM**
> - `REPLAY_SQN_NUM` is 2 (defined in sa.h:56)
> - If `sz` from `rsn_size()` is already large, doubling it could overflow
> - `sz` is `uint32_t`, max value 4,294,967,295
> - Overflow wraps to small value
>
> **Issue 3 (Line 157): Addition Overflow**
> - Adding `sizeof(struct rte_ipsec_sa)` to `sz`
> - If `sz` is already near UINT32_MAX, addition overflows
> - Returns small wrapped value as allocation size
>
> **Mitigating Factor:**
> Line 147 checks `if (n > WINDOW_BUCKET_MAX)` which limits `nb_bucket`.
> However, `WINDOW_BUCKET_MAX` value is not visible in this context.
>
> #### Exploitability
>
> **Attack Vector:**
> 1. Attacker requests SA with large replay window size
> 2. `replay_num_bucket()` calculates large `nb_bucket` value
> 3. Integer overflow in size calculations
> 4. `rte_zmalloc_socket()` allocates undersized buffer
> 5. Subsequent operations write beyond allocated memory
>
> **Impact:**
> - Heap-based buffer overflow
> - Memory corruption in SA structure
> - Potential arbitrary code execution through heap exploitation
> - Crash/DoS through memory corruption
>
> **CVSS v3.1 Score:** 7.0 (HIGH)
> - Attack Vector: Local
> - Attack Complexity: Low
> - Impact: High (heap corruption, code execution potential)
>
> #### Recommended Fix
>
> ```c
> static size_t
> rsn_size(uint32_t nb_bucket)
> {
> size_t sz;
> struct replay_sqn *rsn;
>
> /* Check for multiplication overflow */
> if (nb_bucket > (SIZE_MAX - sizeof(*rsn)) / sizeof(rsn->window[0]))
> return 0; // Signal error
>
> sz = sizeof(*rsn) + nb_bucket * sizeof(rsn->window[0]);
>
> /* Check for alignment overflow */
> if (sz > SIZE_MAX - RTE_CACHE_LINE_SIZE + 1)
> return 0;
>
> sz = RTE_ALIGN_CEIL(sz, RTE_CACHE_LINE_SIZE);
> return sz;
> }
>
> static int32_t
> ipsec_sa_size(uint64_t type, uint32_t *wnd_sz, uint32_t *nb_bucket)
> {
> uint32_t n, sz, wsz;
>
> /* ... existing code ... */
>
> sz = rsn_size(n);
> if (sz == 0) // Check for overflow
> return -EINVAL;
>
> if ((type & RTE_IPSEC_SATP_SQN_MASK) == RTE_IPSEC_SATP_SQN_ATOM) {
> /* Check for multiplication overflow before doubling */
> if (sz > UINT32_MAX / REPLAY_SQN_NUM)
> return -EINVAL;
> sz *= REPLAY_SQN_NUM;
> }
>
> /* Check for addition overflow */
> if (sz > UINT32_MAX - sizeof(struct rte_ipsec_sa))
> return -EINVAL;
>
> sz += sizeof(struct rte_ipsec_sa);
> return sz;
> }
> ```
>
> ---
>
> ### IPSEC-003: Null Pointer Dereference in Crypto Transform Validation
>
> **Severity:** MEDIUM
> **CWE:** CWE-476 (NULL Pointer Dereference)
> **File:** `sa.c`
> **Function:** `fill_crypto_xform()`
> **Lines:** 50-56
>
> #### Vulnerable Code
>
> ```c
> static int
> fill_crypto_xform(struct crypto_xform *xform, uint64_t type,
> const struct rte_ipsec_sa_prm *prm)
> {
> struct rte_crypto_sym_xform *xf, *xfn;
>
> memset(xform, 0, sizeof(*xform));
>
> xf = prm->crypto_xform;
> if (xf == NULL)
> return -EINVAL;
>
> xfn = xf->next;
>
> /* for AEAD just one xform required */
> if (xf->type == RTE_CRYPTO_SYM_XFORM_AEAD) {
> if (xfn != NULL)
> return -EINVAL;
> xform->aead = &xf->aead;
>
> /* GMAC has only auth */
> } else if (xf->type == RTE_CRYPTO_SYM_XFORM_AUTH &&
> xf->auth.algo == RTE_CRYPTO_AUTH_AES_GMAC) {
> if (xfn != NULL) // LINE 53: CHECK xfn != NULL
> return -EINVAL;
> xform->auth = &xf->auth;
> xform->cipher = &xfn->cipher; // LINE 56: DEREFERENCE NULL xfn
>
> /* ... rest of function ... */
> }
> ```
>
> #### Analysis
>
> **Logic Error:**
> - Line 53: Checks `if (xfn != NULL)` and returns error if condition is true
> - Line 54: Returns `-EINVAL` when `xfn != NULL`
> - Line 55: Continues execution only if `xfn == NULL`
> - Line 56: Dereferences `xfn->cipher` when `xfn` is guaranteed to be NULL
>
> **This is a logic bug.** The code should check `if (xfn == NULL)` for GMAC, but
> instead checks `if (xfn != NULL)`.
ACK, looks like a real one to me.
>
> Looking at the rest of the function (lines 64-83), other paths properly validate
> that `xfn != NULL` before use:
> ```c
> } else if ((type & RTE_IPSEC_SATP_DIR_MASK) == RTE_IPSEC_SATP_DIR_IB) {
> /* wrong order or no cipher */
> if (xfn == NULL || xf->type != RTE_CRYPTO_SYM_XFORM_AUTH ||
> xfn->type != RTE_CRYPTO_SYM_XFORM_CIPHER)
> return -EINVAL;
>
> xform->auth = &xf->auth;
> xform->cipher = &xfn->cipher; // SAFE: xfn checked != NULL
> ```
>
> #### Exploitability
>
> **Attack Vector:**
> 1. Configure SA with AES-GMAC authentication
> 2. Provide crypto_xform with `xf->next == NULL`
> 3. Code passes line 53 check (xfn == NULL)
> 4. Line 56 dereferences NULL pointer
> 5. Segmentation fault / crash
>
> **Impact:**
> - Denial of Service (application crash)
> - Null pointer dereference may be exploitable on some systems
> - Disruption of IPsec service
>
> **CVSS v3.1 Score:** 5.5 (MEDIUM)
> - Attack Vector: Local
> - Attack Complexity: Low
> - Availability Impact: High (DoS)
>
> #### Recommended Fix
>
> The condition on line 53 should be inverted:
>
> ```c
> /* GMAC has only auth */
> } else if (xf->type == RTE_CRYPTO_SYM_XFORM_AUTH &&
> xf->auth.algo == RTE_CRYPTO_AUTH_AES_GMAC) {
> if (xfn == NULL) // FIXED: Check for NULL, not NOT NULL
> return -EINVAL;
> xform->auth = &xf->auth;
> xform->cipher = &xfn->cipher; // Now safe
> ```
>
> **Alternative:** Based on the comment "GMAC has only auth", it appears GMAC
> should NOT have a cipher transform. The correct fix may be:
>
> ```c
> /* GMAC has only auth */
> } else if (xf->type == RTE_CRYPTO_SYM_XFORM_AUTH &&
> xf->auth.algo == RTE_CRYPTO_AUTH_AES_GMAC) {
> if (xfn != NULL)
> return -EINVAL;
> xform->auth = &xf->auth;
> // Do NOT set xform->cipher for GMAC - remove line 56
> ```
>
> **Recommended:** Review AES-GMAC RFC 4543 specification to determine
> correct behavior.
>
> ---
>
> ### IPSEC-004: Race Condition in SAD Counter Updates
>
> **Severity:** MEDIUM
> **CWE:** CWE-362 (Concurrent Execution using Shared Resource with Improper
> Synchronization)
> **File:** `ipsec_sad.c`
> **Function:** `add_specific()`
> **Lines:** 110, 112
>
> #### Vulnerable Code
>
> ```c
> static inline int
> add_specific(struct rte_ipsec_sad *sad, const void *key,
> int key_type, void *sa)
> {
> void *tmp_val;
> int ret, notexist;
>
> /* Check if the key is present in the table. */
> ret = rte_hash_lookup_with_hash(sad->hash[key_type], key,
> rte_hash_crc(key, sad->keysize[key_type], sad->init_val));
> notexist = (ret == -ENOENT);
>
> /* Add an SA to the corresponding table.*/
> ret = rte_hash_add_key_with_hash_data(sad->hash[key_type], key,
> rte_hash_crc(key, sad->keysize[key_type], sad->init_val), sa);
> if (ret != 0)
> return ret;
>
> /* ... middle section ... */
>
> /* Update a counter for a given SPI */
> ret = rte_hash_lookup_with_hash(sad->hash[RTE_IPSEC_SAD_SPI_ONLY], key,
> rte_hash_crc(key, sad->keysize[RTE_IPSEC_SAD_SPI_ONLY],
> sad->init_val));
> if (ret < 0)
> return ret;
> if (key_type == RTE_IPSEC_SAD_SPI_DIP)
> sad->cnt_arr[ret].cnt_dip += notexist; // LINE 110: NON-ATOMIC
> INCREMENT
> else
> sad->cnt_arr[ret].cnt_dip_sip += notexist; // LINE 112: NON-ATOMIC
> INCREMENT
>
> return 0;
> }
> ```
>
> #### Analysis
>
> **Race Condition:**
> - SAD can be created with `RTE_IPSEC_SAD_FLAG_RW_CONCURRENCY` flag (line
> 307-308)
> - When this flag is set, hash tables support concurrent read/write operations
> - Counter increments at lines 110 and 112 are NOT atomic
> - Multiple threads can simultaneously add SAs with the same SPI
This is not supported by current API.
Right now SAD only supports multiple-readers/single-write MT model.
I think it is quite clearly documented in the SAD API.
False positive.
>
> **Race Scenario:**
> ```
> Thread 1: Thread 2:
> Read cnt_arr[ret].cnt_dip (=5)
> Read cnt_arr[ret].cnt_dip (=5)
> Increment (5 + 1 = 6)
> Increment (5 + 1 = 6)
> Write back (cnt_dip = 6)
> Write back (cnt_dip = 6)
>
> Expected result: 7
> Actual result: 6 (lost update)
> ```
>
> **Similar Issue in `del_specific()` (line 191):**
> ```c
> if (--(*cnt) != 0) // NON-ATOMIC DECREMENT
> return 0;
> ```
>
> #### Exploitability
>
> **Attack Vector:**
> 1. Configure SAD with RW_CONCURRENCY flag
> 2. Initiate concurrent SA add operations from multiple threads with same SPI
> 3. Race condition causes counter inconsistency
> 4. Counter underflow in subsequent delete operations
> 5. Reference counting corruption leads to use-after-free
>
> **Impact:**
> - Counter inconsistency (lost updates)
> - Reference counting corruption
> - Potential use-after-free if counters control SA lifetime
> - Logic errors in SAD entry management
> - Memory leaks or double-free conditions
>
> **CVSS v3.1 Score:** 4.0 (MEDIUM)
> - Attack Vector: Local
> - Attack Complexity: High (requires specific concurrent scenario)
> - Impact: Medium (logic errors, potential memory corruption)
>
> #### Recommended Fix
>
> Use atomic operations for counter updates:
>
> ```c
> static inline int
> add_specific(struct rte_ipsec_sad *sad, const void *key,
> int key_type, void *sa)
> {
> void *tmp_val;
> int ret, notexist;
>
> /* ... existing code ... */
>
> /* Update a counter for a given SPI - ATOMIC OPERATION */
> ret = rte_hash_lookup_with_hash(sad->hash[RTE_IPSEC_SAD_SPI_ONLY], key,
> rte_hash_crc(key, sad->keysize[RTE_IPSEC_SAD_SPI_ONLY],
> sad->init_val));
> if (ret < 0)
> return ret;
>
> if (key_type == RTE_IPSEC_SAD_SPI_DIP)
> __atomic_add_fetch(&sad->cnt_arr[ret].cnt_dip, notexist,
> __ATOMIC_SEQ_CST);
> else
> __atomic_add_fetch(&sad->cnt_arr[ret].cnt_dip_sip, notexist,
> __ATOMIC_SEQ_CST);
>
> return 0;
> }
>
> static inline int
> del_specific(struct rte_ipsec_sad *sad, const void *key, int key_type)
> {
> void *tmp_val;
> int ret;
> uint32_t *cnt;
> uint32_t old_val;
>
> /* ... existing code ... */
>
> cnt = (key_type == RTE_IPSEC_SAD_SPI_DIP) ?
> &sad->cnt_arr[ret].cnt_dip :
> &sad->cnt_arr[ret].cnt_dip_sip;
>
> /* Atomic decrement and check */
> old_val = __atomic_sub_fetch(cnt, 1, __ATOMIC_SEQ_CST);
> if (old_val != 0)
> return 0;
>
> /* ... rest of function ... */
> }
> ```
>
> ---
>
> ### IPSEC-005: Non-Atomic Statistics Updates in Data Plane
>
> **Severity:** MEDIUM
> **CWE:** CWE-362 (Race Condition)
> **Files:** `sa.c`, `esp_inb.c`, `esp_outb.c`
> **Lines:** Multiple locations
>
> #### Vulnerable Code
>
> **Location 1: sa.c:678-679**
> ```c
> uint16_t
> pkt_flag_process(const struct rte_ipsec_session *ss,
> struct rte_mbuf *mb[], uint16_t num)
> {
> uint32_t i, k, bytes;
> uint32_t dr[num];
>
> k = 0;
> bytes = 0;
> for (i = 0; i != num; i++) {
> if ((mb[i]->ol_flags & RTE_MBUF_F_RX_SEC_OFFLOAD_FAILED) == 0) {
> k++;
> bytes += mb[i]->pkt_len;
> }
> else
> dr[i - k] = i;
> }
>
> ss->sa->statistics.count += k; // LINE 678: NON-ATOMIC
> ss->sa->statistics.bytes += bytes; // LINE 679: NON-ATOMIC
> ```
>
> **Location 2: esp_inb.c:629-630**
> ```c
> sa->statistics.count += k; // NON-ATOMIC
> sa->statistics.bytes += bytes; // NON-ATOMIC
> ```
>
> **Location 3: esp_outb.c:690-691, 723-724**
> ```c
> sa->statistics.count += k; // NON-ATOMIC
> sa->statistics.bytes += bytes; // NON-ATOMIC
Yep, looks like a valid bug that was overlooked.
> ```
>
> #### Analysis
>
> **Statistics Structure (sa.h:135-142):**
> ```c
> struct __rte_cache_aligned rte_ipsec_sa {
> /* ... */
>
> /* Statistics */
> struct {
> uint64_t count;
> uint64_t bytes;
> struct {
> uint64_t count;
> uint64_t authentication_failed;
> } errors;
> } statistics;
> };
> ```
>
> **Race Condition:**
> - SA structures can be accessed by multiple threads in data plane
> - Statistics counters are updated without atomic operations
> - Read-Modify-Write operations are not atomic
> - Multiple cores processing packets for same SA cause lost updates
>
> **Impact on Operations:**
> - Monitoring and telemetry receive incorrect statistics
> - Security auditing is compromised
> - Performance metrics are unreliable
> - Could mask security incidents if error counters are incorrect
>
> #### Exploitability
>
> **Attack Vector:**
> 1. Process IPsec traffic on multiple cores simultaneously
> 2. All cores update same SA statistics
> 3. Statistics become increasingly inaccurate under high load
> 4. Security monitoring systems receive false data
>
> **Impact:**
> - Statistics integrity compromised
> - Monitoring/alerting unreliable
> - Cannot accurately track packet counts for security auditing
> - Could hide attack patterns if counters are incorrect
>
> **CVSS v3.1 Score:** 3.0 (LOW-MEDIUM)
> - Attack Vector: Local
> - Impact: Low (information integrity)
> - However, severity increases in security monitoring contexts
>
> #### Recommended Fix
>
> Use atomic operations for all statistics updates:
>
> ```c
> /* Option 1: Use RTE_ATOMIC macro (DPDK style) */
> struct __rte_cache_aligned rte_ipsec_sa {
> /* ... */
>
> /* Statistics */
> struct {
> RTE_ATOMIC(uint64_t) count;
> RTE_ATOMIC(uint64_t) bytes;
> struct {
> RTE_ATOMIC(uint64_t) count;
> RTE_ATOMIC(uint64_t) authentication_failed;
> } errors;
> } statistics;
> };
>
> /* Update code */
> __atomic_add_fetch(&ss->sa->statistics.count, k, __ATOMIC_RELAXED);
> __atomic_add_fetch(&ss->sa->statistics.bytes, bytes, __ATOMIC_RELAXED);
>
> /* Option 2: Use per-core statistics with aggregation */
> // Maintain per-lcore statistics to avoid contention
> // Aggregate when reading via telemetry interface
> ```
>
> ---
>
> ### IPSEC-006: Timing Attack in SAD Lookup Operations
>
> **Severity:** LOW
> **CWE:** CWE-208 (Observable Timing Discrepancy)
> **File:** `ipsec_sad.c`
> **Function:** `add_specific()`, `__ipsec_sad_lookup()`
> **Lines:** 77-78, 104-106, etc.
>
> #### Vulnerable Code
>
> ```c
> ret = rte_hash_lookup_with_hash(sad->hash[key_type], key,
> rte_hash_crc(key, sad->keysize[key_type], sad->init_val));
> ```
>
> #### Analysis
>
> **Timing Variations:**
> - Hash table lookups have variable execution time
> - Time depends on:
> - Hash bucket occupancy (collisions)
> - Cache hits/misses
> - Key comparison results
> - Different SPIs may have different lookup times
>
> **Information Leakage:**
> - Attacker can measure lookup times
> - Timing differences reveal information about SAD structure:
> - Whether specific SPIs are present
> - Hash table occupancy patterns
> - Collision chains
>
> **Practical Exploitability:**
> - Requires precise timing measurement
> - Requires ability to trigger SAD lookups
> - Generally difficult to exploit in production networks
> - More relevant for side-channel research
>
> #### Impact
>
> **CVSS v3.1 Score:** 3.5 (LOW)
> - Attack Vector: Local/Network
> - Attack Complexity: High
> - Impact: Low (information disclosure)
>
> #### Recommended Mitigation
>
> ```c
> /* Use constant-time hash lookup for security-sensitive operations */
> // This requires architectural changes to hash table implementation
> // or use of dedicated constant-time data structures
>
> /* Alternative: Add artificial delays to normalize timing */
> static inline int
> ct_hash_lookup(struct rte_hash *hash, const void *key, hash_sig_t sig)
> {
> int ret;
> uint64_t start_tsc, elapsed_tsc;
>
> start_tsc = rte_rdtsc();
> ret = rte_hash_lookup_with_hash(hash, key, sig);
> elapsed_tsc = rte_rdtsc() - start_tsc;
>
> /* Pad to constant time (e.g., worst-case lookup time) */
> const uint64_t target_cycles = 1000; // Tune based on measurements
> if (elapsed_tsc < target_cycles)
> rte_delay_us_sleep((target_cycles - elapsed_tsc) / rte_get_tsc_hz() *
> 1000000);
>
> return ret;
> }
> ```
>
> **Note:** Constant-time mitigation adds performance overhead. Consider risk
> vs. performance trade-off.
That's something very unpractical.
False positive.
>
> ---
>
> ### IPSEC-007: Non-Constant Time String Comparison
>
> **Severity:** LOW
> **CWE:** CWE-208 (Observable Timing Discrepancy)
> **File:** `ipsec_sad.c`
> **Function:** `rte_ipsec_sad_create()`
> **Lines:** 361-362
>
> #### Vulnerable Code
>
> ```c
> TAILQ_FOREACH(te, sad_list, next) {
> tmp_sad = (struct rte_ipsec_sad *)te->data;
> if (strncmp(sad_name, tmp_sad->name,
> RTE_IPSEC_SAD_NAMESIZE) == 0) // NON-CONSTANT TIME
> break;
> }
> ```
>
> #### Analysis
>
> **Non-Constant Time Comparison:**
> - `strncmp()` returns early when first difference is found
> - Comparison time reveals information about string similarity
> - Attacker can measure timing to determine:
> - How many characters match
> - Structure of existing SAD names
If attacker has access to CP, he can simply query what SADs exist :)
False positive.
> **Practical Impact:**
> - Used during SAD creation (control plane operation)
> - Not in hot path (data plane)
> - Limited security impact
> - More of a code hygiene issue
>
> #### Recommended Fix
>
> ```c
> /* Use constant-time comparison for security-sensitive operations */
> static inline int
> ct_strncmp(const char *s1, const char *s2, size_t n)
> {
> unsigned char diff = 0;
> size_t i;
>
> for (i = 0; i < n; i++)
> diff |= (s1[i] ^ s2[i]);
>
> return diff;
> }
>
> /* Replace in code */
> if (ct_strncmp(sad_name, tmp_sad->name, RTE_IPSEC_SAD_NAMESIZE) == 0)
> break;
> ```
>
> **CVSS v3.1 Score:** 2.0 (LOW)
>
> ---
>
> ## ATTACK SCENARIOS
>
> ### Scenario 1: Control Plane Buffer Overflow Attack
>
> **Attacker Goal:** Gain code execution through SA initialization
>
> **Steps:**
> 1. Attacker gains access to IPsec control plane API (e.g., management interface)
> 2. Creates malicious SA configuration:
> ```c
> struct rte_ipsec_sa_prm prm = {
> .tun.hdr_len = 120, // Exceeds buffer size
> .type = RTE_IPSEC_SATP_NATT_ENABLE,
> // ... craft payload to overwrite specific memory ...
> };
> ```
> 3. Submits SA initialization request
> 4. If validation is bypassed, buffer overflow occurs in `esp_outb_tun_init()`
> 5. Overwrites `sqn` and `statistics` members in SA structure
> 6. Potential ROP chain or function pointer overwrite
> 7. Achieves arbitrary code execution
>
> **Prerequisites:**
> - Access to SA configuration API
> - Ability to bypass or circumvent validation in `rte_ipsec_sa_init()`
>
> **Detection:**
> - Monitor SA initialization requests for suspicious parameters
> - Log all SA creation events
> - Alert on abnormal `tun.hdr_len` values > 64
>
> ---
>
> ### Scenario 2: Integer Overflow Leading to Heap Corruption
>
> **Attacker Goal:** Corrupt heap memory through undersized allocation
>
> **Steps:**
> 1. Attacker configures SA with very large replay window:
> ```c
> prm.ipsec_xform.replay_win_sz = 0xFFFFFF00; // Near max uint32
> ```
> 2. `replay_num_bucket()` calculates extremely large bucket count
> 3. Integer overflow in `rsn_size()` wraps to small value
> 4. Allocation size calculation overflows in `ipsec_sa_size()`
> 5. Small buffer allocated for large SA structure
> 6. Subsequent replay window operations overflow buffer
> 7. Heap corruption leads to:
> - Adjacent structure overwrite
> - Heap metadata corruption
> - Potential arbitrary code execution
>
> **Prerequisites:**
> - Control over SA replay window size parameter
> - Large enough value to trigger overflow
>
> **Detection:**
> - Validate replay window size at API boundary
> - Monitor allocation failures
> - Implement heap corruption detection (canaries, ASAN)
>
> ---
>
> ### Scenario 3: Race Condition Exploitation
>
> **Attacker Goal:** Corrupt SAD reference counts
>
> **Steps:**
> 1. Configure SAD with `RTE_IPSEC_SAD_FLAG_RW_CONCURRENCY`
> 2. Launch concurrent SA add operations from multiple threads:
> - All with same SPI but different DIP/SIP combinations
> 3. Race condition causes counter corruption:
> - Multiple threads increment same counter simultaneously
> - Lost updates lead to incorrect reference count
> 4. Delete SAs to trigger counter decrement:
> - Counter underflows due to incorrect initial value
> 5. SAD entry deleted prematurely while still in use
> 6. Use-after-free condition when accessing deleted SAD entry
>
> **Prerequisites:**
> - Multi-core system with concurrent SAD operations
> - Control over SA add/delete timing
>
> **Detection:**
> - Implement counter consistency checks
> - Use atomic operations (prevents exploit)
> - Monitor for unexpected SAD state
>
> ---
>
> ## REMEDIATION RECOMMENDATIONS
>
> ### Immediate Actions (Priority 1)
>
> 1. **IPSEC-001: Buffer Overflow**
> - Add bounds checking in `esp_outb_tun_init()`
> - Validate header length before copy
> - Status: **CRITICAL - Patch immediately**
>
> 2. **IPSEC-002: Integer Overflow**
> - Implement overflow checks in size calculations
> - Use safe arithmetic for multiplications
> - Status: **CRITICAL - Patch immediately**
>
> 3. **IPSEC-003: Null Pointer Dereference**
> - Fix logic error in GMAC validation
> - Add explicit null checks
> - Status: **HIGH - Fix in next release**
>
> ### Short-term Actions (Priority 2)
>
> 4. **IPSEC-004: Race Condition**
> - Replace increment/decrement with atomic operations
> - Test under concurrent load
> - Status: **MEDIUM - Fix in next minor release**
>
> 5. **IPSEC-005: Statistics Race**
> - Implement atomic statistics updates
> - Consider per-core statistics aggregation
> - Status: **MEDIUM - Fix in next minor release**
>
> ### Long-term Hardening (Priority 3)
>
> 6. **IPSEC-006: Timing Attack**
> - Evaluate constant-time hash implementation
> - Assess risk vs. performance trade-off
> - Status: **LOW - Consider for major release**
>
> 7. **IPSEC-007: String Comparison**
> - Replace with constant-time comparison
> - Code hygiene improvement
> - Status: **LOW - Include in hardening sprint**
>
> ---
>
> ## SECURITY HARDENING SUGGESTIONS
>
> ### 1. Input Validation Framework
>
> Implement comprehensive input validation at API boundaries:
>
> ```c
> /* Example validation framework */
> static int
> validate_sa_params(const struct rte_ipsec_sa_prm *prm)
> {
> /* Header length validation */
> if (prm->tun.hdr_len > IPSEC_MAX_HDR_SIZE - sizeof(struct rte_udp_hdr))
> return -EINVAL;
>
> /* Replay window size validation */
> if (prm->ipsec_xform.replay_win_sz > MAX_SAFE_REPLAY_WINDOW)
> return -EINVAL;
>
> /* SPI validation */
> if (prm->ipsec_xform.spi == 0)
> return -EINVAL;
>
> /* Crypto transform validation */
> if (prm->crypto_xform == NULL)
> return -EINVAL;
>
> return 0;
> }
> ```
>
> ### 2. Fuzzing and Testing
>
> Implement comprehensive fuzzing:
> - Use AFL++, libFuzzer, or similar
> - Focus on SA initialization paths
> - Test with random replay window sizes
> - Concurrent operation fuzzing
>
> ### 3. Memory Safety
>
> Deploy memory safety tools:
> - Enable AddressSanitizer (ASAN) in testing
> - Enable ThreadSanitizer (TSAN) for concurrency testing
> - Use Valgrind for production-like testing
> - Implement heap canaries
>
> ### 4. Static Analysis
>
> Integrate static analysis tools:
> - Coverity Scan
> - Clang Static Analyzer
> - Cppcheck
> - CodeQL
>
> ### 5. Runtime Protection
>
> Enable runtime protections:
> - Stack canaries (-fstack-protector-strong)
> - Position Independent Executable (PIE)
> - RELRO (relocation read-only)
> - Fortify source (-D_FORTIFY_SOURCE=2)
>
> ### 6. Audit Logging
>
> Implement security event logging:
> ```c
> /* Log all SA operations */
> static void
> log_sa_operation(const char *op, const struct rte_ipsec_sa_prm *prm, int result)
> {
> rte_log(RTE_LOG_INFO, ipsec_logtype,
> "SA %s: spi=%u, result=%d, hdr_len=%u, replay_win=%u\n",
> op, prm->ipsec_xform.spi, result,
> prm->tun.hdr_len, prm->ipsec_xform.replay_win_sz);
> }
> ```
>
> ### 7. Secure Coding Guidelines
>
> Document secure coding practices:
> - Always validate buffer sizes before copy
> - Use overflow-safe arithmetic
> - Employ atomic operations for shared counters
> - Validate all API inputs
> - Use const pointers where applicable
>
> ---
>
> ## CONCLUSION
>
> The DPDK IPsec library demonstrates generally sound engineering practices but
> contains several exploitable vulnerabilities requiring remediation. The most
> critical issues are:
>
> 1. **Buffer overflow in tunnel header copy** - Immediate patching required
> 2. **Integer overflow in size calculations** - High risk of heap corruption
> 3. **Null pointer dereference** - Causes crash/DoS
>
> Secondary issues include race conditions and timing side-channels that should be
> addressed for security hardening.
>
> **Recommended Timeline:**
> - **Week 1:** Patch IPSEC-001 and IPSEC-002 (critical)
> - **Week 2-3:** Fix IPSEC-003 and test thoroughly
> - **Month 2:** Address race conditions (IPSEC-004, IPSEC-005)
> - **Month 3+:** Implement hardening measures and address timing issues
>
> **Overall Risk Assessment:** MEDIUM-HIGH
> - Critical vulnerabilities exist but require control plane access
> - Data plane is relatively robust
> - Proper API usage provides significant mitigation
> - Concurrent operation support needs hardening
>
> ---
>
> ## REFERENCES
>
> - **DPDK Documentation:** https://doc.dpdk.org/
> - **RFC 4303:** IP Encapsulating Security Payload (ESP)
> - **RFC 4106:** AES-GCM for IPsec ESP
> - **CWE Database:** https://cwe.mitre.org/
> - **CVSS Calculator:** https://www.first.org/cvss/calculator/3.1
>
> ---
>
> **Report Prepared By:** Claude Code Security Analysis
> **Date:** April 8, 2026
> **Version:** 1.0
> **Classification:** CONFIDENTIAL - Internal Security Review
More information about the dev
mailing list