[BUG] common/cnxk: missing AES-XCBC auth key length validation in CN9K inbound SA creation causes out-of-bounds write into traffic selector
侯朋朋
pengpeng at iscas.ac.cn
Tue Mar 10 03:04:06 CET 2026
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
pengpeng at iscas.ac.cn
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mails.dpdk.org/archives/dev/attachments/20260310/f47f8589/attachment-0001.htm>
More information about the dev
mailing list