net/tcp: Add tcp_parse_auth_options()
Introduce a helper that: (1) shares the common code with TCP-MD5 header options parsing (2) looks for hash signature only once for both TCP-MD5 and TCP-AO (3) fails with -EEXIST if any TCP sign option is present twice, see RFC5925 (2.2): ">> A single TCP segment MUST NOT have more than one TCP-AO in its options sequence. When multiple TCP-AOs appear, TCP MUST discard the segment." Co-developed-by: Francesco Ruggeri <fruggeri@arista.com> Signed-off-by: Francesco Ruggeri <fruggeri@arista.com> Co-developed-by: Salam Noureddine <noureddine@arista.com> Signed-off-by: Salam Noureddine <noureddine@arista.com> Signed-off-by: Dmitry Safonov <dima@arista.com> Acked-by: David Ahern <dsahern@kernel.org> Signed-off-by: David S. Miller <davem@davemloft.net>
This commit is contained in:
parent
1e03d32bea
commit
f7dca36fc5
@ -20,6 +20,7 @@
|
||||
FN(IP_NOPROTO) \
|
||||
FN(SOCKET_RCVBUFF) \
|
||||
FN(PROTO_MEM) \
|
||||
FN(TCP_AUTH_HDR) \
|
||||
FN(TCP_MD5NOTFOUND) \
|
||||
FN(TCP_MD5UNEXPECTED) \
|
||||
FN(TCP_MD5FAILURE) \
|
||||
@ -142,6 +143,11 @@ enum skb_drop_reason {
|
||||
* drop out of udp_memory_allocated.
|
||||
*/
|
||||
SKB_DROP_REASON_PROTO_MEM,
|
||||
/**
|
||||
* @SKB_DROP_REASON_TCP_AUTH_HDR: TCP-MD5 or TCP-AO hashes are met
|
||||
* twice or set incorrectly.
|
||||
*/
|
||||
SKB_DROP_REASON_TCP_AUTH_HDR,
|
||||
/**
|
||||
* @SKB_DROP_REASON_TCP_MD5NOTFOUND: no MD5 hash and one expected,
|
||||
* corresponding to LINUX_MIB_TCPMD5NOTFOUND
|
||||
|
@ -438,7 +438,6 @@ int tcp_mmap(struct file *file, struct socket *sock,
|
||||
void tcp_parse_options(const struct net *net, const struct sk_buff *skb,
|
||||
struct tcp_options_received *opt_rx,
|
||||
int estab, struct tcp_fastopen_cookie *foc);
|
||||
const u8 *tcp_parse_md5sig_option(const struct tcphdr *th);
|
||||
|
||||
/*
|
||||
* BPF SKB-less helpers
|
||||
@ -2675,6 +2674,29 @@ static inline u64 tcp_transmit_time(const struct sock *sk)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline int tcp_parse_auth_options(const struct tcphdr *th,
|
||||
const u8 **md5_hash, const struct tcp_ao_hdr **aoh)
|
||||
{
|
||||
const u8 *md5_tmp, *ao_tmp;
|
||||
int ret;
|
||||
|
||||
ret = tcp_do_parse_auth_options(th, &md5_tmp, &ao_tmp);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (md5_hash)
|
||||
*md5_hash = md5_tmp;
|
||||
|
||||
if (aoh) {
|
||||
if (!ao_tmp)
|
||||
*aoh = NULL;
|
||||
else
|
||||
*aoh = (struct tcp_ao_hdr *)(ao_tmp - 2);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline bool tcp_ao_required(struct sock *sk, const void *saddr,
|
||||
int family)
|
||||
{
|
||||
|
@ -152,7 +152,9 @@ int tcp_v6_parse_ao(struct sock *sk, int cmd, sockptr_t optval, int optlen);
|
||||
void tcp_ao_established(struct sock *sk);
|
||||
void tcp_ao_finish_connect(struct sock *sk, struct sk_buff *skb);
|
||||
void tcp_ao_connect_init(struct sock *sk);
|
||||
|
||||
void tcp_ao_syncookie(struct sock *sk, const struct sk_buff *skb,
|
||||
struct tcp_request_sock *treq,
|
||||
unsigned short int family);
|
||||
#else /* CONFIG_TCP_AO */
|
||||
|
||||
static inline int tcp_ao_transmit_skb(struct sock *sk, struct sk_buff *skb,
|
||||
@ -185,4 +187,17 @@ static inline void tcp_ao_connect_init(struct sock *sk)
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(CONFIG_TCP_MD5SIG) || defined(CONFIG_TCP_AO)
|
||||
int tcp_do_parse_auth_options(const struct tcphdr *th,
|
||||
const u8 **md5_hash, const u8 **ao_hash);
|
||||
#else
|
||||
static inline int tcp_do_parse_auth_options(const struct tcphdr *th,
|
||||
const u8 **md5_hash, const u8 **ao_hash)
|
||||
{
|
||||
*md5_hash = NULL;
|
||||
*ao_hash = NULL;
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* _TCP_AO_H */
|
||||
|
@ -4398,7 +4398,8 @@ tcp_inbound_md5_hash(const struct sock *sk, const struct sk_buff *skb,
|
||||
l3index = sdif ? dif : 0;
|
||||
|
||||
hash_expected = tcp_md5_do_lookup(sk, l3index, saddr, family);
|
||||
hash_location = tcp_parse_md5sig_option(th);
|
||||
if (tcp_parse_auth_options(th, &hash_location, NULL))
|
||||
return SKB_DROP_REASON_TCP_AUTH_HDR;
|
||||
|
||||
/* We've parsed the options - do we have a hash? */
|
||||
if (!hash_expected && !hash_location)
|
||||
|
@ -4255,39 +4255,58 @@ static bool tcp_fast_parse_options(const struct net *net,
|
||||
return true;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_TCP_MD5SIG
|
||||
#if defined(CONFIG_TCP_MD5SIG) || defined(CONFIG_TCP_AO)
|
||||
/*
|
||||
* Parse MD5 Signature option
|
||||
* Parse Signature options
|
||||
*/
|
||||
const u8 *tcp_parse_md5sig_option(const struct tcphdr *th)
|
||||
int tcp_do_parse_auth_options(const struct tcphdr *th,
|
||||
const u8 **md5_hash, const u8 **ao_hash)
|
||||
{
|
||||
int length = (th->doff << 2) - sizeof(*th);
|
||||
const u8 *ptr = (const u8 *)(th + 1);
|
||||
unsigned int minlen = TCPOLEN_MD5SIG;
|
||||
|
||||
if (IS_ENABLED(CONFIG_TCP_AO))
|
||||
minlen = sizeof(struct tcp_ao_hdr) + 1;
|
||||
|
||||
*md5_hash = NULL;
|
||||
*ao_hash = NULL;
|
||||
|
||||
/* If not enough data remaining, we can short cut */
|
||||
while (length >= TCPOLEN_MD5SIG) {
|
||||
while (length >= minlen) {
|
||||
int opcode = *ptr++;
|
||||
int opsize;
|
||||
|
||||
switch (opcode) {
|
||||
case TCPOPT_EOL:
|
||||
return NULL;
|
||||
return 0;
|
||||
case TCPOPT_NOP:
|
||||
length--;
|
||||
continue;
|
||||
default:
|
||||
opsize = *ptr++;
|
||||
if (opsize < 2 || opsize > length)
|
||||
return NULL;
|
||||
if (opcode == TCPOPT_MD5SIG)
|
||||
return opsize == TCPOLEN_MD5SIG ? ptr : NULL;
|
||||
return -EINVAL;
|
||||
if (opcode == TCPOPT_MD5SIG) {
|
||||
if (opsize != TCPOLEN_MD5SIG)
|
||||
return -EINVAL;
|
||||
if (unlikely(*md5_hash || *ao_hash))
|
||||
return -EEXIST;
|
||||
*md5_hash = ptr;
|
||||
} else if (opcode == TCPOPT_AO) {
|
||||
if (opsize <= sizeof(struct tcp_ao_hdr))
|
||||
return -EINVAL;
|
||||
if (unlikely(*md5_hash || *ao_hash))
|
||||
return -EEXIST;
|
||||
*ao_hash = ptr;
|
||||
}
|
||||
}
|
||||
ptr += opsize - 2;
|
||||
length -= opsize;
|
||||
}
|
||||
return NULL;
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(tcp_parse_md5sig_option);
|
||||
EXPORT_SYMBOL(tcp_do_parse_auth_options);
|
||||
#endif
|
||||
|
||||
/* Sorry, PAWS as specified is broken wrt. pure-ACKs -DaveM
|
||||
|
@ -670,7 +670,9 @@ EXPORT_SYMBOL(tcp_v4_send_check);
|
||||
* Exception: precedence violation. We do not implement it in any case.
|
||||
*/
|
||||
|
||||
#ifdef CONFIG_TCP_MD5SIG
|
||||
#ifdef CONFIG_TCP_AO
|
||||
#define OPTION_BYTES MAX_TCP_OPTION_SPACE
|
||||
#elif defined(CONFIG_TCP_MD5SIG)
|
||||
#define OPTION_BYTES TCPOLEN_MD5SIG_ALIGNED
|
||||
#else
|
||||
#define OPTION_BYTES sizeof(__be32)
|
||||
@ -685,8 +687,8 @@ static void tcp_v4_send_reset(const struct sock *sk, struct sk_buff *skb)
|
||||
} rep;
|
||||
struct ip_reply_arg arg;
|
||||
#ifdef CONFIG_TCP_MD5SIG
|
||||
const __u8 *md5_hash_location = NULL;
|
||||
struct tcp_md5sig_key *key = NULL;
|
||||
const __u8 *hash_location = NULL;
|
||||
unsigned char newhash[16];
|
||||
int genhash;
|
||||
struct sock *sk1 = NULL;
|
||||
@ -727,8 +729,11 @@ static void tcp_v4_send_reset(const struct sock *sk, struct sk_buff *skb)
|
||||
|
||||
net = sk ? sock_net(sk) : dev_net(skb_dst(skb)->dev);
|
||||
#ifdef CONFIG_TCP_MD5SIG
|
||||
/* Invalid TCP option size or twice included auth */
|
||||
if (tcp_parse_auth_options(tcp_hdr(skb), &md5_hash_location, NULL))
|
||||
return;
|
||||
|
||||
rcu_read_lock();
|
||||
hash_location = tcp_parse_md5sig_option(th);
|
||||
if (sk && sk_fullsock(sk)) {
|
||||
const union tcp_md5_addr *addr;
|
||||
int l3index;
|
||||
@ -739,7 +744,7 @@ static void tcp_v4_send_reset(const struct sock *sk, struct sk_buff *skb)
|
||||
l3index = tcp_v4_sdif(skb) ? inet_iif(skb) : 0;
|
||||
addr = (union tcp_md5_addr *)&ip_hdr(skb)->saddr;
|
||||
key = tcp_md5_do_lookup(sk, l3index, addr, AF_INET);
|
||||
} else if (hash_location) {
|
||||
} else if (md5_hash_location) {
|
||||
const union tcp_md5_addr *addr;
|
||||
int sdif = tcp_v4_sdif(skb);
|
||||
int dif = inet_iif(skb);
|
||||
@ -771,7 +776,7 @@ static void tcp_v4_send_reset(const struct sock *sk, struct sk_buff *skb)
|
||||
|
||||
|
||||
genhash = tcp_v4_md5_hash_skb(newhash, key, NULL, skb);
|
||||
if (genhash || memcmp(hash_location, newhash, 16) != 0)
|
||||
if (genhash || memcmp(md5_hash_location, newhash, 16) != 0)
|
||||
goto out;
|
||||
|
||||
}
|
||||
|
@ -990,7 +990,7 @@ static void tcp_v6_send_reset(const struct sock *sk, struct sk_buff *skb)
|
||||
u32 seq = 0, ack_seq = 0;
|
||||
struct tcp_md5sig_key *key = NULL;
|
||||
#ifdef CONFIG_TCP_MD5SIG
|
||||
const __u8 *hash_location = NULL;
|
||||
const __u8 *md5_hash_location = NULL;
|
||||
unsigned char newhash[16];
|
||||
int genhash;
|
||||
struct sock *sk1 = NULL;
|
||||
@ -1012,8 +1012,11 @@ static void tcp_v6_send_reset(const struct sock *sk, struct sk_buff *skb)
|
||||
|
||||
net = sk ? sock_net(sk) : dev_net(skb_dst(skb)->dev);
|
||||
#ifdef CONFIG_TCP_MD5SIG
|
||||
/* Invalid TCP option size or twice included auth */
|
||||
if (tcp_parse_auth_options(th, &md5_hash_location, NULL))
|
||||
return;
|
||||
|
||||
rcu_read_lock();
|
||||
hash_location = tcp_parse_md5sig_option(th);
|
||||
if (sk && sk_fullsock(sk)) {
|
||||
int l3index;
|
||||
|
||||
@ -1022,7 +1025,7 @@ static void tcp_v6_send_reset(const struct sock *sk, struct sk_buff *skb)
|
||||
*/
|
||||
l3index = tcp_v6_sdif(skb) ? tcp_v6_iif_l3_slave(skb) : 0;
|
||||
key = tcp_v6_md5_do_lookup(sk, &ipv6h->saddr, l3index);
|
||||
} else if (hash_location) {
|
||||
} else if (md5_hash_location) {
|
||||
int dif = tcp_v6_iif_l3_slave(skb);
|
||||
int sdif = tcp_v6_sdif(skb);
|
||||
int l3index;
|
||||
@ -1051,7 +1054,7 @@ static void tcp_v6_send_reset(const struct sock *sk, struct sk_buff *skb)
|
||||
goto out;
|
||||
|
||||
genhash = tcp_v6_md5_hash_skb(newhash, key, NULL, skb);
|
||||
if (genhash || memcmp(hash_location, newhash, 16) != 0)
|
||||
if (genhash || memcmp(md5_hash_location, newhash, 16) != 0)
|
||||
goto out;
|
||||
}
|
||||
#endif
|
||||
|
Loading…
Reference in New Issue
Block a user