From 4c311c928b72f618bb6fb12fbd5aad73462f6ce8 Mon Sep 17 00:00:00 2001 From: Timo Buhrmester Date: Wed, 29 Jun 2016 14:54:59 +0000 Subject: [PATCH 01/10] lib.ipsec.esp: Anti-replay (RFC 4303 App. A) --- src/lib/ipsec/esp.lua | 34 ++++- src/lib/ipsec/track_seq_no.c | 250 +++++++++++++++++++++++++++++++++-- src/lib/ipsec/track_seq_no.h | 10 +- 3 files changed, 281 insertions(+), 13 deletions(-) diff --git a/src/lib/ipsec/esp.lua b/src/lib/ipsec/esp.lua index 5c6c0c07f7..f6fbf795f7 100644 --- a/src/lib/ipsec/esp.lua +++ b/src/lib/ipsec/esp.lua @@ -39,7 +39,19 @@ local ffi = require("ffi") local C = ffi.C require("lib.ipsec.track_seq_no_h") +ffi.cdef[[ +struct arepl_state +{ + uint8_t *win; + uint32_t W; + uint64_t T; +}; + +bool arepl_pass(uint32_t seq_hi, uint32_t seq_lo, struct arepl_state *st); +void arepl_accept(uint32_t seq_hi, uint32_t seq_lo, struct arepl_state *st); +uint32_t arepl_infer_seq_hi(uint32_t seq_lo, struct arepl_state *st); +]] local ETHERNET_SIZE = ethernet:sizeof() local IPV6_SIZE = ipv6:sizeof() local PAYLOAD_OFFSET = ETHERNET_SIZE + IPV6_SIZE @@ -123,6 +135,14 @@ function esp_v6_decrypt:new (conf) o.CTEXT_OFFSET = ESP_SIZE + gcm.IV_SIZE o.PLAIN_OVERHEAD = PAYLOAD_OFFSET + ESP_SIZE + gcm.IV_SIZE + gcm.AUTH_SIZE o.window_size = conf.window_size or 128 + + -- XXX is this garbage collected? + o.arepl_state = ffi.new("struct arepl_state") + + -- XXX identical to new(uint8_t[?], WINSZ)? GC? + o.arepl_state.win = ffi.new("uint8_t[" .. o.window_size / 8 .. "]") + + o.arepl_state.W = o.window_size return setmetatable(o, {__index=esp_v6_decrypt}) end @@ -143,10 +163,17 @@ function esp_v6_decrypt:decapsulate (p) local ctext_start = payload + self.CTEXT_OFFSET local ctext_length = length - self.PLAIN_OVERHEAD local seq_low = self.esp:seq_no() - local seq_high = C.track_seq_no(seq_low, self.seq:low(), self.seq:high(), self.window_size) + local seq_high = ffi.C.arepl_infer_seq_hi(seq_low, self.arepl_state) + + if not ffi.C.arepl_pass(seq_high, seq_low, self.arepl_state) then + return false + end + + -- XXX does decryption actually verify that seq_low was unspoofed? if gcm:decrypt(ctext_start, seq_low, seq_high, iv_start, ctext_start, ctext_length) then self.seq:low(seq_low) self.seq:high(seq_high) + ffi.C.arepl_accept(seq_high, seq_low, self.arepl_state) local esp_tail_start = ctext_start + ctext_length - ESP_TAIL_SIZE self.esp_tail:new_from_mem(esp_tail_start, ESP_TAIL_SIZE) local ptext_length = ctext_length - self.esp_tail:pad_length() - ESP_TAIL_SIZE @@ -191,6 +218,9 @@ ABCDEFGHIJKLMNOPQRSTUVWXYZ assert(packet.length(p2) == packet.length(p) and C.memcmp(p, p2, packet.length(p)) == 0, "integrity check failed") + -- Check replayed packet XXX do this a lot more thoroughly + p2 = packet.clone(p_enc) + assert(not dec:decapsulate(p2), "replayed decapsulation succeeded") -- Check invalid packets. local p_invalid = packet.from_string("invalid") assert(not enc:encapsulate(p_invalid), "encapsulated invalid packet") @@ -217,6 +247,7 @@ ABCDEFGHIJKLMNOPQRSTUVWXYZ enc.seq:high(1) dec.seq:low(2^32 - dec.window_size) dec.seq:high(0) + ffi.C.arepl_accept(dec.seq:high(), dec.seq:low(), dec.arepl_state) local p3 = packet.clone(p) enc:encapsulate(p3) assert(dec:decapsulate(p3), @@ -228,6 +259,7 @@ ABCDEFGHIJKLMNOPQRSTUVWXYZ enc.seq:high(1) dec.seq:low(dec.window_size+1) dec.seq:high(1) + ffi.C.arepl_accept(dec.seq:high(), dec.seq:low(), dec.arepl_state) local p4 = packet.clone(p) enc:encapsulate(p4) assert(not dec:decapsulate(p4), diff --git a/src/lib/ipsec/track_seq_no.c b/src/lib/ipsec/track_seq_no.c index 135946442b..5ccc11468a 100644 --- a/src/lib/ipsec/track_seq_no.c +++ b/src/lib/ipsec/track_seq_no.c @@ -1,14 +1,242 @@ +// See https://tools.ietf.org/html/rfc4303#page-38 + +#include +#include +#include #include +#include +#include +#include -// See https://tools.ietf.org/html/rfc4303#page-38 -// This is a only partial implementation that attempts to keep track of the -// ESN counter, but does not detect replayed packets. -uint32_t track_seq_no (uint32_t seq_no, uint32_t Tl, uint32_t Th, uint32_t W) { - if (Tl >= W - 1) { // Case A - if (seq_no >= Tl - W + 1) return Th; - else return Th + 1; - } else { // Case B - if (seq_no >= Tl - W + 1) return Th - 1; - else return Th; - } +#define DEBUG 1 + +#ifdef DEBUG +#define DBG(FMT, ...) printf("arepl: " FMT "\n", __VA_ARGS__) +#else +#define DBG(FMT, ...) +#endif + +/* LO32/HI32: Get lower/upper 32 bit of a 64 bit value. + * Should be completely portable and endian-independent. + * It shouldn't be difficult for the compiler to recognize this as + * division/multiplication by powers of two and hence replace it with + * bit shifts, automagically in the right direction. + */ +#define LO32(U64) ((uint32_t)((uint64_t)(U64) % 4294967296ull)) // 2**32 +#define HI32(U64) ((uint32_t)((uint64_t)(U64) / 4294967296ull)) // 2**32 + +/* MK64: Likewise, make a 64 bit value from two 32 bit values */ +#define MK64(L, H) ((uint64_t)(((uint32_t)(H)) * 4294967296ull + ((uint32_t)(L)))) + + +/* struct arepl_state: Holds the state we need to keep for + * anti-replay purposes, that is, mainly a "window" of bits + * representing "have-seen" information about sequence numbers. + * + * `win` ends up pointing into an array of size `W`/8 so that + * it has a total of `W` bits, each representing the information + * whether we have or have not seen a packet with a particular + * sequence number. + * + * `W` is the window size in bits, `T` is the leading edge of our + * window, which is by definition the largest sequence number we have + * seen and verified so far. The window thus at any given time + * starts at sequence number `T`-`W`+1 and ends at `T`. + * Sequence numbers are being mapped to bit positions in `win` + * simply by calculating % `W` + */ +struct arepl_state +{ + uint8_t *win; + + uint32_t W; + uint64_t T; +}; + + +// public interface +bool arepl_pass(uint32_t seq_hi, uint32_t seq_lo, struct arepl_state *st); +void arepl_accept(uint32_t seq_hi, uint32_t seq_lo, struct arepl_state *st); +uint32_t arepl_infer_seq_hi(uint32_t seq_lo, struct arepl_state *st); + + +// local helpers +static void advance_wnd(struct arepl_state *st, uint64_t newT); +static void set_pktbit(struct arepl_state *st, uint64_t seq, bool on); +static bool get_pktbit(struct arepl_state *st, uint64_t seq); + + + +/* arepl_pass: Determine whether a packet with the sequence number + * made from `seq_hi` and `seq_lo` could be a legitimate packet. + * + * "Could", because we can't really tell if the received packet with + * this sequence number is in fact valid (since we haven't yet + * integrity-checked it - the sequence number may be spoofed), but we + * can give an authoritative "no" if we have already seen and accepted + * a packet with this number. + * + * If our answer is NOT "no", the caller will, provided the packet was + * valid, use arepl_accept() for us to mark the sequence number as seen. + */ +bool +arepl_pass(uint32_t seq_hi, uint32_t seq_lo, struct arepl_state *st) +{ + uint64_t seq = MK64(seq_lo, seq_hi); + + /* + * `seq` can never be less than the lower end of our window + * because `arepl_infer_seq_hi()` assumes the sender incremented their + * upper 32 bit of the sequence number whenever the lower 32 bit + * of it are less than the lower 32 bit of T. + * So the cases left to check are a) `seq` is inside the window + * and b) `seq` is ahead of the window. + */ + + if (seq <= st->T && get_pktbit(st, seq)) { + DBG("Rejecting replayed packet seq=%"PRIu64 + " (hi=%"PRIu32", lo=%"PRIu32"); T=%"PRIu64", W=%"PRIu32, + seq, seq_hi, seq_lo, st->T, st->W); + + return false; // replayed, reject + } + + /* + * From a sequence number point of view, this one looks fine; + * it is either an old packet we haven't seen yet, or the + * next/a future packet. + * + * Successful decryption later on will tell if the packet (and + * hence the sequence number) was legit, at which point arepl_accept() + * will be called, the have-seen bit representing this sequence number + * will be set and the window possibly advances. + */ + return true; } + +/* Signal that the packet received with this sequence number was + * in fact valid -- we record that we have seen it so as to prevent + * future replays of it. + */ +void +arepl_accept(uint32_t seq_hi, uint32_t seq_lo, struct arepl_state *st) +{ + uint64_t seq = MK64(seq_lo, seq_hi); + + uint64_t oldT = st->T; // XXX only for the DBG + + if (seq > st->T) + advance_wnd(st, seq); + + DBG("Accepting packet seq=%"PRIu64 + " (hi=%"PRIu32", lo=%"PRIu32"); T=%"PRIu64"->%"PRIu64 + " (+%"PRIu64"), W=%"PRIu32, + seq, seq_hi, seq_lo, oldT, st->T, st->T - oldT, st->W); + + set_pktbit(st, seq, 1); +} + +/* Guess what the upper 32 bit of a received half sequence number probably + * was, based on our window position and following the alogrithm in RFC 4303 App. A + */ +uint32_t +arepl_infer_seq_hi(uint32_t seq_lo, struct arepl_state *st) +{ + uint32_t Tl = LO32(st->T); + uint32_t Th = HI32(st->T); + + if (Tl >= st->W - 1) { // Case A + if (seq_lo >= Tl - st->W + 1) + return Th; + else + return Th + 1; + } else { // Case B + if (seq_lo >= Tl - st->W + 1) + return Th - 1; + else + return Th; + } +} + +// local helpers + +/* Advance the window so that the "head" bit corresponds to sequence + * number `newT`. + */ +static void +advance_wnd(struct arepl_state *st, uint64_t newT) +{ + uint64_t diff = newT - st->T; + + /* For advances greater than the window size, don't clear + * more bits than the window has */ + if (diff > st->W) + diff = st->W; + + uint64_t T = newT; + + /* Clear all bits corresponding to the sequence numbers that used to + * be ahead of, but are now inside our window since we haven't seen + * them yet */ + while (diff--) { + //DBG("kill %"PRIu64, T); + set_pktbit(st, T--, 0); + } + + /* Advance the window head */ + st->T = newT; +} + +/* Set/clear the bit in our window that corresponds to sequence number `seq` */ +static inline void +set_pktbit(struct arepl_state *st, uint64_t seq, bool on) +{ + size_t bitno = seq % st->W; + size_t blockno = bitno / 8; + bitno %= 8; + + /* First clear the bit, then maybe set it. This way + * we don't have to branch on `on` */ + st->win[blockno] &= ~(1u << bitno); + st->win[blockno] |= (uint8_t)on << bitno; +} + +/* Get the bit in our window that corresponds to sequence number `seq` */ +static inline bool +get_pktbit(struct arepl_state *st, uint64_t seq) +{ + size_t bitno = seq % st->W; + size_t blockno = bitno / 8; + bitno = bitno % 8; + + return st->win[blockno] & (1u << bitno); +} + +// done using ffi.C.new now +// struct arepl_state * +// arepl_alloc_state(uint32_t winsz) +// { +// struct arepl_state *st = malloc(sizeof *st); +// if (!st) +// return NULL; +// +// st->win = malloc(winsz / 8); +// if (!st->win) { +// free(st); +// return NULL; +// } +// +// memset(st->win, 0, winsz / 8); +// st->W = winsz; +// st->T = 0; +// +// return st; +// } +// +// void +// arepl_free_state(struct arepl_state *st) +// { +// if (st) +// free(st->win); +// free(st); +// } diff --git a/src/lib/ipsec/track_seq_no.h b/src/lib/ipsec/track_seq_no.h index 8d5e5105b2..8dde94e497 100644 --- a/src/lib/ipsec/track_seq_no.h +++ b/src/lib/ipsec/track_seq_no.h @@ -1 +1,9 @@ -uint32_t track_seq_no (uint32_t, uint32_t, uint32_t, uint32_t); +struct arepl_state; + +// done using ffi.C.new now +//struct arepl_state *arepl_alloc_state(uint32_t winsz); +//void arepl_free_state(struct arepl_state *st); + +bool arepl_pass(uint32_t seq_hi, uint32_t seq_lo, struct arepl_state *st); +void arepl_accept(uint32_t seq_hi, uint32_t seq_lo, struct arepl_state *st); +uint32_t arepl_infer_seq_hi(uint32_t seq_lo, struct arepl_state *st); From 325ddcab0b2f982e924975e1a9d8e15f627b7034 Mon Sep 17 00:00:00 2001 From: Max Rottenkolber Date: Thu, 30 Jun 2016 15:09:09 +0200 Subject: [PATCH 02/10] lib.ipsec.seq_no_t: remove unused `full' method. --- src/lib/ipsec/seq_no_t.lua | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/lib/ipsec/seq_no_t.lua b/src/lib/ipsec/seq_no_t.lua index a1b6f13db3..7f4e46da6c 100644 --- a/src/lib/ipsec/seq_no_t.lua +++ b/src/lib/ipsec/seq_no_t.lua @@ -6,8 +6,6 @@ local ffi = require("ffi") local seq_no_t = ffi.typeof("union { uint64_t no; uint32_t no32[2]; }") local seq_no = {} -function seq_no:full () return self.no end - local low, high if ffi.abi("le") then low = 0; high = 1 elseif ffi.abi("be") then low = 1; high = 0 end From b4a7968dfcb6c2e062ae035fe78dac6f101261a1 Mon Sep 17 00:00:00 2001 From: Max Rottenkolber Date: Thu, 30 Jun 2016 15:23:12 +0200 Subject: [PATCH 03/10] lib.ipsec.esp: refactor track_seq_no. --- src/lib/ipsec/esp.lua | 43 ++---- src/lib/ipsec/track_seq_no.c | 276 ++++++++--------------------------- src/lib/ipsec/track_seq_no.h | 11 +- 3 files changed, 69 insertions(+), 261 deletions(-) diff --git a/src/lib/ipsec/esp.lua b/src/lib/ipsec/esp.lua index f6fbf795f7..a688d8b29d 100644 --- a/src/lib/ipsec/esp.lua +++ b/src/lib/ipsec/esp.lua @@ -37,21 +37,10 @@ local seq_no_t = require("lib.ipsec.seq_no_t") local lib = require("core.lib") local ffi = require("ffi") local C = ffi.C -require("lib.ipsec.track_seq_no_h") - -ffi.cdef[[ -struct arepl_state -{ - uint8_t *win; - uint32_t W; - uint64_t T; -}; +require("lib.ipsec.track_seq_no_h") +local window_t = ffi.typeof("uint8_t[?]") -bool arepl_pass(uint32_t seq_hi, uint32_t seq_lo, struct arepl_state *st); -void arepl_accept(uint32_t seq_hi, uint32_t seq_lo, struct arepl_state *st); -uint32_t arepl_infer_seq_hi(uint32_t seq_lo, struct arepl_state *st); -]] local ETHERNET_SIZE = ethernet:sizeof() local IPV6_SIZE = ipv6:sizeof() local PAYLOAD_OFFSET = ETHERNET_SIZE + IPV6_SIZE @@ -135,14 +124,8 @@ function esp_v6_decrypt:new (conf) o.CTEXT_OFFSET = ESP_SIZE + gcm.IV_SIZE o.PLAIN_OVERHEAD = PAYLOAD_OFFSET + ESP_SIZE + gcm.IV_SIZE + gcm.AUTH_SIZE o.window_size = conf.window_size or 128 - - -- XXX is this garbage collected? - o.arepl_state = ffi.new("struct arepl_state") - - -- XXX identical to new(uint8_t[?], WINSZ)? GC? - o.arepl_state.win = ffi.new("uint8_t[" .. o.window_size / 8 .. "]") - - o.arepl_state.W = o.window_size + assert(o.window_size % 8 == 0, "window_size must be a multiple of 8.") + o.window = ffi.new(window_t, o.window_size / 8) return setmetatable(o, {__index=esp_v6_decrypt}) end @@ -163,17 +146,9 @@ function esp_v6_decrypt:decapsulate (p) local ctext_start = payload + self.CTEXT_OFFSET local ctext_length = length - self.PLAIN_OVERHEAD local seq_low = self.esp:seq_no() - local seq_high = ffi.C.arepl_infer_seq_hi(seq_low, self.arepl_state) - - if not ffi.C.arepl_pass(seq_high, seq_low, self.arepl_state) then - return false - end - - -- XXX does decryption actually verify that seq_low was unspoofed? - if gcm:decrypt(ctext_start, seq_low, seq_high, iv_start, ctext_start, ctext_length) then - self.seq:low(seq_low) - self.seq:high(seq_high) - ffi.C.arepl_accept(seq_high, seq_low, self.arepl_state) + local seq_high = tonumber(C.check_seq_no(seq_low, self.seq.no, self.window, self.window_size)) + if seq_high >= 0 and gcm:decrypt(ctext_start, seq_low, seq_high, iv_start, ctext_start, ctext_length) then + self.seq.no = C.track_seq_no(seq_high, seq_low, self.seq.no, self.window, self.window_size) local esp_tail_start = ctext_start + ctext_length - ESP_TAIL_SIZE self.esp_tail:new_from_mem(esp_tail_start, ESP_TAIL_SIZE) local ptext_length = ctext_length - self.esp_tail:pad_length() - ESP_TAIL_SIZE @@ -247,7 +222,7 @@ ABCDEFGHIJKLMNOPQRSTUVWXYZ enc.seq:high(1) dec.seq:low(2^32 - dec.window_size) dec.seq:high(0) - ffi.C.arepl_accept(dec.seq:high(), dec.seq:low(), dec.arepl_state) + C.track_seq_no(dec.seq:high(), dec.seq:low(), dec.seq.no, dec.window, dec.window_size) local p3 = packet.clone(p) enc:encapsulate(p3) assert(dec:decapsulate(p3), @@ -259,7 +234,7 @@ ABCDEFGHIJKLMNOPQRSTUVWXYZ enc.seq:high(1) dec.seq:low(dec.window_size+1) dec.seq:high(1) - ffi.C.arepl_accept(dec.seq:high(), dec.seq:low(), dec.arepl_state) + C.track_seq_no(dec.seq:high(), dec.seq:low(), dec.seq.no, dec.window, dec.window_size) local p4 = packet.clone(p) enc:encapsulate(p4) assert(not dec:decapsulate(p4), diff --git a/src/lib/ipsec/track_seq_no.c b/src/lib/ipsec/track_seq_no.c index 5ccc11468a..11c4ed3475 100644 --- a/src/lib/ipsec/track_seq_no.c +++ b/src/lib/ipsec/track_seq_no.c @@ -1,27 +1,12 @@ // See https://tools.ietf.org/html/rfc4303#page-38 -#include -#include #include #include -#include -#include -#include - -#define DEBUG 1 - -#ifdef DEBUG -#define DBG(FMT, ...) printf("arepl: " FMT "\n", __VA_ARGS__) -#else -#define DBG(FMT, ...) -#endif - -/* LO32/HI32: Get lower/upper 32 bit of a 64 bit value. - * Should be completely portable and endian-independent. - * It shouldn't be difficult for the compiler to recognize this as - * division/multiplication by powers of two and hence replace it with - * bit shifts, automagically in the right direction. - */ + +/* LO32/HI32: Get lower/upper 32 bit of a 64 bit value. Should be completely + portable and endian-independent. It shouldn't be difficult for the compiler + to recognize this as division/multiplication by powers of two and hence + replace it with bit shifts, automagically in the right direction. */ #define LO32(U64) ((uint32_t)((uint64_t)(U64) % 4294967296ull)) // 2**32 #define HI32(U64) ((uint32_t)((uint64_t)(U64) / 4294967296ull)) // 2**32 @@ -29,214 +14,69 @@ #define MK64(L, H) ((uint64_t)(((uint32_t)(H)) * 4294967296ull + ((uint32_t)(L)))) -/* struct arepl_state: Holds the state we need to keep for - * anti-replay purposes, that is, mainly a "window" of bits - * representing "have-seen" information about sequence numbers. - * - * `win` ends up pointing into an array of size `W`/8 so that - * it has a total of `W` bits, each representing the information - * whether we have or have not seen a packet with a particular - * sequence number. - * - * `W` is the window size in bits, `T` is the leading edge of our - * window, which is by definition the largest sequence number we have - * seen and verified so far. The window thus at any given time - * starts at sequence number `T`-`W`+1 and ends at `T`. - * Sequence numbers are being mapped to bit positions in `win` - * simply by calculating % `W` - */ -struct arepl_state -{ - uint8_t *win; - - uint32_t W; - uint64_t T; -}; - - -// public interface -bool arepl_pass(uint32_t seq_hi, uint32_t seq_lo, struct arepl_state *st); -void arepl_accept(uint32_t seq_hi, uint32_t seq_lo, struct arepl_state *st); -uint32_t arepl_infer_seq_hi(uint32_t seq_lo, struct arepl_state *st); - - -// local helpers -static void advance_wnd(struct arepl_state *st, uint64_t newT); -static void set_pktbit(struct arepl_state *st, uint64_t seq, bool on); -static bool get_pktbit(struct arepl_state *st, uint64_t seq); - - - -/* arepl_pass: Determine whether a packet with the sequence number - * made from `seq_hi` and `seq_lo` could be a legitimate packet. - * - * "Could", because we can't really tell if the received packet with - * this sequence number is in fact valid (since we haven't yet - * integrity-checked it - the sequence number may be spoofed), but we - * can give an authoritative "no" if we have already seen and accepted - * a packet with this number. - * - * If our answer is NOT "no", the caller will, provided the packet was - * valid, use arepl_accept() for us to mark the sequence number as seen. - */ -bool -arepl_pass(uint32_t seq_hi, uint32_t seq_lo, struct arepl_state *st) -{ - uint64_t seq = MK64(seq_lo, seq_hi); - - /* - * `seq` can never be less than the lower end of our window - * because `arepl_infer_seq_hi()` assumes the sender incremented their - * upper 32 bit of the sequence number whenever the lower 32 bit - * of it are less than the lower 32 bit of T. - * So the cases left to check are a) `seq` is inside the window - * and b) `seq` is ahead of the window. - */ - - if (seq <= st->T && get_pktbit(st, seq)) { - DBG("Rejecting replayed packet seq=%"PRIu64 - " (hi=%"PRIu32", lo=%"PRIu32"); T=%"PRIu64", W=%"PRIu32, - seq, seq_hi, seq_lo, st->T, st->W); - - return false; // replayed, reject - } - - /* - * From a sequence number point of view, this one looks fine; - * it is either an old packet we haven't seen yet, or the - * next/a future packet. - * - * Successful decryption later on will tell if the packet (and - * hence the sequence number) was legit, at which point arepl_accept() - * will be called, the have-seen bit representing this sequence number - * will be set and the window possibly advances. - */ - return true; +static inline void set_bit (bool on, uint64_t seq, + uint8_t* window, uint32_t W) { + uint32_t bitno = seq % W; + uint32_t blockno = bitno / 8; + bitno %= 8; + + /* First clear the bit, then maybe set it. This way we don't have to branch + on `on` */ + window[blockno] &= ~(1u << bitno); + window[blockno] |= (uint8_t)on << bitno; } -/* Signal that the packet received with this sequence number was - * in fact valid -- we record that we have seen it so as to prevent - * future replays of it. - */ -void -arepl_accept(uint32_t seq_hi, uint32_t seq_lo, struct arepl_state *st) -{ - uint64_t seq = MK64(seq_lo, seq_hi); +/* Get the bit in our window that corresponds to sequence number `seq` */ +static inline bool get_bit (uint64_t seq, + uint8_t *window, uint32_t W) { + uint32_t bitno = seq % W; + uint32_t blockno = bitno / 8; + bitno = bitno % 8; - uint64_t oldT = st->T; // XXX only for the DBG + return window[blockno] & (1u << bitno); +} - if (seq > st->T) - advance_wnd(st, seq); +static void advance_window (uint64_t seq, + uint64_t T, uint8_t* window, uint32_t W) { + uint64_t diff = seq - T; - DBG("Accepting packet seq=%"PRIu64 - " (hi=%"PRIu32", lo=%"PRIu32"); T=%"PRIu64"->%"PRIu64 - " (+%"PRIu64"), W=%"PRIu32, - seq, seq_hi, seq_lo, oldT, st->T, st->T - oldT, st->W); + /* For advances greater than the window size, don't clear more bits than the + window has */ + if (diff > W) diff = W; - set_pktbit(st, seq, 1); + /* Clear all bits corresponding to the sequence numbers that used to be ahead + of, but are now inside our window since we haven't seen them yet */ + while (diff--) set_bit(0, T--, window, W); } -/* Guess what the upper 32 bit of a received half sequence number probably - * was, based on our window position and following the alogrithm in RFC 4303 App. A - */ -uint32_t -arepl_infer_seq_hi(uint32_t seq_lo, struct arepl_state *st) -{ - uint32_t Tl = LO32(st->T); - uint32_t Th = HI32(st->T); - - if (Tl >= st->W - 1) { // Case A - if (seq_lo >= Tl - st->W + 1) - return Th; - else - return Th + 1; - } else { // Case B - if (seq_lo >= Tl - st->W + 1) - return Th - 1; - else - return Th; - } -} -// local helpers - -/* Advance the window so that the "head" bit corresponds to sequence - * number `newT`. - */ -static void -advance_wnd(struct arepl_state *st, uint64_t newT) -{ - uint64_t diff = newT - st->T; - - /* For advances greater than the window size, don't clear - * more bits than the window has */ - if (diff > st->W) - diff = st->W; - - uint64_t T = newT; - - /* Clear all bits corresponding to the sequence numbers that used to - * be ahead of, but are now inside our window since we haven't seen - * them yet */ - while (diff--) { - //DBG("kill %"PRIu64, T); - set_pktbit(st, T--, 0); - } - - /* Advance the window head */ - st->T = newT; +int64_t check_seq_no (uint32_t seq_lo, + uint64_t T, uint8_t *window, uint32_t W) { + uint32_t Tl = LO32(T); + uint32_t Th = HI32(T); + uint32_t seq_hi; + uint64_t seq; + + if (Tl >= W - 1) { // Case A + if (seq_lo >= Tl - W + 1) seq_hi = Th; + else seq_hi = Th + 1; + } else { // Case B + if (seq_lo >= Tl - W + 1) seq_hi = Th - 1; + else seq_hi = Th; + } + seq = MK64(seq_lo, seq_hi); + if (seq <= T && get_bit(seq, window, W)) return -1; + else return seq_hi; } -/* Set/clear the bit in our window that corresponds to sequence number `seq` */ -static inline void -set_pktbit(struct arepl_state *st, uint64_t seq, bool on) -{ - size_t bitno = seq % st->W; - size_t blockno = bitno / 8; - bitno %= 8; - - /* First clear the bit, then maybe set it. This way - * we don't have to branch on `on` */ - st->win[blockno] &= ~(1u << bitno); - st->win[blockno] |= (uint8_t)on << bitno; -} +uint64_t track_seq_no (uint32_t seq_hi, uint32_t seq_lo, + uint64_t T, uint8_t *window, uint32_t W) { + uint64_t seq = MK64(seq_lo, seq_hi); -/* Get the bit in our window that corresponds to sequence number `seq` */ -static inline bool -get_pktbit(struct arepl_state *st, uint64_t seq) -{ - size_t bitno = seq % st->W; - size_t blockno = bitno / 8; - bitno = bitno % 8; - - return st->win[blockno] & (1u << bitno); + if (seq > T) { + advance_window(seq, T, window, W); + T = seq; + } + set_bit(1, seq, window, W); + return T; } - -// done using ffi.C.new now -// struct arepl_state * -// arepl_alloc_state(uint32_t winsz) -// { -// struct arepl_state *st = malloc(sizeof *st); -// if (!st) -// return NULL; -// -// st->win = malloc(winsz / 8); -// if (!st->win) { -// free(st); -// return NULL; -// } -// -// memset(st->win, 0, winsz / 8); -// st->W = winsz; -// st->T = 0; -// -// return st; -// } -// -// void -// arepl_free_state(struct arepl_state *st) -// { -// if (st) -// free(st->win); -// free(st); -// } diff --git a/src/lib/ipsec/track_seq_no.h b/src/lib/ipsec/track_seq_no.h index 8dde94e497..5f11c4d49e 100644 --- a/src/lib/ipsec/track_seq_no.h +++ b/src/lib/ipsec/track_seq_no.h @@ -1,9 +1,2 @@ -struct arepl_state; - -// done using ffi.C.new now -//struct arepl_state *arepl_alloc_state(uint32_t winsz); -//void arepl_free_state(struct arepl_state *st); - -bool arepl_pass(uint32_t seq_hi, uint32_t seq_lo, struct arepl_state *st); -void arepl_accept(uint32_t seq_hi, uint32_t seq_lo, struct arepl_state *st); -uint32_t arepl_infer_seq_hi(uint32_t seq_lo, struct arepl_state *st); +uint64_t track_seq_no (uint32_t, uint32_t, uint64_t, uint8_t *, uint32_t); +int64_t check_seq_no (uint32_t, uint64_t, uint8_t *, uint32_t); From 13a12e941e3317e5accd40790e03354420840767 Mon Sep 17 00:00:00 2001 From: Max Rottenkolber Date: Thu, 30 Jun 2016 16:12:40 +0200 Subject: [PATCH 04/10] snabbmark esp: work around anti-replay window. --- src/program/snabbmark/snabbmark.lua | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/program/snabbmark/snabbmark.lua b/src/program/snabbmark/snabbmark.lua index 5dad61aaff..b968de09c7 100644 --- a/src/program/snabbmark/snabbmark.lua +++ b/src/program/snabbmark/snabbmark.lua @@ -379,6 +379,8 @@ function esp (npackets, packet_size, mode, profile) for i = 1, npackets do plain = packet.clone(encapsulated) dec:decapsulate(plain) + dec.seq.no = 0 + dec.window[0] = 0 packet.free(plain) end local finish = C.get_monotonic_time() From 363b1bf10b730114412abe05a22f09b4a5e79306 Mon Sep 17 00:00:00 2001 From: Timo Buhrmester Date: Tue, 26 Jul 2016 00:49:29 +0000 Subject: [PATCH 05/10] Refactoring broke advance_window; fix that --- src/lib/ipsec/track_seq_no.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/ipsec/track_seq_no.c b/src/lib/ipsec/track_seq_no.c index 11c4ed3475..0a44b0ac50 100644 --- a/src/lib/ipsec/track_seq_no.c +++ b/src/lib/ipsec/track_seq_no.c @@ -46,7 +46,7 @@ static void advance_window (uint64_t seq, /* Clear all bits corresponding to the sequence numbers that used to be ahead of, but are now inside our window since we haven't seen them yet */ - while (diff--) set_bit(0, T--, window, W); + while (diff--) set_bit(0, seq--, window, W); } From 72532f6ee540998692d0e8028e01e857fc0a95c3 Mon Sep 17 00:00:00 2001 From: Timo Buhrmester Date: Tue, 26 Jul 2016 04:11:27 +0000 Subject: [PATCH 06/10] lib.ipsec.esp: More thourough testing of the anti-replay facilities --- src/lib/ipsec/esp.lua | 76 +++++++++++++++++++++++++++++++------------ 1 file changed, 56 insertions(+), 20 deletions(-) diff --git a/src/lib/ipsec/esp.lua b/src/lib/ipsec/esp.lua index a688d8b29d..934b476579 100644 --- a/src/lib/ipsec/esp.lua +++ b/src/lib/ipsec/esp.lua @@ -193,9 +193,6 @@ ABCDEFGHIJKLMNOPQRSTUVWXYZ assert(packet.length(p2) == packet.length(p) and C.memcmp(p, p2, packet.length(p)) == 0, "integrity check failed") - -- Check replayed packet XXX do this a lot more thoroughly - p2 = packet.clone(p_enc) - assert(not dec:decapsulate(p2), "replayed decapsulation succeeded") -- Check invalid packets. local p_invalid = packet.from_string("invalid") assert(not enc:encapsulate(p_invalid), "encapsulated invalid packet") @@ -218,27 +215,66 @@ ABCDEFGHIJKLMNOPQRSTUVWXYZ and C.memcmp(p_min, e_min, packet.length(p_min)) == 0, "integrity check failed") -- Check transmitted Sequence Number wrap around - enc.seq:low(0) - enc.seq:high(1) - dec.seq:low(2^32 - dec.window_size) - dec.seq:high(0) - C.track_seq_no(dec.seq:high(), dec.seq:low(), dec.seq.no, dec.window, dec.window_size) - local p3 = packet.clone(p) - enc:encapsulate(p3) - assert(dec:decapsulate(p3), + C.memset(dec.window, 0, dec.window_size / 8); -- clear window + enc.seq.no = 2^32 - 1 -- so next encapsulated will be seq 2^32 + dec.seq.no = 2^32 - 1 -- pretend to have seen 2^32-1 + local px = packet.clone(p) + enc:encapsulate(px) + assert(dec:decapsulate(px), "Transmitted Sequence Number wrap around failed.") - assert(dec.seq:high() == 1 and dec.seq:low() == 1, + assert(dec.seq:high() == 1 and dec.seq:low() == 0, "Lost Sequence Number synchronization.") -- Check Sequence Number exceeding window - enc.seq:low(0) - enc.seq:high(1) - dec.seq:low(dec.window_size+1) - dec.seq:high(1) - C.track_seq_no(dec.seq:high(), dec.seq:low(), dec.seq.no, dec.window, dec.window_size) - local p4 = packet.clone(p) - enc:encapsulate(p4) - assert(not dec:decapsulate(p4), + C.memset(dec.window, 0, dec.window_size / 8); -- clear window + enc.seq.no = 2^32 + dec.seq.no = 2^32 + dec.window_size + 1 + px = packet.clone(p) + enc:encapsulate(px) + assert(not dec:decapsulate(px), "Accepted out of window Sequence Number.") assert(dec.seq:high() == 1 and dec.seq:low() == dec.window_size+1, "Corrupted Sequence Number.") + -- Test anti-replay: From a set of 15 packets, first send all those + -- that have an even sequence number. Then, send all 15. Verify that + -- in the 2nd run, packets with even sequence numbers are rejected while + -- the others are not. + -- Then do the same thing again, but with offset sequence numbers so that + -- we have a 32bit wraparound in the middle. + local offset = 0 -- close to 2^32 in the 2nd iteration + for offset = 0, 2^32-7, 2^32-7 do -- duh + C.memset(dec.window, 0, dec.window_size / 8); -- clear window + dec.seq.no = offset + for i = 1+offset, 15+offset do + if (i % 2 == 0) then + enc.seq.no = i-1 -- so next seq will be i + px = packet.clone(p) + enc:encapsulate(px); + assert(dec:decapsulate(px), "rejected legitimate packet seq=" .. i) + assert(dec.seq.no == i, "Lost sequence number synchronization") + end + end + for i = 1+offset, 15+offset do + enc.seq.no = i-1 + px = packet.clone(p) + enc:encapsulate(px); + if (i % 2 == 0) then + assert(not dec:decapsulate(px), "accepted replayed packet seq=" .. i) + else + assert(dec:decapsulate(px), "rejected legitimate packet seq=" .. i) + end + end + end + -- Check that packets from way in the past/way in the future + -- (further than the biggest allowable window size) are rejected + -- This is where we ultimately want resynchronation (wrt. future packets) + C.memset(dec.window, 0, dec.window_size / 8); -- clear window + dec.seq.no = 2^34 + 42; + enc.seq.no = 2^36 + 42; + px = packet.clone(p) + enc:encapsulate(px); + assert(not dec:decapsulate(px), "accepted packet from way into the future") + enc.seq.no = 2^32 + 42; + px = packet.clone(p) + enc:encapsulate(px); + assert(not dec:decapsulate(px), "accepted packet from way into the past") end From 29de34ccd7d7264f7eb0f7b12a3f3b2cba0338fe Mon Sep 17 00:00:00 2001 From: Timo Buhrmester Date: Tue, 26 Jul 2016 04:12:38 +0000 Subject: [PATCH 07/10] lib.ipsec.esp: Reinsert a few comments that got refactored out --- src/lib/ipsec/track_seq_no.c | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/lib/ipsec/track_seq_no.c b/src/lib/ipsec/track_seq_no.c index 0a44b0ac50..78ae1c9ead 100644 --- a/src/lib/ipsec/track_seq_no.c +++ b/src/lib/ipsec/track_seq_no.c @@ -14,6 +14,7 @@ #define MK64(L, H) ((uint64_t)(((uint32_t)(H)) * 4294967296ull + ((uint32_t)(L)))) +/* Set/clear the bit in our window that corresponds to sequence number `seq` */ static inline void set_bit (bool on, uint64_t seq, uint8_t* window, uint32_t W) { uint32_t bitno = seq % W; @@ -36,6 +37,10 @@ static inline bool get_bit (uint64_t seq, return window[blockno] & (1u << bitno); } +/* Advance the window so that the "head" bit corresponds to sequence + * number `seq`. Clear all bits for the new sequence numbers that are + * now considered in-window. + */ static void advance_window (uint64_t seq, uint64_t T, uint8_t* window, uint32_t W) { uint64_t diff = seq - T; @@ -50,6 +55,19 @@ static void advance_window (uint64_t seq, } +/* Determine whether a packet with the sequence number made from + * `seq_hi` and `seq_lo` (where `seq_hi` is inferred from our window + * state) could be a legitimate packet. + * + * "Could", because we can't really tell if the received packet with + * this sequence number is in fact valid (since we haven't yet + * integrity-checked it - the sequence number may be spoofed), but we + * can give an authoritative "no" if we have already seen and accepted + * a packet with this number. + * + * If our answer is NOT "no", the caller will, provided the packet was + * valid, use track_seq_no() for us to mark the sequence number as seen. + */ int64_t check_seq_no (uint32_t seq_lo, uint64_t T, uint8_t *window, uint32_t W) { uint32_t Tl = LO32(T); @@ -69,6 +87,10 @@ int64_t check_seq_no (uint32_t seq_lo, else return seq_hi; } +/* Signal that the packet received with this sequence number was + * in fact valid -- we record that we have seen it so as to prevent + * future replays of it. + */ uint64_t track_seq_no (uint32_t seq_hi, uint32_t seq_lo, uint64_t T, uint8_t *window, uint32_t W) { uint64_t seq = MK64(seq_lo, seq_hi); From 54899c07d525981ff351f3e2fef36a5809fc3f73 Mon Sep 17 00:00:00 2001 From: Timo Buhrmester Date: Tue, 26 Jul 2016 05:21:31 +0000 Subject: [PATCH 08/10] lib.ipsec.esp: Use core.lib.logger for the auditable events "integrity error" and "replayed" --- src/lib/ipsec/esp.lua | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/lib/ipsec/esp.lua b/src/lib/ipsec/esp.lua index 934b476579..8c09f9cf0d 100644 --- a/src/lib/ipsec/esp.lua +++ b/src/lib/ipsec/esp.lua @@ -126,6 +126,7 @@ function esp_v6_decrypt:new (conf) o.window_size = conf.window_size or 128 assert(o.window_size % 8 == 0, "window_size must be a multiple of 8.") o.window = ffi.new(window_t, o.window_size / 8) + o.logger = lib.logger_new({ rate = 32, module = 'esp' }); -- XXX here? return setmetatable(o, {__index=esp_v6_decrypt}) end @@ -158,6 +159,15 @@ function esp_v6_decrypt:decapsulate (p) packet.resize(p, PAYLOAD_OFFSET + ptext_length) return true else + local reason = seq_high == -1 and 'replayed' or 'integrity error' + -- This is the information RFC4303 says we SHOULD log + local info = "SPI=" .. tostring(self.spi) .. ", " .. + "src_addr='" .. tostring(self.ip:ntop(self.ip:src())) .. "', " .. + "dst_addr='" .. tostring(self.ip:ntop(self.ip:dst())) .. "', " .. + "seq_low=" .. tostring(seq_low) .. ", " .. + "flow_id=" .. tostring(self.ip:flow_label()) .. ", " .. + "reason='" .. reason .. "'"; + self.logger:log("Rejecting packet ("..info..")") return false end end From c37e01bfa123656c6ff40a0221ba2025b0a035ae Mon Sep 17 00:00:00 2001 From: Timo Buhrmester Date: Sun, 31 Jul 2016 23:26:21 +0000 Subject: [PATCH 09/10] lib.ipsec.esp: One logger per module rather than per decryption context --- src/lib/ipsec/esp.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/ipsec/esp.lua b/src/lib/ipsec/esp.lua index 8c09f9cf0d..da8cb899a6 100644 --- a/src/lib/ipsec/esp.lua +++ b/src/lib/ipsec/esp.lua @@ -37,6 +37,7 @@ local seq_no_t = require("lib.ipsec.seq_no_t") local lib = require("core.lib") local ffi = require("ffi") local C = ffi.C +local logger = lib.logger_new({ rate = 32, module = 'esp' }); require("lib.ipsec.track_seq_no_h") local window_t = ffi.typeof("uint8_t[?]") @@ -126,7 +127,6 @@ function esp_v6_decrypt:new (conf) o.window_size = conf.window_size or 128 assert(o.window_size % 8 == 0, "window_size must be a multiple of 8.") o.window = ffi.new(window_t, o.window_size / 8) - o.logger = lib.logger_new({ rate = 32, module = 'esp' }); -- XXX here? return setmetatable(o, {__index=esp_v6_decrypt}) end @@ -167,7 +167,7 @@ function esp_v6_decrypt:decapsulate (p) "seq_low=" .. tostring(seq_low) .. ", " .. "flow_id=" .. tostring(self.ip:flow_label()) .. ", " .. "reason='" .. reason .. "'"; - self.logger:log("Rejecting packet ("..info..")") + logger:log("Rejecting packet ("..info..")") return false end end From 2e75c1116849c0e8c5553c823e85b2c249569974 Mon Sep 17 00:00:00 2001 From: Timo Buhrmester Date: Thu, 4 Aug 2016 16:38:17 +0000 Subject: [PATCH 10/10] lib.ipsec.esp: Fix typo --- src/lib/ipsec/esp.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/ipsec/esp.lua b/src/lib/ipsec/esp.lua index ef990b8cd3..d011fe56a5 100644 --- a/src/lib/ipsec/esp.lua +++ b/src/lib/ipsec/esp.lua @@ -275,7 +275,7 @@ ABCDEFGHIJKLMNOPQRSTUVWXYZ end -- Check that packets from way in the past/way in the future -- (further than the biggest allowable window size) are rejected - -- This is where we ultimately want resynchronation (wrt. future packets) + -- This is where we ultimately want resynchronization (wrt. future packets) C.memset(dec.window, 0, dec.window_size / 8); -- clear window dec.seq.no = 2^34 + 42; enc.seq.no = 2^36 + 42;