/*- * SPDX-License-Identifier: ISC * * Copyright (C) 2015-2021 Jason A. Donenfeld . All Rights Reserved. * Copyright (C) 2019-2021 Matt Dunwoodie * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "opt_inet6.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "wg_cookie.h" /* Constants for cookies */ #define COOKIE_KEY_SIZE BLAKE2S_KEY_SIZE #define COOKIE_SECRET_SIZE 32 #define COOKIE_MAC1_KEY_LABEL "mac1----" #define COOKIE_COOKIE_KEY_LABEL "cookie--" #define COOKIE_SECRET_MAX_AGE 120 #define COOKIE_SECRET_LATENCY 5 /* Constants for initiation rate limiting */ #define RATELIMIT_SIZE (1 << 13) #define RATELIMIT_MASK (RATELIMIT_SIZE - 1) #define RATELIMIT_SIZE_MAX (RATELIMIT_SIZE * 8) #define NSEC_PER_SEC 1000000000LL #define INITIATIONS_PER_SECOND 20 #define INITIATIONS_BURSTABLE 5 #define INITIATION_COST (NSEC_PER_SEC / INITIATIONS_PER_SECOND) #define TOKEN_MAX (INITIATION_COST * INITIATIONS_BURSTABLE) #define ELEMENT_TIMEOUT 1 /* second */ #define IPV4_MASK_SIZE 4 /* Use all 4 bytes of IPv4 address */ #define IPV6_MASK_SIZE 8 /* Use top 8 bytes (/64) of IPv6 address */ struct cookie_maker { uint8_t cm_mac1_key[COOKIE_KEY_SIZE]; uint8_t cm_cookie_key[COOKIE_KEY_SIZE]; struct lock cm_lock; bool cm_cookie_valid; uint8_t cm_cookie[COOKIE_COOKIE_SIZE]; struct timespec cm_cookie_birthdate; /* nanouptime */ bool cm_mac1_sent; uint8_t cm_mac1_last[COOKIE_MAC_SIZE]; }; struct cookie_checker { struct lock cc_key_lock; uint8_t cc_mac1_key[COOKIE_KEY_SIZE]; uint8_t cc_cookie_key[COOKIE_KEY_SIZE]; struct lock cc_secret_mtx; struct timespec cc_secret_birthdate; /* nanouptime */ uint8_t cc_secret[COOKIE_SECRET_SIZE]; }; struct ratelimit_key { uint8_t ip[IPV6_MASK_SIZE]; }; struct ratelimit_entry { LIST_ENTRY(ratelimit_entry) r_entry; struct ratelimit_key r_key; struct timespec r_last_time; /* nanouptime */ uint64_t r_tokens; }; struct ratelimit { uint8_t rl_secret[SIPHASH_KEY_LENGTH]; struct lock rl_mtx; struct callout rl_gc; LIST_HEAD(, ratelimit_entry) rl_table[RATELIMIT_SIZE]; size_t rl_table_num; bool rl_initialized; }; static void macs_mac1(struct cookie_macs *, const void *, size_t, const uint8_t[COOKIE_KEY_SIZE]); static void macs_mac2(struct cookie_macs *, const void *, size_t, const uint8_t[COOKIE_COOKIE_SIZE]); static void make_cookie(struct cookie_checker *, uint8_t[COOKIE_COOKIE_SIZE], const struct sockaddr *); static void precompute_key(uint8_t[COOKIE_KEY_SIZE], const uint8_t[COOKIE_INPUT_SIZE], const uint8_t *, size_t); static void ratelimit_init(struct ratelimit *); static void ratelimit_deinit(struct ratelimit *); static void ratelimit_gc_callout(void *); static void ratelimit_gc_schedule(struct ratelimit *); static void ratelimit_gc(struct ratelimit *, bool); static int ratelimit_allow(struct ratelimit *, const struct sockaddr *); static struct ratelimit ratelimit_v4; #ifdef INET6 static struct ratelimit ratelimit_v6; #endif static struct objcache *ratelimit_zone; static MALLOC_DEFINE(M_WG_RATELIMIT, "WG ratelimit", "wireguard ratelimit"); static MALLOC_DEFINE(M_WG_COOKIE, "WG cookie", "wireguard cookie"); static inline uint64_t siphash13(const uint8_t key[SIPHASH_KEY_LENGTH], const void *src, size_t len) { SIPHASH_CTX ctx; return SipHashX(&ctx, 1, 3, key, src, len); } static inline bool timer_expired(const struct timespec *birthdate, time_t sec, long nsec) { struct timespec uptime; struct timespec expire = { .tv_sec = sec, .tv_nsec = nsec }; if (birthdate->tv_sec == 0 && birthdate->tv_nsec == 0) return (true); getnanouptime(&uptime); timespecadd(birthdate, &expire, &expire); return timespeccmp(&uptime, &expire, >); } /*----------------------------------------------------------------------------*/ /* Public Functions */ int cookie_init(void) { ratelimit_zone = objcache_create_simple( M_WG_RATELIMIT, sizeof(struct ratelimit_entry)); if (ratelimit_zone == NULL) return (ENOMEM); ratelimit_init(&ratelimit_v4); #ifdef INET6 ratelimit_init(&ratelimit_v6); #endif return (0); } void cookie_deinit(void) { ratelimit_deinit(&ratelimit_v4); #ifdef INET6 ratelimit_deinit(&ratelimit_v6); #endif if (ratelimit_zone != NULL) objcache_destroy(ratelimit_zone); } struct cookie_checker * cookie_checker_alloc(void) { struct cookie_checker *cc; cc = kmalloc(sizeof(*cc), M_WG_COOKIE, M_WAITOK | M_ZERO); lockinit(&cc->cc_key_lock, "cookie_checker_key", 0, 0); lockinit(&cc->cc_secret_mtx, "cookie_checker_secret", 0, 0); return (cc); } void cookie_checker_free(struct cookie_checker *cc) { lockuninit(&cc->cc_key_lock); lockuninit(&cc->cc_secret_mtx); explicit_bzero(cc, sizeof(*cc)); kfree(cc, M_WG_COOKIE); } void cookie_checker_update(struct cookie_checker *cc, const uint8_t key[COOKIE_INPUT_SIZE]) { lockmgr(&cc->cc_key_lock, LK_EXCLUSIVE); if (key != NULL) { precompute_key(cc->cc_mac1_key, key, COOKIE_MAC1_KEY_LABEL, sizeof(COOKIE_MAC1_KEY_LABEL) - 1); precompute_key(cc->cc_cookie_key, key, COOKIE_COOKIE_KEY_LABEL, sizeof(COOKIE_COOKIE_KEY_LABEL) - 1); } else { bzero(cc->cc_mac1_key, sizeof(cc->cc_mac1_key)); bzero(cc->cc_cookie_key, sizeof(cc->cc_cookie_key)); } lockmgr(&cc->cc_key_lock, LK_RELEASE); } void cookie_checker_create_payload(struct cookie_checker *cc, const struct cookie_macs *macs, uint8_t nonce[COOKIE_NONCE_SIZE], uint8_t ecookie[COOKIE_ENCRYPTED_SIZE], const struct sockaddr *sa) { uint8_t cookie[COOKIE_COOKIE_SIZE]; make_cookie(cc, cookie, sa); karc4random_buf(nonce, COOKIE_NONCE_SIZE); lockmgr(&cc->cc_key_lock, LK_SHARED); xchacha20poly1305_encrypt(ecookie, cookie, COOKIE_COOKIE_SIZE, macs->mac1, COOKIE_MAC_SIZE, nonce, cc->cc_cookie_key); lockmgr(&cc->cc_key_lock, LK_RELEASE); explicit_bzero(cookie, sizeof(cookie)); } int cookie_checker_validate_macs(struct cookie_checker *cc, const struct cookie_macs *macs, const void *buf, size_t len, bool check_cookie, const struct sockaddr *sa) { struct cookie_macs our_macs; uint8_t cookie[COOKIE_COOKIE_SIZE]; /* Validate incoming MACs */ lockmgr(&cc->cc_key_lock, LK_SHARED); macs_mac1(&our_macs, buf, len, cc->cc_mac1_key); lockmgr(&cc->cc_key_lock, LK_RELEASE); /* If mac1 is invald, we want to drop the packet */ if (timingsafe_bcmp(our_macs.mac1, macs->mac1, COOKIE_MAC_SIZE) != 0) return (EINVAL); if (check_cookie) { make_cookie(cc, cookie, sa); macs_mac2(&our_macs, buf, len, cookie); /* If mac2 is invalid, we want to send a cookie response. */ if (timingsafe_bcmp(our_macs.mac2, macs->mac2, COOKIE_MAC_SIZE) != 0) return (EAGAIN); /* * If the mac2 is valid, we may want to rate limit the peer. * ratelimit_allow() will return either 0 or ECONNREFUSED, * implying there is no ratelimiting, or we should ratelimit * (refuse), respectively. */ if (sa->sa_family == AF_INET) return ratelimit_allow(&ratelimit_v4, sa); #ifdef INET6 else if (sa->sa_family == AF_INET6) return ratelimit_allow(&ratelimit_v6, sa); #endif else return (EAFNOSUPPORT); } return (0); } struct cookie_maker * cookie_maker_alloc(const uint8_t key[COOKIE_INPUT_SIZE]) { struct cookie_maker *cm; cm = kmalloc(sizeof(*cm), M_WG_COOKIE, M_WAITOK | M_ZERO); precompute_key(cm->cm_mac1_key, key, COOKIE_MAC1_KEY_LABEL, sizeof(COOKIE_MAC1_KEY_LABEL) - 1); precompute_key(cm->cm_cookie_key, key, COOKIE_COOKIE_KEY_LABEL, sizeof(COOKIE_COOKIE_KEY_LABEL) - 1); lockinit(&cm->cm_lock, "cookie_maker", 0, 0); return (cm); } void cookie_maker_free(struct cookie_maker *cm) { lockuninit(&cm->cm_lock); explicit_bzero(cm, sizeof(*cm)); kfree(cm, M_WG_COOKIE); } int cookie_maker_consume_payload(struct cookie_maker *cm, const uint8_t nonce[COOKIE_NONCE_SIZE], const uint8_t ecookie[COOKIE_ENCRYPTED_SIZE]) { uint8_t cookie[COOKIE_COOKIE_SIZE]; int ret = 0; lockmgr(&cm->cm_lock, LK_SHARED); if (!cm->cm_mac1_sent) { ret = ETIMEDOUT; goto out; } if (!xchacha20poly1305_decrypt(cookie, ecookie, COOKIE_ENCRYPTED_SIZE, cm->cm_mac1_last, COOKIE_MAC_SIZE, nonce, cm->cm_cookie_key)) { ret = EINVAL; goto out; } lockmgr(&cm->cm_lock, LK_RELEASE); lockmgr(&cm->cm_lock, LK_EXCLUSIVE); memcpy(cm->cm_cookie, cookie, COOKIE_COOKIE_SIZE); getnanouptime(&cm->cm_cookie_birthdate); cm->cm_cookie_valid = true; cm->cm_mac1_sent = false; out: lockmgr(&cm->cm_lock, LK_RELEASE); return (ret); } void cookie_maker_mac(struct cookie_maker *cm, struct cookie_macs *macs, const void *buf, size_t len) { lockmgr(&cm->cm_lock, LK_EXCLUSIVE); macs_mac1(macs, buf, len, cm->cm_mac1_key); memcpy(cm->cm_mac1_last, macs->mac1, COOKIE_MAC_SIZE); cm->cm_mac1_sent = true; if (cm->cm_cookie_valid && !timer_expired(&cm->cm_cookie_birthdate, COOKIE_SECRET_MAX_AGE - COOKIE_SECRET_LATENCY, 0)) { macs_mac2(macs, buf, len, cm->cm_cookie); } else { bzero(macs->mac2, COOKIE_MAC_SIZE); cm->cm_cookie_valid = false; } lockmgr(&cm->cm_lock, LK_RELEASE); } /*----------------------------------------------------------------------------*/ /* Private functions */ static void precompute_key(uint8_t key[COOKIE_KEY_SIZE], const uint8_t input[COOKIE_INPUT_SIZE], const uint8_t *label, size_t label_len) { struct blake2s_state blake; blake2s_init(&blake, COOKIE_KEY_SIZE); blake2s_update(&blake, label, label_len); blake2s_update(&blake, input, COOKIE_INPUT_SIZE); blake2s_final(&blake, key); } static void macs_mac1(struct cookie_macs *macs, const void *buf, size_t len, const uint8_t key[COOKIE_KEY_SIZE]) { struct blake2s_state state; blake2s_init_key(&state, COOKIE_MAC_SIZE, key, COOKIE_KEY_SIZE); blake2s_update(&state, buf, len); blake2s_final(&state, macs->mac1); } static void macs_mac2(struct cookie_macs *macs, const void *buf, size_t len, const uint8_t key[COOKIE_COOKIE_SIZE]) { struct blake2s_state state; blake2s_init_key(&state, COOKIE_MAC_SIZE, key, COOKIE_COOKIE_SIZE); blake2s_update(&state, buf, len); blake2s_update(&state, macs->mac1, COOKIE_MAC_SIZE); blake2s_final(&state, macs->mac2); } static void make_cookie(struct cookie_checker *cc, uint8_t cookie[COOKIE_COOKIE_SIZE], const struct sockaddr *sa) { struct blake2s_state state; lockmgr(&cc->cc_secret_mtx, LK_EXCLUSIVE); if (timer_expired(&cc->cc_secret_birthdate, COOKIE_SECRET_MAX_AGE, 0)) { karc4random_buf(cc->cc_secret, COOKIE_SECRET_SIZE); getnanouptime(&cc->cc_secret_birthdate); } blake2s_init_key(&state, COOKIE_COOKIE_SIZE, cc->cc_secret, COOKIE_SECRET_SIZE); lockmgr(&cc->cc_secret_mtx, LK_RELEASE); if (sa->sa_family == AF_INET) { const struct sockaddr_in *sin = (const void *)sa; blake2s_update(&state, (const uint8_t *)&sin->sin_addr, sizeof(sin->sin_addr)); blake2s_update(&state, (const uint8_t *)&sin->sin_port, sizeof(sin->sin_port)); blake2s_final(&state, cookie); #ifdef INET6 } else if (sa->sa_family == AF_INET6) { const struct sockaddr_in6 *sin6 = (const void *)sa; blake2s_update(&state, (const uint8_t *)&sin6->sin6_addr, sizeof(sin6->sin6_addr)); blake2s_update(&state, (const uint8_t *)&sin6->sin6_port, sizeof(sin6->sin6_port)); blake2s_final(&state, cookie); #endif } else { karc4random_buf(cookie, COOKIE_COOKIE_SIZE); } } static void ratelimit_init(struct ratelimit *rl) { size_t i; bzero(rl, sizeof(*rl)); lockinit(&rl->rl_mtx, "ratelimit_lock", 0, 0); callout_init_lk(&rl->rl_gc, &rl->rl_mtx); karc4random_buf(rl->rl_secret, sizeof(rl->rl_secret)); for (i = 0; i < RATELIMIT_SIZE; i++) LIST_INIT(&rl->rl_table[i]); rl->rl_table_num = 0; rl->rl_initialized = true; } static void ratelimit_deinit(struct ratelimit *rl) { if (!rl->rl_initialized) return; lockmgr(&rl->rl_mtx, LK_EXCLUSIVE); callout_stop(&rl->rl_gc); ratelimit_gc(rl, true); lockmgr(&rl->rl_mtx, LK_RELEASE); lockuninit(&rl->rl_mtx); rl->rl_initialized = false; } static void ratelimit_gc_callout(void *_rl) { /* callout will lock for us */ ratelimit_gc(_rl, false); } static void ratelimit_gc_schedule(struct ratelimit *rl) { /* * Trigger another GC if needed. There is no point calling GC if * there are no entries in the table. We also want to ensure that * GC occurs on a regular interval, so don't override a currently * pending GC. * * In the case of a forced ratelimit_gc(), there will be no entries * left so we will not schedule another GC. */ if (rl->rl_table_num > 0 && !callout_pending(&rl->rl_gc)) callout_reset(&rl->rl_gc, ELEMENT_TIMEOUT * hz, ratelimit_gc_callout, rl); } static void ratelimit_gc(struct ratelimit *rl, bool force) { struct ratelimit_entry *r, *tr; struct timespec expiry; size_t i; KKASSERT(lockstatus(&rl->rl_mtx, curthread) == LK_EXCLUSIVE); if (rl->rl_table_num == 0) return; getnanouptime(&expiry); expiry.tv_sec -= ELEMENT_TIMEOUT; for (i = 0; i < RATELIMIT_SIZE; i++) { LIST_FOREACH_MUTABLE(r, &rl->rl_table[i], r_entry, tr) { if (force || timespeccmp(&r->r_last_time, &expiry, <)) { rl->rl_table_num--; LIST_REMOVE(r, r_entry); objcache_put(ratelimit_zone, r); } } } ratelimit_gc_schedule(rl); } static int ratelimit_allow(struct ratelimit *rl, const struct sockaddr *sa) { struct timespec diff; struct ratelimit_entry *r; struct ratelimit_key key = { 0 }; uint64_t bucket, tokens; size_t len; int ret = ECONNREFUSED; if (sa->sa_family == AF_INET) { len = IPV4_MASK_SIZE; memcpy(key.ip, &((const struct sockaddr_in *)sa)->sin_addr, len); } #ifdef INET6 else if (sa->sa_family == AF_INET6) { len = IPV6_MASK_SIZE; memcpy(key.ip, &((const struct sockaddr_in6 *)sa)->sin6_addr, len); } #endif else { return (ret); } bucket = siphash13(rl->rl_secret, &key, len) & RATELIMIT_MASK; lockmgr(&rl->rl_mtx, LK_EXCLUSIVE); LIST_FOREACH(r, &rl->rl_table[bucket], r_entry) { if (memcmp(&r->r_key, &key, len) != 0) continue; /* * Found an entry for the endpoint. We apply standard token * bucket, by calculating the time lapsed since last_time, * adding that, ensuring that we cap the tokens at TOKEN_MAX. * If the endpoint has no tokens left (i.e., tokens < * INITIATION_COST) then we block the request. Otherwise, we * subtract the INITITIATION_COST and return OK. */ diff = r->r_last_time; getnanouptime(&r->r_last_time); timespecsub(&r->r_last_time, &diff, &diff); tokens = r->r_tokens; tokens += diff.tv_sec * NSEC_PER_SEC + diff.tv_nsec; if (tokens > TOKEN_MAX) tokens = TOKEN_MAX; if (tokens >= INITIATION_COST) { r->r_tokens = tokens - INITIATION_COST; goto ok; } else { r->r_tokens = tokens; goto error; } } /* * Didn't have an entry for the endpoint, so let's add one if we * have space. */ if (rl->rl_table_num >= RATELIMIT_SIZE_MAX) goto error; if ((r = objcache_get(ratelimit_zone, M_NOWAIT)) == NULL) goto error; bzero(r, sizeof(*r)); /* objcache_get() doesn't ensure M_ZERO. */ rl->rl_table_num++; /* Insert the new entry and initialize it. */ LIST_INSERT_HEAD(&rl->rl_table[bucket], r, r_entry); r->r_key = key; r->r_tokens = TOKEN_MAX - INITIATION_COST; getnanouptime(&r->r_last_time); /* We've added a new entry; let's trigger GC. */ ratelimit_gc_schedule(rl); ok: ret = 0; error: lockmgr(&rl->rl_mtx, LK_RELEASE); return (ret); } #ifdef WG_SELFTESTS #include "selftest/cookie.c" #endif /* WG_SELFTESTS */