diff --git a/sys/netinet/ip_carp.c b/sys/netinet/ip_carp.c --- a/sys/netinet/ip_carp.c +++ b/sys/netinet/ip_carp.c @@ -301,6 +301,7 @@ int sc_advbase; /* seconds */ int sc_init_counter; uint64_t sc_counter; + uint64_t sc_replay_counter; /* DF-0301: last seen adv counter for replay protection */ /* authentication */ #define CARP_HMAC_PAD 64 @@ -629,6 +630,7 @@ sc->sc_vhid = -1; /* required setting */ sc->sc_advskew = 0; sc->sc_init_counter = 1; + sc->sc_replay_counter = 0; sc->sc_naddrs = 0; sc->sc_naddrs6 = 0; @@ -1145,7 +1147,23 @@ tmp_counter = tmp_counter<<32; tmp_counter += ntohl(ch->carp_counter[1]); - /* XXX Replay protection goes here */ + /* Replay protection: reject advertisements whose counter is not + * strictly greater than the last one we accepted. This prevents + * an on-path attacker from replaying a captured advertisement + * to suppress failover (DoS). A counter of 0 means uninitialized; + * the first advertisement is always accepted. + */ + if (sc->sc_replay_counter != 0 && + tmp_counter <= sc->sc_replay_counter) { + carpstats.carps_badauth++; + CARP_LOG("%s: replayed advertisement (counter %llu <= %llu)\n", + cifp->if_xname, + (unsigned long long)tmp_counter, + (unsigned long long)sc->sc_replay_counter); + m_freem(m); + return; + } + sc->sc_replay_counter = tmp_counter; sc->sc_init_counter = 0; sc->sc_counter = tmp_counter;