Hi,

  I found a missing input validation issue in the Marvell cnxk IPsec
  Security Association (SA) creation paths. When DES_CBC or 3DES_CBC is
  used, the cipher key length from the user-supplied xform is never
  validated before being passed to memcpy(), which can cause an
  out-of-bounds write into adjacent SA structure members.


  The issue affects three independent code paths (ON/OT/OW families).
  The root cause is the same in all three: the AES key length validation
  block is only entered for AES-family algorithms, and no equivalent
  check exists for DES/3DES.


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


  == Root Cause ==


  Consider on_fill_ipsec_common_sa() in
  drivers/common/cnxk/cnxk_security.c, lines 940-991.


  The function first calls on_ipsec_sa_ctl_set(), which for 3DES_CBC
  only sets the enc_type field without examining key length (line
  848-850):


      case RTE_CRYPTO_CIPHER_3DES_CBC:
          ctl->enc_type = ROC_IE_SA_ENC_3DES_CBC;
          break;


  Compare this with AES_CBC, which captures the key length into
  aes_key_len for later validation (line 851-854):


      case RTE_CRYPTO_CIPHER_AES_CBC:
          ctl->enc_type = ROC_IE_SA_ENC_AES_CBC;
          aes_key_len = 
cipher_xform->cipher.key.length;
          break;


  Back in on_fill_ipsec_common_sa(), the cipher key is then copied with
  the user-supplied length, unconditionally (lines 973-988):


      if (cipher_xform) {
          cipher_key = 
cipher_xform->cipher.key.data;
          cipher_key_len = 
cipher_xform->cipher.key.length;
      }
      ...
      if (cipher_key_len != 0)
          memcpy(common_sa->cipher_key, cipher_key, 
cipher_key_len);


  The only length validation that follows is restricted to AES types
  (lines 900-920):


      if (ctl->enc_type == ROC_IE_SA_ENC_AES_CBC ||
          ctl->enc_type == ROC_IE_SA_ENC_AES_CTR ||
          ctl->enc_type == ROC_IE_SA_ENC_AES_GCM ||
          ctl->enc_type == ROC_IE_SA_ENC_AES_CCM ||
          ctl->auth_type == 
ROC_IE_SA_AUTH_AES_GMAC) {
          switch (aes_key_len) {
          case 16: ... case 24: ... case 32: ... break;
          default: return -EINVAL;
          }
      }


  For DES/3DES, this block is never entered. No other validation exists
  in this path. The function returns 0 (success) regardless of
  cipher_key_len.


  == Affected Structure Layout ==


  The destination buffer cipher_key[32] is immediately followed by
  active SA members in all three SA structure families:


  ON family (roc_ie_on.h, struct roc_ie_on_common_sa):


      uint8_t cipher_key[32];          
/* w1-w4 */
      union roc_ie_on_bit_perfect_iv iv; /* w5-w6, adjacent */


  OT family (roc_ie_ot.h, struct roc_ot_ipsec_outb_sa):


      uint8_t cipher_key[32];          
/* Word4-7 */
      union roc_ot_ipsec_outb_iv iv;   /* Word8-9, 
adjacent */


  OW family (roc_ie_ow.h, struct roc_ow_ipsec_outb_sa):


      uint8_t cipher_key[32];          
/* Word4-7 */
      union roc_ow_ipsec_outb_iv iv;   /* Word8-9, 
adjacent */


  For inbound SA structures, cipher_key[32] is followed by the w8 union
  containing the salt field, also adjacent.


  == All Affected Sites ==


  1. on_fill_ipsec_common_sa() -- line 988
     memcpy(common_sa->cipher_key, cipher_key, 
cipher_key_len);
     Called from: cnxk_on_ipsec_outb_sa_create() (line 1015)
                
 cnxk_on_ipsec_inb_sa_create()  (line 1151)
     Callers:    cn9k_eth_sec_session_create()


  2. ot_ipsec_sa_common_param_fill() -- line 174
     memcpy(cipher_key, key, length);
     Called from: cnxk_ot_ipsec_inb_sa_fill()  (line 324)
                
 cnxk_ot_ipsec_outb_sa_fill() (line 437)
     Callers:    cn10k_eth_sec_session_create()


  3. ow_ipsec_sa_common_param_fill() -- line 1368
     memcpy(cipher_key, key, length);
     Called from: cnxk_ow_ipsec_inb_sa_fill()  (line 1509)
                
 cnxk_ow_ipsec_outb_sa_fill() (line 1619)
     Callers:    cn20k_eth_sec_session_create()


  None of these callers (cn9k/cn10k/cn20k_eth_sec_session_create)
  invoke cnxk_ipsec_xform_verify() before calling the SA fill
  functions.


  == Why Inline Paths Are Unprotected ==


  The validation function cnxk_ipsec_xform_verify() already exists in
  drivers/crypto/cnxk/cnxk_ipsec.h and correctly rejects invalid key
  lengths:


      /* 3DES: exactly 24 bytes */
      if (crypto_xform->cipher.algo == 
RTE_CRYPTO_CIPHER_3DES_CBC &&
          crypto_xform->cipher.key.length == 24)
          return 0;


      /* DES: exactly 8 bytes */
      if (crypto_xform->cipher.algo == 
RTE_CRYPTO_CIPHER_DES_CBC &&
          crypto_xform->cipher.key.length == 8)
          return 0;


  However, this function is defined as static inline in the crypto
  driver header and is only called from the lookaside crypto session
  creation paths (cn9k_ipsec.c, cn10k_ipsec.c, cn20k_ipsec.c under
  drivers/crypto/cnxk/). The inline IPsec ethdev paths (cn9k/cn10k/
  cn20k_ethdev_sec.c under drivers/net/cnxk/) never call it.


  Additionally, the DPDK security framework function
  rte_security_session_create() (lib/security/rte_security.c:70)
  performs no key length validation against the capability table -- it
  passes conf directly to the driver's session_create callback.


  == Concrete Example ==


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


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


  Call chain:
    rte_security_session_create()          
-- no validation
      -> cn9k_eth_sec_session_create()     -- 
no xform_verify call
        -> cnxk_on_ipsec_outb_sa_create()
          -> on_fill_ipsec_common_sa()
            -> on_ipsec_sa_ctl_set()  
     -- sets enc_type only
            -> memcpy(cipher_key, key, 40) -- 
overflows by 8 bytes


  Result: the first 32 bytes fill cipher_key[32], the remaining 8 bytes
  overwrite the adjacent iv member. The function returns 0 (success),
  and the corrupted SA is accepted for use.


  == Suggested Fix ==


  Add DES/3DES key length validation alongside the existing AES checks.
  The simplest approach is to add explicit checks in each
  *_common_param_fill / on_fill_ipsec_common_sa function, before the
  memcpy. For example:


      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;


  Alternatively (and more comprehensively), the existing
  cnxk_ipsec_xform_verify() could be moved to the shared common layer
  (drivers/common/cnxk/) and called from all session_create paths --
  both crypto and inline. This would also close similar gaps for other
  algorithm/key combinations in the inline path.


  == Impact ==


  - Corrupts IV/salt fields in the IPsec SA, leading to incorrect
    cryptographic operations or hardware errors on the Octeon crypto
    engine.
  - The SA is accepted as successfully created, so the caller has no
    indication that the SA state is corrupted.
  - Requires the application to pass an invalid key length, which would
    not occur in correct usage but represents a missing defensive 
check
    at an API boundary.




 Best regards,
 Pengpeng Hou
 [email protected]

Reply via email to