diff options
| -rw-r--r-- | sys/net/pf_syncookies.c | 411 |
1 files changed, 411 insertions, 0 deletions
diff --git a/sys/net/pf_syncookies.c b/sys/net/pf_syncookies.c new file mode 100644 index 00000000000..c0ee1f85501 --- /dev/null +++ b/sys/net/pf_syncookies.c @@ -0,0 +1,411 @@ +/* $OpenBSD: pf_syncookies.c,v 1.1 2018/02/06 23:37:24 henning Exp $ */ + +/* Copyright (c) 2016,2017 Henning Brauer <henning@openbsd.org> + * Copyright (c) 2016 Alexandr Nedvedicky <sashan@openbsd.org> + * + * syncookie parts based on FreeBSD sys/netinet/tcp_syncache.c + * + * Copyright (c) 2001 McAfee, Inc. + * Copyright (c) 2006,2013 Andre Oppermann, Internet Business Solutions AG + * All rights reserved. + * + * This software was developed for the FreeBSD Project by Jonathan Lemon + * and McAfee Research, the Security Research Division of McAfee, Inc. under + * DARPA/SPAWAR contract N66001-01-C-8035 ("CBOSS"), as part of the + * DARPA CHATS research program. [2001 McAfee, Inc.] + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* + * when we're under synflood, we use syncookies to prevent state table + * exhaustion. Trigger for the synflood mode is the number of half-open + * connections in the state table. + * We leave synflood mode when the number of half-open states - including + * in-flight syncookies - drops far enough again + */ + +/* + * syncookie enabled Initial Sequence Number: + * 24 bit MAC + * 3 bit WSCALE index + * 3 bit MSS index + * 1 bit SACK permitted + * 1 bit odd/even secret + * + * References: + * RFC4987 TCP SYN Flooding Attacks and Common Mitigations + * http://cr.yp.to/syncookies.html (overview) + * http://cr.yp.to/syncookies/archive (details) + */ + +#include "pflog.h" + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/mbuf.h> +#include <sys/filio.h> +#include <sys/socket.h> +#include <sys/socketvar.h> +#include <sys/kernel.h> +#include <sys/time.h> +#include <sys/pool.h> +#include <sys/proc.h> +#include <sys/rwlock.h> +#include <sys/syslog.h> + +#include <net/if.h> +#include <net/if_var.h> +#include <net/if_types.h> +#include <net/route.h> + +#include <netinet/in.h> +#include <netinet/ip.h> +#include <netinet/ip_var.h> +#include <netinet/tcp.h> +#include <netinet/tcp_seq.h> +#include <netinet/udp.h> +#include <netinet/ip_icmp.h> +#include <netinet/in_pcb.h> +#include <netinet/tcp_timer.h> +#include <netinet/tcp_var.h> +#include <netinet/tcp_fsm.h> +#include <netinet/udp_var.h> +#include <netinet/icmp_var.h> +#include <netinet/ip_divert.h> + +#include <net/pfvar.h> +#include <net/pfvar_priv.h> + +#if NPFLOG > 0 +#include <net/if_pflog.h> +#endif /* NPFLOG > 0 */ + +union pf_syncookie { + uint8_t cookie; + struct { + uint8_t oddeven:1, + sack_ok:1, + wscale_idx:3, + mss_idx:3; + } flags; +}; + +#define PF_SYNCOOKIE_SECRET_SIZE 16 +#define PF_SYNCOOKIE_SECRET_LIFETIME 15 /* seconds */ + +static struct { + struct timeout keytimeout; + volatile uint oddeven; + uint8_t key[2][PF_SYNCOOKIE_SECRET_SIZE]; + uint32_t hiwat; /* absolute; # of states */ + uint32_t lowat; +} pf_syncookie_status; + +void pf_syncookie_rotate(void *); +void pf_syncookie_newkey(void); +uint32_t pf_syncookie_mac(struct pf_pdesc *, union pf_syncookie, + uint32_t); +uint32_t pf_syncookie_generate(struct pf_pdesc *, uint16_t); + +void +pf_syncookies_init(void) +{ + timeout_set(&pf_syncookie_status.keytimeout, + pf_syncookie_rotate, NULL); + pf_syncookie_status.hiwat = PFSTATE_HIWAT/4; + pf_syncookie_status.lowat = PFSTATE_HIWAT/8; + pf_syncookies_setmode(PF_SYNCOOKIES_NEVER); +} + +int +pf_syncookies_setmode(u_int8_t mode) +{ + if (mode > PF_SYNCOOKIES_MODE_MAX) + return (EINVAL); + + if (pf_status.syncookies_mode == mode) + return (0); + + pf_status.syncookies_mode = mode; + if (pf_status.syncookies_mode == PF_SYNCOOKIES_ALWAYS) { + pf_syncookie_newkey(); + pf_status.syncookies_active = 1; + } + return (0); +} + +int +pf_syncookies_setwats(u_int32_t hiwat, u_int32_t lowat) +{ + if (lowat > hiwat) + return (EINVAL); + + pf_syncookie_status.hiwat = hiwat; + pf_syncookie_status.lowat = lowat; + return (0); +} + +int +pf_synflood_check(struct pf_pdesc *pd) +{ + KASSERT (pd->proto == IPPROTO_TCP); + + if (pd->m && (pd->m->m_pkthdr.pf.tag & PF_TAG_SYNCOOKIE_RECREATED)) + return (0); + + if (pf_status.syncookies_mode != PF_SYNCOOKIES_ADAPTIVE) + return (pf_status.syncookies_mode); + + if (!pf_status.syncookies_active && + pf_status.states_halfopen > pf_syncookie_status.hiwat) { + pf_syncookie_newkey(); + pf_status.syncookies_active = 1; + DPFPRINTF(LOG_WARNING, + "synflood detected, enabling syncookies"); + } + + return (pf_status.syncookies_active); +} + +void +pf_syncookie_send(struct pf_pdesc *pd) +{ + uint16_t mss; + uint32_t iss; + + mss = max(tcp_mssdflt, pf_get_mss(pd)); + iss = pf_syncookie_generate(pd, mss); + pf_send_tcp(NULL, pd->af, pd->dst, pd->src, *pd->dport, *pd->sport, + iss, ntohl(pd->hdr.tcp.th_seq) + 1, TH_SYN|TH_ACK, 0, mss, + 0, 1, 0, pd->rdomain); + pf_status.syncookies_inflight[pf_syncookie_status.oddeven]++; +} + +uint8_t +pf_syncookie_validate(struct pf_pdesc *pd) +{ + uint32_t hash, ack, seq; + union pf_syncookie cookie; + + KASSERT(pd->proto == IPPROTO_TCP); + + seq = ntohl(pd->hdr.tcp.th_seq) - 1; + ack = ntohl(pd->hdr.tcp.th_ack) - 1; + cookie.cookie = (ack & 0xff) ^ (ack >> 24); + hash = pf_syncookie_mac(pd, cookie, seq); + + if ((ack & ~0xff) != (hash & ~0xff)) + return (0); + + pf_status.syncookies_inflight[cookie.flags.oddeven]--; + return (1); +} + +/* + * all following functions private + */ +void +pf_syncookie_rotate(void *arg) +{ + /* do we want to disable syncookies? */ + if (pf_status.syncookies_active && + ((pf_status.syncookies_mode == PF_SYNCOOKIES_ADAPTIVE && + pf_status.states_halfopen + pf_status.syncookies_inflight[0] + + pf_status.syncookies_inflight[1] < pf_syncookie_status.lowat) || + pf_status.syncookies_mode == PF_SYNCOOKIES_NEVER)) { + pf_status.syncookies_active = 0; + DPFPRINTF(LOG_WARNING, "syncookies disabled"); + } + + /* nothing in flight any more? delete keys and return */ + if (!pf_status.syncookies_active && + pf_status.syncookies_inflight[0] == 0 && + pf_status.syncookies_inflight[1] == 0) { + memset(pf_syncookie_status.key[0], 0, PF_SYNCOOKIE_SECRET_SIZE); + memset(pf_syncookie_status.key[1], 0, PF_SYNCOOKIE_SECRET_SIZE); + return; + } + + /* new key, including timeout */ + pf_syncookie_newkey(); +} + +void +pf_syncookie_newkey(void) +{ + pf_syncookie_status.oddeven = (pf_syncookie_status.oddeven + 1) & 0x1; + pf_status.syncookies_inflight[pf_syncookie_status.oddeven] = 0; + arc4random_buf(pf_syncookie_status.key[pf_syncookie_status.oddeven], + PF_SYNCOOKIE_SECRET_SIZE); + timeout_add_sec(&pf_syncookie_status.keytimeout, + PF_SYNCOOKIE_SECRET_LIFETIME); +} + +/* + * Distribution and probability of certain MSS values. Those in between are + * rounded down to the next lower one. + * [An Analysis of TCP Maximum Segment Sizes, S. Alcock and R. Nelson, 2011] + * .2% .3% 5% 7% 7% 20% 15% 45% + */ +static int pf_syncookie_msstab[] = + { 216, 536, 1200, 1360, 1400, 1440, 1452, 1460 }; + +/* + * Distribution and probability of certain WSCALE values. + * The absence of the WSCALE option is encoded with index zero. + * [WSCALE values histograms, Allman, 2012] + * X 10 10 35 5 6 14 10% by host + * X 11 4 5 5 18 49 3% by connections + */ +static int pf_syncookie_wstab[] = { 0, 0, 1, 2, 4, 6, 7, 8 }; + +uint32_t +pf_syncookie_mac(struct pf_pdesc *pd, union pf_syncookie cookie, uint32_t seq) +{ + SIPHASH_CTX ctx; + uint32_t siphash[2]; + + KASSERT(pd->proto == IPPROTO_TCP); + + SipHash24_Init(&ctx, + (SIPHASH_KEY *)&pf_syncookie_status.key[cookie.flags.oddeven]); + + switch (pd->af) { + case AF_INET: + SipHash24_Update(&ctx, pd->src, sizeof(pd->src->v4)); + SipHash24_Update(&ctx, pd->dst, sizeof(pd->dst->v4)); + break; + case AF_INET6: + SipHash24_Update(&ctx, pd->src, sizeof(pd->src->v6)); + SipHash24_Update(&ctx, pd->dst, sizeof(pd->dst->v6)); + break; + default: + panic("unknown address family"); + } + + SipHash24_Update(&ctx, pd->sport, sizeof(*pd->sport)); + SipHash24_Update(&ctx, pd->dport, sizeof(*pd->dport)); + SipHash24_Update(&ctx, &seq, sizeof(seq)); + SipHash24_Update(&ctx, &cookie, sizeof(cookie)); + SipHash24_Final((uint8_t *)&siphash, &ctx); + + return (siphash[0] ^ siphash[1]); +} + +uint32_t +pf_syncookie_generate(struct pf_pdesc *pd, uint16_t mss) +{ + uint8_t i, wscale; + uint32_t iss, hash; + union pf_syncookie cookie; + + cookie.cookie = 0; + + /* map MSS */ + for (i = nitems(pf_syncookie_msstab) - 1; + pf_syncookie_msstab[i] > mss && i > 0; i--) + /* nada */; + cookie.flags.mss_idx = i; + + /* map WSCALE */ + wscale = pf_get_wscale(pd); + for (i = nitems(pf_syncookie_wstab) - 1; + pf_syncookie_wstab[i] > wscale && i > 0; i--) + /* nada */; + cookie.flags.wscale_idx = i; + cookie.flags.sack_ok = 0; /* XXX */ + + cookie.flags.oddeven = pf_syncookie_status.oddeven; + hash = pf_syncookie_mac(pd, cookie, ntohl(pd->hdr.tcp.th_seq)); + + /* + * Put the flags into the hash and XOR them to get better ISS number + * variance. This doesn't enhance the cryptographic strength and is + * done to prevent the 8 cookie bits from showing up directly on the + * wire. + */ + iss = hash & ~0xff; + iss |= cookie.cookie ^ (hash >> 24); + + return (iss); +} + +struct mbuf * +pf_syncookie_recreate_syn(struct pf_pdesc *pd) +{ + uint8_t wscale; + uint16_t mss; + uint32_t ack, seq; + union pf_syncookie cookie; + + seq = ntohl(pd->hdr.tcp.th_seq) - 1; + ack = ntohl(pd->hdr.tcp.th_ack) - 1; + cookie.cookie = (ack & 0xff) ^ (ack >> 24); + + if (cookie.flags.mss_idx >= nitems(pf_syncookie_msstab) || + cookie.flags.wscale_idx >= nitems(pf_syncookie_wstab)) + return (NULL); + + mss = pf_syncookie_msstab[cookie.flags.mss_idx]; + wscale = pf_syncookie_wstab[cookie.flags.wscale_idx]; + + return (pf_build_tcp(NULL, pd->af, pd->src, pd->dst, *pd->sport, + *pd->dport, seq, 0, TH_SYN, wscale, mss, pd->ttl, 0, + PF_TAG_SYNCOOKIE_RECREATED, cookie.flags.sack_ok, pd->rdomain)); +} + +#define PF_TCPOPTLEN_SACKPERMITTED 2 + +int +pf_check_sack(struct pf_pdesc *pd) +{ + struct tcphdr *th = &pd->hdr.tcp; + int hlen = (th->th_off << 2) - sizeof(*th); + uint8_t opts[MAX_TCPOPTLEN], *opt = opts; + int olen; + + if (hlen < PF_TCPOPTLEN_SACKPERMITTED || hlen > MAX_TCPOPTLEN || + !pf_pull_hdr(pd->m, pd->off + sizeof(*th), opts, hlen, NULL, NULL, + pd->af)) + return (0); + + while (hlen >= PF_TCPOPTLEN_SACKPERMITTED) { + olen = opt[1]; + switch (*opt) { + case TCPOPT_EOL: /* FALLTHROUGH */ + case TCPOPT_NOP: + opt++; + hlen--; + break; + case TCPOPT_SACK_PERMITTED: + return (1); + default: + if (olen < 2) + olen = 2; + hlen -= olen; + opt += olen; + } + } + + return (0); +} |
