Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
+16
View File
@@ -0,0 +1,16 @@
# SPDX-License-Identifier: GPL-2.0-only
#
# PSP configuration
#
config INET_PSP
bool "PSP Security Protocol support"
depends on INET
select SKB_DECRYPTED
select SKB_EXTENSIONS
select SOCK_VALIDATE_XMIT
help
Enable kernel support for the PSP Security Protocol (PSP).
For more information see:
https://raw.githubusercontent.com/google/psp/main/doc/PSP_Arch_Spec.pdf
If unsure, say N.
+5
View File
@@ -0,0 +1,5 @@
# SPDX-License-Identifier: GPL-2.0-only
obj-$(CONFIG_INET_PSP) += psp.o
psp-y := psp_main.o psp_nl.o psp_sock.o psp-nl-gen.o
+171
View File
@@ -0,0 +1,171 @@
// SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause)
/* Do not edit directly, auto-generated from: */
/* Documentation/netlink/specs/psp.yaml */
/* YNL-GEN kernel source */
/* To regenerate run: tools/net/ynl/ynl-regen.sh */
#include <net/netlink.h>
#include <net/genetlink.h>
#include "psp-nl-gen.h"
#include <uapi/linux/psp.h>
/* Common nested types */
const struct nla_policy psp_keys_nl_policy[PSP_A_KEYS_SPI + 1] = {
[PSP_A_KEYS_KEY] = { .type = NLA_BINARY, },
[PSP_A_KEYS_SPI] = { .type = NLA_U32, },
};
/* PSP_CMD_DEV_GET - do */
static const struct nla_policy psp_dev_get_nl_policy[PSP_A_DEV_ID + 1] = {
[PSP_A_DEV_ID] = NLA_POLICY_MIN(NLA_U32, 1),
};
/* PSP_CMD_DEV_SET - do */
static const struct nla_policy psp_dev_set_nl_policy[PSP_A_DEV_PSP_VERSIONS_ENA + 1] = {
[PSP_A_DEV_ID] = NLA_POLICY_MIN(NLA_U32, 1),
[PSP_A_DEV_PSP_VERSIONS_ENA] = NLA_POLICY_MASK(NLA_U32, 0xf),
};
/* PSP_CMD_KEY_ROTATE - do */
static const struct nla_policy psp_key_rotate_nl_policy[PSP_A_DEV_ID + 1] = {
[PSP_A_DEV_ID] = NLA_POLICY_MIN(NLA_U32, 1),
};
/* PSP_CMD_RX_ASSOC - do */
static const struct nla_policy psp_rx_assoc_nl_policy[PSP_A_ASSOC_SOCK_FD + 1] = {
[PSP_A_ASSOC_DEV_ID] = NLA_POLICY_MIN(NLA_U32, 1),
[PSP_A_ASSOC_VERSION] = NLA_POLICY_MAX(NLA_U32, 3),
[PSP_A_ASSOC_SOCK_FD] = { .type = NLA_U32, },
};
/* PSP_CMD_TX_ASSOC - do */
static const struct nla_policy psp_tx_assoc_nl_policy[PSP_A_ASSOC_SOCK_FD + 1] = {
[PSP_A_ASSOC_DEV_ID] = NLA_POLICY_MIN(NLA_U32, 1),
[PSP_A_ASSOC_VERSION] = NLA_POLICY_MAX(NLA_U32, 3),
[PSP_A_ASSOC_TX_KEY] = NLA_POLICY_NESTED(psp_keys_nl_policy),
[PSP_A_ASSOC_SOCK_FD] = { .type = NLA_U32, },
};
/* PSP_CMD_GET_STATS - do */
static const struct nla_policy psp_get_stats_nl_policy[PSP_A_STATS_DEV_ID + 1] = {
[PSP_A_STATS_DEV_ID] = NLA_POLICY_MIN(NLA_U32, 1),
};
/* PSP_CMD_DEV_ASSOC - do */
static const struct nla_policy psp_dev_assoc_nl_policy[PSP_A_DEV_NSID + 1] = {
[PSP_A_DEV_ID] = NLA_POLICY_MIN(NLA_U32, 1),
[PSP_A_DEV_IFINDEX] = { .type = NLA_U32, },
[PSP_A_DEV_NSID] = { .type = NLA_S32, },
};
/* PSP_CMD_DEV_DISASSOC - do */
static const struct nla_policy psp_dev_disassoc_nl_policy[PSP_A_DEV_NSID + 1] = {
[PSP_A_DEV_ID] = NLA_POLICY_MIN(NLA_U32, 1),
[PSP_A_DEV_IFINDEX] = { .type = NLA_U32, },
[PSP_A_DEV_NSID] = { .type = NLA_S32, },
};
/* Ops table for psp */
static const struct genl_split_ops psp_nl_ops[] = {
{
.cmd = PSP_CMD_DEV_GET,
.pre_doit = psp_device_get_locked,
.doit = psp_nl_dev_get_doit,
.post_doit = psp_device_unlock,
.policy = psp_dev_get_nl_policy,
.maxattr = PSP_A_DEV_ID,
.flags = GENL_CMD_CAP_DO,
},
{
.cmd = PSP_CMD_DEV_GET,
.dumpit = psp_nl_dev_get_dumpit,
.flags = GENL_CMD_CAP_DUMP,
},
{
.cmd = PSP_CMD_DEV_SET,
.pre_doit = psp_device_get_locked_admin,
.doit = psp_nl_dev_set_doit,
.post_doit = psp_device_unlock,
.policy = psp_dev_set_nl_policy,
.maxattr = PSP_A_DEV_PSP_VERSIONS_ENA,
.flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO,
},
{
.cmd = PSP_CMD_KEY_ROTATE,
.pre_doit = psp_device_get_locked_admin,
.doit = psp_nl_key_rotate_doit,
.post_doit = psp_device_unlock,
.policy = psp_key_rotate_nl_policy,
.maxattr = PSP_A_DEV_ID,
.flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO,
},
{
.cmd = PSP_CMD_RX_ASSOC,
.pre_doit = psp_assoc_device_get_locked,
.doit = psp_nl_rx_assoc_doit,
.post_doit = psp_device_unlock,
.policy = psp_rx_assoc_nl_policy,
.maxattr = PSP_A_ASSOC_SOCK_FD,
.flags = GENL_CMD_CAP_DO,
},
{
.cmd = PSP_CMD_TX_ASSOC,
.pre_doit = psp_assoc_device_get_locked,
.doit = psp_nl_tx_assoc_doit,
.post_doit = psp_device_unlock,
.policy = psp_tx_assoc_nl_policy,
.maxattr = PSP_A_ASSOC_SOCK_FD,
.flags = GENL_CMD_CAP_DO,
},
{
.cmd = PSP_CMD_GET_STATS,
.pre_doit = psp_device_get_locked,
.doit = psp_nl_get_stats_doit,
.post_doit = psp_device_unlock,
.policy = psp_get_stats_nl_policy,
.maxattr = PSP_A_STATS_DEV_ID,
.flags = GENL_CMD_CAP_DO,
},
{
.cmd = PSP_CMD_GET_STATS,
.dumpit = psp_nl_get_stats_dumpit,
.flags = GENL_CMD_CAP_DUMP,
},
{
.cmd = PSP_CMD_DEV_ASSOC,
.pre_doit = psp_device_get_locked_dev_assoc,
.doit = psp_nl_dev_assoc_doit,
.post_doit = psp_device_unlock,
.policy = psp_dev_assoc_nl_policy,
.maxattr = PSP_A_DEV_NSID,
.flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO,
},
{
.cmd = PSP_CMD_DEV_DISASSOC,
.pre_doit = psp_device_get_locked,
.doit = psp_nl_dev_disassoc_doit,
.post_doit = psp_device_unlock,
.policy = psp_dev_disassoc_nl_policy,
.maxattr = PSP_A_DEV_NSID,
.flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO,
},
};
static const struct genl_multicast_group psp_nl_mcgrps[] = {
[PSP_NLGRP_MGMT] = { "mgmt", },
[PSP_NLGRP_USE] = { "use", },
};
struct genl_family psp_nl_family __ro_after_init = {
.name = PSP_FAMILY_NAME,
.version = PSP_FAMILY_VERSION,
.netnsok = true,
.parallel_ops = true,
.module = THIS_MODULE,
.split_ops = psp_nl_ops,
.n_split_ops = ARRAY_SIZE(psp_nl_ops),
.mcgrps = psp_nl_mcgrps,
.n_mcgrps = ARRAY_SIZE(psp_nl_mcgrps),
};
+49
View File
@@ -0,0 +1,49 @@
/* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause) */
/* Do not edit directly, auto-generated from: */
/* Documentation/netlink/specs/psp.yaml */
/* YNL-GEN kernel header */
/* To regenerate run: tools/net/ynl/ynl-regen.sh */
#ifndef _LINUX_PSP_GEN_H
#define _LINUX_PSP_GEN_H
#include <net/netlink.h>
#include <net/genetlink.h>
#include <uapi/linux/psp.h>
/* Common nested types */
extern const struct nla_policy psp_keys_nl_policy[PSP_A_KEYS_SPI + 1];
int psp_device_get_locked(const struct genl_split_ops *ops,
struct sk_buff *skb, struct genl_info *info);
int psp_device_get_locked_admin(const struct genl_split_ops *ops,
struct sk_buff *skb, struct genl_info *info);
int psp_assoc_device_get_locked(const struct genl_split_ops *ops,
struct sk_buff *skb, struct genl_info *info);
int psp_device_get_locked_dev_assoc(const struct genl_split_ops *ops,
struct sk_buff *skb,
struct genl_info *info);
void
psp_device_unlock(const struct genl_split_ops *ops, struct sk_buff *skb,
struct genl_info *info);
int psp_nl_dev_get_doit(struct sk_buff *skb, struct genl_info *info);
int psp_nl_dev_get_dumpit(struct sk_buff *skb, struct netlink_callback *cb);
int psp_nl_dev_set_doit(struct sk_buff *skb, struct genl_info *info);
int psp_nl_key_rotate_doit(struct sk_buff *skb, struct genl_info *info);
int psp_nl_rx_assoc_doit(struct sk_buff *skb, struct genl_info *info);
int psp_nl_tx_assoc_doit(struct sk_buff *skb, struct genl_info *info);
int psp_nl_get_stats_doit(struct sk_buff *skb, struct genl_info *info);
int psp_nl_get_stats_dumpit(struct sk_buff *skb, struct netlink_callback *cb);
int psp_nl_dev_assoc_doit(struct sk_buff *skb, struct genl_info *info);
int psp_nl_dev_disassoc_doit(struct sk_buff *skb, struct genl_info *info);
enum {
PSP_NLGRP_MGMT,
PSP_NLGRP_USE,
};
extern struct genl_family psp_nl_family;
#endif /* _LINUX_PSP_GEN_H */
+56
View File
@@ -0,0 +1,56 @@
/* SPDX-License-Identifier: GPL-2.0-only */
#ifndef __PSP_PSP_H
#define __PSP_PSP_H
#include <linux/list.h>
#include <linux/lockdep.h>
#include <linux/mutex.h>
#include <net/netns/generic.h>
#include <net/psp.h>
#include <net/sock.h>
extern struct xarray psp_devs;
extern struct mutex psp_devs_lock;
void psp_dev_free(struct psp_dev *psd);
int psp_dev_check_access(struct psp_dev *psd, struct net *net, bool admin);
bool psp_has_assoc_dev_in_ns(struct psp_dev *psd, struct net *net);
int psp_attach_netdev_notifier(void);
void psp_nl_notify_dev(struct psp_dev *psd, u32 cmd);
struct psp_assoc *psp_assoc_create(struct psp_dev *psd);
struct psp_dev *psp_dev_get_for_sock(struct sock *sk);
void psp_dev_tx_key_del(struct psp_dev *psd, struct psp_assoc *pas);
int psp_sock_assoc_set_rx(struct sock *sk, struct psp_assoc *pas,
struct psp_key_parsed *key,
struct netlink_ext_ack *extack);
int psp_sock_assoc_set_tx(struct sock *sk, struct psp_dev *psd,
u32 version, struct psp_key_parsed *key,
struct netlink_ext_ack *extack);
void psp_assocs_key_rotated(struct psp_dev *psd);
static inline void psp_dev_get(struct psp_dev *psd)
{
refcount_inc(&psd->refcnt);
}
static inline bool psp_dev_tryget(struct psp_dev *psd)
{
return refcount_inc_not_zero(&psd->refcnt);
}
static inline void psp_dev_put(struct psp_dev *psd)
{
if (refcount_dec_and_test(&psd->refcnt))
psp_dev_free(psd);
}
static inline bool psp_dev_is_registered(struct psp_dev *psd)
{
lockdep_assert_held(&psd->lock);
return !!psd->ops;
}
#endif /* __PSP_PSP_H */
+490
View File
@@ -0,0 +1,490 @@
// SPDX-License-Identifier: GPL-2.0-only
#include <linux/bitfield.h>
#include <linux/list.h>
#include <linux/netdevice.h>
#include <linux/xarray.h>
#include <net/net_namespace.h>
#include <net/psp.h>
#include <net/udp.h>
#include "psp.h"
#include "psp-nl-gen.h"
DEFINE_XARRAY_ALLOC1(psp_devs);
struct mutex psp_devs_lock;
/**
* DOC: PSP locking
*
* psp_devs_lock protects the psp_devs xarray.
* Ordering is take the psp_devs_lock and then the instance lock.
* Each instance is protected by RCU, and has a refcount.
* When driver unregisters the instance gets flushed, but struct sticks around.
*/
/**
* psp_dev_check_access() - check if user in a given net ns can access PSP dev
* @psd: PSP device structure user is trying to access
* @net: net namespace user is in
* @admin: If true, only allow access from @psd's main device's netns,
* for admin operations like config changes and key rotation.
* If false, also allow access from network namespaces that have
* an associated device with @psd, for read-only and association
* management operations.
*
* Return: 0 if PSP device should be visible in @net, errno otherwise.
*/
int psp_dev_check_access(struct psp_dev *psd, struct net *net, bool admin)
{
if (dev_net(psd->main_netdev) == net)
return 0;
if (!admin && psp_has_assoc_dev_in_ns(psd, net))
return 0;
return -ENOENT;
}
/**
* psp_dev_create() - create and register PSP device
* @netdev: main netdevice
* @psd_ops: driver callbacks
* @psd_caps: device capabilities
* @priv_ptr: back-pointer to driver private data
*
* Return: pointer to allocated PSP device, or ERR_PTR.
*/
struct psp_dev *
psp_dev_create(struct net_device *netdev,
struct psp_dev_ops *psd_ops, struct psp_dev_caps *psd_caps,
void *priv_ptr)
{
struct psp_dev *psd;
static u32 last_id;
int err;
if (WARN_ON(!psd_caps->versions ||
!psd_ops->set_config ||
!psd_ops->key_rotate ||
!psd_ops->rx_spi_alloc ||
!psd_ops->tx_key_add ||
!psd_ops->tx_key_del ||
!psd_ops->get_stats))
return ERR_PTR(-EINVAL);
psd = kzalloc_obj(*psd);
if (!psd)
return ERR_PTR(-ENOMEM);
psd->main_netdev = netdev;
INIT_LIST_HEAD(&psd->assoc_dev_list);
psd->ops = psd_ops;
psd->caps = psd_caps;
psd->drv_priv = priv_ptr;
mutex_init(&psd->lock);
INIT_LIST_HEAD(&psd->active_assocs);
INIT_LIST_HEAD(&psd->prev_assocs);
INIT_LIST_HEAD(&psd->stale_assocs);
refcount_set(&psd->refcnt, 1);
mutex_lock(&psp_devs_lock);
err = xa_alloc_cyclic(&psp_devs, &psd->id, psd, xa_limit_16b,
&last_id, GFP_KERNEL);
if (err) {
mutex_unlock(&psp_devs_lock);
kfree(psd);
return ERR_PTR(err);
}
mutex_lock(&psd->lock);
mutex_unlock(&psp_devs_lock);
/* notify before netdev assignment
* There's no strong reason for it, but thinking is to avoid creating
* implicit expectations about the PSP dev <> netdev relationship.
*/
psp_nl_notify_dev(psd, PSP_CMD_DEV_ADD_NTF);
rcu_assign_pointer(netdev->psp_dev, psd);
mutex_unlock(&psd->lock);
return psd;
}
EXPORT_SYMBOL(psp_dev_create);
void psp_dev_free(struct psp_dev *psd)
{
mutex_lock(&psp_devs_lock);
xa_erase(&psp_devs, psd->id);
mutex_unlock(&psp_devs_lock);
mutex_destroy(&psd->lock);
kfree_rcu(psd, rcu);
}
/**
* psp_dev_unregister() - unregister PSP device
* @psd: PSP device structure
*/
void psp_dev_unregister(struct psp_dev *psd)
{
struct psp_assoc_dev *entry, *entry_tmp;
struct psp_assoc *pas, *next;
mutex_lock(&psp_devs_lock);
mutex_lock(&psd->lock);
psp_nl_notify_dev(psd, PSP_CMD_DEV_DEL_NTF);
/* Wait until psp_dev_free() to call xa_erase() to prevent a
* different psd from being added to the xarray with this id, while
* there are still references to this psd being held.
*/
xa_store(&psp_devs, psd->id, NULL, GFP_KERNEL);
mutex_unlock(&psp_devs_lock);
list_splice_init(&psd->active_assocs, &psd->prev_assocs);
list_splice_init(&psd->prev_assocs, &psd->stale_assocs);
list_for_each_entry_safe(pas, next, &psd->stale_assocs, assocs_list)
psp_dev_tx_key_del(psd, pas);
list_for_each_entry_safe(entry, entry_tmp, &psd->assoc_dev_list,
dev_list) {
list_del(&entry->dev_list);
rcu_assign_pointer(entry->assoc_dev->psp_dev, NULL);
netdev_put(entry->assoc_dev, &entry->dev_tracker);
kfree(entry);
}
psd->assoc_dev_cnt = 0;
rcu_assign_pointer(psd->main_netdev->psp_dev, NULL);
psd->ops = NULL;
psd->drv_priv = NULL;
mutex_unlock(&psd->lock);
psp_dev_put(psd);
}
EXPORT_SYMBOL(psp_dev_unregister);
unsigned int psp_key_size(u32 version)
{
switch (version) {
case PSP_VERSION_HDR0_AES_GCM_128:
case PSP_VERSION_HDR0_AES_GMAC_128:
return 16;
case PSP_VERSION_HDR0_AES_GCM_256:
case PSP_VERSION_HDR0_AES_GMAC_256:
return 32;
default:
return 0;
}
}
EXPORT_SYMBOL(psp_key_size);
static void psp_write_headers(struct net *net, struct sk_buff *skb, __be32 spi,
u8 ver, unsigned int udp_len, __be16 sport)
{
struct udphdr *uh = udp_hdr(skb);
struct psphdr *psph = (struct psphdr *)(uh + 1);
const struct sock *sk = skb->sk;
uh->dest = htons(PSP_DEFAULT_UDP_PORT);
/* A bit of theory: Selection of the source port.
*
* We need some entropy, so that multiple flows use different
* source ports for better RSS spreading at the receiver.
*
* We also need that all packets belonging to one TCP flow
* use the same source port through their duration,
* so that all these packets land in the same receive queue.
*
* udp_flow_src_port() is using sk_txhash, inherited from
* skb_set_hash_from_sk() call in __tcp_transmit_skb().
* This field is subject to reshuffling, thanks to
* sk_rethink_txhash() calls in various TCP functions.
*
* Instead, use sk->sk_hash which is constant through
* the whole flow duration.
*/
if (likely(sk)) {
u32 hash = sk->sk_hash;
int min, max;
/* These operations are cheap, no need to cache the result
* in another socket field.
*/
inet_get_local_port_range(net, &min, &max);
/* Since this is being sent on the wire obfuscate hash a bit
* to minimize possibility that any useful information to an
* attacker is leaked. Only upper 16 bits are relevant in the
* computation for 16 bit port value because we use a
* reciprocal divide.
*/
hash ^= hash << 16;
uh->source = htons(reciprocal_scale(hash, max - min + 1) + min);
} else {
uh->source = udp_flow_src_port(net, skb, 0, 0, false);
}
uh->check = 0;
uh->len = htons(udp_len);
psph->nexthdr = IPPROTO_TCP;
psph->hdrlen = PSP_HDRLEN_NOOPT;
psph->crypt_offset = 0;
psph->verfl = FIELD_PREP(PSPHDR_VERFL_VERSION, ver) |
FIELD_PREP(PSPHDR_VERFL_ONE, 1);
psph->spi = spi;
memset(&psph->iv, 0, sizeof(psph->iv));
}
/* Encapsulate a TCP packet with PSP by adding the UDP+PSP headers and filling
* them in.
*/
bool psp_dev_encapsulate(struct net *net, struct sk_buff *skb, __be32 spi,
u8 ver, __be16 sport)
{
u32 network_len = skb_network_header_len(skb);
u32 ethr_len = skb_mac_header_len(skb);
u32 bufflen = ethr_len + network_len;
if (skb->protocol != htons(ETH_P_IP) &&
skb->protocol != htons(ETH_P_IPV6))
return false;
if (skb_cow_head(skb, PSP_ENCAP_HLEN))
return false;
skb_push(skb, PSP_ENCAP_HLEN);
skb->mac_header -= PSP_ENCAP_HLEN;
skb->network_header -= PSP_ENCAP_HLEN;
skb->transport_header -= PSP_ENCAP_HLEN;
memmove(skb->data, skb->data + PSP_ENCAP_HLEN, bufflen);
if (skb->protocol == htons(ETH_P_IP)) {
ip_hdr(skb)->protocol = IPPROTO_UDP;
be16_add_cpu(&ip_hdr(skb)->tot_len, PSP_ENCAP_HLEN);
ip_hdr(skb)->check = 0;
ip_hdr(skb)->check =
ip_fast_csum((u8 *)ip_hdr(skb), ip_hdr(skb)->ihl);
} else {
ipv6_hdr(skb)->nexthdr = IPPROTO_UDP;
be16_add_cpu(&ipv6_hdr(skb)->payload_len, PSP_ENCAP_HLEN);
}
skb_set_inner_ipproto(skb, IPPROTO_TCP);
skb_set_inner_transport_header(skb, skb_transport_offset(skb) +
PSP_ENCAP_HLEN);
skb->encapsulation = 1;
psp_write_headers(net, skb, spi, ver,
skb->len - skb_transport_offset(skb), sport);
return true;
}
EXPORT_SYMBOL(psp_dev_encapsulate);
/* Receive handler for PSP packets.
*
* Accepts only already-authenticated packets. The full PSP header is
* stripped according to psph->hdrlen; any optional fields it advertises
* (virtualization cookies, etc.) are ignored and discarded along with the
* rest of the header. The caller should ensure that skb->data is pointing
* to the mac header, and that skb->mac_len is set. This function does not
* currently adjust skb->csum (CHECKSUM_COMPLETE is not supported).
*/
int psp_dev_rcv(struct sk_buff *skb, u16 dev_id, u8 generation, bool strip_icv)
{
int l2_hlen = 0, l3_hlen, encap, psp_hlen;
struct psp_skb_ext *pse;
struct psphdr *psph;
struct ethhdr *eth;
struct udphdr *uh;
__be16 proto;
bool is_udp;
eth = (struct ethhdr *)skb->data;
proto = __vlan_get_protocol(skb, eth->h_proto, &l2_hlen);
if (proto == htons(ETH_P_IP))
l3_hlen = sizeof(struct iphdr);
else if (proto == htons(ETH_P_IPV6))
l3_hlen = sizeof(struct ipv6hdr);
else
return -EINVAL;
if (unlikely(!pskb_may_pull(skb, l2_hlen + l3_hlen + PSP_ENCAP_HLEN)))
return -EINVAL;
if (proto == htons(ETH_P_IP)) {
struct iphdr *iph = (struct iphdr *)(skb->data + l2_hlen);
if (unlikely(iph->ihl < 5))
return -EINVAL;
is_udp = iph->protocol == IPPROTO_UDP;
l3_hlen = iph->ihl * 4;
if (l3_hlen != sizeof(struct iphdr) &&
!pskb_may_pull(skb, l2_hlen + l3_hlen + PSP_ENCAP_HLEN))
return -EINVAL;
} else {
struct ipv6hdr *ipv6h = (struct ipv6hdr *)(skb->data + l2_hlen);
is_udp = ipv6h->nexthdr == IPPROTO_UDP;
}
if (unlikely(!is_udp))
return -EINVAL;
uh = (struct udphdr *)(skb->data + l2_hlen + l3_hlen);
if (unlikely(uh->dest != htons(PSP_DEFAULT_UDP_PORT)))
return -EINVAL;
psph = (struct psphdr *)(skb->data + l2_hlen + l3_hlen +
sizeof(struct udphdr));
/* Strip the full PSP header per psph->hdrlen; VC/options are pulled
* into the linear region only so they can be discarded with the
* rest of the header.
*/
psp_hlen = (psph->hdrlen + 1) * 8;
if (unlikely(psp_hlen < sizeof(struct psphdr)))
return -EINVAL;
if (psp_hlen > sizeof(struct psphdr) &&
!pskb_may_pull(skb, l2_hlen + l3_hlen +
sizeof(struct udphdr) + psp_hlen))
return -EINVAL;
psph = (struct psphdr *)(skb->data + l2_hlen + l3_hlen +
sizeof(struct udphdr));
pse = skb_ext_add(skb, SKB_EXT_PSP);
if (!pse)
return -EINVAL;
pse->spi = psph->spi;
pse->dev_id = dev_id;
pse->generation = generation;
pse->version = FIELD_GET(PSPHDR_VERFL_VERSION, psph->verfl);
encap = sizeof(struct udphdr) + psp_hlen;
encap += strip_icv ? PSP_TRL_SIZE : 0;
if (proto == htons(ETH_P_IP)) {
struct iphdr *iph = (struct iphdr *)(skb->data + l2_hlen);
if (unlikely(ntohs(iph->tot_len) < l3_hlen + encap))
return -EINVAL;
iph->protocol = psph->nexthdr;
iph->tot_len = htons(ntohs(iph->tot_len) - encap);
iph->check = 0;
iph->check = ip_fast_csum((u8 *)iph, iph->ihl);
} else {
struct ipv6hdr *ipv6h = (struct ipv6hdr *)(skb->data + l2_hlen);
if (unlikely(ntohs(ipv6h->payload_len) < encap))
return -EINVAL;
ipv6h->nexthdr = psph->nexthdr;
ipv6h->payload_len = htons(ntohs(ipv6h->payload_len) - encap);
}
memmove(skb->data + sizeof(struct udphdr) + psp_hlen,
skb->data, l2_hlen + l3_hlen);
skb_pull(skb, sizeof(struct udphdr) + psp_hlen);
if (strip_icv)
pskb_trim(skb, skb->len - PSP_TRL_SIZE);
return 0;
}
EXPORT_SYMBOL(psp_dev_rcv);
static void psp_dev_disassoc_one(struct psp_dev *psd, struct net_device *dev)
{
struct psp_assoc_dev *entry;
list_for_each_entry(entry, &psd->assoc_dev_list, dev_list) {
if (entry->assoc_dev == dev) {
list_del(&entry->dev_list);
psd->assoc_dev_cnt--;
rcu_assign_pointer(entry->assoc_dev->psp_dev, NULL);
netdev_put(entry->assoc_dev, &entry->dev_tracker);
kfree(entry);
return;
}
}
}
static int psp_netdev_event(struct notifier_block *nb, unsigned long event,
void *ptr)
{
struct net_device *dev = netdev_notifier_info_to_dev(ptr);
struct psp_dev *psd;
if (event != NETDEV_UNREGISTER)
return NOTIFY_DONE;
rcu_read_lock();
psd = rcu_dereference(dev->psp_dev);
if (psd && psp_dev_tryget(psd)) {
rcu_read_unlock();
mutex_lock(&psd->lock);
if (psp_dev_is_registered(psd))
psp_nl_notify_dev(psd, PSP_CMD_DEV_CHANGE_NTF);
psp_dev_disassoc_one(psd, dev);
mutex_unlock(&psd->lock);
psp_dev_put(psd);
} else {
rcu_read_unlock();
}
return NOTIFY_DONE;
}
static struct notifier_block psp_netdev_notifier = {
.notifier_call = psp_netdev_event,
};
static DEFINE_MUTEX(psp_notifier_lock);
static bool psp_notifier_registered;
/* Register the netdevice notifier when the first device association
* is created. In many installations no associations will be created and
* the notifier won't be needed.
*
* Must be called without psd->lock held, due to lock ordering:
* rtnl_lock -> psd->lock (the notifier callback runs under rtnl_lock
* and takes psd->lock).
*/
int psp_attach_netdev_notifier(void)
{
int err = 0;
if (READ_ONCE(psp_notifier_registered))
return 0;
mutex_lock(&psp_notifier_lock);
if (!psp_notifier_registered) {
err = register_netdevice_notifier(&psp_netdev_notifier);
if (!err)
WRITE_ONCE(psp_notifier_registered, true);
}
mutex_unlock(&psp_notifier_lock);
return err;
}
static int __init psp_init(void)
{
mutex_init(&psp_devs_lock);
return genl_register_family(&psp_nl_family);
}
subsys_initcall(psp_init);
+958
View File
@@ -0,0 +1,958 @@
// SPDX-License-Identifier: GPL-2.0-only
#include <linux/ethtool.h>
#include <linux/net_namespace.h>
#include <linux/skbuff.h>
#include <linux/xarray.h>
#include <net/genetlink.h>
#include <net/psp.h>
#include <net/sock.h>
#include "psp-nl-gen.h"
#include "psp.h"
/* Netlink helpers */
static struct sk_buff *psp_nl_reply_new(struct genl_info *info)
{
struct sk_buff *rsp;
void *hdr;
rsp = genlmsg_new(GENLMSG_DEFAULT_SIZE, GFP_KERNEL);
if (!rsp)
return NULL;
hdr = genlmsg_iput(rsp, info);
if (!hdr) {
nlmsg_free(rsp);
return NULL;
}
return rsp;
}
static int psp_nl_reply_send(struct sk_buff *rsp, struct genl_info *info)
{
/* Note that this *only* works with a single message per skb! */
nlmsg_end(rsp, (struct nlmsghdr *)rsp->data);
return genlmsg_reply(rsp, info);
}
/**
* psp_nl_multicast_per_ns() - multicast a notification to each unique netns
* @psd: PSP device (must be locked)
* @group: multicast group
* @build_ntf: callback to build an skb for a given netns, or NULL on failure
* @ctx: opaque context passed to @build_ntf
*
* Iterates all unique network namespaces from the associated device list
* plus the main device's netns. For each unique netns, calls @build_ntf
* to construct a notification skb and multicasts it.
*/
static void
psp_nl_multicast_per_ns(struct psp_dev *psd, unsigned int group,
struct sk_buff *(*build_ntf)(struct psp_dev *,
struct net *,
void *),
void *ctx)
{
struct psp_assoc_dev *entry;
struct xarray sent_nets;
struct net *main_net;
struct sk_buff *ntf;
/* device may be changing netns in parallel */
rcu_read_lock();
main_net = maybe_get_net(dev_net_rcu(psd->main_netdev));
rcu_read_unlock();
if (!main_net)
return;
xa_init(&sent_nets);
list_for_each_entry(entry, &psd->assoc_dev_list, dev_list) {
struct net *assoc_net = dev_net(entry->assoc_dev);
int ret;
if (net_eq(assoc_net, main_net))
continue;
ret = xa_insert(&sent_nets, (unsigned long)assoc_net, assoc_net,
GFP_KERNEL);
if (ret == -EBUSY)
continue;
ntf = build_ntf(psd, assoc_net, ctx);
if (!ntf)
continue;
genlmsg_multicast_netns(&psp_nl_family, assoc_net, ntf, 0,
group, GFP_KERNEL);
}
xa_destroy(&sent_nets);
/* Send to main device netns */
ntf = build_ntf(psd, main_net, ctx);
if (ntf)
genlmsg_multicast_netns(&psp_nl_family, main_net, ntf, 0, group,
GFP_KERNEL);
put_net(main_net);
}
static struct sk_buff *psp_nl_clone_ntf(struct psp_dev *psd, struct net *net,
void *ctx)
{
return skb_clone(ctx, GFP_KERNEL);
}
static void psp_nl_multicast_all_ns(struct psp_dev *psd, struct sk_buff *ntf,
unsigned int group)
{
psp_nl_multicast_per_ns(psd, group, psp_nl_clone_ntf, ntf);
nlmsg_consume(ntf);
}
/* Device stuff */
static struct psp_dev *
psp_device_get_and_lock(struct net *net, struct nlattr *dev_id,
bool admin)
{
struct psp_dev *psd;
int err;
mutex_lock(&psp_devs_lock);
psd = xa_load(&psp_devs, nla_get_u32(dev_id));
if (!psd) {
mutex_unlock(&psp_devs_lock);
return ERR_PTR(-ENODEV);
}
mutex_lock(&psd->lock);
mutex_unlock(&psp_devs_lock);
err = psp_dev_check_access(psd, net, admin);
if (err) {
mutex_unlock(&psd->lock);
return ERR_PTR(err);
}
return psd;
}
static int __psp_device_get_locked(const struct genl_split_ops *ops,
struct sk_buff *skb, struct genl_info *info,
bool admin)
{
if (GENL_REQ_ATTR_CHECK(info, PSP_A_DEV_ID))
return -EINVAL;
info->user_ptr[0] = psp_device_get_and_lock(genl_info_net(info),
info->attrs[PSP_A_DEV_ID],
admin);
return PTR_ERR_OR_ZERO(info->user_ptr[0]);
}
int psp_device_get_locked_admin(const struct genl_split_ops *ops,
struct sk_buff *skb, struct genl_info *info)
{
return __psp_device_get_locked(ops, skb, info, true);
}
int psp_device_get_locked(const struct genl_split_ops *ops,
struct sk_buff *skb, struct genl_info *info)
{
return __psp_device_get_locked(ops, skb, info, false);
}
/*
* Non-admin version of psp_device_get_locked() + psp_attach_netdev_notifier()
* only used for dev-assoc.
*/
int psp_device_get_locked_dev_assoc(const struct genl_split_ops *ops,
struct sk_buff *skb, struct genl_info *info)
{
int err;
err = psp_attach_netdev_notifier();
if (err)
return err;
return __psp_device_get_locked(ops, skb, info, false);
}
static struct net *psp_nl_resolve_assoc_dev_ns(struct psp_dev *psd,
struct genl_info *info)
{
struct net *net;
int nsid;
if (GENL_REQ_ATTR_CHECK(info, PSP_A_DEV_IFINDEX))
return ERR_PTR(-EINVAL);
if (info->attrs[PSP_A_DEV_NSID]) {
/* Only callers in the main netns may specify nsid */
if (dev_net(psd->main_netdev) != genl_info_net(info)) {
NL_SET_BAD_ATTR(info->extack,
info->attrs[PSP_A_DEV_NSID]);
return ERR_PTR(-EPERM);
}
nsid = nla_get_s32(info->attrs[PSP_A_DEV_NSID]);
net = get_net_ns_by_id(genl_info_net(info), nsid);
if (!net) {
NL_SET_BAD_ATTR(info->extack,
info->attrs[PSP_A_DEV_NSID]);
return ERR_PTR(-EINVAL);
}
} else {
net = get_net(genl_info_net(info));
}
return net;
}
void
psp_device_unlock(const struct genl_split_ops *ops, struct sk_buff *skb,
struct genl_info *info)
{
struct socket *socket = info->user_ptr[1];
struct psp_dev *psd = info->user_ptr[0];
mutex_unlock(&psd->lock);
if (socket)
sockfd_put(socket);
}
bool psp_has_assoc_dev_in_ns(struct psp_dev *psd, struct net *net)
{
struct psp_assoc_dev *entry;
list_for_each_entry(entry, &psd->assoc_dev_list, dev_list) {
if (dev_net(entry->assoc_dev) == net)
return true;
}
return false;
}
static int psp_nl_fill_assoc_dev_list(struct psp_dev *psd, struct sk_buff *rsp,
struct net *cur_net,
struct net *filter_net)
{
struct psp_assoc_dev *entry;
struct net *dev_net_ns;
struct nlattr *nest;
int nsid;
list_for_each_entry(entry, &psd->assoc_dev_list, dev_list) {
dev_net_ns = dev_net(entry->assoc_dev);
if (filter_net && dev_net_ns != filter_net)
continue;
/* When filtering by namespace, all devices are in the caller's
* namespace so nsid is always NETNSA_NSID_NOT_ASSIGNED (-1).
* Otherwise, calculate the nsid relative to cur_net.
*/
nsid = filter_net ? NETNSA_NSID_NOT_ASSIGNED :
peernet2id_alloc(cur_net, dev_net_ns,
GFP_KERNEL);
nest = nla_nest_start(rsp, PSP_A_DEV_ASSOC_LIST);
if (!nest)
return -EMSGSIZE;
if (nla_put_u32(rsp, PSP_A_ASSOC_DEV_INFO_IFINDEX,
entry->assoc_dev->ifindex) ||
nla_put_s32(rsp, PSP_A_ASSOC_DEV_INFO_NSID, nsid)) {
nla_nest_cancel(rsp, nest);
return -EMSGSIZE;
}
nla_nest_end(rsp, nest);
}
return 0;
}
static int
psp_nl_dev_fill(struct psp_dev *psd, struct sk_buff *rsp,
const struct genl_info *info)
{
struct net *cur_net;
void *hdr;
int err;
cur_net = genl_info_net(info);
hdr = genlmsg_iput(rsp, info);
if (!hdr)
return -EMSGSIZE;
if (nla_put_u32(rsp, PSP_A_DEV_ID, psd->id) ||
nla_put_u32(rsp, PSP_A_DEV_IFINDEX, psd->main_netdev->ifindex) ||
nla_put_u32(rsp, PSP_A_DEV_PSP_VERSIONS_CAP, psd->caps->versions) ||
nla_put_u32(rsp, PSP_A_DEV_PSP_VERSIONS_ENA, psd->config.versions))
goto err_cancel_msg;
if (cur_net == dev_net(psd->main_netdev)) {
/* Primary device - dump assoc list */
err = psp_nl_fill_assoc_dev_list(psd, rsp, cur_net, NULL);
if (err)
goto err_cancel_msg;
} else {
/* In netns: set by-association flag and dump filtered
* assoc list containing only devices in cur_net
*/
if (nla_put_flag(rsp, PSP_A_DEV_BY_ASSOCIATION))
goto err_cancel_msg;
err = psp_nl_fill_assoc_dev_list(psd, rsp, cur_net, cur_net);
if (err)
goto err_cancel_msg;
}
genlmsg_end(rsp, hdr);
return 0;
err_cancel_msg:
genlmsg_cancel(rsp, hdr);
return -EMSGSIZE;
}
static struct sk_buff *psp_nl_build_dev_ntf(struct psp_dev *psd,
struct net *net, void *ctx)
{
u32 cmd = *(u32 *)ctx;
struct genl_info info;
struct sk_buff *ntf;
if (!genl_has_listeners(&psp_nl_family, net, PSP_NLGRP_MGMT))
return NULL;
ntf = genlmsg_new(GENLMSG_DEFAULT_SIZE, GFP_KERNEL);
if (!ntf)
return NULL;
genl_info_init_ntf(&info, &psp_nl_family, cmd);
genl_info_net_set(&info, net);
if (psp_nl_dev_fill(psd, ntf, &info)) {
nlmsg_free(ntf);
return NULL;
}
return ntf;
}
void psp_nl_notify_dev(struct psp_dev *psd, u32 cmd)
{
psp_nl_multicast_per_ns(psd, PSP_NLGRP_MGMT,
psp_nl_build_dev_ntf, &cmd);
}
int psp_nl_dev_get_doit(struct sk_buff *req, struct genl_info *info)
{
struct psp_dev *psd = info->user_ptr[0];
struct sk_buff *rsp;
int err;
rsp = genlmsg_new(GENLMSG_DEFAULT_SIZE, GFP_KERNEL);
if (!rsp)
return -ENOMEM;
err = psp_nl_dev_fill(psd, rsp, info);
if (err)
goto err_free_msg;
return genlmsg_reply(rsp, info);
err_free_msg:
nlmsg_free(rsp);
return err;
}
static int
psp_nl_dev_get_dumpit_one(struct sk_buff *rsp, struct netlink_callback *cb,
struct psp_dev *psd)
{
if (psp_dev_check_access(psd, sock_net(rsp->sk), false))
return 0;
return psp_nl_dev_fill(psd, rsp, genl_info_dump(cb));
}
int psp_nl_dev_get_dumpit(struct sk_buff *rsp, struct netlink_callback *cb)
{
struct psp_dev *psd;
int err = 0;
mutex_lock(&psp_devs_lock);
xa_for_each_start(&psp_devs, cb->args[0], psd, cb->args[0]) {
mutex_lock(&psd->lock);
err = psp_nl_dev_get_dumpit_one(rsp, cb, psd);
mutex_unlock(&psd->lock);
if (err)
break;
}
mutex_unlock(&psp_devs_lock);
return err;
}
int psp_nl_dev_set_doit(struct sk_buff *skb, struct genl_info *info)
{
struct psp_dev *psd = info->user_ptr[0];
struct psp_dev_config new_config;
struct sk_buff *rsp;
int err;
memcpy(&new_config, &psd->config, sizeof(new_config));
if (info->attrs[PSP_A_DEV_PSP_VERSIONS_ENA]) {
new_config.versions =
nla_get_u32(info->attrs[PSP_A_DEV_PSP_VERSIONS_ENA]);
if (new_config.versions & ~psd->caps->versions) {
NL_SET_ERR_MSG(info->extack, "Requested PSP versions not supported by the device");
return -EINVAL;
}
} else {
NL_SET_ERR_MSG(info->extack, "No settings present");
return -EINVAL;
}
rsp = psp_nl_reply_new(info);
if (!rsp)
return -ENOMEM;
if (memcmp(&new_config, &psd->config, sizeof(new_config))) {
err = psd->ops->set_config(psd, &new_config, info->extack);
if (err)
goto err_free_rsp;
memcpy(&psd->config, &new_config, sizeof(new_config));
}
psp_nl_notify_dev(psd, PSP_CMD_DEV_CHANGE_NTF);
return psp_nl_reply_send(rsp, info);
err_free_rsp:
nlmsg_free(rsp);
return err;
}
int psp_nl_key_rotate_doit(struct sk_buff *skb, struct genl_info *info)
{
struct psp_dev *psd = info->user_ptr[0];
struct genl_info ntf_info;
struct sk_buff *ntf, *rsp;
u8 prev_gen;
int err;
rsp = psp_nl_reply_new(info);
if (!rsp)
return -ENOMEM;
genl_info_init_ntf(&ntf_info, &psp_nl_family, PSP_CMD_KEY_ROTATE_NTF);
ntf = psp_nl_reply_new(&ntf_info);
if (!ntf) {
err = -ENOMEM;
goto err_free_rsp;
}
if (nla_put_u32(rsp, PSP_A_DEV_ID, psd->id) ||
nla_put_u32(ntf, PSP_A_DEV_ID, psd->id)) {
err = -EMSGSIZE;
goto err_free_ntf;
}
/* suggest the next gen number, driver can override */
prev_gen = psd->generation;
psd->generation = (prev_gen + 1) & PSP_GEN_VALID_MASK;
err = psd->ops->key_rotate(psd, info->extack);
if (err)
goto err_free_ntf;
WARN_ON_ONCE((psd->generation && psd->generation == prev_gen) ||
psd->generation & ~PSP_GEN_VALID_MASK);
psp_assocs_key_rotated(psd);
psd->stats.rotations++;
nlmsg_end(ntf, (struct nlmsghdr *)ntf->data);
psp_nl_multicast_all_ns(psd, ntf, PSP_NLGRP_USE);
return psp_nl_reply_send(rsp, info);
err_free_ntf:
nlmsg_free(ntf);
err_free_rsp:
nlmsg_free(rsp);
return err;
}
int psp_nl_dev_assoc_doit(struct sk_buff *skb, struct genl_info *info)
{
struct psp_dev *psd = info->user_ptr[0];
struct psp_assoc_dev *psp_assoc_dev;
struct net_device *assoc_dev;
struct sk_buff *rsp;
u32 assoc_ifindex;
struct net *net;
int err;
if (psd->assoc_dev_cnt >= PSP_ASSOC_DEV_MAX) {
NL_SET_ERR_MSG(info->extack,
"Maximum number of associated devices reached");
return -ENOSPC;
}
net = psp_nl_resolve_assoc_dev_ns(psd, info);
if (IS_ERR(net))
return PTR_ERR(net);
psp_assoc_dev = kzalloc_obj(*psp_assoc_dev);
if (!psp_assoc_dev) {
err = -ENOMEM;
goto err_put_net;
}
assoc_ifindex = nla_get_u32(info->attrs[PSP_A_DEV_IFINDEX]);
assoc_dev = netdev_get_by_index(net, assoc_ifindex,
&psp_assoc_dev->dev_tracker,
GFP_KERNEL);
if (!assoc_dev) {
NL_SET_BAD_ATTR(info->extack, info->attrs[PSP_A_DEV_IFINDEX]);
err = -ENODEV;
goto err_free_assoc;
}
/* Check if device is already associated with a PSP device */
if (cmpxchg(&assoc_dev->psp_dev, NULL, RCU_INITIALIZER(psd))) {
NL_SET_ERR_MSG(info->extack,
"Device already associated with a PSP device");
err = -EBUSY;
goto err_put_dev;
}
psp_assoc_dev->assoc_dev = assoc_dev;
/* Check for race with NETDEV_UNREGISTER. The cmpxchg above is a
* full barrier, and the unregister path has synchronize_net()
* between setting NETREG_UNREGISTERING and reading psp_dev in the
* notifier. So at least one side would do the clean-up if we are in
* the middle of unregitering assoc_dev.
* And the clean-up is serialized by psd->lock.
*/
if (READ_ONCE(assoc_dev->reg_state) != NETREG_REGISTERED) {
err = -ENODEV;
goto err_clean_ptr;
}
rsp = psp_nl_reply_new(info);
if (!rsp) {
err = -ENOMEM;
goto err_clean_ptr;
}
list_add_tail(&psp_assoc_dev->dev_list, &psd->assoc_dev_list);
psd->assoc_dev_cnt++;
put_net(net);
psp_nl_notify_dev(psd, PSP_CMD_DEV_CHANGE_NTF);
return psp_nl_reply_send(rsp, info);
err_clean_ptr:
rcu_assign_pointer(assoc_dev->psp_dev, NULL);
err_put_dev:
netdev_put(assoc_dev, &psp_assoc_dev->dev_tracker);
err_free_assoc:
kfree(psp_assoc_dev);
err_put_net:
put_net(net);
return err;
}
int psp_nl_dev_disassoc_doit(struct sk_buff *skb, struct genl_info *info)
{
struct psp_assoc_dev *entry, *found = NULL;
struct psp_dev *psd = info->user_ptr[0];
struct sk_buff *rsp;
u32 assoc_ifindex;
struct net *net;
net = psp_nl_resolve_assoc_dev_ns(psd, info);
if (IS_ERR(net))
return PTR_ERR(net);
assoc_ifindex = nla_get_u32(info->attrs[PSP_A_DEV_IFINDEX]);
/* Search the association list by ifindex and netns */
list_for_each_entry(entry, &psd->assoc_dev_list, dev_list) {
if (entry->assoc_dev->ifindex == assoc_ifindex &&
dev_net(entry->assoc_dev) == net) {
found = entry;
break;
}
}
if (!found) {
put_net(net);
NL_SET_BAD_ATTR(info->extack, info->attrs[PSP_A_DEV_IFINDEX]);
return -ENODEV;
}
rsp = psp_nl_reply_new(info);
if (!rsp) {
put_net(net);
return -ENOMEM;
}
put_net(net);
/* Notify before removal so listeners in the disassociated namespace
* still receive the notification.
*/
psp_nl_notify_dev(psd, PSP_CMD_DEV_CHANGE_NTF);
/* Remove from the association list */
list_del(&found->dev_list);
psd->assoc_dev_cnt--;
rcu_assign_pointer(found->assoc_dev->psp_dev, NULL);
netdev_put(found->assoc_dev, &found->dev_tracker);
kfree(found);
return psp_nl_reply_send(rsp, info);
}
/* Key etc. */
int psp_assoc_device_get_locked(const struct genl_split_ops *ops,
struct sk_buff *skb, struct genl_info *info)
{
struct socket *socket;
struct psp_dev *psd;
struct nlattr *id;
int fd, err;
if (GENL_REQ_ATTR_CHECK(info, PSP_A_ASSOC_SOCK_FD))
return -EINVAL;
fd = nla_get_u32(info->attrs[PSP_A_ASSOC_SOCK_FD]);
socket = sockfd_lookup(fd, &err);
if (!socket)
return err;
if (!sk_is_tcp(socket->sk)) {
NL_SET_ERR_MSG_ATTR(info->extack,
info->attrs[PSP_A_ASSOC_SOCK_FD],
"Unsupported socket family and type");
err = -EOPNOTSUPP;
goto err_sock_put;
}
psd = psp_dev_get_for_sock(socket->sk);
if (psd) {
/* Extra care needed here, psp_dev_get_for_sock() only gives
* us access to struct psp_dev's memory, which is quite weak.
*/
mutex_lock(&psd->lock);
if (!psp_dev_is_registered(psd) ||
psp_dev_check_access(psd, genl_info_net(info), false)) {
mutex_unlock(&psd->lock);
psp_dev_put(psd);
psd = NULL;
}
}
if (!psd && GENL_REQ_ATTR_CHECK(info, PSP_A_ASSOC_DEV_ID)) {
err = -EINVAL;
goto err_sock_put;
}
id = info->attrs[PSP_A_ASSOC_DEV_ID];
if (psd) {
if (id && psd->id != nla_get_u32(id)) {
mutex_unlock(&psd->lock);
NL_SET_ERR_MSG_ATTR(info->extack, id,
"Device id vs socket mismatch");
err = -EINVAL;
goto err_psd_put;
}
psp_dev_put(psd);
} else {
psd = psp_device_get_and_lock(genl_info_net(info), id, false);
if (IS_ERR(psd)) {
err = PTR_ERR(psd);
goto err_sock_put;
}
}
info->user_ptr[0] = psd;
info->user_ptr[1] = socket;
return 0;
err_psd_put:
psp_dev_put(psd);
err_sock_put:
sockfd_put(socket);
return err;
}
static int
psp_nl_parse_key(struct genl_info *info, u32 attr, struct psp_key_parsed *key,
unsigned int key_sz)
{
struct nlattr *nest = info->attrs[attr];
struct nlattr *tb[PSP_A_KEYS_SPI + 1];
u32 spi;
int err;
err = nla_parse_nested(tb, ARRAY_SIZE(tb) - 1, nest,
psp_keys_nl_policy, info->extack);
if (err)
return err;
if (NL_REQ_ATTR_CHECK(info->extack, nest, tb, PSP_A_KEYS_KEY) ||
NL_REQ_ATTR_CHECK(info->extack, nest, tb, PSP_A_KEYS_SPI))
return -EINVAL;
if (nla_len(tb[PSP_A_KEYS_KEY]) != key_sz) {
NL_SET_ERR_MSG_ATTR(info->extack, tb[PSP_A_KEYS_KEY],
"incorrect key length");
return -EINVAL;
}
spi = nla_get_u32(tb[PSP_A_KEYS_SPI]);
if (!(spi & PSP_SPI_KEY_ID)) {
NL_SET_ERR_MSG_ATTR(info->extack, tb[PSP_A_KEYS_KEY],
"invalid SPI: lower 31b must be non-zero");
return -EINVAL;
}
key->spi = cpu_to_be32(spi);
memcpy(key->key, nla_data(tb[PSP_A_KEYS_KEY]), key_sz);
return 0;
}
static int
psp_nl_put_key(struct sk_buff *skb, u32 attr, u32 version,
struct psp_key_parsed *key)
{
int key_sz = psp_key_size(version);
void *nest;
nest = nla_nest_start(skb, attr);
if (nla_put_u32(skb, PSP_A_KEYS_SPI, be32_to_cpu(key->spi)) ||
nla_put(skb, PSP_A_KEYS_KEY, key_sz, key->key)) {
nla_nest_cancel(skb, nest);
return -EMSGSIZE;
}
nla_nest_end(skb, nest);
return 0;
}
int psp_nl_rx_assoc_doit(struct sk_buff *skb, struct genl_info *info)
{
struct socket *socket = info->user_ptr[1];
struct psp_dev *psd = info->user_ptr[0];
struct psp_key_parsed key;
struct psp_assoc *pas;
struct sk_buff *rsp;
u32 version;
int err;
if (GENL_REQ_ATTR_CHECK(info, PSP_A_ASSOC_VERSION))
return -EINVAL;
version = nla_get_u32(info->attrs[PSP_A_ASSOC_VERSION]);
if (!(psd->caps->versions & (1 << version))) {
NL_SET_BAD_ATTR(info->extack, info->attrs[PSP_A_ASSOC_VERSION]);
return -EOPNOTSUPP;
}
rsp = psp_nl_reply_new(info);
if (!rsp)
return -ENOMEM;
pas = psp_assoc_create(psd);
if (!pas) {
err = -ENOMEM;
goto err_free_rsp;
}
pas->version = version;
err = psd->ops->rx_spi_alloc(psd, version, &key, info->extack);
if (err)
goto err_free_pas;
if (nla_put_u32(rsp, PSP_A_ASSOC_DEV_ID, psd->id) ||
psp_nl_put_key(rsp, PSP_A_ASSOC_RX_KEY, version, &key)) {
err = -EMSGSIZE;
goto err_free_pas;
}
err = psp_sock_assoc_set_rx(socket->sk, pas, &key, info->extack);
if (err) {
NL_SET_BAD_ATTR(info->extack, info->attrs[PSP_A_ASSOC_SOCK_FD]);
goto err_free_pas;
}
psp_assoc_put(pas);
return psp_nl_reply_send(rsp, info);
err_free_pas:
psp_assoc_put(pas);
err_free_rsp:
nlmsg_free(rsp);
return err;
}
int psp_nl_tx_assoc_doit(struct sk_buff *skb, struct genl_info *info)
{
struct socket *socket = info->user_ptr[1];
struct psp_dev *psd = info->user_ptr[0];
struct psp_key_parsed key;
struct sk_buff *rsp;
unsigned int key_sz;
u32 version;
int err;
if (GENL_REQ_ATTR_CHECK(info, PSP_A_ASSOC_VERSION) ||
GENL_REQ_ATTR_CHECK(info, PSP_A_ASSOC_TX_KEY))
return -EINVAL;
version = nla_get_u32(info->attrs[PSP_A_ASSOC_VERSION]);
if (!(psd->caps->versions & (1 << version))) {
NL_SET_BAD_ATTR(info->extack, info->attrs[PSP_A_ASSOC_VERSION]);
return -EOPNOTSUPP;
}
key_sz = psp_key_size(version);
if (!key_sz)
return -EINVAL;
err = psp_nl_parse_key(info, PSP_A_ASSOC_TX_KEY, &key, key_sz);
if (err < 0)
return err;
rsp = psp_nl_reply_new(info);
if (!rsp)
return -ENOMEM;
err = psp_sock_assoc_set_tx(socket->sk, psd, version, &key,
info->extack);
if (err)
goto err_free_msg;
return psp_nl_reply_send(rsp, info);
err_free_msg:
nlmsg_free(rsp);
return err;
}
static int
psp_nl_stats_fill(struct psp_dev *psd, struct sk_buff *rsp,
const struct genl_info *info)
{
unsigned int required_cnt = sizeof(struct psp_dev_stats) / sizeof(u64);
struct psp_dev_stats stats;
void *hdr;
int i;
memset(&stats, 0xff, sizeof(stats));
psd->ops->get_stats(psd, &stats);
for (i = 0; i < required_cnt; i++)
if (WARN_ON_ONCE(stats.required[i] == ETHTOOL_STAT_NOT_SET))
return -EOPNOTSUPP;
hdr = genlmsg_iput(rsp, info);
if (!hdr)
return -EMSGSIZE;
if (nla_put_u32(rsp, PSP_A_STATS_DEV_ID, psd->id) ||
nla_put_uint(rsp, PSP_A_STATS_KEY_ROTATIONS,
psd->stats.rotations) ||
nla_put_uint(rsp, PSP_A_STATS_STALE_EVENTS, psd->stats.stales) ||
nla_put_uint(rsp, PSP_A_STATS_RX_PACKETS, stats.rx_packets) ||
nla_put_uint(rsp, PSP_A_STATS_RX_BYTES, stats.rx_bytes) ||
nla_put_uint(rsp, PSP_A_STATS_RX_AUTH_FAIL, stats.rx_auth_fail) ||
nla_put_uint(rsp, PSP_A_STATS_RX_ERROR, stats.rx_error) ||
nla_put_uint(rsp, PSP_A_STATS_RX_BAD, stats.rx_bad) ||
nla_put_uint(rsp, PSP_A_STATS_TX_PACKETS, stats.tx_packets) ||
nla_put_uint(rsp, PSP_A_STATS_TX_BYTES, stats.tx_bytes) ||
nla_put_uint(rsp, PSP_A_STATS_TX_ERROR, stats.tx_error))
goto err_cancel_msg;
genlmsg_end(rsp, hdr);
return 0;
err_cancel_msg:
genlmsg_cancel(rsp, hdr);
return -EMSGSIZE;
}
int psp_nl_get_stats_doit(struct sk_buff *skb, struct genl_info *info)
{
struct psp_dev *psd = info->user_ptr[0];
struct sk_buff *rsp;
int err;
rsp = genlmsg_new(GENLMSG_DEFAULT_SIZE, GFP_KERNEL);
if (!rsp)
return -ENOMEM;
err = psp_nl_stats_fill(psd, rsp, info);
if (err)
goto err_free_msg;
return genlmsg_reply(rsp, info);
err_free_msg:
nlmsg_free(rsp);
return err;
}
static int
psp_nl_stats_get_dumpit_one(struct sk_buff *rsp, struct netlink_callback *cb,
struct psp_dev *psd)
{
if (psp_dev_check_access(psd, sock_net(rsp->sk), false))
return 0;
return psp_nl_stats_fill(psd, rsp, genl_info_dump(cb));
}
int psp_nl_get_stats_dumpit(struct sk_buff *rsp, struct netlink_callback *cb)
{
struct psp_dev *psd;
int err = 0;
mutex_lock(&psp_devs_lock);
xa_for_each_start(&psp_devs, cb->args[0], psd, cb->args[0]) {
mutex_lock(&psd->lock);
err = psp_nl_stats_get_dumpit_one(rsp, cb, psd);
mutex_unlock(&psd->lock);
if (err)
break;
}
mutex_unlock(&psp_devs_lock);
return err;
}
+293
View File
@@ -0,0 +1,293 @@
// SPDX-License-Identifier: GPL-2.0-only
#include <linux/file.h>
#include <linux/net.h>
#include <linux/rcupdate.h>
#include <linux/tcp.h>
#include <net/ip.h>
#include <net/psp.h>
#include "psp.h"
struct psp_dev *psp_dev_get_for_sock(struct sock *sk)
{
struct psp_dev *psd = NULL;
struct dst_entry *dst;
rcu_read_lock();
dst = __sk_dst_get(sk);
if (dst) {
psd = rcu_dereference(dst_dev_rcu(dst)->psp_dev);
if (psd && !psp_dev_tryget(psd))
psd = NULL;
}
rcu_read_unlock();
return psd;
}
static struct sk_buff *
psp_validate_xmit(struct sock *sk, struct net_device *dev, struct sk_buff *skb)
{
struct psp_assoc *pas;
bool good;
rcu_read_lock();
pas = psp_skb_get_assoc_rcu(skb);
good = !pas || rcu_access_pointer(dev->psp_dev) == pas->psd;
rcu_read_unlock();
if (!good) {
sk_skb_reason_drop(sk, skb, SKB_DROP_REASON_PSP_OUTPUT);
return NULL;
}
return skb;
}
struct psp_assoc *psp_assoc_create(struct psp_dev *psd)
{
struct psp_assoc *pas;
lockdep_assert_held(&psd->lock);
pas = kzalloc_flex(*pas, drv_data, psd->caps->assoc_drv_spc,
GFP_KERNEL_ACCOUNT);
if (!pas)
return NULL;
pas->psd = psd;
pas->dev_id = psd->id;
pas->generation = psd->generation;
psp_dev_get(psd);
refcount_set(&pas->refcnt, 1);
list_add_tail(&pas->assocs_list, &psd->active_assocs);
return pas;
}
static struct psp_assoc *psp_assoc_dummy(struct psp_assoc *pas)
{
struct psp_dev *psd = pas->psd;
size_t sz;
lockdep_assert_held(&psd->lock);
sz = struct_size(pas, drv_data, psd->caps->assoc_drv_spc);
return kmemdup(pas, sz, GFP_KERNEL);
}
static int psp_dev_tx_key_add(struct psp_dev *psd, struct psp_assoc *pas,
struct netlink_ext_ack *extack)
{
return psd->ops->tx_key_add(psd, pas, extack);
}
void psp_dev_tx_key_del(struct psp_dev *psd, struct psp_assoc *pas)
{
if (pas->tx.spi)
psd->ops->tx_key_del(psd, pas);
list_del(&pas->assocs_list);
}
static void psp_assoc_free(struct work_struct *work)
{
struct psp_assoc *pas = container_of(work, struct psp_assoc, work);
struct psp_dev *psd = pas->psd;
mutex_lock(&psd->lock);
if (psd->ops)
psp_dev_tx_key_del(psd, pas);
mutex_unlock(&psd->lock);
psp_dev_put(psd);
kfree(pas);
}
static void psp_assoc_free_queue(struct rcu_head *head)
{
struct psp_assoc *pas = container_of(head, struct psp_assoc, rcu);
INIT_WORK(&pas->work, psp_assoc_free);
schedule_work(&pas->work);
}
/**
* psp_assoc_put() - release a reference on a PSP association
* @pas: association to release
*/
void psp_assoc_put(struct psp_assoc *pas)
{
if (pas && refcount_dec_and_test(&pas->refcnt))
call_rcu(&pas->rcu, psp_assoc_free_queue);
}
void psp_sk_assoc_free(struct sock *sk)
{
struct psp_assoc *pas = rcu_dereference_protected(sk->psp_assoc, 1);
rcu_assign_pointer(sk->psp_assoc, NULL);
psp_assoc_put(pas);
}
int psp_sock_assoc_set_rx(struct sock *sk, struct psp_assoc *pas,
struct psp_key_parsed *key,
struct netlink_ext_ack *extack)
{
int err;
memcpy(&pas->rx, key, sizeof(*key));
lock_sock(sk);
if (psp_sk_assoc(sk)) {
NL_SET_ERR_MSG(extack, "Socket already has PSP state");
err = -EBUSY;
goto exit_unlock;
}
refcount_inc(&pas->refcnt);
rcu_assign_pointer(sk->psp_assoc, pas);
err = 0;
exit_unlock:
release_sock(sk);
return err;
}
static int psp_sock_recv_queue_check(struct sock *sk, struct psp_assoc *pas)
{
struct psp_skb_ext *pse;
struct sk_buff *skb;
skb_rbtree_walk(skb, &tcp_sk(sk)->out_of_order_queue) {
pse = skb_ext_find(skb, SKB_EXT_PSP);
if (!psp_pse_matches_pas(pse, pas))
return -EBUSY;
}
skb_queue_walk(&sk->sk_receive_queue, skb) {
pse = skb_ext_find(skb, SKB_EXT_PSP);
if (!psp_pse_matches_pas(pse, pas))
return -EBUSY;
}
return 0;
}
int psp_sock_assoc_set_tx(struct sock *sk, struct psp_dev *psd,
u32 version, struct psp_key_parsed *key,
struct netlink_ext_ack *extack)
{
struct inet_connection_sock *icsk;
struct psp_assoc *pas, *dummy;
int err;
lock_sock(sk);
pas = psp_sk_assoc(sk);
if (!pas) {
NL_SET_ERR_MSG(extack, "Socket has no Rx key");
err = -EINVAL;
goto exit_unlock;
}
if (pas->psd != psd) {
NL_SET_ERR_MSG(extack, "Rx key from different device");
err = -EINVAL;
goto exit_unlock;
}
if (pas->version != version) {
NL_SET_ERR_MSG(extack,
"PSP version mismatch with existing state");
err = -EINVAL;
goto exit_unlock;
}
if (pas->tx.spi) {
NL_SET_ERR_MSG(extack, "Tx key already set");
err = -EBUSY;
goto exit_unlock;
}
err = psp_sock_recv_queue_check(sk, pas);
if (err) {
NL_SET_ERR_MSG(extack, "Socket has incompatible segments already in the recv queue");
goto exit_unlock;
}
/* Pass a fake association to drivers to make sure they don't
* try to store pointers to it. For re-keying we'll need to
* re-allocate the assoc structures.
*/
dummy = psp_assoc_dummy(pas);
if (!dummy) {
err = -ENOMEM;
goto exit_unlock;
}
memcpy(&dummy->tx, key, sizeof(*key));
err = psp_dev_tx_key_add(psd, dummy, extack);
if (err)
goto exit_free_dummy;
memcpy(pas->drv_data, dummy->drv_data, psd->caps->assoc_drv_spc);
memcpy(&pas->tx, key, sizeof(*key));
WRITE_ONCE(sk->sk_validate_xmit_skb, psp_validate_xmit);
tcp_write_collapse_fence(sk);
pas->upgrade_seq = tcp_sk(sk)->rcv_nxt;
icsk = inet_csk(sk);
icsk->icsk_ext_hdr_len += psp_sk_overhead(sk);
icsk->icsk_sync_mss(sk, icsk->icsk_pmtu_cookie);
exit_free_dummy:
kfree(dummy);
exit_unlock:
release_sock(sk);
return err;
}
void psp_assocs_key_rotated(struct psp_dev *psd)
{
struct psp_assoc *pas, *next;
/* Mark the stale associations as invalid, they will no longer
* be able to Rx any traffic.
*/
list_for_each_entry_safe(pas, next, &psd->prev_assocs, assocs_list) {
pas->generation |= ~PSP_GEN_VALID_MASK;
psd->stats.stales++;
}
list_splice_init(&psd->prev_assocs, &psd->stale_assocs);
list_splice_init(&psd->active_assocs, &psd->prev_assocs);
/* TODO: we should inform the sockets that got shut down */
}
void psp_twsk_init(struct inet_timewait_sock *tw, const struct sock *sk)
{
struct psp_assoc *pas = psp_sk_assoc(sk);
if (pas)
refcount_inc(&pas->refcnt);
rcu_assign_pointer(tw->psp_assoc, pas);
tw->tw_validate_xmit_skb = psp_validate_xmit;
}
void psp_twsk_assoc_free(struct inet_timewait_sock *tw)
{
struct psp_assoc *pas = rcu_dereference_protected(tw->psp_assoc, 1);
rcu_assign_pointer(tw->psp_assoc, NULL);
psp_assoc_put(pas);
}
void psp_reply_set_decrypted(const struct sock *sk, struct sk_buff *skb)
{
struct psp_assoc *pas;
rcu_read_lock();
pas = psp_sk_get_assoc_rcu(sk);
if (pas && pas->tx.spi)
skb->decrypted = 1;
rcu_read_unlock();
}