Hi,
I found a missing input validation issue in the CN9K (ON family)
inbound IPsec SA creation path. When AES-XCBC-MAC is used as the
authentication algorithm, the auth key length from the user-supplied
xform is copied into a 16-byte fixed buffer without any length check,
allowing an out-of-bounds write that can corrupt the adjacent traffic
selector member within the same SA structure.
This is distinct from the DES/3DES cipher key overflow previously
reported in the shared helper functions (on_fill_ipsec_common_sa /
ot/ow_ipsec_sa_common_param_fill). The vulnerable code here is in
cnxk_on_ipsec_inb_sa_create() itself, affecting the auth key path.
Confirmed against current HEAD (8dc80afd, 2026-03-05).
== Root Cause ==
In cnxk_on_ipsec_inb_sa_create() (cnxk_security.c:1140), when the
auth algorithm is not AEAD/NULL/AES_GMAC, the function enters the
else branch at line 1164 and extracts the auth key directly from
the user-supplied xform (lines 1166-1167):
auth_key = auth_xform->auth.key.data;
auth_key_len = auth_xform->auth.key.length;
Then for AES-XCBC-MAC, the key is copied with the user-supplied
length into a 16-byte fixed buffer (lines 1186-1187):
case RTE_CRYPTO_AUTH_AES_XCBC_MAC:
memcpy(in_sa->aes_xcbc.key, auth_key,
auth_key_len);
ctx_len = offsetof(struct roc_ie_on_inb_sa,
aes_xcbc.selector);
break;
There is no check that auth_key_len <= 16.
== Affected Structure Layout ==
The inbound SA union (roc_ie_on.h:201-225) for AES-XCBC is:
struct roc_ie_on_inb_sa {
struct roc_ie_on_common_sa common_sa;
/* w0-w7 */
uint8_t udp_encap[8];
/* w8 */
/* w9-w33, union of auth-algo-specific
layouts */
union {
struct {
uint8_t
hmac_key[48];
struct
roc_ie_on_traffic_selector selector;
} sha1_or_gcm;
struct {
uint8_t
key[16]; /* <-- 16 bytes */
uint8_t
unused[32]; /* 32 bytes padding */
struct
roc_ie_on_traffic_selector selector;
} aes_xcbc;
/* <-- active for XCBC
*/
struct {
uint8_t
hmac_key[64];
uint8_t
hmac_iv[64];
struct
roc_ie_on_traffic_selector selector;
} sha2;
};
};
The destination key[16] is followed by unused[32] and then the
active traffic selector member:
struct roc_ie_on_traffic_selector {
uint16_t src_port[2];
/* port range */
uint16_t dst_port[2];
/* port range */
union {
struct {
uint32_t
src_addr[2]; /* IPv4 address range */
uint32_t
dst_addr[2];
} ipv4;
struct {
uint8_t
src_addr[32]; /* IPv6 address range */
uint8_t
dst_addr[32];
} ipv6;
};
};
So the memory layout from key[0] is:
offset 0..15: key[16]
(destination buffer)
offset 16..47: unused[32] (padding,
benign to overwrite)
offset 48+: selector
(active traffic filter member)
Any auth_key_len > 48 will write past unused[] and corrupt the
selector. The traffic selector controls which inbound IPsec
packets the hardware accepts based on source/destination IP
addresses and port ranges.
== The Subsequent roc_aes_xcbc_key_derive() Does Not Undo the Damage ==
After the memcpy, the code calls roc_aes_xcbc_key_derive() at
line 1195-1198:
if (auth_xform->auth.algo ==
RTE_CRYPTO_AUTH_AES_XCBC_MAC) {
const uint8_t *auth_key =
auth_xform->auth.key.data;
roc_aes_xcbc_key_derive(auth_key,
hmac_opad_ipad);
}
roc_aes_xcbc_key_derive() (roc_aes.c:203) writes exactly 48 bytes
(3 x 16-byte derived subkeys k1, k2, k3) into hmac_opad_ipad,
which overlaps the union starting at the sha2 variant. This
overwrites only the first 48 bytes of the union area, which
corresponds to key[16] + unused[32] in the aes_xcbc variant.
Critically, it does NOT touch anything at offset 48+, so bytes
written by the oversized memcpy into the selector region remain
intact and corrupted.
== Affected Call Chains ==
The vulnerable sink is reached from two entry points:
cn9k_eth_sec_session_create()
[cn9k_ethdev_sec.c:577]
inbound path (line 659):
-> cnxk_on_ipsec_inb_sa_create()
[cnxk_security.c:1140]
-> memcpy(in_sa->aes_xcbc.key,
auth_key, auth_key_len)
[line 1187]
The session_update path (cn9k_eth_sec_session_update, line 445)
only supports outbound, so it is not affected by this specific bug.
However, the outbound SA creation path (cnxk_on_ipsec_outb_sa_create)
also has a related issue: it calls roc_aes_xcbc_key_derive(auth_key,
...) at line 1128, which reads exactly 16 bytes from auth_key via
aes_key_expand(). If auth_key.length < 16, this would be an
out-of-bounds read, though that is a separate issue.
Neither cn9k_eth_sec_session_create() nor cnxk_on_ipsec_inb_sa_create()
calls cnxk_ipsec_xform_verify() before reaching the memcpy.
== Why the Inline Path Is Unprotected ==
cnxk_ipsec_xform_verify() (drivers/crypto/cnxk/cnxk_ipsec.h:120)
correctly validates AES-XCBC key length via ipsec_xform_auth_verify()
(line 84-86):
if (crypto_xform->auth.algo ==
RTE_CRYPTO_AUTH_AES_XCBC_MAC &&
keylen == ROC_CPT_AES_XCBC_KEY_LENGTH)
/* == 16 */
return 0;
But this is only called from the lookaside crypto paths
(drivers/crypto/cnxk/cn9k_ipsec.c, lines 212 and 296). The inline
ethdev path (cn9k_ethdev_sec.c) never calls it. The DPDK security
framework (rte_security_session_create) also performs no key length
validation.
== Concrete Example ==
AES-XCBC-MAC with a 64-byte auth key through the CN9K inbound path:
auth_xform.auth.algo = RTE_CRYPTO_AUTH_AES_XCBC_MAC;
auth_xform.auth.key.length = 64;
auth_xform.auth.key.data = key_64bytes;
...
rte_security_session_create(ctx, &conf, mp);
Call chain:
rte_security_session_create()
-- no validation
-> cn9k_eth_sec_session_create()
-- no xform_verify
-> cnxk_on_ipsec_inb_sa_create()
-> auth_key_len = 64
[line 1167]
-> memcpy(aes_xcbc.key, key, 64)
[line 1187]
bytes 0-15 ->
key[16] OK
bytes 16-47 ->
unused[32] benign
bytes 48-63 ->
selector OVERFLOW
-> roc_aes_xcbc_key_derive()
[line 1198]
writes 48 bytes to
hmac_opad_ipad
(covers key+unused, NOT
selector)
-> return ctx_len
SUCCESS (positive value)
The corrupted SA is accepted. The traffic selector, which controls
hardware-level inbound packet filtering by IP address/port, now
contains attacker-controlled data from the oversized key.
== Suggested Fix ==
Add auth key length validation before the memcpy. The simplest
approach:
case RTE_CRYPTO_AUTH_AES_XCBC_MAC:
+ if (auth_key_len !=
ROC_CPT_AES_XCBC_KEY_LENGTH) {
+ plt_err("Invalid AES-XCBC key
length %d", auth_key_len);
+ return -EINVAL;
+ }
memcpy(in_sa->aes_xcbc.key, auth_key,
auth_key_len);
ctx_len = offsetof(struct roc_ie_on_inb_sa,
aes_xcbc.selector);
break;
For completeness, the same pattern should also be applied to the
other auth algorithms in this function (SHA1_HMAC into hmac_key[48],
SHA2 variants into hmac_key[64]), as they have the same unchecked
memcpy pattern -- though their larger buffers make overflow into
the selector require proportionally larger invalid keys.
Alternatively, call cnxk_ipsec_xform_verify() (moved to the shared
common layer) from cn9k_eth_sec_session_create() before entering
cnxk_on_ipsec_inb_sa_create(). This would close all key length
validation gaps in the CN9K inline path at once.
== Impact ==
- Corrupts the hardware traffic selector in the inbound IPsec SA,
which controls IP address/port-based filtering of incoming
encrypted packets.
- The SA is accepted as successfully created (returns positive
ctx_len), so the application receives no error indication.
- Requires the application to supply an invalid key length.
Standard AES-XCBC keys are exactly 16 bytes. This represents a
missing defensive check at an API boundary.
Best regards,
Pengpeng Hou
[email protected]