Skip to content

Commit

Permalink
tcp: authopt: Hook into tcp core
Browse files Browse the repository at this point in the history
The tcp_authopt features exposes a minimal interface to the rest of the
TCP stack. Only a few functions are exposed and if the feature is
disabled they return neutral values, avoiding ifdefs in the rest of the
code.

Add calls into tcp authopt from send, receive and accept code.

Signed-off-by: Leonard Crestez <cdleonard@gmail.com>
  • Loading branch information
cdleonard authored and intel-lab-lkp committed Aug 24, 2021
1 parent 8666d84 commit ed73860
Show file tree
Hide file tree
Showing 7 changed files with 401 additions and 1 deletion.
56 changes: 56 additions & 0 deletions include/net/tcp_authopt.h
Expand Up @@ -23,6 +23,7 @@ struct tcp_authopt_key_info {
u8 alg_id;
u8 keylen;
u8 key[TCP_AUTHOPT_MAXKEYLEN];
u8 maclen;
struct sockaddr_storage addr;
struct tcp_authopt_alg_imp *alg;
};
Expand All @@ -43,11 +44,49 @@ struct tcp_authopt_info {
};

#ifdef CONFIG_TCP_AUTHOPT
struct tcp_authopt_key_info *tcp_authopt_select_key(const struct sock *sk,
const struct sock *addr_sk,
u8 *rnextkeyid);
void tcp_authopt_clear(struct sock *sk);
int tcp_set_authopt(struct sock *sk, sockptr_t optval, unsigned int optlen);
int tcp_get_authopt_val(struct sock *sk, struct tcp_authopt *key);
int tcp_set_authopt_key(struct sock *sk, sockptr_t optval, unsigned int optlen);
int tcp_authopt_hash(
char *hash_location,
struct tcp_authopt_key_info *key,
struct sock *sk, struct sk_buff *skb);
int __tcp_authopt_openreq(struct sock *newsk, const struct sock *oldsk, struct request_sock *req);
static inline int tcp_authopt_openreq(
struct sock *newsk,
const struct sock *oldsk,
struct request_sock *req)
{
if (!rcu_dereference(tcp_sk(oldsk)->authopt_info))
return 0;
else
return __tcp_authopt_openreq(newsk, oldsk, req);
}
int __tcp_authopt_inbound_check(
struct sock *sk,
struct sk_buff *skb,
struct tcp_authopt_info *info);
static inline int tcp_authopt_inbound_check(struct sock *sk, struct sk_buff *skb)
{
struct tcp_authopt_info *info = rcu_dereference(tcp_sk(sk)->authopt_info);

if (info)
return __tcp_authopt_inbound_check(sk, skb, info);
else
return 0;
}
#else
static inline struct tcp_authopt_key_info *tcp_authopt_select_key(
const struct sock *sk,
const struct sock *addr_sk,
u8 *rnextkeyid)
{
return NULL;
}
static inline int tcp_set_authopt(struct sock *sk, sockptr_t optval, unsigned int optlen)
{
return -ENOPROTOOPT;
Expand All @@ -63,6 +102,23 @@ static inline int tcp_set_authopt_key(struct sock *sk, sockptr_t optval, unsigne
{
return -ENOPROTOOPT;
}
static inline int tcp_authopt_hash(
char *hash_location,
struct tcp_authopt_key_info *key,
struct sock *sk, struct sk_buff *skb)
{
return -EINVAL;
}
static inline int tcp_authopt_openreq(struct sock *newsk,
const struct sock *oldsk,
struct request_sock *req)
{
return 0;
}
static inline int tcp_authopt_inbound_check(struct sock *sk, struct sk_buff *skb)
{
return 0;
}
#endif

#endif /* _LINUX_TCP_AUTHOPT_H */
246 changes: 246 additions & 0 deletions net/ipv4/tcp_authopt.c
Expand Up @@ -205,6 +205,67 @@ static struct tcp_authopt_key_info *tcp_authopt_key_lookup_exact(const struct so
return NULL;
}

struct tcp_authopt_key_info *tcp_authopt_lookup_send(struct tcp_authopt_info *info,
const struct sock *addr_sk,
int send_id)
{
struct tcp_authopt_key_info *result = NULL;
struct tcp_authopt_key_info *key;

hlist_for_each_entry_rcu(key, &info->head, node, 0) {
if (send_id >= 0 && key->send_id != send_id)
continue;
if (key->flags & TCP_AUTHOPT_KEY_ADDR_BIND) {
if (addr_sk->sk_family == AF_INET) {
struct sockaddr_in *key_addr = (struct sockaddr_in *)&key->addr;
const struct in_addr *daddr =
(const struct in_addr *)&addr_sk->sk_daddr;

if (WARN_ON(key_addr->sin_family != AF_INET))
continue;
if (memcmp(daddr, &key_addr->sin_addr, sizeof(*daddr)))
continue;
}
if (addr_sk->sk_family == AF_INET6) {
struct sockaddr_in6 *key_addr = (struct sockaddr_in6 *)&key->addr;
const struct in6_addr *daddr = &addr_sk->sk_v6_daddr;

if (WARN_ON(key_addr->sin6_family != AF_INET6))
continue;
if (memcmp(daddr, &key_addr->sin6_addr, sizeof(*daddr)))
continue;
}
}
if (result && net_ratelimit())
pr_warn("ambiguous tcp authentication keys configured for send\n");
result = key;
}

return result;
}

/**
* tcp_authopt_select_key - select key for sending
*
* addr_sk is the sock used for comparing daddr, it is only different from sk in
* the synack case.
*
* Result is protected by RCU and can't be stored, it may only be passed to
* tcp_authopt_hash and only under a single rcu_read_lock.
*/
struct tcp_authopt_key_info *tcp_authopt_select_key(const struct sock *sk,
const struct sock *addr_sk,
u8 *rnextkeyid)
{
struct tcp_authopt_info *info;

info = rcu_dereference(tcp_sk(sk)->authopt_info);
if (!info)
return NULL;

return tcp_authopt_lookup_send(info, addr_sk, -1);
}

static struct tcp_authopt_info *__tcp_authopt_info_get_or_create(struct sock *sk)
{
struct tcp_sock *tp = tcp_sk(sk);
Expand Down Expand Up @@ -389,12 +450,65 @@ int tcp_set_authopt_key(struct sock *sk, sockptr_t optval, unsigned int optlen)
key_info->alg = alg;
key_info->keylen = opt.keylen;
memcpy(key_info->key, opt.key, opt.keylen);
key_info->maclen = alg->maclen;
memcpy(&key_info->addr, &opt.addr, sizeof(key_info->addr));
hlist_add_head_rcu(&key_info->node, &info->head);

return 0;
}

static int tcp_authopt_clone_keys(struct sock *newsk,
const struct sock *oldsk,
struct tcp_authopt_info *new_info,
struct tcp_authopt_info *old_info)
{
struct tcp_authopt_key_info *old_key;
struct tcp_authopt_key_info *new_key;

hlist_for_each_entry_rcu(old_key, &old_info->head, node, lockdep_sock_is_held(sk)) {
new_key = sock_kmalloc(newsk, sizeof(*new_key), GFP_ATOMIC);
if (!new_key)
return -ENOMEM;
memcpy(new_key, old_key, sizeof(*new_key));
tcp_authopt_alg_incref(old_key->alg);
hlist_add_head_rcu(&new_key->node, &new_info->head);
}

return 0;
}

/** Called to create accepted sockets.
*
* Need to copy authopt info from listen socket.
*/
int __tcp_authopt_openreq(struct sock *newsk, const struct sock *oldsk, struct request_sock *req)
{
struct tcp_authopt_info *old_info;
struct tcp_authopt_info *new_info;
int err;

old_info = rcu_dereference(tcp_sk(oldsk)->authopt_info);
if (!old_info)
return 0;

new_info = kmalloc(sizeof(*new_info), GFP_ATOMIC | __GFP_ZERO);
if (!new_info)
return -ENOMEM;

sk_nocaps_add(newsk, NETIF_F_GSO_MASK);
new_info->src_isn = tcp_rsk(req)->snt_isn;
new_info->dst_isn = tcp_rsk(req)->rcv_isn;
INIT_HLIST_HEAD(&new_info->head);
err = tcp_authopt_clone_keys(newsk, oldsk, new_info, old_info);
if (err) {
__tcp_authopt_info_free(newsk, new_info);
return err;
}
rcu_assign_pointer(tcp_sk(newsk)->authopt_info, new_info);

return 0;
}

/* feed traffic key into shash */
static int tcp_authopt_shash_traffic_key(struct shash_desc *desc,
struct sock *sk,
Expand Down Expand Up @@ -817,6 +931,12 @@ static int tcp_authopt_hash_packet(struct crypto_shash *tfm,
return crypto_shash_final(desc, macbuf);
}

/**
* __tcp_authopt_calc_mac - Compute packet MAC using key
*
* @macbuf: output buffer. Must be large enough to fit the digestsize of the
* underlying transform before truncation. Please use TCP_AUTHOPT_MAXMACBUF
*/
int __tcp_authopt_calc_mac(struct sock *sk,
struct sk_buff *skb,
struct tcp_authopt_key_info *key,
Expand Down Expand Up @@ -861,3 +981,129 @@ int __tcp_authopt_calc_mac(struct sock *sk,
tcp_authopt_put_mac_shash(key, mac_tfm);
return err;
}

/**
* tcp_authopt_hash - fill in the mac
*
* The key must come from tcp_authopt_select_key.
*/
int tcp_authopt_hash(char *hash_location,
struct tcp_authopt_key_info *key,
struct sock *sk,
struct sk_buff *skb)
{
/* MAC inside option is truncated to 12 bytes but crypto API needs output
* buffer to be large enough so we use a buffer on the stack.
*/
u8 macbuf[TCP_AUTHOPT_MAXMACBUF];
int err;

if (WARN_ON(key->maclen > sizeof(macbuf)))
return -ENOBUFS;

err = __tcp_authopt_calc_mac(sk, skb, key, false, macbuf);
if (err) {
/* If mac calculation fails and caller doesn't handle the error
* try to make it obvious inside the packet.
*/
memset(hash_location, 0, key->maclen);
return err;
}
memcpy(hash_location, macbuf, key->maclen);

return 0;
}

static struct tcp_authopt_key_info *tcp_authopt_lookup_recv(struct sock *sk,
struct sk_buff *skb,
struct tcp_authopt_info *info,
int recv_id)
{
struct tcp_authopt_key_info *result = NULL;
struct tcp_authopt_key_info *key;

/* multiple matches will cause occasional failures */
hlist_for_each_entry_rcu(key, &info->head, node, 0) {
if (recv_id >= 0 && key->recv_id != recv_id)
continue;
if (key->flags & TCP_AUTHOPT_KEY_ADDR_BIND) {
if (sk->sk_family == AF_INET) {
struct sockaddr_in *key_addr = (struct sockaddr_in *)&key->addr;
struct iphdr *iph = (struct iphdr *)skb_network_header(skb);

if (WARN_ON(key_addr->sin_family != AF_INET))
continue;
if (WARN_ON(iph->version != 4))
continue;
if (memcmp(&iph->saddr, &key_addr->sin_addr, sizeof(iph->saddr)))
continue;
}
if (sk->sk_family == AF_INET6) {
struct sockaddr_in6 *key_addr = (struct sockaddr_in6 *)&key->addr;
struct ipv6hdr *iph = (struct ipv6hdr *)skb_network_header(skb);

if (WARN_ON(key_addr->sin6_family != AF_INET6))
continue;
if (WARN_ON(iph->version != 6))
continue;
if (memcmp(&iph->saddr, &key_addr->sin6_addr, sizeof(iph->saddr)))
continue;
}
}
if (result && net_ratelimit())
pr_warn("ambiguous tcp authentication keys configured for receive\n");
result = key;
}

return result;
}

int __tcp_authopt_inbound_check(struct sock *sk, struct sk_buff *skb, struct tcp_authopt_info *info)
{
struct tcphdr *th = (struct tcphdr *)skb_transport_header(skb);
struct tcphdr_authopt *opt;
struct tcp_authopt_key_info *key;
u8 macbuf[TCP_AUTHOPT_MAXMACBUF];
int err;

opt = (struct tcphdr_authopt *)tcp_authopt_find_option(th);
key = tcp_authopt_lookup_recv(sk, skb, info, opt ? opt->keyid : -1);

/* nothing found or expected */
if (!opt && !key)
return 0;
if (!opt && key) {
net_info_ratelimited("TCP Authentication Missing\n");
return -EINVAL;
}
if (opt && !key) {
/* RFC5925 Section 7.3:
* A TCP-AO implementation MUST allow for configuration of the behavior
* of segments with TCP-AO but that do not match an MKT. The initial
* default of this configuration SHOULD be to silently accept such
* connections.
*/
if (info->flags & TCP_AUTHOPT_FLAG_REJECT_UNEXPECTED) {
net_info_ratelimited("TCP Authentication Unexpected: Rejected\n");
return -EINVAL;
} else {
net_info_ratelimited("TCP Authentication Unexpected: Accepted\n");
return 0;
}
}

/* bad inbound key len */
if (key->maclen + 4 != opt->len)
return -EINVAL;

err = __tcp_authopt_calc_mac(sk, skb, key, true, macbuf);
if (err)
return err;

if (memcmp(macbuf, opt->mac, key->maclen)) {
net_info_ratelimited("TCP Authentication Failed\n");
return -EINVAL;
}

return 0;
}
17 changes: 17 additions & 0 deletions net/ipv4/tcp_input.c
Expand Up @@ -72,6 +72,7 @@
#include <linux/prefetch.h>
#include <net/dst.h>
#include <net/tcp.h>
#include <net/tcp_authopt.h>
#include <net/inet_common.h>
#include <linux/ipsec.h>
#include <asm/unaligned.h>
Expand Down Expand Up @@ -5969,6 +5970,20 @@ void tcp_init_transfer(struct sock *sk, int bpf_op, struct sk_buff *skb)
tcp_init_buffer_space(sk);
}

static void tcp_authopt_finish_connect(struct sock *sk, struct sk_buff *skb)
{
#ifdef CONFIG_TCP_AUTHOPT
struct tcp_authopt_info *info;

info = rcu_dereference_protected(tcp_sk(sk)->authopt_info, lockdep_sock_is_held(sk));
if (!info)
return;

info->src_isn = ntohl(tcp_hdr(skb)->ack_seq) - 1;
info->dst_isn = ntohl(tcp_hdr(skb)->seq);
#endif
}

void tcp_finish_connect(struct sock *sk, struct sk_buff *skb)
{
struct tcp_sock *tp = tcp_sk(sk);
Expand All @@ -5977,6 +5992,8 @@ void tcp_finish_connect(struct sock *sk, struct sk_buff *skb)
tcp_set_state(sk, TCP_ESTABLISHED);
icsk->icsk_ack.lrcvtime = tcp_jiffies32;

tcp_authopt_finish_connect(sk, skb);

if (skb) {
icsk->icsk_af_ops->sk_rx_dst_set(sk, skb);
security_inet_conn_established(sk, skb);
Expand Down

0 comments on commit ed73860

Please sign in to comment.