Index: sys/netipsec/ipsec.h =================================================================== --- sys/netipsec/ipsec.h +++ sys/netipsec/ipsec.h @@ -325,7 +325,7 @@ int udp_ipsec_input(struct mbuf *, int, int); int udp_ipsec_pcbctl(struct inpcb *, struct sockopt *); -int ipsec_chkreplay(uint32_t, struct secasvar *); +int ipsec_chkreplay(uint32_t, uint32_t *, struct secasvar *); int ipsec_updatereplay(uint32_t, struct secasvar *); int ipsec_updateid(struct secasvar *, crypto_session_t *, crypto_session_t *); int ipsec_initialized(void); Index: sys/netipsec/ipsec.c =================================================================== --- sys/netipsec/ipsec.c +++ sys/netipsec/ipsec.c @@ -1173,6 +1173,66 @@ return (sz); } + +#define IPSEC_BITMAP_INDEX_MASK(w) (w - 1) +#define IPSEC_REDUNDANT_BIT_SHIFTS 5 +#define IPSEC_REDUNDANT_BITS (1 << IPSEC_REDUNDANT_BIT_SHIFTS) +#define IPSEC_BITMAP_LOC_MASK (IPSEC_REDUNDANT_BITS - 1) + +/* + * Functions below are responsible for checking and updating bitmap. + * These are used to separate ipsec_chkreplay() and ipsec_updatereplay() + * from window implementation + * + * Based on RFC 6479. Blocks are 32 bits unsigned integers + */ + +static inline int +check_window(const struct secreplay *replay, uint64_t seq) +{ + int index, bit_location; + + bit_location = seq & IPSEC_BITMAP_LOC_MASK; + index = (seq >> IPSEC_REDUNDANT_BIT_SHIFTS) + & IPSEC_BITMAP_INDEX_MASK(replay->bitmap_size); + + /* This packet already seen? */ + return ((replay->bitmap)[index] & (1 << bit_location)); +} + +static inline void +advance_window(const struct secreplay *replay, uint64_t seq) +{ + int i; + uint64_t index, index_cur, diff; + + index_cur = replay->last >> IPSEC_REDUNDANT_BIT_SHIFTS; + index = seq >> IPSEC_REDUNDANT_BIT_SHIFTS; + diff = index - index_cur; + + if (diff > replay->bitmap_size) { + /* something unusual in this case */ + diff = replay->bitmap_size; + } + + for (i = 0; i < diff; i++) { + replay->bitmap[(i + index_cur + 1) + & IPSEC_BITMAP_INDEX_MASK(replay->bitmap_size)] = 0; + } +} + +static inline void +set_window(const struct secreplay *replay, uint64_t seq) +{ + int index, bit_location; + + bit_location = seq & IPSEC_BITMAP_LOC_MASK; + index = (seq >> IPSEC_REDUNDANT_BIT_SHIFTS) + & IPSEC_BITMAP_INDEX_MASK(replay->bitmap_size); + + replay->bitmap[index] |= (1 << bit_location); +} + /* * Check the variable replay window. * ipsec_chkreplay() performs replay check before ICV verification. @@ -1181,20 +1241,17 @@ * beforehand). * 0 (zero) is returned if packet disallowed, 1 if packet permitted. * - * Based on RFC 6479. Blocks are 32 bits unsigned integers + * Based on RFC 4303 */ -#define IPSEC_BITMAP_INDEX_MASK(w) (w - 1) -#define IPSEC_REDUNDANT_BIT_SHIFTS 5 -#define IPSEC_REDUNDANT_BITS (1 << IPSEC_REDUNDANT_BIT_SHIFTS) -#define IPSEC_BITMAP_LOC_MASK (IPSEC_REDUNDANT_BITS - 1) - int -ipsec_chkreplay(uint32_t seq, struct secasvar *sav) +ipsec_chkreplay(uint32_t seq, uint32_t *seqhigh, struct secasvar *sav) { - const struct secreplay *replay; - uint32_t wsizeb; /* Constant: window size. */ - int index, bit_location; + char buf[128]; + struct secreplay *replay; + uint32_t window; + uint32_t tl, th, bl; + uint32_t seqh; IPSEC_ASSERT(sav != NULL, ("Null SA")); IPSEC_ASSERT(sav->replay != NULL, ("Null replay state")); @@ -1205,36 +1262,96 @@ if (replay->wsize == 0) return (1); - /* Constant. */ - wsizeb = replay->wsize << 3; - - /* Sequence number of 0 is invalid. */ - if (seq == 0) + /* Zero sequence number is not allowed. */ + if (seq == 0 && replay->last == 0) return (0); - /* First time is always okay. */ - if (replay->count == 0) - return (1); + window = replay->wsize << 3; /* Size of window */ + tl = (uint32_t)replay->last; /* Top of window, lower part */ + th = (uint32_t)(replay->last >> 32); /* Top of window, high part */ + bl = tl - window + 1; /* Bottom of window, lower part */ + + /* + * We keep the high part intact when: + * 1) the seq is within [bl, 0xffffffff] and the whole window is + * within one subspace; + * 2) the seq is within [0, bl) and window spans two subspaces. + */ + if ((tl >= window - 1 && seq >= bl) || + (tl < window - 1 && seq < bl)) { + *seqhigh = th; + if (seq <= tl) { + /* Sequence number inside window - check against replay */ + if (check_window(replay, seq)) + return (0); + } - /* Larger sequences are okay. */ - if (seq > replay->lastseq) + /* Sequence number above top of window or not found in bitmap */ return (1); + } - /* Over range to check, i.e. too old or wrapped. */ - if (replay->lastseq - seq >= wsizeb) - return (0); + /* + * If ESN is not enabled and packet with highest sequence number + * was received we should report overflow + */ + if (tl == 0xffffffff && !(sav->flags & SADB_X_SAFLAGS_ESN)) { + /* Set overflow flag. */ + replay->overflow++; + + if ((sav->flags & SADB_X_EXT_CYCSEQ) == 0) { + if (sav->sah->saidx.proto == IPPROTO_ESP) + ESPSTAT_INC(esps_wrap); + else if (sav->sah->saidx.proto == IPPROTO_AH) + AHSTAT_INC(ahs_wrap); + return (0); + } - /* The sequence is inside the sliding window - * now check the bit in the bitmap - * bit location only depends on the sequence number + ipseclog((LOG_WARNING, "%s: replay counter made %d cycle. %s\n", + __func__, replay->overflow, + ipsec_sa2str(sav, buf, sizeof(buf)))); + } + + /* + * Seq is within [bl, 0xffffffff] and bl is within + * [0xffffffff-window, 0xffffffff]. This means we got a seq + * which is within our replay window, but in the previous + * subspace. */ - bit_location = seq & IPSEC_BITMAP_LOC_MASK; - index = (seq >> IPSEC_REDUNDANT_BIT_SHIFTS) - & IPSEC_BITMAP_INDEX_MASK(replay->bitmap_size); + if (tl < window - 1 && seq >= bl) { + if (th == 0) + return (0); + *seqhigh = th - 1; + seqh = th - 1; + if (check_window(replay, seq)) + return (0); + return (1); + } + + /* + * Seq is within [0, bl) but the whole window is within one subspace. + * This means that seq has wrapped and is in next subspace + */ + *seqhigh = th + 1; + seqh = th + 1; + + /* Don't let high part wrap. */ + if (seqh == 0) { + /* Set overflow flag. */ + replay->overflow++; + + if ((sav->flags & SADB_X_EXT_CYCSEQ) == 0) { + if (sav->sah->saidx.proto == IPPROTO_ESP) + ESPSTAT_INC(esps_wrap); + else if (sav->sah->saidx.proto == IPPROTO_AH) + AHSTAT_INC(ahs_wrap); + return (0); + } + + ipseclog((LOG_WARNING, "%s: replay counter made %d cycle. %s\n", + __func__, replay->overflow, + ipsec_sa2str(sav, buf, sizeof(buf)))); + } - /* This packet already seen? */ - if ((replay->bitmap)[index] & (1 << bit_location)) - return (0); return (1); } @@ -1246,84 +1363,90 @@ int ipsec_updatereplay(uint32_t seq, struct secasvar *sav) { - char buf[128]; struct secreplay *replay; - uint32_t wsizeb; /* Constant: window size. */ - int diff, index, bit_location; + uint32_t window; + uint32_t tl, th, bl; + uint32_t seqh; IPSEC_ASSERT(sav != NULL, ("Null SA")); IPSEC_ASSERT(sav->replay != NULL, ("Null replay state")); replay = sav->replay; + /* No need to check replay if disabled. */ if (replay->wsize == 0) - goto ok; /* No need to check replay. */ - - /* Constant. */ - wsizeb = replay->wsize << 3; + return (0); - /* Sequence number of 0 is invalid. */ - if (seq == 0) + /* Zero sequence number is not allowed. */ + if (seq == 0 && replay->last == 0) return (1); - /* The packet is too old, no need to update */ - if (wsizeb + seq < replay->lastseq) - goto ok; + window = replay->wsize << 3; /* Size of window */ + tl = (uint32_t)replay->last; /* Top of window, lower part */ + th = (uint32_t)(replay->last >> 32); /* Top of window, high part */ + bl = tl - window + 1; /* Bottom of window, lower part */ - /* Now update the bit */ - index = (seq >> IPSEC_REDUNDANT_BIT_SHIFTS); - - /* First check if the sequence number is in the range */ - if (seq > replay->lastseq) { - int id; - int index_cur = replay->lastseq >> IPSEC_REDUNDANT_BIT_SHIFTS; - - diff = index - index_cur; - if (diff > replay->bitmap_size) { - /* something unusual in this case */ - diff = replay->bitmap_size; - } - - for (id = 0; id < diff; ++id) { - replay->bitmap[(id + index_cur + 1) - & IPSEC_BITMAP_INDEX_MASK(replay->bitmap_size)] = 0; + /* + * We keep the high part intact when: + * 1) the seq is within [bl, 0xffffffff] and the whole window is + * within one subspace; + * 2) the seq is within [0, bl) and window spans two subspaces. + */ + if ((tl >= window - 1 && seq >= bl) || + (tl < window - 1 && seq < bl)) { + seqh = th; + if (seq <= tl) { + /* Sequence number inside window - check against replay */ + if (check_window(replay, seq)) + return (1); + set_window(replay, seq); + } else { + advance_window(replay, ((uint64_t)seqh << 32) | seq); + set_window(replay, seq); + replay->last = ((uint64_t)seqh << 32) | seq; } - replay->lastseq = seq; + /* Sequence number above top of window or not found in bitmap */ + replay->count++; + return (0); } - index &= IPSEC_BITMAP_INDEX_MASK(replay->bitmap_size); - bit_location = seq & IPSEC_BITMAP_LOC_MASK; - - /* this packet has already been received */ - if (replay->bitmap[index] & (1 << bit_location)) + if (!(sav->flags & SADB_X_SAFLAGS_ESN)) return (1); - replay->bitmap[index] |= (1 << bit_location); - -ok: - if (replay->count == ~0) { - /* Set overflow flag. */ - replay->overflow++; - - /* Don't increment, no more packets accepted. */ - if ((sav->flags & SADB_X_EXT_CYCSEQ) == 0) { - if (sav->sah->saidx.proto == IPPROTO_AH) - AHSTAT_INC(ahs_wrap); - else if (sav->sah->saidx.proto == IPPROTO_ESP) - ESPSTAT_INC(esps_wrap); + /* + * Seq is within [bl, 0xffffffff] and bl is within + * [0xffffffff-window, 0xffffffff]. This means we got a seq + * which is within our replay window, but in the previous + * subspace. + */ + if (tl < window - 1 && seq >= bl) { + if (th == 0) + return (1); + if (check_window(replay, seq)) return (1); - } - ipseclog((LOG_WARNING, "%s: replay counter made %d cycle. %s\n", - __func__, replay->overflow, - ipsec_sa2str(sav, buf, sizeof(buf)))); + set_window(replay, seq); + replay->count++; + return (0); } + /* + * Seq is within [0, bl) but the whole window is within one subspace. + * This means that seq has wrapped and is in next subspace + */ + seqh = th + 1; + + /* Don't let high part wrap. */ + if (seqh == 0) + return (1); + + advance_window(replay, ((uint64_t)seqh << 32) | seq); + set_window(replay, seq); + replay->last = ((uint64_t)seqh << 32) | seq; replay->count++; return (0); } - int ipsec_updateid(struct secasvar *sav, crypto_session_t *new, crypto_session_t *old) Index: sys/netipsec/key_debug.c =================================================================== --- sys/netipsec/key_debug.c +++ sys/netipsec/key_debug.c @@ -809,8 +809,8 @@ int len, l; IPSEC_ASSERT(rpl != NULL, ("null rpl")); - printf(" secreplay{ count=%u bitmap_size=%u wsize=%u seq=%u lastseq=%u", - rpl->count, rpl->bitmap_size, rpl->wsize, rpl->seq, rpl->lastseq); + printf(" secreplay{ count=%lu bitmap_size=%u wsize=%u last=%lu", + rpl->count, rpl->bitmap_size, rpl->wsize, rpl->last); if (rpl->bitmap == NULL) { printf(" }\n"); Index: sys/netipsec/keydb.h =================================================================== --- sys/netipsec/keydb.h +++ sys/netipsec/keydb.h @@ -202,10 +202,9 @@ * (c) read only except during creation / free */ struct secreplay { - u_int32_t count; /* (m) */ + u_int64_t count; /* (m) */ u_int wsize; /* (c) window size, i.g. 4 bytes */ - u_int32_t seq; /* (m) used by sender */ - u_int32_t lastseq; /* (m) used by receiver */ + u_int64_t last; /* (m) used by receiver */ u_int32_t *bitmap; /* (m) used by receiver */ u_int bitmap_size; /* (c) size of the bitmap array */ int overflow; /* (m) overflow flag */ Index: sys/netipsec/xform_ah.c =================================================================== --- sys/netipsec/xform_ah.c +++ sys/netipsec/xform_ah.c @@ -534,6 +534,7 @@ struct newah *ah; crypto_session_t cryptoid; int hl, rplen, authsize, ahsize, error; + uint32_t seqh; IPSEC_ASSERT(sav != NULL, ("null SA")); IPSEC_ASSERT(sav->key_auth != NULL, ("null authentication key")); @@ -557,7 +558,7 @@ /* Check replay window, if applicable. */ SECASVAR_LOCK(sav); if (sav->replay != NULL && sav->replay->wsize != 0 && - ipsec_chkreplay(ntohl(ah->ah_seq), sav) == 0) { + ipsec_chkreplay(ntohl(ah->ah_seq), &seqh, sav) == 0) { SECASVAR_UNLOCK(sav); AHSTAT_INC(ahs_replay); DPRINTF(("%s: packet replay failure: %s\n", __func__, @@ -925,7 +926,9 @@ /* Insert packet replay counter, as requested. */ SECASVAR_LOCK(sav); if (sav->replay) { - if (sav->replay->count == ~0 && + if ((sav->replay->count == ~0 || + (!(sav->flags & SADB_X_SAFLAGS_ESN) && + ((uint32_t)sav->replay->count) == ~0)) && (sav->flags & SADB_X_EXT_CYCSEQ) == 0) { SECASVAR_UNLOCK(sav); DPRINTF(("%s: replay counter wrapped for SA %s/%08lx\n", @@ -940,7 +943,7 @@ if (!V_ipsec_replay) #endif sav->replay->count++; - ah->ah_seq = htonl(sav->replay->count); + ah->ah_seq = htonl((uint32_t)sav->replay->count); } cryptoid = sav->tdb_cryptoid; SECASVAR_UNLOCK(sav); Index: sys/netipsec/xform_esp.c =================================================================== --- sys/netipsec/xform_esp.c +++ sys/netipsec/xform_esp.c @@ -262,6 +262,7 @@ uint8_t *ivp; crypto_session_t cryptoid; int alen, error, hlen, plen; + uint32_t seqh; IPSEC_ASSERT(sav != NULL, ("null SA")); IPSEC_ASSERT(sav->tdb_encalgxform != NULL, ("null encoding xform")); @@ -320,7 +321,7 @@ */ SECASVAR_LOCK(sav); if (esph != NULL && sav->replay != NULL && sav->replay->wsize != 0) { - if (ipsec_chkreplay(ntohl(esp->esp_seq), sav) == 0) { + if (ipsec_chkreplay(ntohl(esp->esp_seq), &seqh, sav) == 0) { SECASVAR_UNLOCK(sav); DPRINTF(("%s: packet replay check for %s\n", __func__, ipsec_sa2str(sav, buf, sizeof(buf)))); @@ -740,7 +741,7 @@ if (!V_ipsec_replay) #endif sav->replay->count++; - replay = htonl(sav->replay->count); + replay = htonl((uint32_t)sav->replay->count); bcopy((caddr_t) &replay, mtod(mo, caddr_t) + roff + sizeof(uint32_t), sizeof(uint32_t));