Hi,

  I found a missing input validation issue in the CN20K (OW family)
  inline IPsec session creation path. When DES_CBC or 3DES_CBC is used
  as the cipher algorithm, the key length from the user-supplied crypto
  xform is never validated before being copied into a fixed-size buffer,
  allowing an out-of-bounds write that corrupts adjacent SA structure
  members.


  Confirmed against current HEAD (8dc80afd, 2026-03-05).


  == Affected Call Chains ==


  There are four distinct entry points that reach the vulnerable sink,
  covering both session create and session update, inbound and outbound:


    cn20k_eth_sec_session_create()          
  [cn20k_ethdev_sec.c:686]
      inbound path  -> cnxk_ow_ipsec_inb_sa_fill()  
 [cnxk_security.c:1495]
                      
   -> ow_ipsec_sa_common_param_fill()  [line 1509]
      outbound path -> cnxk_ow_ipsec_outb_sa_fill()  
[cnxk_security.c:1605]
                      
   -> ow_ipsec_sa_common_param_fill()  [line 1619]


    cn20k_eth_sec_session_update()          
  [cn20k_ethdev_sec.c:1030]
      inbound path  -> cnxk_ow_ipsec_inb_sa_fill()  
 [line 1065]
      outbound path -> cnxk_ow_ipsec_outb_sa_fill()  
[line 1095]


  None of these paths call cnxk_ipsec_xform_verify() before reaching
  the SA fill functions.


  == Root Cause ==


  In ow_ipsec_sa_common_param_fill() (cnxk_security.c:1210), the
  3DES_CBC cipher algorithm handler only sets the encryption type
  without recording or validating the key length (lines 1300-1302):


      case RTE_CRYPTO_CIPHER_3DES_CBC:
          w2->s.enc_type = ROC_IE_SA_ENC_3DES_CBC;
          break;


  Immediately after the switch, the key pointer and length are taken
  directly from the user-supplied xform (lines 1307-1308):


      key = cipher_xfrm->cipher.key.data;
      length = cipher_xfrm->cipher.key.length;


  The key is then copied unconditionally at line 1366-1368:


      if (key != NULL && length != 0) {
          /* Copy encryption key */
          memcpy(cipher_key, key, length);
          ...
      }


  The only length validation that follows is restricted to AES-family
  algorithms (lines 1374-1392):


      if (w2->s.enc_type == ROC_IE_SA_ENC_AES_CBC ||
          w2->s.enc_type == ROC_IE_SA_ENC_AES_CCM ||
          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: ...
          case ROC_CPT_AES192_KEY_LEN: ...
          case ROC_CPT_AES256_KEY_LEN: ...
          default: return -EINVAL;
          }
      }


  For 3DES_CBC (enc_type == ROC_IE_SA_ENC_3DES_CBC), this validation
  block is never entered. The function returns 0 regardless of key
  length.


  == Affected Structure Layouts ==


  The destination cipher_key parameter points into the SA structure.
  In both OW SA variants, cipher_key[32] is immediately followed by
  active members:


  Outbound SA (roc_ie_ow.h, struct roc_ow_ipsec_outb_sa):


      /* Word4 - Word7 */
      uint8_t cipher_key[ROC_CTX_MAX_CKEY_LEN];   /* 
32 bytes */


      /* Word8 - Word9 */
      union roc_ow_ipsec_outb_iv iv;        
       /* adjacent */


  The outbound fill function passes sa->cipher_key and sa->iv.s.salt
  as separate arguments (cnxk_security.c:1619):


      rc = ow_ipsec_sa_common_param_fill(
               &w2, 
sa->cipher_key, sa->iv.s.salt,
               sa->hmac_opad_ipad, 
ipsec_xfrm, crypto_xfrm);


  Inbound SA (roc_ie_ow.h, struct roc_ow_ipsec_inb_sa):


      /* Word4 - Word7 */
      uint8_t cipher_key[ROC_CTX_MAX_CKEY_LEN];   /* 
32 bytes */


      /* Word8 - Word9 */
      union {
          struct {
              uint32_t rsvd8;
              uint8_t salt[4];    
                 /* active */
          } s;
          uint64_t u64;
      } w8;


  The inbound fill function passes sa->cipher_key and sa->w8.s.salt
  (cnxk_security.c:1509):


      rc = ow_ipsec_sa_common_param_fill(
               &w2, 
sa->cipher_key, sa->w8.s.salt,
               sa->hmac_opad_ipad, 
ipsec_xfrm, crypto_xfrm);


  Static assertions confirm the layout (roc_ie_ow.h:525-526):


      PLT_STATIC_ASSERT(offsetof(struct roc_ow_ipsec_outb_sa,
          cipher_key) == 4 * sizeof(uint64_t));
      PLT_STATIC_ASSERT(offsetof(struct roc_ow_ipsec_outb_sa,
          iv)         == 8 * 
sizeof(uint64_t));


  cipher_key occupies word4-7 (offset 32, size 32), iv starts at
  word8 (offset 64). Any key length > 32 overwrites iv.


  == Why the Inline Path Is Unprotected ==


  The validation function cnxk_ipsec_xform_verify() exists in
  drivers/crypto/cnxk/cnxk_ipsec.h and correctly rejects 3DES keys
  that are not exactly 24 bytes (line 47-49):


      if (crypto_xform->cipher.algo == 
RTE_CRYPTO_CIPHER_3DES_CBC &&
          crypto_xform->cipher.key.length == 24)
          return 0;


  However, this function is defined as static inline in the crypto
  (lookaside) driver header and is called only from the lookaside
  session creation paths in drivers/crypto/cnxk/cn20k_ipsec.c
  (lines 286, 400).


  The inline IPsec path in drivers/net/cnxk/cn20k_ethdev_sec.c never
  calls cnxk_ipsec_xform_verify(). There is no other key length
  validation between the API entry point and the memcpy.


  The DPDK security framework (rte_security_session_create() in
  lib/security/rte_security.c:70) also performs no key length
  validation; it passes the conf directly to the driver callback.


  == Concrete Example ==


  3DES_CBC with a 40-byte key through the CN20K outbound path:


      cipher_xform.cipher.algo = RTE_CRYPTO_CIPHER_3DES_CBC;
      cipher_xform.cipher.key.length = 40;
      cipher_xform.cipher.key.data = key_40bytes;
      ...
      rte_security_session_create(ctx, &conf, mp);


  Call chain:


    rte_security_session_create()          
          -- no validation
      -> cn20k_eth_sec_session_create()      
        -- no xform_verify
        -> cnxk_ow_ipsec_outb_sa_fill()
          -> ow_ipsec_sa_common_param_fill()
            -> 3DES_CBC: sets enc_type 
only          [line 1300-1302]
            -> key = ...; length = 40  
              [line 1307-1308]
            -> memcpy(cipher_key, key, 
40)           [line 1368]
               first 32 bytes -> 
cipher_key[32]      OK
               next 8 bytes  
 -> iv (outb) or w8/salt (inb)  OVERFLOW
            -> AES check skipped (enc_type is 
3DES)  [line 1374]
            -> return 0      
                      
  SUCCESS


  The corrupted SA is accepted. For outbound, the iv used for packet
  encryption is now partially overwritten with key material. For
  inbound, the salt used for GCM/GMAC nonce construction is corrupted.


  == Note on Related Paths ==


  The same root cause (missing DES/3DES key length validation in the
  shared helper, combined with no cnxk_ipsec_xform_verify() call from
  the inline path) also affects:


    - ON family: on_fill_ipsec_common_sa()   line 988, CN9K 
path
    - OT family: ot_ipsec_sa_common_param_fill() line 174, CN10K path


  These share the same fix strategy and could be addressed together.


  == Suggested Fix ==


  Option A (minimal, local):
  Add DES/3DES validation in ow_ipsec_sa_common_param_fill() before
  the memcpy, after the cipher algorithm switch:


      if (cipher_xfrm != NULL) {
          switch (cipher_xfrm->cipher.algo) {
          ...
          case RTE_CRYPTO_CIPHER_3DES_CBC:
              w2->s.enc_type = 
ROC_IE_SA_ENC_3DES_CBC;
  +           if 
(cipher_xfrm->cipher.key.length != 24)
  +               return -EINVAL;
              break;
          case RTE_CRYPTO_CIPHER_DES_CBC:
  +           if 
(cipher_xfrm->cipher.key.length != 8)
  +               return -EINVAL;
              ...
          }
      }


  Apply the same pattern to ot_ipsec_sa_common_param_fill() and
  on_fill_ipsec_common_sa() / on_ipsec_sa_ctl_set().


  Option B (comprehensive):
  Move cnxk_ipsec_xform_verify() from drivers/crypto/cnxk/cnxk_ipsec.h
  to the shared layer (drivers/common/cnxk/), and add calls in
  cn9k/cn10k/cn20k_eth_sec_session_create() and session_update()
  before the SA fill calls. This closes the validation gap for all
  algorithm/key combinations at once.


  == Impact ==


  - Corrupts iv (outbound) or salt (inbound) fields in the hardware
    IPsec SA, causing incorrect cryptographic operations or hardware
    errors on the Octeon CN20K crypto engine.
  - SA creation returns success, so the application proceeds to use a
    corrupted SA with no error indication.
  - Requires the application to supply an invalid key length, which
    would not occur in correct usage. This is a missing defensive 
check
    at an API boundary, not an externally exploitable vulnerability.


Best regards,
Pengpeng Hou
[email protected]

Reply via email to