From: henning Date: Tue, 6 Feb 2018 23:37:24 +0000 (+0000) Subject: syncookies implementation to be used in pf, based on the FreeBSD one by X-Git-Url: http://artulab.com/gitweb/?a=commitdiff_plain;h=d28b993ccbb9662df1a3635bfa10b530fd5baf65;p=openbsd syncookies implementation to be used in pf, based on the FreeBSD one by Andre Oppermann, heavily adjusted for pf instead of stack use and with entirely rewritten timeout machinery and new hashing with bits from sashan, widely discussed with the other network hackers --- 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 + * Copyright (c) 2016 Alexandr Nedvedicky + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#if NPFLOG > 0 +#include +#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); +}