git: a057769205c3 - main - in_pcb: use jenkins hash over the entire IPv6 (or IPv4) address
- Go to: [ bottom of page ] [ top of archives ] [ this month ]
Date: Sun, 26 Dec 2021 18:48:54 UTC
The branch main has been updated by glebius: URL: https://cgit.FreeBSD.org/src/commit/?id=a057769205c3a14e14d4e2a4a3635f6db78c4098 commit a057769205c3a14e14d4e2a4a3635f6db78c4098 Author: Gleb Smirnoff <glebius@FreeBSD.org> AuthorDate: 2021-12-26 18:47:28 +0000 Commit: Gleb Smirnoff <glebius@FreeBSD.org> CommitDate: 2021-12-26 18:47:28 +0000 in_pcb: use jenkins hash over the entire IPv6 (or IPv4) address The intent is to provide more entropy than can be provided by just the 32-bits of the IPv6 address which overlaps with 6to4 tunnels. This is needed to mitigate potential algorithmic complexity attacks from attackers who can control large numbers of IPv6 addresses. Together with: gallatin Reviewed by: dwmalone, rscheff Differential revision: https://reviews.freebsd.org/D33254 --- sys/netinet/in_pcb.c | 44 ++++++++++++++++++++++++++------------------ sys/netinet/in_pcb.h | 40 ++++++++++++++++++++++++++++++++-------- sys/netinet/in_pcb_var.h | 3 +++ sys/netinet6/in6_pcb.c | 15 +++++++-------- 4 files changed, 68 insertions(+), 34 deletions(-) diff --git a/sys/netinet/in_pcb.c b/sys/netinet/in_pcb.c index b1cbef537c88..b0ee7aa8f522 100644 --- a/sys/netinet/in_pcb.c +++ b/sys/netinet/in_pcb.c @@ -49,7 +49,9 @@ __FBSDID("$FreeBSD$"); #include "opt_rss.h" #include <sys/param.h> +#include <sys/hash.h> #include <sys/systm.h> +#include <sys/libkern.h> #include <sys/lock.h> #include <sys/malloc.h> #include <sys/mbuf.h> @@ -246,6 +248,16 @@ SYSCTL_COUNTER_U64(_net_inet_ip_rl, OID_AUTO, chgrl, CTLFLAG_RD, #endif /* INET */ +VNET_DEFINE(uint32_t, in_pcbhashseed); +static void +in_pcbhashseed_init(void) +{ + + V_in_pcbhashseed = arc4random(); +} +VNET_SYSINIT(in_pcbhashseed_init, SI_SUB_PROTO_DOMAIN, SI_ORDER_FIRST, + in_pcbhashseed_init, 0); + /* * in_pcb.c: manage the Protocol Control Blocks. * @@ -2085,8 +2097,8 @@ in_pcblookup_local(struct inpcbinfo *pcbinfo, struct in_addr laddr, * Look for an unconnected (wildcard foreign addr) PCB that * matches the local address and port we're looking for. */ - head = &pcbinfo->ipi_hashbase[INP_PCBHASH(INADDR_ANY, lport, - 0, pcbinfo->ipi_hashmask)]; + head = &pcbinfo->ipi_hashbase[INP_PCBHASH_WILD(lport, + pcbinfo->ipi_hashmask)]; CK_LIST_FOREACH(inp, head, inp_hash) { #ifdef INET6 /* XXX inp locking */ @@ -2214,7 +2226,7 @@ in_pcblookup_lbgroup(const struct inpcbinfo *pcbinfo, if (grp->il_lport != lport) continue; - idx = INP_PCBLBGROUP_PKTHASH(faddr->s_addr, lport, fport) % + idx = INP_PCBLBGROUP_PKTHASH(faddr, lport, fport) % grp->il_inpcnt; if (grp->il_laddr.s_addr == laddr->s_addr) { if (numa_domain == M_NODOM || @@ -2260,7 +2272,7 @@ in_pcblookup_hash_locked(struct inpcbinfo *pcbinfo, struct in_addr faddr, * First look for an exact match. */ tmpinp = NULL; - head = &pcbinfo->ipi_hashbase[INP_PCBHASH(faddr.s_addr, lport, fport, + head = &pcbinfo->ipi_hashbase[INP_PCBHASH(&faddr, lport, fport, pcbinfo->ipi_hashmask)]; CK_LIST_FOREACH(inp, head, inp_hash) { #ifdef INET6 @@ -2315,8 +2327,8 @@ in_pcblookup_hash_locked(struct inpcbinfo *pcbinfo, struct in_addr faddr, * 4. non-jailed, wild. */ - head = &pcbinfo->ipi_hashbase[INP_PCBHASH(INADDR_ANY, lport, - 0, pcbinfo->ipi_hashmask)]; + head = &pcbinfo->ipi_hashbase[INP_PCBHASH_WILD(lport, + pcbinfo->ipi_hashmask)]; CK_LIST_FOREACH(inp, head, inp_hash) { #ifdef INET6 /* XXX inp locking */ @@ -2439,7 +2451,6 @@ in_pcbinshash(struct inpcb *inp) struct inpcbporthead *pcbporthash; struct inpcbinfo *pcbinfo = inp->inp_pcbinfo; struct inpcbport *phd; - u_int32_t hashkey_faddr; int so_options; INP_WLOCK_ASSERT(inp); @@ -2450,13 +2461,12 @@ in_pcbinshash(struct inpcb *inp) #ifdef INET6 if (inp->inp_vflag & INP_IPV6) - hashkey_faddr = INP6_PCBHASHKEY(&inp->in6p_faddr); + pcbhash = &pcbinfo->ipi_hashbase[INP6_PCBHASH(&inp->in6p_faddr, + inp->inp_lport, inp->inp_fport, pcbinfo->ipi_hashmask)]; else #endif - hashkey_faddr = inp->inp_faddr.s_addr; - - pcbhash = &pcbinfo->ipi_hashbase[INP_PCBHASH(hashkey_faddr, - inp->inp_lport, inp->inp_fport, pcbinfo->ipi_hashmask)]; + pcbhash = &pcbinfo->ipi_hashbase[INP_PCBHASH(&inp->inp_faddr, + inp->inp_lport, inp->inp_fport, pcbinfo->ipi_hashmask)]; pcbporthash = &pcbinfo->ipi_porthashbase[ INP_PCBPORTHASH(inp->inp_lport, pcbinfo->ipi_porthashmask)]; @@ -2516,7 +2526,6 @@ in_pcbrehash(struct inpcb *inp) { struct inpcbinfo *pcbinfo = inp->inp_pcbinfo; struct inpcbhead *head; - u_int32_t hashkey_faddr; INP_WLOCK_ASSERT(inp); INP_HASH_WLOCK_ASSERT(pcbinfo); @@ -2526,13 +2535,12 @@ in_pcbrehash(struct inpcb *inp) #ifdef INET6 if (inp->inp_vflag & INP_IPV6) - hashkey_faddr = INP6_PCBHASHKEY(&inp->in6p_faddr); + head = &pcbinfo->ipi_hashbase[INP6_PCBHASH(&inp->in6p_faddr, + inp->inp_lport, inp->inp_fport, pcbinfo->ipi_hashmask)]; else #endif - hashkey_faddr = inp->inp_faddr.s_addr; - - head = &pcbinfo->ipi_hashbase[INP_PCBHASH(hashkey_faddr, - inp->inp_lport, inp->inp_fport, pcbinfo->ipi_hashmask)]; + head = &pcbinfo->ipi_hashbase[INP_PCBHASH(&inp->inp_faddr, + inp->inp_lport, inp->inp_fport, pcbinfo->ipi_hashmask)]; CK_LIST_REMOVE(inp, inp_hash); CK_LIST_INSERT_HEAD(head, inp, inp_hash); diff --git a/sys/netinet/in_pcb.h b/sys/netinet/in_pcb.h index 02e6c7b60e38..0e87a68e81fa 100644 --- a/sys/netinet/in_pcb.h +++ b/sys/netinet/in_pcb.h @@ -73,7 +73,8 @@ typedef uint64_t inp_gen_t; /* * PCB with AF_INET6 null bind'ed laddr can receive AF_INET input packet. * So, AF_INET6 null laddr is also used as AF_INET null laddr, by utilizing - * the following structure. + * the following structure. This requires padding always be zeroed out, + * which is done right after inpcb allocation and stays through its lifetime. */ struct in_addr_4in6 { u_int32_t ia46_pad32[3]; @@ -530,13 +531,36 @@ int inp_so_options(const struct inpcb *inp); #define INP_HASH_WLOCK_ASSERT(ipi) mtx_assert(&(ipi)->ipi_hash_lock, \ MA_OWNED) -#define INP_PCBHASH(faddr, lport, fport, mask) \ - (((faddr) ^ ((faddr) >> 16) ^ ntohs((lport) ^ (fport))) & (mask)) -#define INP_PCBPORTHASH(lport, mask) \ - (ntohs((lport)) & (mask)) -#define INP_PCBLBGROUP_PKTHASH(faddr, lport, fport) \ - ((faddr) ^ ((faddr) >> 16) ^ ntohs((lport) ^ (fport))) -#define INP6_PCBHASHKEY(faddr) ((faddr)->s6_addr32[3]) +/* + * Wildcard matching hash is not just a microoptimisation! The hash for + * wildcard IPv4 and wildcard IPv6 must be the same, otherwise AF_INET6 + * wildcard bound pcb won't be able to receive AF_INET connections, while: + * jenkins_hash(&zeroes, 1, s) != jenkins_hash(&zeroes, 4, s) + * See also comment above struct in_addr_4in6. + */ +#define IN_ADDR_JHASH32(addr) \ + ((addr)->s_addr == INADDR_ANY ? V_in_pcbhashseed : \ + jenkins_hash32((&(addr)->s_addr), 1, V_in_pcbhashseed)) +#define IN6_ADDR_JHASH32(addr) \ + (memcmp((addr), &in6addr_any, sizeof(in6addr_any)) == 0 ? \ + V_in_pcbhashseed : \ + jenkins_hash32((addr)->__u6_addr.__u6_addr32, \ + nitems((addr)->__u6_addr.__u6_addr32), V_in_pcbhashseed)) + +#define INP_PCBHASH(faddr, lport, fport, mask) \ + ((IN_ADDR_JHASH32(faddr) ^ ntohs((lport) ^ (fport))) & (mask)) +#define INP6_PCBHASH(faddr, lport, fport, mask) \ + ((IN6_ADDR_JHASH32(faddr) ^ ntohs((lport) ^ (fport))) & (mask)) + +#define INP_PCBHASH_WILD(lport, mask) \ + ((V_in_pcbhashseed ^ ntohs(lport)) & (mask)) + +#define INP_PCBLBGROUP_PKTHASH(faddr, lport, fport) \ + (IN_ADDR_JHASH32(faddr) ^ ntohs((lport) ^ (fport))) +#define INP6_PCBLBGROUP_PKTHASH(faddr, lport, fport) \ + (IN6_ADDR_JHASH32(faddr) ^ ntohs((lport) ^ (fport))) + +#define INP_PCBPORTHASH(lport, mask) (ntohs((lport)) & (mask)) /* * Flags for inp_vflags -- historically version flags only diff --git a/sys/netinet/in_pcb_var.h b/sys/netinet/in_pcb_var.h index 4db20418708d..31214b6092f3 100644 --- a/sys/netinet/in_pcb_var.h +++ b/sys/netinet/in_pcb_var.h @@ -44,6 +44,9 @@ * Definitions shared between netinet/in_pcb.c and netinet6/in6_pcb.c */ +VNET_DECLARE(uint32_t, in_pcbhashseed); +#define V_in_pcbhashseed VNET(in_pcbhashseed) + bool inp_smr_lock(struct inpcb *, const inp_lookup_t); int in_pcb_lport(struct inpcb *, struct in_addr *, u_short *, struct ucred *, int); diff --git a/sys/netinet6/in6_pcb.c b/sys/netinet6/in6_pcb.c index f86c72958a9e..2d76a8b3db77 100644 --- a/sys/netinet6/in6_pcb.c +++ b/sys/netinet6/in6_pcb.c @@ -75,6 +75,7 @@ __FBSDID("$FreeBSD$"); #include "opt_route.h" #include "opt_rss.h" +#include <sys/hash.h> #include <sys/param.h> #include <sys/systm.h> #include <sys/malloc.h> @@ -787,8 +788,7 @@ in6_pcblookup_local(struct inpcbinfo *pcbinfo, struct in6_addr *laddr, * Look for an unconnected (wildcard foreign addr) PCB that * matches the local address and port we're looking for. */ - head = &pcbinfo->ipi_hashbase[INP_PCBHASH( - INP6_PCBHASHKEY(&in6addr_any), lport, 0, + head = &pcbinfo->ipi_hashbase[INP_PCBHASH_WILD(lport, pcbinfo->ipi_hashmask)]; CK_LIST_FOREACH(inp, head, inp_hash) { /* XXX inp locking */ @@ -972,8 +972,8 @@ in6_pcblookup_lbgroup(const struct inpcbinfo *pcbinfo, if (grp->il_lport != lport) continue; - idx = INP_PCBLBGROUP_PKTHASH(INP6_PCBHASHKEY(faddr), lport, - fport) % grp->il_inpcnt; + idx = INP6_PCBLBGROUP_PKTHASH(faddr, lport, fport) % + grp->il_inpcnt; if (IN6_ARE_ADDR_EQUAL(&grp->il6_laddr, laddr)) { if (numa_domain == M_NODOM || grp->il_numa_domain == numa_domain) { @@ -1015,8 +1015,8 @@ in6_pcblookup_hash_locked(struct inpcbinfo *pcbinfo, struct in6_addr *faddr, * First look for an exact match. */ tmpinp = NULL; - head = &pcbinfo->ipi_hashbase[INP_PCBHASH( - INP6_PCBHASHKEY(faddr), lport, fport, pcbinfo->ipi_hashmask)]; + head = &pcbinfo->ipi_hashbase[INP6_PCBHASH(faddr, lport, fport, + pcbinfo->ipi_hashmask)]; CK_LIST_FOREACH(inp, head, inp_hash) { /* XXX inp locking */ if ((inp->inp_vflag & INP_IPV6) == 0) @@ -1064,8 +1064,7 @@ in6_pcblookup_hash_locked(struct inpcbinfo *pcbinfo, struct in6_addr *faddr, * 3. non-jailed, non-wild. * 4. non-jailed, wild. */ - head = &pcbinfo->ipi_hashbase[INP_PCBHASH( - INP6_PCBHASHKEY(&in6addr_any), lport, 0, + head = &pcbinfo->ipi_hashbase[INP_PCBHASH_WILD(lport, pcbinfo->ipi_hashmask)]; CK_LIST_FOREACH(inp, head, inp_hash) { /* XXX inp locking */