[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