Skip to content

Commit 0a3a809

Browse files
0x7f454c46davem330
authored andcommitted
net/tcp: Verify inbound TCP-AO signed segments
Now there is a common function to verify signature on TCP segments: tcp_inbound_hash(). It has checks for all possible cross-interactions with MD5 signs as well as with unsigned segments. The rules from RFC5925 are: (1) Any TCP segment can have at max only one signature. (2) TCP connections can't switch between using TCP-MD5 and TCP-AO. (3) TCP-AO connections can't stop using AO, as well as unsigned connections can't suddenly start using AO. Co-developed-by: Francesco Ruggeri <[email protected]> Signed-off-by: Francesco Ruggeri <[email protected]> Co-developed-by: Salam Noureddine <[email protected]> Signed-off-by: Salam Noureddine <[email protected]> Signed-off-by: Dmitry Safonov <[email protected]> Acked-by: David Ahern <[email protected]> Signed-off-by: David S. Miller <[email protected]>
1 parent 9427c6a commit 0a3a809

File tree

8 files changed

+248
-47
lines changed

8 files changed

+248
-47
lines changed

include/net/dropreason-core.h

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@
2424
FN(TCP_MD5NOTFOUND) \
2525
FN(TCP_MD5UNEXPECTED) \
2626
FN(TCP_MD5FAILURE) \
27+
FN(TCP_AONOTFOUND) \
28+
FN(TCP_AOUNEXPECTED) \
29+
FN(TCP_AOKEYNOTFOUND) \
30+
FN(TCP_AOFAILURE) \
2731
FN(SOCKET_BACKLOG) \
2832
FN(TCP_FLAGS) \
2933
FN(TCP_ZEROWINDOW) \
@@ -163,6 +167,19 @@ enum skb_drop_reason {
163167
* to LINUX_MIB_TCPMD5FAILURE
164168
*/
165169
SKB_DROP_REASON_TCP_MD5FAILURE,
170+
/**
171+
* @SKB_DROP_REASON_TCP_AONOTFOUND: no TCP-AO hash and one was expected
172+
*/
173+
SKB_DROP_REASON_TCP_AONOTFOUND,
174+
/**
175+
* @SKB_DROP_REASON_TCP_AOUNEXPECTED: TCP-AO hash is present and it
176+
* was not expected.
177+
*/
178+
SKB_DROP_REASON_TCP_AOUNEXPECTED,
179+
/** @SKB_DROP_REASON_TCP_AOKEYNOTFOUND: TCP-AO key is unknown */
180+
SKB_DROP_REASON_TCP_AOKEYNOTFOUND,
181+
/** @SKB_DROP_REASON_TCP_AOFAILURE: TCP-AO hash is wrong */
182+
SKB_DROP_REASON_TCP_AOFAILURE,
166183
/**
167184
* @SKB_DROP_REASON_SOCKET_BACKLOG: failed to add skb to socket backlog (
168185
* see LINUX_MIB_TCPBACKLOGDROP)

include/net/tcp.h

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1809,7 +1809,7 @@ tcp_md5_do_lookup_any_l3index(const struct sock *sk,
18091809
enum skb_drop_reason
18101810
tcp_inbound_md5_hash(const struct sock *sk, const struct sk_buff *skb,
18111811
const void *saddr, const void *daddr,
1812-
int family, int dif, int sdif);
1812+
int family, int l3index, const __u8 *hash_location);
18131813

18141814

18151815
#define tcp_twsk_md5_key(twsk) ((twsk)->tw_md5_key)
@@ -1831,7 +1831,7 @@ tcp_md5_do_lookup_any_l3index(const struct sock *sk,
18311831
static inline enum skb_drop_reason
18321832
tcp_inbound_md5_hash(const struct sock *sk, const struct sk_buff *skb,
18331833
const void *saddr, const void *daddr,
1834-
int family, int dif, int sdif)
1834+
int family, int l3index, const __u8 *hash_location)
18351835
{
18361836
return SKB_NOT_DROPPED_YET;
18371837
}
@@ -2730,4 +2730,53 @@ static inline bool tcp_ao_required(struct sock *sk, const void *saddr,
27302730
return false;
27312731
}
27322732

2733+
/* Called with rcu_read_lock() */
2734+
static inline enum skb_drop_reason
2735+
tcp_inbound_hash(struct sock *sk, const struct request_sock *req,
2736+
const struct sk_buff *skb,
2737+
const void *saddr, const void *daddr,
2738+
int family, int dif, int sdif)
2739+
{
2740+
const struct tcphdr *th = tcp_hdr(skb);
2741+
const struct tcp_ao_hdr *aoh;
2742+
const __u8 *md5_location;
2743+
int l3index;
2744+
2745+
/* Invalid option or two times meet any of auth options */
2746+
if (tcp_parse_auth_options(th, &md5_location, &aoh))
2747+
return SKB_DROP_REASON_TCP_AUTH_HDR;
2748+
2749+
if (req) {
2750+
if (tcp_rsk_used_ao(req) != !!aoh)
2751+
return SKB_DROP_REASON_TCP_AOFAILURE;
2752+
}
2753+
2754+
/* sdif set, means packet ingressed via a device
2755+
* in an L3 domain and dif is set to the l3mdev
2756+
*/
2757+
l3index = sdif ? dif : 0;
2758+
2759+
/* Fast path: unsigned segments */
2760+
if (likely(!md5_location && !aoh)) {
2761+
/* Drop if there's TCP-MD5 or TCP-AO key with any rcvid/sndid
2762+
* for the remote peer. On TCP-AO established connection
2763+
* the last key is impossible to remove, so there's
2764+
* always at least one current_key.
2765+
*/
2766+
if (tcp_ao_required(sk, saddr, family))
2767+
return SKB_DROP_REASON_TCP_AONOTFOUND;
2768+
if (unlikely(tcp_md5_do_lookup(sk, l3index, saddr, family))) {
2769+
NET_INC_STATS(sock_net(sk), LINUX_MIB_TCPMD5NOTFOUND);
2770+
return SKB_DROP_REASON_TCP_MD5NOTFOUND;
2771+
}
2772+
return SKB_NOT_DROPPED_YET;
2773+
}
2774+
2775+
if (aoh)
2776+
return tcp_inbound_ao_hash(sk, skb, family, req, aoh);
2777+
2778+
return tcp_inbound_md5_hash(sk, skb, saddr, daddr, family,
2779+
l3index, md5_location);
2780+
}
2781+
27332782
#endif /* _TCP_H */

include/net/tcp_ao.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,9 @@ struct tcp6_ao_context {
111111
};
112112

113113
struct tcp_sigpool;
114+
#define TCP_AO_ESTABLISHED (TCPF_ESTABLISHED | TCPF_FIN_WAIT1 | TCPF_FIN_WAIT2 | \
115+
TCPF_CLOSE | TCPF_CLOSE_WAIT | \
116+
TCPF_LAST_ACK | TCPF_CLOSING)
114117

115118
int tcp_ao_transmit_skb(struct sock *sk, struct sk_buff *skb,
116119
struct tcp_ao_key *key, struct tcphdr *th,
@@ -130,6 +133,10 @@ int tcp_ao_calc_traffic_key(struct tcp_ao_key *mkt, u8 *key, void *ctx,
130133
unsigned int len, struct tcp_sigpool *hp);
131134
void tcp_ao_destroy_sock(struct sock *sk, bool twsk);
132135
void tcp_ao_time_wait(struct tcp_timewait_sock *tcptw, struct tcp_sock *tp);
136+
enum skb_drop_reason tcp_inbound_ao_hash(struct sock *sk,
137+
const struct sk_buff *skb, unsigned short int family,
138+
const struct request_sock *req,
139+
const struct tcp_ao_hdr *aoh);
133140
struct tcp_ao_key *tcp_ao_do_lookup(const struct sock *sk,
134141
const union tcp_ao_addr *addr,
135142
int family, int sndid, int rcvid);
@@ -208,6 +215,13 @@ static inline void tcp_ao_syncookie(struct sock *sk, const struct sk_buff *skb,
208215
{
209216
}
210217

218+
static inline enum skb_drop_reason tcp_inbound_ao_hash(struct sock *sk,
219+
const struct sk_buff *skb, unsigned short int family,
220+
const struct request_sock *req, const struct tcp_ao_hdr *aoh)
221+
{
222+
return SKB_NOT_DROPPED_YET;
223+
}
224+
211225
static inline struct tcp_ao_key *tcp_ao_do_lookup(const struct sock *sk,
212226
const union tcp_ao_addr *addr, int family, int sndid, int rcvid)
213227
{

net/ipv4/tcp.c

Lines changed: 8 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -4375,42 +4375,23 @@ EXPORT_SYMBOL(tcp_md5_hash_key);
43754375
enum skb_drop_reason
43764376
tcp_inbound_md5_hash(const struct sock *sk, const struct sk_buff *skb,
43774377
const void *saddr, const void *daddr,
4378-
int family, int dif, int sdif)
4378+
int family, int l3index, const __u8 *hash_location)
43794379
{
4380-
/*
4381-
* This gets called for each TCP segment that arrives
4382-
* so we want to be efficient.
4380+
/* This gets called for each TCP segment that has TCP-MD5 option.
43834381
* We have 3 drop cases:
43844382
* o No MD5 hash and one expected.
43854383
* o MD5 hash and we're not expecting one.
43864384
* o MD5 hash and its wrong.
43874385
*/
4388-
const __u8 *hash_location = NULL;
4389-
struct tcp_md5sig_key *hash_expected;
43904386
const struct tcphdr *th = tcp_hdr(skb);
43914387
const struct tcp_sock *tp = tcp_sk(sk);
4392-
int genhash, l3index;
4388+
struct tcp_md5sig_key *key;
43934389
u8 newhash[16];
4390+
int genhash;
43944391

4395-
/* sdif set, means packet ingressed via a device
4396-
* in an L3 domain and dif is set to the l3mdev
4397-
*/
4398-
l3index = sdif ? dif : 0;
4399-
4400-
hash_expected = tcp_md5_do_lookup(sk, l3index, saddr, family);
4401-
if (tcp_parse_auth_options(th, &hash_location, NULL))
4402-
return SKB_DROP_REASON_TCP_AUTH_HDR;
4403-
4404-
/* We've parsed the options - do we have a hash? */
4405-
if (!hash_expected && !hash_location)
4406-
return SKB_NOT_DROPPED_YET;
4392+
key = tcp_md5_do_lookup(sk, l3index, saddr, family);
44074393

4408-
if (hash_expected && !hash_location) {
4409-
NET_INC_STATS(sock_net(sk), LINUX_MIB_TCPMD5NOTFOUND);
4410-
return SKB_DROP_REASON_TCP_MD5NOTFOUND;
4411-
}
4412-
4413-
if (!hash_expected && hash_location) {
4394+
if (!key && hash_location) {
44144395
NET_INC_STATS(sock_net(sk), LINUX_MIB_TCPMD5UNEXPECTED);
44154396
return SKB_DROP_REASON_TCP_MD5UNEXPECTED;
44164397
}
@@ -4420,14 +4401,10 @@ tcp_inbound_md5_hash(const struct sock *sk, const struct sk_buff *skb,
44204401
* IPv4-mapped case.
44214402
*/
44224403
if (family == AF_INET)
4423-
genhash = tcp_v4_md5_hash_skb(newhash,
4424-
hash_expected,
4425-
NULL, skb);
4404+
genhash = tcp_v4_md5_hash_skb(newhash, key, NULL, skb);
44264405
else
4427-
genhash = tp->af_specific->calc_md5_hash(newhash,
4428-
hash_expected,
4406+
genhash = tp->af_specific->calc_md5_hash(newhash, key,
44294407
NULL, skb);
4430-
44314408
if (genhash || memcmp(hash_location, newhash, 16) != 0) {
44324409
NET_INC_STATS(sock_net(sk), LINUX_MIB_TCPMD5FAILURE);
44334410
if (family == AF_INET) {

net/ipv4/tcp_ao.c

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -761,6 +761,148 @@ void tcp_ao_syncookie(struct sock *sk, const struct sk_buff *skb,
761761
treq->maclen = tcp_ao_maclen(key);
762762
}
763763

764+
static enum skb_drop_reason
765+
tcp_ao_verify_hash(const struct sock *sk, const struct sk_buff *skb,
766+
unsigned short int family, struct tcp_ao_info *info,
767+
const struct tcp_ao_hdr *aoh, struct tcp_ao_key *key,
768+
u8 *traffic_key, u8 *phash, u32 sne)
769+
{
770+
u8 maclen = aoh->length - sizeof(struct tcp_ao_hdr);
771+
const struct tcphdr *th = tcp_hdr(skb);
772+
void *hash_buf = NULL;
773+
774+
if (maclen != tcp_ao_maclen(key))
775+
return SKB_DROP_REASON_TCP_AOFAILURE;
776+
777+
hash_buf = kmalloc(tcp_ao_digest_size(key), GFP_ATOMIC);
778+
if (!hash_buf)
779+
return SKB_DROP_REASON_NOT_SPECIFIED;
780+
781+
/* XXX: make it per-AF callback? */
782+
tcp_ao_hash_skb(family, hash_buf, key, sk, skb, traffic_key,
783+
(phash - (u8 *)th), sne);
784+
if (memcmp(phash, hash_buf, maclen)) {
785+
kfree(hash_buf);
786+
return SKB_DROP_REASON_TCP_AOFAILURE;
787+
}
788+
kfree(hash_buf);
789+
return SKB_NOT_DROPPED_YET;
790+
}
791+
792+
enum skb_drop_reason
793+
tcp_inbound_ao_hash(struct sock *sk, const struct sk_buff *skb,
794+
unsigned short int family, const struct request_sock *req,
795+
const struct tcp_ao_hdr *aoh)
796+
{
797+
const struct tcphdr *th = tcp_hdr(skb);
798+
u8 *phash = (u8 *)(aoh + 1); /* hash goes just after the header */
799+
struct tcp_ao_info *info;
800+
enum skb_drop_reason ret;
801+
struct tcp_ao_key *key;
802+
__be32 sisn, disn;
803+
u8 *traffic_key;
804+
u32 sne = 0;
805+
806+
info = rcu_dereference(tcp_sk(sk)->ao_info);
807+
if (!info)
808+
return SKB_DROP_REASON_TCP_AOUNEXPECTED;
809+
810+
if (unlikely(th->syn)) {
811+
sisn = th->seq;
812+
disn = 0;
813+
}
814+
815+
/* Fast-path */
816+
if (likely((1 << sk->sk_state) & TCP_AO_ESTABLISHED)) {
817+
enum skb_drop_reason err;
818+
struct tcp_ao_key *current_key;
819+
820+
/* Check if this socket's rnext_key matches the keyid in the
821+
* packet. If not we lookup the key based on the keyid
822+
* matching the rcvid in the mkt.
823+
*/
824+
key = READ_ONCE(info->rnext_key);
825+
if (key->rcvid != aoh->keyid) {
826+
key = tcp_ao_established_key(info, -1, aoh->keyid);
827+
if (!key)
828+
goto key_not_found;
829+
}
830+
831+
/* Delayed retransmitted SYN */
832+
if (unlikely(th->syn && !th->ack))
833+
goto verify_hash;
834+
835+
sne = 0;
836+
/* Established socket, traffic key are cached */
837+
traffic_key = rcv_other_key(key);
838+
err = tcp_ao_verify_hash(sk, skb, family, info, aoh, key,
839+
traffic_key, phash, sne);
840+
if (err)
841+
return err;
842+
current_key = READ_ONCE(info->current_key);
843+
/* Key rotation: the peer asks us to use new key (RNext) */
844+
if (unlikely(aoh->rnext_keyid != current_key->sndid)) {
845+
/* If the key is not found we do nothing. */
846+
key = tcp_ao_established_key(info, aoh->rnext_keyid, -1);
847+
if (key)
848+
/* pairs with tcp_ao_del_cmd */
849+
WRITE_ONCE(info->current_key, key);
850+
}
851+
return SKB_NOT_DROPPED_YET;
852+
}
853+
854+
/* Lookup key based on peer address and keyid.
855+
* current_key and rnext_key must not be used on tcp listen
856+
* sockets as otherwise:
857+
* - request sockets would race on those key pointers
858+
* - tcp_ao_del_cmd() allows async key removal
859+
*/
860+
key = tcp_ao_inbound_lookup(family, sk, skb, -1, aoh->keyid);
861+
if (!key)
862+
goto key_not_found;
863+
864+
if (th->syn && !th->ack)
865+
goto verify_hash;
866+
867+
if ((1 << sk->sk_state) & (TCPF_LISTEN | TCPF_NEW_SYN_RECV)) {
868+
/* Make the initial syn the likely case here */
869+
if (unlikely(req)) {
870+
sne = 0;
871+
sisn = htonl(tcp_rsk(req)->rcv_isn);
872+
disn = htonl(tcp_rsk(req)->snt_isn);
873+
} else if (unlikely(th->ack && !th->syn)) {
874+
/* Possible syncookie packet */
875+
sisn = htonl(ntohl(th->seq) - 1);
876+
disn = htonl(ntohl(th->ack_seq) - 1);
877+
sne = 0;
878+
} else if (unlikely(!th->syn)) {
879+
/* no way to figure out initial sisn/disn - drop */
880+
return SKB_DROP_REASON_TCP_FLAGS;
881+
}
882+
} else if ((1 << sk->sk_state) & (TCPF_SYN_SENT | TCPF_SYN_RECV)) {
883+
disn = info->lisn;
884+
if (th->syn || th->rst)
885+
sisn = th->seq;
886+
else
887+
sisn = info->risn;
888+
} else {
889+
WARN_ONCE(1, "TCP-AO: Unexpected sk_state %d", sk->sk_state);
890+
return SKB_DROP_REASON_TCP_AOFAILURE;
891+
}
892+
verify_hash:
893+
traffic_key = kmalloc(tcp_ao_digest_size(key), GFP_ATOMIC);
894+
if (!traffic_key)
895+
return SKB_DROP_REASON_NOT_SPECIFIED;
896+
tcp_ao_calc_key_skb(key, traffic_key, skb, sisn, disn, family);
897+
ret = tcp_ao_verify_hash(sk, skb, family, info, aoh, key,
898+
traffic_key, phash, sne);
899+
kfree(traffic_key);
900+
return ret;
901+
902+
key_not_found:
903+
return SKB_DROP_REASON_TCP_AOKEYNOTFOUND;
904+
}
905+
764906
static int tcp_ao_cache_traffic_keys(const struct sock *sk,
765907
struct tcp_ao_info *ao,
766908
struct tcp_ao_key *ao_key)

net/ipv4/tcp_ipv4.c

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2204,9 +2204,9 @@ int tcp_v4_rcv(struct sk_buff *skb)
22042204
if (!xfrm4_policy_check(sk, XFRM_POLICY_IN, skb))
22052205
drop_reason = SKB_DROP_REASON_XFRM_POLICY;
22062206
else
2207-
drop_reason = tcp_inbound_md5_hash(sk, skb,
2208-
&iph->saddr, &iph->daddr,
2209-
AF_INET, dif, sdif);
2207+
drop_reason = tcp_inbound_hash(sk, req, skb,
2208+
&iph->saddr, &iph->daddr,
2209+
AF_INET, dif, sdif);
22102210
if (unlikely(drop_reason)) {
22112211
sk_drops_add(sk, skb);
22122212
reqsk_put(req);
@@ -2283,8 +2283,8 @@ int tcp_v4_rcv(struct sk_buff *skb)
22832283
goto discard_and_relse;
22842284
}
22852285

2286-
drop_reason = tcp_inbound_md5_hash(sk, skb, &iph->saddr,
2287-
&iph->daddr, AF_INET, dif, sdif);
2286+
drop_reason = tcp_inbound_hash(sk, NULL, skb, &iph->saddr, &iph->daddr,
2287+
AF_INET, dif, sdif);
22882288
if (drop_reason)
22892289
goto discard_and_relse;
22902290

net/ipv6/tcp_ao.c

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -53,11 +53,12 @@ int tcp_v6_ao_calc_key_skb(struct tcp_ao_key *mkt, u8 *key,
5353
const struct sk_buff *skb,
5454
__be32 sisn, __be32 disn)
5555
{
56-
const struct ipv6hdr *iph = ipv6_hdr(skb);
57-
const struct tcphdr *th = tcp_hdr(skb);
56+
const struct ipv6hdr *iph = ipv6_hdr(skb);
57+
const struct tcphdr *th = tcp_hdr(skb);
5858

59-
return tcp_v6_ao_calc_key(mkt, key, &iph->saddr, &iph->daddr,
60-
th->source, th->dest, sisn, disn);
59+
return tcp_v6_ao_calc_key(mkt, key, &iph->saddr,
60+
&iph->daddr, th->source,
61+
th->dest, sisn, disn);
6162
}
6263

6364
int tcp_v6_ao_calc_key_sk(struct tcp_ao_key *mkt, u8 *key,

0 commit comments

Comments
 (0)