Hi,
I found a missing input validation issue in the Marvell cnxk IPsec
Security Association (SA) creation paths. When DES_CBC or 3DES_CBC is
used, the cipher key length from the user-supplied xform is never
validated before being passed to memcpy(), which can cause an
out-of-bounds write into adjacent SA structure members.
The issue affects three independent code paths (ON/OT/OW families).
The root cause is the same in all three: the AES key length validation
block is only entered for AES-family algorithms, and no equivalent
check exists for DES/3DES.
Confirmed against current HEAD (8dc80afd, 2026-03-05).
== Root Cause ==
Consider on_fill_ipsec_common_sa() in
drivers/common/cnxk/cnxk_security.c, lines 940-991.
The function first calls on_ipsec_sa_ctl_set(), which for 3DES_CBC
only sets the enc_type field without examining key length (line
848-850):
case RTE_CRYPTO_CIPHER_3DES_CBC:
ctl->enc_type = ROC_IE_SA_ENC_3DES_CBC;
break;
Compare this with AES_CBC, which captures the key length into
aes_key_len for later validation (line 851-854):
case RTE_CRYPTO_CIPHER_AES_CBC:
ctl->enc_type = ROC_IE_SA_ENC_AES_CBC;
aes_key_len =
cipher_xform->cipher.key.length;
break;
Back in on_fill_ipsec_common_sa(), the cipher key is then copied with
the user-supplied length, unconditionally (lines 973-988):
if (cipher_xform) {
cipher_key =
cipher_xform->cipher.key.data;
cipher_key_len =
cipher_xform->cipher.key.length;
}
...
if (cipher_key_len != 0)
memcpy(common_sa->cipher_key, cipher_key,
cipher_key_len);
The only length validation that follows is restricted to AES types
(lines 900-920):
if (ctl->enc_type == ROC_IE_SA_ENC_AES_CBC ||
ctl->enc_type == ROC_IE_SA_ENC_AES_CTR ||
ctl->enc_type == ROC_IE_SA_ENC_AES_GCM ||
ctl->enc_type == ROC_IE_SA_ENC_AES_CCM ||
ctl->auth_type ==
ROC_IE_SA_AUTH_AES_GMAC) {
switch (aes_key_len) {
case 16: ... case 24: ... case 32: ... break;
default: return -EINVAL;
}
}
For DES/3DES, this block is never entered. No other validation exists
in this path. The function returns 0 (success) regardless of
cipher_key_len.
== Affected Structure Layout ==
The destination buffer cipher_key[32] is immediately followed by
active SA members in all three SA structure families:
ON family (roc_ie_on.h, struct roc_ie_on_common_sa):
uint8_t cipher_key[32];
/* w1-w4 */
union roc_ie_on_bit_perfect_iv iv; /* w5-w6, adjacent */
OT family (roc_ie_ot.h, struct roc_ot_ipsec_outb_sa):
uint8_t cipher_key[32];
/* Word4-7 */
union roc_ot_ipsec_outb_iv iv; /* Word8-9,
adjacent */
OW family (roc_ie_ow.h, struct roc_ow_ipsec_outb_sa):
uint8_t cipher_key[32];
/* Word4-7 */
union roc_ow_ipsec_outb_iv iv; /* Word8-9,
adjacent */
For inbound SA structures, cipher_key[32] is followed by the w8 union
containing the salt field, also adjacent.
== All Affected Sites ==
1. on_fill_ipsec_common_sa() -- line 988
memcpy(common_sa->cipher_key, cipher_key,
cipher_key_len);
Called from: cnxk_on_ipsec_outb_sa_create() (line 1015)
cnxk_on_ipsec_inb_sa_create() (line 1151)
Callers: cn9k_eth_sec_session_create()
2. ot_ipsec_sa_common_param_fill() -- line 174
memcpy(cipher_key, key, length);
Called from: cnxk_ot_ipsec_inb_sa_fill() (line 324)
cnxk_ot_ipsec_outb_sa_fill() (line 437)
Callers: cn10k_eth_sec_session_create()
3. ow_ipsec_sa_common_param_fill() -- line 1368
memcpy(cipher_key, key, length);
Called from: cnxk_ow_ipsec_inb_sa_fill() (line 1509)
cnxk_ow_ipsec_outb_sa_fill() (line 1619)
Callers: cn20k_eth_sec_session_create()
None of these callers (cn9k/cn10k/cn20k_eth_sec_session_create)
invoke cnxk_ipsec_xform_verify() before calling the SA fill
functions.
== Why Inline Paths Are Unprotected ==
The validation function cnxk_ipsec_xform_verify() already exists in
drivers/crypto/cnxk/cnxk_ipsec.h and correctly rejects invalid key
lengths:
/* 3DES: exactly 24 bytes */
if (crypto_xform->cipher.algo ==
RTE_CRYPTO_CIPHER_3DES_CBC &&
crypto_xform->cipher.key.length == 24)
return 0;
/* DES: exactly 8 bytes */
if (crypto_xform->cipher.algo ==
RTE_CRYPTO_CIPHER_DES_CBC &&
crypto_xform->cipher.key.length == 8)
return 0;
However, this function is defined as static inline in the crypto
driver header and is only called from the lookaside crypto session
creation paths (cn9k_ipsec.c, cn10k_ipsec.c, cn20k_ipsec.c under
drivers/crypto/cnxk/). The inline IPsec ethdev paths (cn9k/cn10k/
cn20k_ethdev_sec.c under drivers/net/cnxk/) never call it.
Additionally, the DPDK security framework function
rte_security_session_create() (lib/security/rte_security.c:70)
performs no key length validation against the capability table -- it
passes conf directly to the driver's session_create callback.
== Concrete Example ==
3DES_CBC with a 40-byte key through the CN9K outbound path:
cipher_xform.cipher.algo = RTE_CRYPTO_CIPHER_3DES_CBC;
cipher_xform.cipher.key.length = 40;
rte_security_session_create(ctx, &conf, mp);
Call chain:
rte_security_session_create()
-- no validation
-> cn9k_eth_sec_session_create() --
no xform_verify call
-> cnxk_on_ipsec_outb_sa_create()
-> on_fill_ipsec_common_sa()
-> on_ipsec_sa_ctl_set()
-- sets enc_type only
-> memcpy(cipher_key, key, 40) --
overflows by 8 bytes
Result: the first 32 bytes fill cipher_key[32], the remaining 8 bytes
overwrite the adjacent iv member. The function returns 0 (success),
and the corrupted SA is accepted for use.
== Suggested Fix ==
Add DES/3DES key length validation alongside the existing AES checks.
The simplest approach is to add explicit checks in each
*_common_param_fill / on_fill_ipsec_common_sa function, before the
memcpy. For example:
if (w2->s.enc_type == ROC_IE_SA_ENC_DES_CBC &&
length != 8)
return -EINVAL;
if (w2->s.enc_type == ROC_IE_SA_ENC_3DES_CBC &&
length != 24)
return -EINVAL;
Alternatively (and more comprehensively), the existing
cnxk_ipsec_xform_verify() could be moved to the shared common layer
(drivers/common/cnxk/) and called from all session_create paths --
both crypto and inline. This would also close similar gaps for other
algorithm/key combinations in the inline path.
== Impact ==
- Corrupts IV/salt fields in the IPsec SA, leading to incorrect
cryptographic operations or hardware errors on the Octeon crypto
engine.
- The SA is accepted as successfully created, so the caller has no
indication that the SA state is corrupted.
- Requires the application to pass an invalid key length, which would
not occur in correct usage but represents a missing defensive
check
at an API boundary.
Best regards,
Pengpeng Hou
[email protected]