<meta http-equiv="Content-Type" content="text/html; charset=GB18030"><font face="ËÎÌå" size="2"><div>Hi,</div><div><br></div><div>  I found a missing input validation issue in the CN20K (OW family)</div><div>  inline IPsec session creation path. When DES_CBC or 3DES_CBC is used</div><div>  as the cipher algorithm, the key length from the user-supplied crypto</div><div>  xform is never validated before being copied into a fixed-size buffer,</div><div>  allowing an out-of-bounds write that corrupts adjacent SA structure</div><div>  members.</div><div><br></div><div>  Confirmed against current HEAD (8dc80afd, 2026-03-05).</div><div><br></div><div>  == Affected Call Chains ==</div><div><br></div><div>  There are four distinct entry points that reach the vulnerable sink,</div><div>  covering both session create and session update, inbound and outbound:</div><div><br></div><div>    cn20k_eth_sec_session_create()            [cn20k_ethdev_sec.c:686]</div><div>      inbound path  -> cnxk_ow_ipsec_inb_sa_fill()   [cnxk_security.c:1495]</div><div>                         -> ow_ipsec_sa_common_param_fill()  [line 1509]</div><div>      outbound path -> cnxk_ow_ipsec_outb_sa_fill()  [cnxk_security.c:1605]</div><div>                         -> ow_ipsec_sa_common_param_fill()  [line 1619]</div><div><br></div><div>    cn20k_eth_sec_session_update()            [cn20k_ethdev_sec.c:1030]</div><div>      inbound path  -> cnxk_ow_ipsec_inb_sa_fill()   [line 1065]</div><div>      outbound path -> cnxk_ow_ipsec_outb_sa_fill()  [line 1095]</div><div><br></div><div>  None of these paths call cnxk_ipsec_xform_verify() before reaching</div><div>  the SA fill functions.</div><div><br></div><div>  == Root Cause ==</div><div><br></div><div>  In ow_ipsec_sa_common_param_fill() (cnxk_security.c:1210), the</div><div>  3DES_CBC cipher algorithm handler only sets the encryption type</div><div>  without recording or validating the key length (lines 1300-1302):</div><div><br></div><div>      case RTE_CRYPTO_CIPHER_3DES_CBC:</div><div>          w2->s.enc_type = ROC_IE_SA_ENC_3DES_CBC;</div><div>          break;</div><div><br></div><div>  Immediately after the switch, the key pointer and length are taken</div><div>  directly from the user-supplied xform (lines 1307-1308):</div><div><br></div><div>      key = cipher_xfrm->cipher.key.data;</div><div>      length = cipher_xfrm->cipher.key.length;</div><div><br></div><div>  The key is then copied unconditionally at line 1366-1368:</div><div><br></div><div>      if (key != NULL && length != 0) {</div><div>          /* Copy encryption key */</div><div>          memcpy(cipher_key, key, length);</div><div>          ...</div><div>      }</div><div><br></div><div>  The only length validation that follows is restricted to AES-family</div><div>  algorithms (lines 1374-1392):</div><div><br></div><div>      if (w2->s.enc_type == ROC_IE_SA_ENC_AES_CBC ||</div><div>          w2->s.enc_type == ROC_IE_SA_ENC_AES_CCM ||</div><div>          w2->s.enc_type == ROC_IE_SA_ENC_AES_CTR ||</div><div>          w2->s.enc_type == ROC_IE_SA_ENC_AES_GCM ||</div><div>          w2->s.enc_type == ROC_IE_SA_ENC_AES_CCM ||</div><div>          w2->s.auth_type == ROC_IE_SA_AUTH_AES_GMAC) {</div><div>          switch (length) {</div><div>          case ROC_CPT_AES128_KEY_LEN: ...</div><div>          case ROC_CPT_AES192_KEY_LEN: ...</div><div>          case ROC_CPT_AES256_KEY_LEN: ...</div><div>          default: return -EINVAL;</div><div>          }</div><div>      }</div><div><br></div><div>  For 3DES_CBC (enc_type == ROC_IE_SA_ENC_3DES_CBC), this validation</div><div>  block is never entered. The function returns 0 regardless of key</div><div>  length.</div><div><br></div><div>  == Affected Structure Layouts ==</div><div><br></div><div>  The destination cipher_key parameter points into the SA structure.</div><div>  In both OW SA variants, cipher_key[32] is immediately followed by</div><div>  active members:</div><div><br></div><div>  Outbound SA (roc_ie_ow.h, struct roc_ow_ipsec_outb_sa):</div><div><br></div><div>      /* Word4 - Word7 */</div><div>      uint8_t cipher_key[ROC_CTX_MAX_CKEY_LEN];   /* 32 bytes */</div><div><br></div><div>      /* Word8 - Word9 */</div><div>      union roc_ow_ipsec_outb_iv iv;               /* adjacent */</div><div><br></div><div>  The outbound fill function passes sa->cipher_key and sa->iv.s.salt</div><div>  as separate arguments (cnxk_security.c:1619):</div><div><br></div><div>      rc = ow_ipsec_sa_common_param_fill(</div><div>               &w2, sa->cipher_key, sa->iv.s.salt,</div><div>               sa->hmac_opad_ipad, ipsec_xfrm, crypto_xfrm);</div><div><br></div><div>  Inbound SA (roc_ie_ow.h, struct roc_ow_ipsec_inb_sa):</div><div><br></div><div>      /* Word4 - Word7 */</div><div>      uint8_t cipher_key[ROC_CTX_MAX_CKEY_LEN];   /* 32 bytes */</div><div><br></div><div>      /* Word8 - Word9 */</div><div>      union {</div><div>          struct {</div><div>              uint32_t rsvd8;</div><div>              uint8_t salt[4];                     /* active */</div><div>          } s;</div><div>          uint64_t u64;</div><div>      } w8;</div><div><br></div><div>  The inbound fill function passes sa->cipher_key and sa->w8.s.salt</div><div>  (cnxk_security.c:1509):</div><div><br></div><div>      rc = ow_ipsec_sa_common_param_fill(</div><div>               &w2, sa->cipher_key, sa->w8.s.salt,</div><div>               sa->hmac_opad_ipad, ipsec_xfrm, crypto_xfrm);</div><div><br></div><div>  Static assertions confirm the layout (roc_ie_ow.h:525-526):</div><div><br></div><div>      PLT_STATIC_ASSERT(offsetof(struct roc_ow_ipsec_outb_sa,</div><div>          cipher_key) == 4 * sizeof(uint64_t));</div><div>      PLT_STATIC_ASSERT(offsetof(struct roc_ow_ipsec_outb_sa,</div><div>          iv)         == 8 * sizeof(uint64_t));</div><div><br></div><div>  cipher_key occupies word4-7 (offset 32, size 32), iv starts at</div><div>  word8 (offset 64). Any key length > 32 overwrites iv.</div><div><br></div><div>  == Why the Inline Path Is Unprotected ==</div><div><br></div><div>  The validation function cnxk_ipsec_xform_verify() exists in</div><div>  drivers/crypto/cnxk/cnxk_ipsec.h and correctly rejects 3DES keys</div><div>  that are not exactly 24 bytes (line 47-49):</div><div><br></div><div>      if (crypto_xform->cipher.algo == RTE_CRYPTO_CIPHER_3DES_CBC &&</div><div>          crypto_xform->cipher.key.length == 24)</div><div>          return 0;</div><div><br></div><div>  However, this function is defined as static inline in the crypto</div><div>  (lookaside) driver header and is called only from the lookaside</div><div>  session creation paths in drivers/crypto/cnxk/cn20k_ipsec.c</div><div>  (lines 286, 400).</div><div><br></div><div>  The inline IPsec path in drivers/net/cnxk/cn20k_ethdev_sec.c never</div><div>  calls cnxk_ipsec_xform_verify(). There is no other key length</div><div>  validation between the API entry point and the memcpy.</div><div><br></div><div>  The DPDK security framework (rte_security_session_create() in</div><div>  lib/security/rte_security.c:70) also performs no key length</div><div>  validation; it passes the conf directly to the driver callback.</div><div><br></div><div>  == Concrete Example ==</div><div><br></div><div>  3DES_CBC with a 40-byte key through the CN20K outbound path:</div><div><br></div><div>      cipher_xform.cipher.algo = RTE_CRYPTO_CIPHER_3DES_CBC;</div><div>      cipher_xform.cipher.key.length = 40;</div><div>      cipher_xform.cipher.key.data = key_40bytes;</div><div>      ...</div><div>      rte_security_session_create(ctx, &conf, mp);</div><div><br></div><div>  Call chain:</div><div><br></div><div>    rte_security_session_create()                    -- no validation</div><div>      -> cn20k_eth_sec_session_create()              -- no xform_verify</div><div>        -> cnxk_ow_ipsec_outb_sa_fill()</div><div>          -> ow_ipsec_sa_common_param_fill()</div><div>            -> 3DES_CBC: sets enc_type only          [line 1300-1302]</div><div>            -> key = ...; length = 40                [line 1307-1308]</div><div>            -> memcpy(cipher_key, key, 40)           [line 1368]</div><div>               first 32 bytes -> cipher_key[32]      OK</div><div>               next 8 bytes   -> iv (outb) or w8/salt (inb)  OVERFLOW</div><div>            -> AES check skipped (enc_type is 3DES)  [line 1374]</div><div>            -> return 0                              SUCCESS</div><div><br></div><div>  The corrupted SA is accepted. For outbound, the iv used for packet</div><div>  encryption is now partially overwritten with key material. For</div><div>  inbound, the salt used for GCM/GMAC nonce construction is corrupted.</div><div><br></div><div>  == Note on Related Paths ==</div><div><br></div><div>  The same root cause (missing DES/3DES key length validation in the</div><div>  shared helper, combined with no cnxk_ipsec_xform_verify() call from</div><div>  the inline path) also affects:</div><div><br></div><div>    - ON family: on_fill_ipsec_common_sa()   line 988, CN9K path</div><div>    - OT family: ot_ipsec_sa_common_param_fill() line 174, CN10K path</div><div><br></div><div>  These share the same fix strategy and could be addressed together.</div><div><br></div><div>  == Suggested Fix ==</div><div><br></div><div>  Option A (minimal, local):</div><div>  Add DES/3DES validation in ow_ipsec_sa_common_param_fill() before</div><div>  the memcpy, after the cipher algorithm switch:</div><div><br></div><div>      if (cipher_xfrm != NULL) {</div><div>          switch (cipher_xfrm->cipher.algo) {</div><div>          ...</div><div>          case RTE_CRYPTO_CIPHER_3DES_CBC:</div><div>              w2->s.enc_type = ROC_IE_SA_ENC_3DES_CBC;</div><div>  +           if (cipher_xfrm->cipher.key.length != 24)</div><div>  +               return -EINVAL;</div><div>              break;</div><div>          case RTE_CRYPTO_CIPHER_DES_CBC:</div><div>  +           if (cipher_xfrm->cipher.key.length != 8)</div><div>  +               return -EINVAL;</div><div>              ...</div><div>          }</div><div>      }</div><div><br></div><div>  Apply the same pattern to ot_ipsec_sa_common_param_fill() and</div><div>  on_fill_ipsec_common_sa() / on_ipsec_sa_ctl_set().</div><div><br></div><div>  Option B (comprehensive):</div><div>  Move cnxk_ipsec_xform_verify() from drivers/crypto/cnxk/cnxk_ipsec.h</div><div>  to the shared layer (drivers/common/cnxk/), and add calls in</div><div>  cn9k/cn10k/cn20k_eth_sec_session_create() and session_update()</div><div>  before the SA fill calls. This closes the validation gap for all</div><div>  algorithm/key combinations at once.</div><div><br></div><div>  == Impact ==</div><div><br></div><div>  - Corrupts iv (outbound) or salt (inbound) fields in the hardware</div><div>    IPsec SA, causing incorrect cryptographic operations or hardware</div><div>    errors on the Octeon CN20K crypto engine.</div><div>  - SA creation returns success, so the application proceeds to use a</div><div>    corrupted SA with no error indication.</div><div>  - Requires the application to supply an invalid key length, which</div><div>    would not occur in correct usage. This is a missing defensive check</div><div>    at an API boundary, not an externally exploitable vulnerability.</div><div><br></div><div><div>Best regards,</div><div>Pengpeng Hou</div><div><a href="mailto:pengpeng@iscas.ac.cn" rel="noopener" target="_blank" style="outline: none; cursor: pointer; color: rgb(30, 84, 148);">pengpeng@iscas.ac.cn</a></div></div><div><br></div></font>