Hi,
I found a "copy-before-check" issue in ot_ipsec_sa_common_param_fill()
(drivers/common/cnxk/cnxk_security.c). When AES-GMAC is used as the
authentication algorithm, the key is copied into cipher_key[32] before
the AES key length validation switch. If the key length exceeds 32,
the memcpy overflows into the adjacent SA member, and only afterward
is the invalid length detected and rejected with -EINVAL.
While the overflow is transient (the session creation fails and the
staging buffer is cleared on next use), this violates the
validate-before-write principle and should be fixed.
Confirmed against current HEAD (8dc80afd, 2026-03-05).
== Affected Code Flow ==
ot_ipsec_sa_common_param_fill() in cnxk_security.c, lines 13-200.
Step 1 -- AES-GMAC sets key and length from auth xform (line 137-144):
case RTE_CRYPTO_AUTH_AES_GMAC:
w2->s.auth_type = ROC_IE_SA_AUTH_AES_GMAC;
key = auth_xfrm->auth.key.data;
length =
auth_xfrm->auth.key.length; // e.g., 40
memcpy(salt_key, &ipsec_xfrm->salt,
4);
tmp_salt = (uint32_t *)salt_key;
*tmp_salt = rte_be_to_cpu_32(*tmp_salt);
break;
Step 2 -- Key is copied unconditionally (line 172-174):
if (key != NULL && length != 0) {
/* Copy encryption key */
memcpy(cipher_key, key, length);
// OVERFLOW: 40 > 32
...
}
Step 3 -- AES key length is validated AFTER the copy (line 180-198):
/* Set AES key length */
if (w2->s.enc_type == ROC_IE_SA_ENC_AES_CBC ||
w2->s.enc_type == ROC_IE_SA_ENC_AES_CTR ||
w2->s.enc_type == ROC_IE_SA_ENC_AES_GCM ||
w2->s.enc_type == ROC_IE_SA_ENC_AES_CCM ||
w2->s.auth_type ==
ROC_IE_SA_AUTH_AES_GMAC) {
switch (length) {
case ROC_CPT_AES128_KEY_LEN: /*
16 */
...
case ROC_CPT_AES192_KEY_LEN: /*
24 */
...
case ROC_CPT_AES256_KEY_LEN: /*
32 */
...
default:
plt_err("Invalid AES key
length");
return -EINVAL; //
rejected, but memcpy already done
}
}
For AES-GMAC, auth_type == ROC_IE_SA_AUTH_AES_GMAC, so this
validation block IS entered. A key length of 40 hits the default
case and returns -EINVAL. However, the memcpy at line 174 has
already written 40 bytes into cipher_key[32], overflowing 8 bytes
into the adjacent member.
== Affected Structure Layout ==
When called from cnxk_ot_ipsec_inb_sa_fill() (line 324), the
cipher_key parameter points to sa->cipher_key in the inbound SA:
struct roc_ot_ipsec_inb_sa {
(roc_ie_ot.h:284)
...
/* Word4 - Word7 */
uint8_t cipher_key[32];
/* ROC_CTX_MAX_CKEY_LEN */
/* Word8 - Word9 */
union {
struct {
uint32_t rsvd8;
uint8_t
salt[4]; /* active member */
} s;
uint64_t u64;
} w8;
uint64_t rsvd9;
...
};
Static assertions confirm (roc_ie_ot.h:404-407):
PLT_STATIC_ASSERT(offsetof(struct roc_ot_ipsec_inb_sa,
cipher_key) == 4 * sizeof(uint64_t));
/* offset 32 */
PLT_STATIC_ASSERT(offsetof(struct roc_ot_ipsec_inb_sa,
w8) == 8 *
sizeof(uint64_t)); /* offset 64 */
A 40-byte copy writes 32 bytes into cipher_key and 8 bytes into
the adjacent w8 union, whose active member salt[4] is overwritten.
The same pattern applies to the outbound SA (cipher_key[32]
followed by union roc_ot_ipsec_outb_iv iv).
== Why This Is Low-Severity ==
Unlike the DES/3DES cipher key overflow (where the function returns
success and the corrupted SA is used), in this case:
1. The AES key length switch at line 186 DOES catch the invalid
length and returns -EINVAL.
2. The callers properly propagate the error:
- cnxk_ot_ipsec_inb_sa_fill() returns rc at line 327-328.
- cn10k_eth_sec_session_create() checks rc at line 851 and
jumps to the error path. The SA is not written to
hardware.
- cn10k_eth_sec_session_update() similarly checks at line
1152.
3. The overwritten staging buffer (dev->inb.sa_dptr) is memset to
zero before every session creation (line 847), so the
transient
overflow does not persist.
Therefore the corrupted SA is never used, and the overflow is
transient. The practical impact is minimal.
== Affected Call Chains ==
cn10k_eth_sec_session_create()
[cn10k_ethdev_sec.c:734]
inbound: cnxk_ot_ipsec_inb_sa_fill()
[line 850]
outbound: cnxk_ot_ipsec_outb_sa_fill() [line
940]
-> ot_ipsec_sa_common_param_fill()
[line 324 / 437]
-> memcpy(cipher_key, key, length)
[line 174]
-> switch(length) ... return
-EINVAL [line 186-198]
cn10k_eth_sec_session_update()
[cn10k_ethdev_sec.c:1107]
inbound: cnxk_ot_ipsec_inb_sa_fill()
[line 1151]
outbound: cnxk_ot_ipsec_outb_sa_fill() [line
1186]
-> same path as above
The same "copy-before-check" pattern also exists in the OW family
helper ow_ipsec_sa_common_param_fill() (line 1366-1392), affecting
cn20k paths.
== Suggested Fix ==
Move the AES key length validation before the memcpy. The minimal
change:
if (key != NULL && length != 0) {
+ /* Validate key length before copy */
+ if (w2->s.enc_type ==
ROC_IE_SA_ENC_AES_CBC ||
+ w2->s.enc_type ==
ROC_IE_SA_ENC_AES_CTR ||
+ w2->s.enc_type ==
ROC_IE_SA_ENC_AES_GCM ||
+ w2->s.enc_type ==
ROC_IE_SA_ENC_AES_CCM ||
+ w2->s.auth_type ==
ROC_IE_SA_AUTH_AES_GMAC) {
+ switch (length) {
+ case 16: case 24: case 32:
break;
+ default: return -EINVAL;
+ }
+ }
+ /* Also validate DES/3DES key lengths */
+ 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;
+
memcpy(cipher_key, key, length);
...
}
- /* Set AES key length (move the aes_key_len assignment
here,
- after the switch has been moved above) */
This consolidates all key length validation before the memcpy and
also fixes the DES/3DES gap reported separately. Apply the same
restructuring to ow_ipsec_sa_common_param_fill().
Best regards,
Pengpeng Hou
[email protected]