Commit 6b46ca26 authored by Jakub Kicinski's avatar Jakub Kicinski Committed by Paolo Abeni
Browse files

net: psp: add socket security association code



Add the ability to install PSP Rx and Tx crypto keys on TCP
connections. Netlink ops are provided for both operations.
Rx side combines allocating a new Rx key and installing it
on the socket. Theoretically these are separate actions,
but in practice they will always be used one after the
other. We can add distinct "alloc" and "install" ops later.

Reviewed-by: default avatarWillem de Bruijn <willemb@google.com>
Signed-off-by: default avatarJakub Kicinski <kuba@kernel.org>
Co-developed-by: default avatarDaniel Zahka <daniel.zahka@gmail.com>
Signed-off-by: default avatarDaniel Zahka <daniel.zahka@gmail.com>
Reviewed-by: default avatarEric Dumazet <edumazet@google.com>
Link: https://patch.msgid.link/20250917000954.859376-9-daniel.zahka@gmail.com


Signed-off-by: default avatarPaolo Abeni <pabeni@redhat.com>
parent 0917bb13
Loading
Loading
Loading
Loading
+70 −0
Original line number Diff line number Diff line
@@ -38,6 +38,44 @@ attribute-sets:
        type: u32
        enum: version
        enum-as-flags: true
  -
    name: assoc
    attributes:
      -
        name: dev-id
        doc: PSP device ID.
        type: u32
        checks:
          min: 1
      -
        name: version
        doc: |
          PSP versions (AEAD and protocol version) used by this association,
          dictates the size of the key.
        type: u32
        enum: version
      -
        name: rx-key
        type: nest
        nested-attributes: keys
      -
        name: tx-key
        type: nest
        nested-attributes: keys
      -
        name: sock-fd
        doc: Sockets which should be bound to the association immediately.
        type: u32
  -
    name: keys
    attributes:
      -
        name: key
        type: binary
      -
        name: spi
        doc: Security Parameters Index (SPI) of the association.
        type: u32

operations:
  list:
@@ -107,6 +145,38 @@ operations:
      notify: key-rotate
      mcgrp: use

    -
      name: rx-assoc
      doc: Allocate a new Rx key + SPI pair, associate it with a socket.
      attribute-set: assoc
      do:
        request:
          attributes:
            - dev-id
            - version
            - sock-fd
        reply:
          attributes:
            - dev-id
            - rx-key
        pre: psp-assoc-device-get-locked
        post: psp-device-unlock
    -
      name: tx-assoc
      doc: Add a PSP Tx association.
      attribute-set: assoc
      do:
        request:
          attributes:
            - dev-id
            - version
            - tx-key
            - sock-fd
        reply:
          attributes: []
        pre: psp-assoc-device-get-locked
        post: psp-device-unlock

mcast-groups:
  list:
    -
+105 −9
Original line number Diff line number Diff line
@@ -4,7 +4,9 @@
#define __NET_PSP_HELPERS_H

#include <linux/skbuff.h>
#include <linux/rcupdate.h>
#include <net/sock.h>
#include <net/tcp.h>
#include <net/psp/types.h>

struct inet_timewait_sock;
@@ -16,41 +18,130 @@ psp_dev_create(struct net_device *netdev, struct psp_dev_ops *psd_ops,
void psp_dev_unregister(struct psp_dev *psd);

/* Kernel-facing API */
void psp_assoc_put(struct psp_assoc *pas);

static inline void *psp_assoc_drv_data(struct psp_assoc *pas)
{
	return pas->drv_data;
}

#if IS_ENABLED(CONFIG_INET_PSP)
static inline void psp_sk_assoc_free(struct sock *sk) { }
static inline void
psp_twsk_init(struct inet_timewait_sock *tw, const struct sock *sk) { }
static inline void psp_twsk_assoc_free(struct inet_timewait_sock *tw) { }
static inline void
psp_reply_set_decrypted(struct sk_buff *skb) { }
unsigned int psp_key_size(u32 version);
void psp_sk_assoc_free(struct sock *sk);
void psp_twsk_init(struct inet_timewait_sock *tw, const struct sock *sk);
void psp_twsk_assoc_free(struct inet_timewait_sock *tw);
void psp_reply_set_decrypted(struct sk_buff *skb);

static inline struct psp_assoc *psp_sk_assoc(const struct sock *sk)
{
	return rcu_dereference_check(sk->psp_assoc, lockdep_sock_is_held(sk));
}

static inline void
psp_enqueue_set_decrypted(struct sock *sk, struct sk_buff *skb)
{
	struct psp_assoc *pas;

	pas = psp_sk_assoc(sk);
	if (pas && pas->tx.spi)
		skb->decrypted = 1;
}

static inline unsigned long
__psp_skb_coalesce_diff(const struct sk_buff *one, const struct sk_buff *two,
			unsigned long diffs)
{
	struct psp_skb_ext *a, *b;

	a = skb_ext_find(one, SKB_EXT_PSP);
	b = skb_ext_find(two, SKB_EXT_PSP);

	diffs |= (!!a) ^ (!!b);
	if (!diffs && unlikely(a))
		diffs |= memcmp(a, b, sizeof(*a));
	return diffs;
}

static inline bool
psp_is_allowed_nondata(struct sk_buff *skb, struct psp_assoc *pas)
{
	bool fin = !!(TCP_SKB_CB(skb)->tcp_flags & TCPHDR_FIN);
	u32 end_seq = TCP_SKB_CB(skb)->end_seq;
	u32 seq = TCP_SKB_CB(skb)->seq;
	bool pure_fin;

	pure_fin = fin && end_seq - seq == 1;

	return seq == end_seq || (pure_fin && seq == pas->upgrade_seq);
}

static inline bool
psp_pse_matches_pas(struct psp_skb_ext *pse, struct psp_assoc *pas)
{
	return pse && pas->rx.spi == pse->spi &&
	       pas->generation == pse->generation &&
	       pas->version == pse->version &&
	       pas->dev_id == pse->dev_id;
}

static inline enum skb_drop_reason
psp_sk_rx_policy_check(struct sock *sk, struct sk_buff *skb)
__psp_sk_rx_policy_check(struct sk_buff *skb, struct psp_assoc *pas)
{
	struct psp_skb_ext *pse = skb_ext_find(skb, SKB_EXT_PSP);

	if (!pas)
		return pse ? SKB_DROP_REASON_PSP_INPUT : 0;

	if (likely(psp_pse_matches_pas(pse, pas))) {
		if (unlikely(!pas->peer_tx))
			pas->peer_tx = 1;

		return 0;
	}

	if (!pse) {
		if (!pas->tx.spi ||
		    (!pas->peer_tx && psp_is_allowed_nondata(skb, pas)))
			return 0;
	}

	return SKB_DROP_REASON_PSP_INPUT;
}

static inline enum skb_drop_reason
psp_sk_rx_policy_check(struct sock *sk, struct sk_buff *skb)
{
	return __psp_sk_rx_policy_check(skb, psp_sk_assoc(sk));
}

static inline enum skb_drop_reason
psp_twsk_rx_policy_check(struct inet_timewait_sock *tw, struct sk_buff *skb)
{
	return 0;
	return __psp_sk_rx_policy_check(skb, rcu_dereference(tw->psp_assoc));
}

static inline struct psp_assoc *psp_sk_get_assoc_rcu(struct sock *sk)
{
	struct inet_timewait_sock *tw;
	struct psp_assoc *pas;
	int state;

	state = 1 << READ_ONCE(sk->sk_state);
	if (!sk_is_inet(sk) || state & TCPF_NEW_SYN_RECV)
		return NULL;

	tw = inet_twsk(sk);
	pas = state & TCPF_TIME_WAIT ? rcu_dereference(tw->psp_assoc) :
				       rcu_dereference(sk->psp_assoc);
	return pas;
}

static inline struct psp_assoc *psp_skb_get_assoc_rcu(struct sk_buff *skb)
{
	if (!skb->decrypted || !skb->sk)
		return NULL;

	return psp_sk_get_assoc_rcu(skb->sk);
}
#else
static inline void psp_sk_assoc_free(struct sock *sk) { }
@@ -60,6 +151,11 @@ static inline void psp_twsk_assoc_free(struct inet_timewait_sock *tw) { }
static inline void
psp_reply_set_decrypted(struct sk_buff *skb) { }

static inline struct psp_assoc *psp_sk_assoc(const struct sock *sk)
{
	return NULL;
}

static inline void
psp_enqueue_set_decrypted(struct sock *sk, struct sk_buff *skb) { }

+57 −0
Original line number Diff line number Diff line
@@ -51,6 +51,7 @@ struct psp_dev_config {
 * @refcnt:	reference count for the instance
 * @id:		instance id
 * @config:	current device configuration
 * @active_assocs:	list of registered associations
 *
 * @rcu:	RCU head for freeing the structure
 */
@@ -68,6 +69,8 @@ struct psp_dev {

	struct psp_dev_config config;

	struct list_head active_assocs;

	struct rcu_head rcu;
};

@@ -80,6 +83,12 @@ struct psp_dev_caps {
	 * Set this field to 0 to indicate PSP is not supported at all.
	 */
	u32 versions;

	/**
	 * @assoc_drv_spc: size of driver-specific state in Tx assoc
	 * Determines the size of struct psp_assoc::drv_spc
	 */
	u32 assoc_drv_spc;
};

#define PSP_MAX_KEY	32
@@ -91,6 +100,32 @@ struct psp_skb_ext {
	u8 version;
};

struct psp_key_parsed {
	__be32 spi;
	u8 key[PSP_MAX_KEY];
};

struct psp_assoc {
	struct psp_dev *psd;

	u16 dev_id;
	u8 generation;
	u8 version;
	u8 peer_tx;

	u32 upgrade_seq;

	struct psp_key_parsed tx;
	struct psp_key_parsed rx;

	refcount_t refcnt;
	struct rcu_head rcu;
	struct work_struct work;
	struct list_head assocs_list;

	u8 drv_data[] __aligned(8);
};

/**
 * struct psp_dev_ops - netdev driver facing PSP callbacks
 */
@@ -107,6 +142,28 @@ struct psp_dev_ops {
	 * @key_rotate: rotate the device key
	 */
	int (*key_rotate)(struct psp_dev *psd, struct netlink_ext_ack *extack);

	/**
	 * @rx_spi_alloc: allocate an Rx SPI+key pair
	 * Allocate an Rx SPI and resulting derived key.
	 * This key should remain valid until key rotation.
	 */
	int (*rx_spi_alloc)(struct psp_dev *psd, u32 version,
			    struct psp_key_parsed *assoc,
			    struct netlink_ext_ack *extack);

	/**
	 * @tx_key_add: add a Tx key to the device
	 * Install an association in the device. Core will allocate space
	 * for the driver to use at drv_data.
	 */
	int (*tx_key_add)(struct psp_dev *psd, struct psp_assoc *pas,
			  struct netlink_ext_ack *extack);
	/**
	 * @tx_key_del: remove a Tx key from the device
	 * Remove an association from the device.
	 */
	void (*tx_key_del)(struct psp_dev *psd, struct psp_assoc *pas);
};

#endif /* __NET_PSP_H */
+21 −0
Original line number Diff line number Diff line
@@ -26,6 +26,25 @@ enum {
	PSP_A_DEV_MAX = (__PSP_A_DEV_MAX - 1)
};

enum {
	PSP_A_ASSOC_DEV_ID = 1,
	PSP_A_ASSOC_VERSION,
	PSP_A_ASSOC_RX_KEY,
	PSP_A_ASSOC_TX_KEY,
	PSP_A_ASSOC_SOCK_FD,

	__PSP_A_ASSOC_MAX,
	PSP_A_ASSOC_MAX = (__PSP_A_ASSOC_MAX - 1)
};

enum {
	PSP_A_KEYS_KEY = 1,
	PSP_A_KEYS_SPI,

	__PSP_A_KEYS_MAX,
	PSP_A_KEYS_MAX = (__PSP_A_KEYS_MAX - 1)
};

enum {
	PSP_CMD_DEV_GET = 1,
	PSP_CMD_DEV_ADD_NTF,
@@ -34,6 +53,8 @@ enum {
	PSP_CMD_DEV_CHANGE_NTF,
	PSP_CMD_KEY_ROTATE,
	PSP_CMD_KEY_ROTATE_NTF,
	PSP_CMD_RX_ASSOC,
	PSP_CMD_TX_ASSOC,

	__PSP_CMD_MAX,
	PSP_CMD_MAX = (__PSP_CMD_MAX - 1)
+1 −0
Original line number Diff line number Diff line
@@ -6,6 +6,7 @@ config INET_PSP
	bool "PSP Security Protocol support"
	depends on INET
	select SKB_DECRYPTED
	select SOCK_VALIDATE_XMIT
	help
	Enable kernel support for the PSP protocol.
	For more information see:
Loading