icmp: use per rate limit randomized jitter

Using the same random jitter for multiple rate limits allows an
attacker to use one rate limiter to figure out the current jitter
and then use this knowledge to de-randomize the other rate limiters.
This can be mitigated by using a separate randomized jitter for each
rate limiter.
This issue was reported as issue number 10 in Keyu Man et al.:
SCAD: Towards a Universal and Automated Network Side-Channel
Vulnerability Detection

Reviewed by:		rrs, Peter Lei, glebius
MFC after:		3 days
Sponsored by:		Netflix, Inc.
Differential Revision:	https://reviews.freebsd.org/D48804
This commit is contained in:
Michael Tuexen
2025-02-10 22:16:20 +01:00
parent 0b707d5fe8
commit 923c223f27
2 changed files with 39 additions and 31 deletions
+12 -8
View File
@@ -88,7 +88,7 @@ SYSCTL_PROC(_net_inet_icmp, ICMPCTL_ICMPLIM, icmplim, CTLTYPE_UINT |
&sysctl_icmplim_and_jitter, "IU", &sysctl_icmplim_and_jitter, "IU",
"Maximum number of ICMP responses per second"); "Maximum number of ICMP responses per second");
VNET_DEFINE_STATIC(int, icmplim_curr_jitter) = 0; VNET_DEFINE_STATIC(int, icmplim_curr_jitter[BANDLIM_MAX]) = {0};
#define V_icmplim_curr_jitter VNET(icmplim_curr_jitter) #define V_icmplim_curr_jitter VNET(icmplim_curr_jitter)
VNET_DEFINE_STATIC(u_int, icmplim_jitter) = 16; VNET_DEFINE_STATIC(u_int, icmplim_jitter) = 16;
#define V_icmplim_jitter VNET(icmplim_jitter) #define V_icmplim_jitter VNET(icmplim_jitter)
@@ -1108,14 +1108,16 @@ static const char *icmp_rate_descrs[BANDLIM_MAX] = {
}; };
static void static void
icmplim_new_jitter(void) icmplim_new_jitter(int which)
{ {
/* /*
* Adjust limit +/- to jitter the measurement to deny a side-channel * Adjust limit +/- to jitter the measurement to deny a side-channel
* port scan as in https://dl.acm.org/doi/10.1145/3372297.3417280 * port scan as in https://dl.acm.org/doi/10.1145/3372297.3417280
*/ */
KASSERT(which >= 0 && which < BANDLIM_MAX,
("%s: which %d", __func__, which));
if (V_icmplim_jitter > 0) if (V_icmplim_jitter > 0)
V_icmplim_curr_jitter = V_icmplim_curr_jitter[which] =
arc4random_uniform(V_icmplim_jitter * 2 + 1) - arc4random_uniform(V_icmplim_jitter * 2 + 1) -
V_icmplim_jitter; V_icmplim_jitter;
} }
@@ -1144,7 +1146,9 @@ sysctl_icmplim_and_jitter(SYSCTL_HANDLER_ARGS)
error = EINVAL; error = EINVAL;
else { else {
V_icmplim_jitter = new; V_icmplim_jitter = new;
icmplim_new_jitter(); for (int i = 0; i < BANDLIM_MAX; i++) {
icmplim_new_jitter(i);
}
} }
} }
} }
@@ -1160,8 +1164,8 @@ icmp_bandlimit_init(void)
for (int i = 0; i < BANDLIM_MAX; i++) { for (int i = 0; i < BANDLIM_MAX; i++) {
V_icmp_rates[i].cr_rate = counter_u64_alloc(M_WAITOK); V_icmp_rates[i].cr_rate = counter_u64_alloc(M_WAITOK);
V_icmp_rates[i].cr_ticks = ticks; V_icmp_rates[i].cr_ticks = ticks;
icmplim_new_jitter(i);
} }
icmplim_new_jitter();
} }
VNET_SYSINIT(icmp_bandlimit, SI_SUB_PROTO_DOMAIN, SI_ORDER_ANY, VNET_SYSINIT(icmp_bandlimit, SI_SUB_PROTO_DOMAIN, SI_ORDER_ANY,
icmp_bandlimit_init, NULL); icmp_bandlimit_init, NULL);
@@ -1190,14 +1194,14 @@ badport_bandlim(int which)
("%s: which %d", __func__, which)); ("%s: which %d", __func__, which));
pps = counter_ratecheck(&V_icmp_rates[which], V_icmplim + pps = counter_ratecheck(&V_icmp_rates[which], V_icmplim +
V_icmplim_curr_jitter); V_icmplim_curr_jitter[which]);
if (pps > 0) { if (pps > 0) {
if (V_icmplim_output) if (V_icmplim_output)
log(LOG_NOTICE, log(LOG_NOTICE,
"Limiting %s response from %jd to %d packets/sec\n", "Limiting %s response from %jd to %d packets/sec\n",
icmp_rate_descrs[which], (intmax_t )pps, icmp_rate_descrs[which], (intmax_t )pps,
V_icmplim + V_icmplim_curr_jitter); V_icmplim + V_icmplim_curr_jitter[which]);
icmplim_new_jitter(); icmplim_new_jitter(which);
} }
if (pps == -1) if (pps == -1)
return (-1); return (-1);
+27 -23
View File
@@ -2750,22 +2750,6 @@ SYSCTL_PROC(_net_inet6_icmp6, ICMPV6CTL_ERRPPSLIMIT, errppslimit,
&sysctl_icmp6lim_and_jitter, "IU", &sysctl_icmp6lim_and_jitter, "IU",
"Maximum number of ICMPv6 error/reply messages per second"); "Maximum number of ICMPv6 error/reply messages per second");
VNET_DEFINE_STATIC(int, icmp6lim_curr_jitter) = 0;
#define V_icmp6lim_curr_jitter VNET(icmp6lim_curr_jitter)
VNET_DEFINE_STATIC(u_int, icmp6lim_jitter) = 8;
#define V_icmp6lim_jitter VNET(icmp6lim_jitter)
SYSCTL_PROC(_net_inet6_icmp6, OID_AUTO, icmp6lim_jitter, CTLTYPE_UINT |
CTLFLAG_VNET | CTLFLAG_RW, &VNET_NAME(icmp6lim_jitter), 0,
&sysctl_icmp6lim_and_jitter, "IU",
"Random errppslimit jitter adjustment limit");
VNET_DEFINE_STATIC(int, icmp6lim_output) = 1;
#define V_icmp6lim_output VNET(icmp6lim_output)
SYSCTL_INT(_net_inet6_icmp6, OID_AUTO, icmp6lim_output,
CTLFLAG_VNET | CTLFLAG_RW, &VNET_NAME(icmp6lim_output), 0,
"Enable logging of ICMPv6 response rate limiting");
typedef enum { typedef enum {
RATELIM_PARAM_PROB = 0, RATELIM_PARAM_PROB = 0,
RATELIM_TOO_BIG, RATELIM_TOO_BIG,
@@ -2787,15 +2771,33 @@ static const char *icmp6_rate_descrs[RATELIM_MAX] = {
[RATELIM_OTHER] = "(other)", [RATELIM_OTHER] = "(other)",
}; };
VNET_DEFINE_STATIC(int, icmp6lim_curr_jitter[RATELIM_MAX]) = {0};
#define V_icmp6lim_curr_jitter VNET(icmp6lim_curr_jitter)
VNET_DEFINE_STATIC(u_int, icmp6lim_jitter) = 8;
#define V_icmp6lim_jitter VNET(icmp6lim_jitter)
SYSCTL_PROC(_net_inet6_icmp6, OID_AUTO, icmp6lim_jitter, CTLTYPE_UINT |
CTLFLAG_VNET | CTLFLAG_RW, &VNET_NAME(icmp6lim_jitter), 0,
&sysctl_icmp6lim_and_jitter, "IU",
"Random errppslimit jitter adjustment limit");
VNET_DEFINE_STATIC(int, icmp6lim_output) = 1;
#define V_icmp6lim_output VNET(icmp6lim_output)
SYSCTL_INT(_net_inet6_icmp6, OID_AUTO, icmp6lim_output,
CTLFLAG_VNET | CTLFLAG_RW, &VNET_NAME(icmp6lim_output), 0,
"Enable logging of ICMPv6 response rate limiting");
static void static void
icmp6lim_new_jitter(void) icmp6lim_new_jitter(int which)
{ {
/* /*
* Adjust limit +/- to jitter the measurement to deny a side-channel * Adjust limit +/- to jitter the measurement to deny a side-channel
* port scan as in https://dl.acm.org/doi/10.1145/3372297.3417280 * port scan as in https://dl.acm.org/doi/10.1145/3372297.3417280
*/ */
KASSERT(which >= 0 && which < RATELIM_MAX,
("%s: which %d", __func__, which));
if (V_icmp6lim_jitter > 0) if (V_icmp6lim_jitter > 0)
V_icmp6lim_curr_jitter = V_icmp6lim_curr_jitter[which] =
arc4random_uniform(V_icmp6lim_jitter * 2 + 1) - arc4random_uniform(V_icmp6lim_jitter * 2 + 1) -
V_icmp6lim_jitter; V_icmp6lim_jitter;
} }
@@ -2824,7 +2826,9 @@ sysctl_icmp6lim_and_jitter(SYSCTL_HANDLER_ARGS)
error = EINVAL; error = EINVAL;
else { else {
V_icmp6lim_jitter = new; V_icmp6lim_jitter = new;
icmp6lim_new_jitter(); for (int i = 0; i < RATELIM_MAX; i++) {
icmp6lim_new_jitter(i);
}
} }
} }
} }
@@ -2844,8 +2848,8 @@ icmp6_ratelimit_init(void)
for (int i = 0; i < RATELIM_MAX; i++) { for (int i = 0; i < RATELIM_MAX; i++) {
V_icmp6_rates[i].cr_rate = counter_u64_alloc(M_WAITOK); V_icmp6_rates[i].cr_rate = counter_u64_alloc(M_WAITOK);
V_icmp6_rates[i].cr_ticks = ticks; V_icmp6_rates[i].cr_ticks = ticks;
icmp6lim_new_jitter(i);
} }
icmp6lim_new_jitter();
} }
VNET_SYSINIT(icmp6_ratelimit, SI_SUB_PROTO_DOMAIN, SI_ORDER_ANY, VNET_SYSINIT(icmp6_ratelimit, SI_SUB_PROTO_DOMAIN, SI_ORDER_ANY,
icmp6_ratelimit_init, NULL); icmp6_ratelimit_init, NULL);
@@ -2907,14 +2911,14 @@ icmp6_ratelimit(const struct in6_addr *dst, const int type, const int code)
}; };
pps = counter_ratecheck(&V_icmp6_rates[which], V_icmp6errppslim + pps = counter_ratecheck(&V_icmp6_rates[which], V_icmp6errppslim +
V_icmp6lim_curr_jitter); V_icmp6lim_curr_jitter[which]);
if (pps > 0) { if (pps > 0) {
if (V_icmp6lim_output) if (V_icmp6lim_output)
log(LOG_NOTICE, "Limiting ICMPv6 %s output from %jd " log(LOG_NOTICE, "Limiting ICMPv6 %s output from %jd "
"to %d packets/sec\n", icmp6_rate_descrs[which], "to %d packets/sec\n", icmp6_rate_descrs[which],
(intmax_t )pps, V_icmp6errppslim + (intmax_t )pps, V_icmp6errppslim +
V_icmp6lim_curr_jitter); V_icmp6lim_curr_jitter[which]);
icmp6lim_new_jitter(); icmp6lim_new_jitter(which);
} }
if (pps == -1) { if (pps == -1) {
ICMP6STAT_INC(icp6s_toofreq); ICMP6STAT_INC(icp6s_toofreq);