Index: lib/libipsec/pfkey.c =================================================================== --- lib/libipsec/pfkey.c +++ lib/libipsec/pfkey.c @@ -1776,6 +1776,7 @@ case SADB_EXT_SPIRANGE: case SADB_X_EXT_POLICY: case SADB_X_EXT_SA2: + case SADB_X_EXT_SA_REPLAY: mhp[ext->sadb_ext_type] = (caddr_t)ext; break; case SADB_X_EXT_NAT_T_TYPE: Index: lib/libipsec/pfkey_dump.c =================================================================== --- lib/libipsec/pfkey_dump.c +++ lib/libipsec/pfkey_dump.c @@ -219,6 +219,7 @@ struct sadb_key *m_auth, *m_enc; struct sadb_ident *m_sid, *m_did; struct sadb_sens *m_sens; + struct sadb_x_sa_replay *m_sa_replay; /* check pfkey message. */ if (pfkey_align(m, mhp)) { @@ -243,6 +244,7 @@ m_sid = (struct sadb_ident *)mhp[SADB_EXT_IDENTITY_SRC]; m_did = (struct sadb_ident *)mhp[SADB_EXT_IDENTITY_DST]; m_sens = (struct sadb_sens *)mhp[SADB_EXT_SENSITIVITY]; + m_sa_replay = (struct sadb_x_sa_replay *)mhp[SADB_X_EXT_SA_REPLAY]; /* source address */ if (m_saddr == NULL) { @@ -306,7 +308,8 @@ /* replay windoe size & flags */ printf("\tseq=0x%08x replay=%u flags=0x%08x ", m_sa2->sadb_x_sa2_sequence, - m_sa->sadb_sa_replay, + m_sa_replay ? (m_sa_replay->sadb_x_sa_replay_replay >> 3) : + m_sa->sadb_sa_replay, m_sa->sadb_sa_flags); /* state */ Index: sys/net/pfkeyv2.h =================================================================== --- sys/net/pfkeyv2.h +++ sys/net/pfkeyv2.h @@ -283,6 +283,14 @@ }; _Static_assert(sizeof(struct sadb_x_nat_t_frag) == 8, "struct size mismatch"); +/* Additional large replay window support + */ +struct sadb_x_sa_replay { + u_int16_t sadb_x_sa_replay_len; + u_int16_t sadb_x_sa_replay_exttype; + u_int32_t sadb_x_sa_replay_replay; /* in packets */ +}; +_Static_assert(sizeof(struct sadb_x_sa_replay) == 8, "struct size mismatch"); #define SADB_EXT_RESERVED 0 #define SADB_EXT_SA 1 @@ -311,7 +319,8 @@ #define SADB_X_EXT_NAT_T_OAI 23 /* Peer's NAT_OA for src of SA. */ #define SADB_X_EXT_NAT_T_OAR 24 /* Peer's NAT_OA for dst of SA. */ #define SADB_X_EXT_NAT_T_FRAG 25 /* Manual MTU override. */ -#define SADB_EXT_MAX 25 +#define SADB_X_EXT_SA_REPLAY 26 /* Replay window override. */ +#define SADB_EXT_MAX 26 #define SADB_SATYPE_UNSPEC 0 #define SADB_SATYPE_AH 2 Index: sys/netipsec/ipsec.c =================================================================== --- sys/netipsec/ipsec.c +++ sys/netipsec/ipsec.c @@ -251,7 +251,6 @@ #endif static void ipsec_delpcbpolicy(struct inpcbpolicy *); static struct secpolicy *ipsec_deepcopy_policy(struct secpolicy *src); -static void vshiftl(unsigned char *, int, int); MALLOC_DEFINE(M_IPSEC_INPCB, "inpcbpolicy", "inpcb-resident ipsec policy"); @@ -1476,57 +1475,70 @@ * beforehand). * 0 (zero) is returned if packet disallowed, 1 if packet permitted. * - * Based on RFC 2401. + * Based on RFC 6479. Blocks are 32 bits unsigned integers */ + +#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(u_int32_t seq, struct secasvar *sav) { const struct secreplay *replay; - u_int32_t diff; - int fr; - u_int32_t wsizeb; /* Constant: bits of window size. */ - int frlast; /* Constant: last frame. */ + u_int32_t wsizeb; /* Constant: window size. */ + int ret, index, bit_location; IPSEC_ASSERT(sav != NULL, ("Null SA")); IPSEC_ASSERT(sav->replay != NULL, ("Null replay state")); + SECASVAR_LOCK(sav); + + ret = 0; replay = sav->replay; + /* No need to check replay if disabled. */ if (replay->wsize == 0) - return (1); /* No need to check replay. */ + goto allowed; /* Constant. */ - frlast = replay->wsize - 1; wsizeb = replay->wsize << 3; /* Sequence number of 0 is invalid. */ if (seq == 0) - return (0); + goto end; /* First time is always okay. */ if (replay->count == 0) - return (1); - - if (seq > replay->lastseq) { - /* Larger sequences are okay. */ - return (1); - } else { - /* seq is equal or less than lastseq. */ - diff = replay->lastseq - seq; + goto allowed; - /* Over range to check, i.e. too old or wrapped. */ - if (diff >= wsizeb) - return (0); - - fr = frlast - diff / 8; - - /* This packet already seen? */ - if ((replay->bitmap)[fr] & (1 << (diff % 8))) - return (0); + /* Larger sequences are okay. */ + if (seq > replay->lastseq) + goto allowed; + + /* Over range to check, i.e. too old or wrapped. */ + if (replay->lastseq - seq >= wsizeb) + goto end; + + /* The sequence is inside the sliding window + * now check the bit in the bitmap + * bit location only depends on the sequence number + */ + bit_location = seq & IPSEC_BITMAP_LOC_MASK; + index = (seq >> IPSEC_REDUNDANT_BIT_SHIFTS) + & IPSEC_BITMAP_INDEX_MASK(replay->bitmap_size); + + /* This packet already seen? */ + if ((replay->bitmap)[index] & (1 << bit_location)) + goto end; + +allowed: + ret = 1; +end: + SECASVAR_UNLOCK(sav); - /* Out of order but good. */ - return (1); - } + return (ret); } /* @@ -1539,72 +1551,61 @@ { char buf[128]; struct secreplay *replay; - u_int32_t diff; - int fr; - u_int32_t wsizeb; /* Constant: bits of window size. */ - int frlast; /* Constant: last frame. */ + u_int32_t wsizeb; /* Constant: window size. */ + int ret, diff, index, bit_location; IPSEC_ASSERT(sav != NULL, ("Null SA")); IPSEC_ASSERT(sav->replay != NULL, ("Null replay state")); + SECASVAR_LOCK(sav); + + ret = 1; replay = sav->replay; if (replay->wsize == 0) goto ok; /* No need to check replay. */ /* Constant. */ - frlast = replay->wsize - 1; wsizeb = replay->wsize << 3; /* Sequence number of 0 is invalid. */ if (seq == 0) - return (1); + goto end; - /* First time. */ - if (replay->count == 0) { - replay->lastseq = seq; - bzero(replay->bitmap, replay->wsize); - (replay->bitmap)[frlast] = 1; + /* The packet is too old, no need to update */ + if (wsizeb + seq < replay->lastseq) goto ok; - } + /* Now update the bit */ + index = (seq >> IPSEC_REDUNDANT_BIT_SHIFTS); + + /* First check if the sequence number is in the range */ if (seq > replay->lastseq) { - /* seq is larger than lastseq. */ - diff = seq - replay->lastseq; + int id; + int index_cur = replay->lastseq >> IPSEC_REDUNDANT_BIT_SHIFTS; - /* New larger sequence number. */ - if (diff < wsizeb) { - /* In window. */ - /* Set bit for this packet. */ - vshiftl(replay->bitmap, diff, replay->wsize); - (replay->bitmap)[frlast] |= 1; - } else { - /* This packet has a "way larger". */ - bzero(replay->bitmap, replay->wsize); - (replay->bitmap)[frlast] = 1; + diff = index - index_cur; + if (diff > replay->bitmap_size) { + /* something unusual in this case */ + diff = replay->bitmap_size; } - replay->lastseq = seq; - /* Larger is good. */ - } else { - /* seq is equal or less than lastseq. */ - diff = replay->lastseq - seq; - - /* Over range to check, i.e. too old or wrapped. */ - if (diff >= wsizeb) - return (1); + for (id = 0; id < diff; ++id) { + replay->bitmap[(id + index_cur + 1) + & IPSEC_BITMAP_INDEX_MASK(replay->bitmap_size)] = 0; + } - fr = frlast - diff / 8; + replay->lastseq = seq; + } - /* This packet already seen? */ - if ((replay->bitmap)[fr] & (1 << (diff % 8))) - return (1); + index &= IPSEC_BITMAP_INDEX_MASK(replay->bitmap_size); + bit_location = seq & IPSEC_BITMAP_LOC_MASK; - /* Mark as seen. */ - (replay->bitmap)[fr] |= (1 << (diff % 8)); + /* this packet has already been received */ + if (replay->bitmap[index] & (1 << bit_location)) + goto end; - /* Out of order but good. */ - } + replay->bitmap[index] |= (1 << bit_location); ok: if (replay->count == ~0) { @@ -1614,39 +1615,18 @@ /* Don't increment, no more packets accepted. */ if ((sav->flags & SADB_X_EXT_CYCSEQ) == 0) - return (1); + goto end; ipseclog((LOG_WARNING, "%s: replay counter made %d cycle. %s\n", __func__, replay->overflow, ipsec_logsastr(sav, buf, sizeof(buf)))); } - replay->count++; - - return (0); -} + ret = 0; -/* - * Shift variable length buffer to left. - * IN: bitmap: pointer to the buffer - * nbit: the number of to shift. - * wsize: buffer size (bytes). - */ -static void -vshiftl(unsigned char *bitmap, int nbit, int wsize) -{ - int s, j, i; - unsigned char over; - - for (j = 0; j < nbit; j += 8) { - s = (nbit - j < 8) ? (nbit - j): 8; - bitmap[0] <<= s; - for (i = 1; i < wsize; i++) { - over = (bitmap[i] >> (8 - s)); - bitmap[i] <<= s; - bitmap[i-1] |= over; - } - } +end: + SECASVAR_UNLOCK(sav); + return (ret); } /* Return a printable string for the address. */ Index: sys/netipsec/key.c =================================================================== --- sys/netipsec/key.c +++ sys/netipsec/key.c @@ -243,7 +243,10 @@ sizeof(struct sadb_address), /* SADB_X_EXT_NAT_T_OAI */ sizeof(struct sadb_address), /* SADB_X_EXT_NAT_T_OAR */ sizeof(struct sadb_x_nat_t_frag),/* SADB_X_EXT_NAT_T_FRAG */ + sizeof(struct sadb_x_sa_replay), /* SADB_X_EXT_SA_REPLAY */ }; +_Static_assert(sizeof(minsize)/sizeof(int) == SADB_EXT_MAX + 1, "minsize size mismatch"); + static const int maxsize[] = { sizeof(struct sadb_msg), /* SADB_EXT_RESERVED */ sizeof(struct sadb_sa), /* SADB_EXT_SA */ @@ -271,7 +274,9 @@ 0, /* SADB_X_EXT_NAT_T_OAI */ 0, /* SADB_X_EXT_NAT_T_OAR */ sizeof(struct sadb_x_nat_t_frag),/* SADB_X_EXT_NAT_T_FRAG */ + sizeof(struct sadb_x_sa_replay), /* SADB_X_EXT_SA_REPLAY */ }; +_Static_assert(sizeof(maxsize)/sizeof(int) == SADB_EXT_MAX + 1, "minsize size mismatch"); static VNET_DEFINE(int, ipsec_esp_keymin) = 256; static VNET_DEFINE(int, ipsec_esp_auth) = 0; @@ -472,6 +477,7 @@ #define KEY_PORTTOSADDR(saddr, port) \ key_porttosaddr((struct sockaddr *)(saddr), (port)) static struct mbuf *key_setsadbxsa2(u_int8_t, u_int32_t, u_int32_t); +static struct mbuf *key_setsadbxsareplay(u_int32_t); static struct mbuf *key_setsadbxpolicy(u_int16_t, u_int8_t, u_int32_t, u_int32_t); static struct seckey *key_dup_keymsg(const struct sadb_key *, u_int, @@ -2940,6 +2946,8 @@ sav->sched = NULL; } if (sav->replay != NULL) { + if (sav->replay->bitmap != NULL) + free(sav->replay->bitmap, M_IPSEC_MISC); free(sav->replay, M_IPSEC_MISC); sav->replay = NULL; } @@ -3108,6 +3116,7 @@ /* SA */ if (mhp->ext[SADB_EXT_SA] != NULL) { const struct sadb_sa *sa0; + u_int32_t replay; sa0 = (const struct sadb_sa *)mhp->ext[SADB_EXT_SA]; if (mhp->extlen[SADB_EXT_SA] < sizeof(*sa0)) { @@ -3119,19 +3128,57 @@ sav->alg_enc = sa0->sadb_sa_encrypt; sav->flags = sa0->sadb_sa_flags; - /* replay window */ - if ((sa0->sadb_sa_flags & SADB_X_EXT_OLD) == 0) { - sav->replay = (struct secreplay *) - malloc(sizeof(struct secreplay)+sa0->sadb_sa_replay, M_IPSEC_MISC, M_NOWAIT|M_ZERO); - if (sav->replay == NULL) { + /* Optional replay window */ + replay = 0; + if ((sa0->sadb_sa_flags & SADB_X_EXT_OLD) == 0) + replay = sa0->sadb_sa_replay; + if ((mhp->ext[SADB_X_EXT_SA_REPLAY]) != NULL) { + replay = ((const struct sadb_x_sa_replay *) + mhp->ext[SADB_X_EXT_SA_REPLAY])->sadb_x_sa_replay_replay; + + if (replay > UINT32_MAX - 32) { + ipseclog((LOG_DEBUG, "%s: replay window too big.\n", + __func__)); + error = EINVAL; + goto fail; + } + + replay = (replay + 7) >> 3; + } + + sav->replay = (struct secreplay *) + malloc(sizeof(struct secreplay), + M_IPSEC_MISC, M_NOWAIT|M_ZERO); + if (sav->replay == NULL) { + ipseclog((LOG_DEBUG, "%s: No more memory.\n", + __func__)); + error = ENOBUFS; + goto fail; + } + + if (replay != 0) { + /* number of 32b blocks to be allocated */ + u_int32_t bitmap_size; + + /* RFC 6479: + * - the allocated replay window size must be a power of two + * - use an extra 32b block as a redundant window + */ + bitmap_size = 1; + while (replay + 4 > bitmap_size) + bitmap_size <<= 1; + bitmap_size = bitmap_size / 4; + + sav->replay->bitmap = malloc(bitmap_size*sizeof(u_int32_t), + M_IPSEC_MISC, M_NOWAIT|M_ZERO); + if (sav->replay->bitmap == NULL) { ipseclog((LOG_DEBUG, "%s: No more memory.\n", __func__)); error = ENOBUFS; goto fail; } - if (sa0->sadb_sa_replay != 0) - sav->replay->bitmap = (caddr_t)(sav->replay+1); - sav->replay->wsize = sa0->sadb_sa_replay; + sav->replay->bitmap_size = bitmap_size; + sav->replay->wsize = replay; } } @@ -3406,7 +3453,7 @@ struct mbuf *result = NULL, *tres = NULL, *m; int i; int dumporder[] = { - SADB_EXT_SA, SADB_X_EXT_SA2, + SADB_EXT_SA, SADB_X_EXT_SA2, SADB_X_EXT_SA_REPLAY, SADB_EXT_LIFETIME_HARD, SADB_EXT_LIFETIME_SOFT, SADB_EXT_LIFETIME_CURRENT, SADB_EXT_ADDRESS_SRC, SADB_EXT_ADDRESS_DST, SADB_EXT_ADDRESS_PROXY, SADB_EXT_KEY_AUTH, @@ -3419,6 +3466,7 @@ SADB_X_EXT_NAT_T_FRAG, #endif }; + u_int32_t replay_count; m = key_setsadbmsg(type, 0, satype, seq, pid, sav->refcnt); if (m == NULL) @@ -3435,13 +3483,25 @@ break; case SADB_X_EXT_SA2: - m = key_setsadbxsa2(sav->sah->saidx.mode, - sav->replay ? sav->replay->count : 0, + SECASVAR_LOCK(sav); + replay_count = sav->replay ? sav->replay->count : 0; + SECASVAR_UNLOCK(sav); + m = key_setsadbxsa2(sav->sah->saidx.mode, replay_count, sav->sah->saidx.reqid); if (!m) goto fail; break; + case SADB_X_EXT_SA_REPLAY: + if (sav->replay == NULL || + sav->replay->wsize <= UINT8_MAX) + continue; + + m = key_setsadbxsareplay(sav->replay->wsize); + if (!m) + goto fail; + break; + case SADB_EXT_ADDRESS_SRC: m = key_setsadbaddr(SADB_EXT_ADDRESS_SRC, &sav->sah->saidx.src.sa, @@ -3634,7 +3694,9 @@ p->sadb_sa_len = PFKEY_UNIT64(len); p->sadb_sa_exttype = SADB_EXT_SA; p->sadb_sa_spi = sav->spi; - p->sadb_sa_replay = (sav->replay != NULL ? sav->replay->wsize : 0); + p->sadb_sa_replay = sav->replay ? + (sav->replay->wsize > UINT8_MAX ? + UINT8_MAX : sav->replay->wsize) : 0; p->sadb_sa_state = sav->state; p->sadb_sa_auth = sav->alg_auth; p->sadb_sa_encrypt = sav->alg_enc; @@ -3719,6 +3781,32 @@ return m; } +/* + * Set data into sadb_x_sa_replay. + */ +static struct mbuf * +key_setsadbxsareplay(u_int32_t replay) +{ + struct mbuf *m; + struct sadb_x_sa_replay *p; + size_t len; + + len = PFKEY_ALIGN8(sizeof(struct sadb_x_sa_replay)); + m = m_get2(len, M_NOWAIT, MT_DATA, 0); + if (m == NULL) + return (NULL); + m_align(m, len); + m->m_len = len; + p = mtod(m, struct sadb_x_sa_replay *); + + bzero(p, len); + p->sadb_x_sa_replay_len = PFKEY_UNIT64(len); + p->sadb_x_sa_replay_exttype = SADB_X_EXT_SA_REPLAY; + p->sadb_x_sa_replay_replay = (replay << 3); + + return m; +} + #ifdef IPSEC_NAT_T /* * Set a type in sadb_x_nat_t_type. @@ -6853,6 +6941,7 @@ int len; int error = -1; struct sadb_lifetime *lt; + u_int32_t replay_count; IPSEC_ASSERT (sav != NULL, ("null sav")); IPSEC_ASSERT (sav->sah != NULL, ("null sa header")); @@ -6876,8 +6965,11 @@ m_cat(result, m); /* create SA extension */ - m = key_setsadbxsa2(sav->sah->saidx.mode, - sav->replay ? sav->replay->count : 0, + SECASVAR_LOCK(sav); + replay_count = sav->replay ? sav->replay->count : 0; + SECASVAR_UNLOCK(sav); + + m = key_setsadbxsa2(sav->sah->saidx.mode, replay_count, sav->sah->saidx.reqid); if (!m) { error = ENOBUFS; @@ -6885,6 +6977,14 @@ } m_cat(result, m); + if (sav->replay && sav->replay->wsize > UINT8_MAX) { + m = key_setsadbxsareplay(sav->replay->wsize); + if (!m) { + error = ENOBUFS; + goto fail; + } + } + /* create lifetime extension (current and soft) */ len = PFKEY_ALIGN8(sizeof(*lt)) * 2; m = m_get2(len, M_NOWAIT, MT_DATA, 0); @@ -7560,6 +7660,7 @@ case SADB_X_EXT_NAT_T_OAR: case SADB_X_EXT_NAT_T_FRAG: #endif + case SADB_X_EXT_SA_REPLAY: /* duplicate check */ /* * XXX Are there duplication payloads of either Index: sys/netipsec/key_debug.c =================================================================== --- sys/netipsec/key_debug.c +++ sys/netipsec/key_debug.c @@ -570,8 +570,11 @@ if (sav->key_enc != NULL) kdebug_sadb_key((struct sadb_ext *)sav->key_enc); - if (sav->replay != NULL) + if (sav->replay != NULL) { + SECASVAR_LOCK(sav); kdebug_secreplay(sav->replay); + SECASVAR_UNLOCK(sav); + } if (sav->lft_c != NULL) kdebug_sec_lifetime(sav->lft_c); if (sav->lft_h != NULL) @@ -595,8 +598,8 @@ if (rpl == NULL) panic("%s: NULL pointer was passed.\n", __func__); - printf(" secreplay{ count=%u wsize=%u seq=%u lastseq=%u", - rpl->count, rpl->wsize, rpl->seq, rpl->lastseq); + printf(" secreplay{ count=%u bitmap_size=%u wsize=%u seq=%u lastseq=%u", + rpl->count, rpl->bitmap_size, rpl->wsize, rpl->seq, rpl->lastseq); if (rpl->bitmap == NULL) { printf(" }\n"); @@ -605,7 +608,7 @@ printf("\n bitmap { "); - for (len = 0; len < rpl->wsize; len++) { + for (len = 0; len < rpl->bitmap_size*4; len++) { for (l = 7; l >= 0; l--) printf("%u", (((rpl->bitmap)[len] >> l) & 1) ? 1 : 0); } Index: sys/netipsec/keydb.h =================================================================== --- sys/netipsec/keydb.h +++ sys/netipsec/keydb.h @@ -35,6 +35,8 @@ #ifdef _KERNEL +#include + #include #ifndef _SOCKADDR_UNION_DEFINED @@ -170,14 +172,18 @@ #define SAV_ISCTR(_sav) ((_sav)->alg_enc == SADB_X_EALG_AESCTR) #define SAV_ISCTRORGCM(_sav) (SAV_ISCTR((_sav)) || SAV_ISGCM((_sav))) -/* replay prevention */ +/* Replay prevention, protected by SECASVAR_LOCK: + * (m) locked by mtx + * (c) read only except during creation / free + */ struct secreplay { - u_int32_t count; - u_int wsize; /* window size, i.g. 4 bytes */ - u_int32_t seq; /* used by sender */ - u_int32_t lastseq; /* used by receiver */ - caddr_t bitmap; /* used by receiver */ - int overflow; /* overflow flag */ + u_int32_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_int32_t *bitmap; /* (m) used by receiver */ + u_int bitmap_size; /* (c) size of the bitmap array */ + int overflow; /* (m) overflow flag */ }; /* socket table due to send PF_KEY messages. */ Index: sys/netipsec/xform_ah.c =================================================================== --- sys/netipsec/xform_ah.c +++ sys/netipsec/xform_ah.c @@ -961,8 +961,11 @@ /* Insert packet replay counter, as requested. */ if (sav->replay) { + SECASVAR_LOCK(sav); + if (sav->replay->count == ~0 && (sav->flags & SADB_X_EXT_CYCSEQ) == 0) { + SECASVAR_UNLOCK(sav); DPRINTF(("%s: replay counter wrapped for SA %s/%08lx\n", __func__, ipsec_address(&sav->sah->saidx.dst, buf, sizeof(buf)), (u_long) ntohl(sav->spi))); @@ -976,6 +979,8 @@ #endif sav->replay->count++; ah->ah_seq = htonl(sav->replay->count); + + SECASVAR_UNLOCK(sav); } /* Get crypto descriptors. */ Index: sys/netipsec/xform_esp.c =================================================================== --- sys/netipsec/xform_esp.c +++ sys/netipsec/xform_esp.c @@ -762,12 +762,14 @@ if (sav->replay) { u_int32_t replay; + SECASVAR_LOCK(sav); #ifdef REGRESSION /* Emulate replay attack when ipsec_replay is TRUE. */ if (!V_ipsec_replay) #endif sav->replay->count++; replay = htonl(sav->replay->count); + SECASVAR_UNLOCK(sav); bcopy((caddr_t) &replay, mtod(mo, caddr_t) + roff + sizeof(u_int32_t), sizeof(u_int32_t));