jcs's openbsd hax
openbsd
at jcs 500 lines 15 kB view raw
1/* 2 * Copyright (c) 2020 Darren Tucker <dtucker@openbsd.org> 3 * Copyright (c) 2024 Damien Miller <djm@mindrot.org> 4 * 5 * Permission to use, copy, modify, and distribute this software for any 6 * purpose with or without fee is hereby granted, provided that the above 7 * copyright notice and this permission notice appear in all copies. 8 * 9 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 */ 17 18#include <sys/socket.h> 19#include <sys/types.h> 20#include <sys/tree.h> 21 22#include <limits.h> 23#include <netdb.h> 24#include <stdio.h> 25#include <string.h> 26#include <stdlib.h> 27 28#include "addr.h" 29#include "canohost.h" 30#include "log.h" 31#include "misc.h" 32#include "srclimit.h" 33#include "xmalloc.h" 34#include "servconf.h" 35#include "match.h" 36 37static int max_children, max_persource, ipv4_masklen, ipv6_masklen; 38static struct per_source_penalty penalty_cfg; 39static char *penalty_exempt; 40 41/* Per connection state, used to enforce unauthenticated connection limit. */ 42static struct child_info { 43 int id; 44 struct xaddr addr; 45} *children; 46 47/* 48 * Penalised addresses, active entries here prohibit connections until expired. 49 * Entries become active when more than penalty_min seconds of penalty are 50 * outstanding. 51 */ 52struct penalty { 53 struct xaddr addr; 54 double expiry; 55 int active; 56 const char *reason; 57 RB_ENTRY(penalty) by_addr; 58 RB_ENTRY(penalty) by_expiry; 59}; 60static int penalty_addr_cmp(struct penalty *a, struct penalty *b); 61static int penalty_expiry_cmp(struct penalty *a, struct penalty *b); 62RB_HEAD(penalties_by_addr, penalty) penalties_by_addr4, penalties_by_addr6; 63RB_HEAD(penalties_by_expiry, penalty) penalties_by_expiry4, penalties_by_expiry6; 64RB_GENERATE_STATIC(penalties_by_addr, penalty, by_addr, penalty_addr_cmp) 65RB_GENERATE_STATIC(penalties_by_expiry, penalty, by_expiry, penalty_expiry_cmp) 66static size_t npenalties4, npenalties6; 67 68static int 69srclimit_mask_addr(const struct xaddr *addr, int bits, struct xaddr *masked) 70{ 71 struct xaddr xmask; 72 73 /* Mask address off address to desired size. */ 74 if (addr_netmask(addr->af, bits, &xmask) != 0 || 75 addr_and(masked, addr, &xmask) != 0) { 76 debug3_f("%s: invalid mask %d bits", __func__, bits); 77 return -1; 78 } 79 return 0; 80} 81 82static int 83srclimit_peer_addr(int sock, struct xaddr *addr) 84{ 85 struct sockaddr_storage storage; 86 socklen_t addrlen = sizeof(storage); 87 struct sockaddr *sa = (struct sockaddr *)&storage; 88 89 if (getpeername(sock, sa, &addrlen) != 0) 90 return 1; /* not remote socket? */ 91 if (addr_sa_to_xaddr(sa, addrlen, addr) != 0) 92 return 1; /* unknown address family? */ 93 return 0; 94} 95 96void 97srclimit_init(int max, int persource, int ipv4len, int ipv6len, 98 struct per_source_penalty *penalty_conf, const char *penalty_exempt_conf) 99{ 100 int i; 101 102 max_children = max; 103 ipv4_masklen = ipv4len; 104 ipv6_masklen = ipv6len; 105 max_persource = persource; 106 penalty_cfg = *penalty_conf; 107 if (penalty_cfg.max_sources4 < 0 || penalty_cfg.max_sources6 < 0) 108 fatal_f("invalid max_sources"); /* shouldn't happen */ 109 penalty_exempt = penalty_exempt_conf == NULL ? 110 NULL : xstrdup(penalty_exempt_conf); 111 RB_INIT(&penalties_by_addr4); 112 RB_INIT(&penalties_by_expiry4); 113 RB_INIT(&penalties_by_addr6); 114 RB_INIT(&penalties_by_expiry6); 115 if (max_persource == INT_MAX) /* no limit */ 116 return; 117 debug("%s: max connections %d, per source %d, masks %d,%d", __func__, 118 max, persource, ipv4len, ipv6len); 119 if (max <= 0) 120 fatal_f("invalid number of sockets: %d", max); 121 children = xcalloc(max_children, sizeof(*children)); 122 for (i = 0; i < max_children; i++) 123 children[i].id = -1; 124} 125 126/* returns 1 if connection allowed, 0 if not allowed. */ 127int 128srclimit_check_allow(int sock, int id) 129{ 130 struct xaddr xa, xb; 131 int i, bits, first_unused, count = 0; 132 char xas[NI_MAXHOST]; 133 134 if (max_persource == INT_MAX) /* no limit */ 135 return 1; 136 137 debug_f("sock %d id %d limit %d", sock, id, max_persource); 138 if (srclimit_peer_addr(sock, &xa) != 0) 139 return 1; 140 bits = xa.af == AF_INET ? ipv4_masklen : ipv6_masklen; 141 if (srclimit_mask_addr(&xa, bits, &xb) != 0) 142 return 1; 143 144 first_unused = max_children; 145 /* Count matching entries and find first unused one. */ 146 for (i = 0; i < max_children; i++) { 147 if (children[i].id == -1) { 148 if (i < first_unused) 149 first_unused = i; 150 } else if (addr_cmp(&children[i].addr, &xb) == 0) { 151 count++; 152 } 153 } 154 if (addr_ntop(&xa, xas, sizeof(xas)) != 0) { 155 debug3_f("addr ntop failed"); 156 return 1; 157 } 158 debug3("%s: new unauthenticated connection from %s/%d, at %d of %d", 159 __func__, xas, bits, count, max_persource); 160 161 if (first_unused == max_children) { /* no free slot found */ 162 debug3_f("no free slot"); 163 return 0; 164 } 165 if (first_unused < 0 || first_unused >= max_children) 166 fatal("%s: internal error: first_unused out of range", 167 __func__); 168 169 if (count >= max_persource) 170 return 0; 171 172 /* Connection allowed, store masked address. */ 173 children[first_unused].id = id; 174 memcpy(&children[first_unused].addr, &xb, sizeof(xb)); 175 return 1; 176} 177 178void 179srclimit_done(int id) 180{ 181 int i; 182 183 if (max_persource == INT_MAX) /* no limit */ 184 return; 185 186 debug_f("id %d", id); 187 /* Clear corresponding state entry. */ 188 for (i = 0; i < max_children; i++) { 189 if (children[i].id == id) { 190 children[i].id = -1; 191 return; 192 } 193 } 194} 195 196static int 197penalty_addr_cmp(struct penalty *a, struct penalty *b) 198{ 199 return addr_cmp(&a->addr, &b->addr); 200 /* Addresses must be unique in by_addr, so no need to tiebreak */ 201} 202 203static int 204penalty_expiry_cmp(struct penalty *a, struct penalty *b) 205{ 206 if (a->expiry != b->expiry) 207 return a->expiry < b->expiry ? -1 : 1; 208 /* Tiebreak on addresses */ 209 return addr_cmp(&a->addr, &b->addr); 210} 211 212static void 213expire_penalties_from_tree(double now, const char *t, 214 struct penalties_by_expiry *by_expiry, 215 struct penalties_by_addr *by_addr, size_t *npenaltiesp) 216{ 217 struct penalty *penalty, *tmp; 218 219 /* XXX avoid full scan of tree, e.g. min-heap */ 220 RB_FOREACH_SAFE(penalty, penalties_by_expiry, by_expiry, tmp) { 221 if (penalty->expiry >= now) 222 break; 223 if (RB_REMOVE(penalties_by_expiry, by_expiry, 224 penalty) != penalty || 225 RB_REMOVE(penalties_by_addr, by_addr, 226 penalty) != penalty) 227 fatal_f("internal error: %s penalty table corrupt", t); 228 free(penalty); 229 if ((*npenaltiesp)-- == 0) 230 fatal_f("internal error: %s npenalties underflow", t); 231 } 232} 233 234static void 235expire_penalties(double now) 236{ 237 expire_penalties_from_tree(now, "ipv4", 238 &penalties_by_expiry4, &penalties_by_addr4, &npenalties4); 239 expire_penalties_from_tree(now, "ipv6", 240 &penalties_by_expiry6, &penalties_by_addr6, &npenalties6); 241} 242 243static void 244addr_masklen_ntop(struct xaddr *addr, int masklen, char *s, size_t slen) 245{ 246 size_t o; 247 248 if (addr_ntop(addr, s, slen) != 0) { 249 strlcpy(s, "UNKNOWN", slen); 250 return; 251 } 252 if ((o = strlen(s)) < slen) 253 snprintf(s + o, slen - o, "/%d", masklen); 254} 255 256int 257srclimit_penalty_check_allow(int sock, const char **reason) 258{ 259 struct xaddr addr; 260 struct penalty find, *penalty; 261 double now; 262 int bits, max_sources, overflow_mode; 263 char addr_s[NI_MAXHOST]; 264 struct penalties_by_addr *by_addr; 265 size_t npenalties; 266 267 if (!penalty_cfg.enabled) 268 return 1; 269 if (srclimit_peer_addr(sock, &addr) != 0) 270 return 1; 271 if (penalty_exempt != NULL) { 272 if (addr_ntop(&addr, addr_s, sizeof(addr_s)) != 0) 273 return 1; /* shouldn't happen */ 274 if (addr_match_list(addr_s, penalty_exempt) == 1) { 275 return 1; 276 } 277 } 278 now = monotime_double(); 279 expire_penalties(now); 280 by_addr = addr.af == AF_INET ? 281 &penalties_by_addr4 : &penalties_by_addr6; 282 max_sources = addr.af == AF_INET ? 283 penalty_cfg.max_sources4 : penalty_cfg.max_sources6; 284 overflow_mode = addr.af == AF_INET ? 285 penalty_cfg.overflow_mode : penalty_cfg.overflow_mode6; 286 npenalties = addr.af == AF_INET ? npenalties4 : npenalties6; 287 if (npenalties >= (size_t)max_sources && 288 overflow_mode == PER_SOURCE_PENALTY_OVERFLOW_DENY_ALL) { 289 *reason = "too many penalised addresses"; 290 return 0; 291 } 292 bits = addr.af == AF_INET ? ipv4_masklen : ipv6_masklen; 293 memset(&find, 0, sizeof(find)); 294 if (srclimit_mask_addr(&addr, bits, &find.addr) != 0) 295 return 1; 296 if ((penalty = RB_FIND(penalties_by_addr, by_addr, &find)) == NULL) 297 return 1; /* no penalty */ 298 if (penalty->expiry < now) { 299 expire_penalties(now); 300 return 1; /* expired penalty */ 301 } 302 if (!penalty->active) 303 return 1; /* Penalty hasn't hit activation threshold yet */ 304 *reason = penalty->reason; 305 return 0; 306} 307 308static void 309srclimit_early_expire_penalties_from_tree(const char *t, 310 struct penalties_by_expiry *by_expiry, 311 struct penalties_by_addr *by_addr, size_t *npenaltiesp, size_t max_sources) 312{ 313 struct penalty *p = NULL; 314 int bits; 315 char s[NI_MAXHOST + 4]; 316 317 /* Delete the soonest-to-expire penalties. */ 318 while (*npenaltiesp > max_sources) { 319 if ((p = RB_MIN(penalties_by_expiry, by_expiry)) == NULL) 320 fatal_f("internal error: %s table corrupt (find)", t); 321 bits = p->addr.af == AF_INET ? ipv4_masklen : ipv6_masklen; 322 addr_masklen_ntop(&p->addr, bits, s, sizeof(s)); 323 debug3_f("%s overflow, remove %s", t, s); 324 if (RB_REMOVE(penalties_by_expiry, by_expiry, p) != p || 325 RB_REMOVE(penalties_by_addr, by_addr, p) != p) 326 fatal_f("internal error: %s table corrupt (remove)", t); 327 free(p); 328 (*npenaltiesp)--; 329 } 330} 331 332static void 333srclimit_early_expire_penalties(void) 334{ 335 srclimit_early_expire_penalties_from_tree("ipv4", 336 &penalties_by_expiry4, &penalties_by_addr4, &npenalties4, 337 (size_t)penalty_cfg.max_sources4); 338 srclimit_early_expire_penalties_from_tree("ipv6", 339 &penalties_by_expiry6, &penalties_by_addr6, &npenalties6, 340 (size_t)penalty_cfg.max_sources6); 341} 342 343void 344srclimit_penalise(struct xaddr *addr, int penalty_type) 345{ 346 struct xaddr masked; 347 struct penalty *penalty = NULL, *existing = NULL; 348 double now; 349 int bits, max_sources = 0, overflow_mode; 350 double penalty_secs; 351 char addrnetmask[NI_MAXHOST + 4]; 352 const char *reason = NULL, *t; 353 size_t *npenaltiesp = NULL; 354 struct penalties_by_addr *by_addr = NULL; 355 struct penalties_by_expiry *by_expiry = NULL; 356 357 if (!penalty_cfg.enabled) 358 return; 359 if (penalty_exempt != NULL) { 360 if (addr_ntop(addr, addrnetmask, sizeof(addrnetmask)) != 0) 361 return; /* shouldn't happen */ 362 if (addr_match_list(addrnetmask, penalty_exempt) == 1) { 363 debug3_f("address %s is exempt", addrnetmask); 364 return; 365 } 366 } 367 368 switch (penalty_type) { 369 case SRCLIMIT_PENALTY_NONE: 370 return; 371 case SRCLIMIT_PENALTY_CRASH: 372 penalty_secs = penalty_cfg.penalty_crash; 373 reason = "penalty: caused crash"; 374 break; 375 case SRCLIMIT_PENALTY_AUTHFAIL: 376 penalty_secs = penalty_cfg.penalty_authfail; 377 reason = "penalty: failed authentication"; 378 break; 379 case SRCLIMIT_PENALTY_NOAUTH: 380 penalty_secs = penalty_cfg.penalty_noauth; 381 reason = "penalty: connections without attempting authentication"; 382 break; 383 case SRCLIMIT_PENALTY_INVALIDUSER: 384 penalty_secs = penalty_cfg.penalty_invaliduser; 385 reason = "penalty: attempted authentication by invalid user"; 386 break; 387 case SRCLIMIT_PENALTY_REFUSECONNECTION: 388 penalty_secs = penalty_cfg.penalty_refuseconnection; 389 reason = "penalty: connection prohibited by RefuseConnection"; 390 break; 391 case SRCLIMIT_PENALTY_GRACE_EXCEEDED: 392 penalty_secs = penalty_cfg.penalty_grace; 393 reason = "penalty: exceeded LoginGraceTime"; 394 break; 395 default: 396 fatal_f("internal error: unknown penalty %d", penalty_type); 397 } 398 399 if (penalty_secs <= 0) 400 return; 401 402 bits = addr->af == AF_INET ? ipv4_masklen : ipv6_masklen; 403 if (srclimit_mask_addr(addr, bits, &masked) != 0) 404 return; 405 addr_masklen_ntop(addr, bits, addrnetmask, sizeof(addrnetmask)); 406 407 now = monotime_double(); 408 expire_penalties(now); 409 by_expiry = addr->af == AF_INET ? 410 &penalties_by_expiry4 : &penalties_by_expiry6; 411 by_addr = addr->af == AF_INET ? 412 &penalties_by_addr4 : &penalties_by_addr6; 413 max_sources = addr->af == AF_INET ? 414 penalty_cfg.max_sources4 : penalty_cfg.max_sources6; 415 overflow_mode = addr->af == AF_INET ? 416 penalty_cfg.overflow_mode : penalty_cfg.overflow_mode6; 417 npenaltiesp = addr->af == AF_INET ? &npenalties4 : &npenalties6; 418 t = addr->af == AF_INET ? "ipv4" : "ipv6"; 419 if (*npenaltiesp >= (size_t)max_sources && 420 overflow_mode == PER_SOURCE_PENALTY_OVERFLOW_DENY_ALL) { 421 verbose_f("%s penalty table full, cannot penalise %s for %s", t, 422 addrnetmask, reason); 423 return; 424 } 425 426 penalty = xcalloc(1, sizeof(*penalty)); 427 penalty->addr = masked; 428 penalty->expiry = now + penalty_secs; 429 penalty->reason = reason; 430 if ((existing = RB_INSERT(penalties_by_addr, by_addr, 431 penalty)) == NULL) { 432 /* penalty didn't previously exist */ 433 if (penalty_secs > penalty_cfg.penalty_min) 434 penalty->active = 1; 435 if (RB_INSERT(penalties_by_expiry, by_expiry, penalty) != NULL) 436 fatal_f("internal error: %s penalty tables corrupt", t); 437 do_log2_f(penalty->active ? 438 SYSLOG_LEVEL_INFO : SYSLOG_LEVEL_VERBOSE, 439 "%s: new %s %s penalty of %.3f seconds for %s", t, 440 addrnetmask, penalty->active ? "active" : "deferred", 441 penalty_secs, reason); 442 if (++(*npenaltiesp) > (size_t)max_sources) 443 srclimit_early_expire_penalties(); /* permissive */ 444 return; 445 } 446 debug_f("%s penalty for %s %s already exists, %lld seconds remaining", 447 existing->active ? "active" : "inactive", t, 448 addrnetmask, (long long)(existing->expiry - now)); 449 /* Expiry information is about to change, remove from tree */ 450 if (RB_REMOVE(penalties_by_expiry, by_expiry, existing) != existing) 451 fatal_f("internal error: %s penalty table corrupt (remove)", t); 452 /* An entry already existed. Accumulate penalty up to maximum */ 453 existing->expiry += penalty_secs; 454 if (existing->expiry - now > penalty_cfg.penalty_max) 455 existing->expiry = now + penalty_cfg.penalty_max; 456 if (existing->expiry - now > penalty_cfg.penalty_min && 457 !existing->active) { 458 logit_f("%s: activating %s penalty of %.3f seconds for %s", 459 addrnetmask, t, existing->expiry - now, reason); 460 existing->active = 1; 461 } 462 existing->reason = penalty->reason; 463 free(penalty); 464 penalty = NULL; 465 /* Re-insert into expiry tree */ 466 if (RB_INSERT(penalties_by_expiry, by_expiry, existing) != NULL) 467 fatal_f("internal error: %s penalty table corrupt (insert)", t); 468} 469 470static void 471srclimit_penalty_info_for_tree(const char *t, 472 struct penalties_by_expiry *by_expiry, size_t npenalties) 473{ 474 struct penalty *p = NULL; 475 int bits; 476 char s[NI_MAXHOST + 4]; 477 double now; 478 479 now = monotime_double(); 480 logit("%zu active %s penalties", npenalties, t); 481 RB_FOREACH(p, penalties_by_expiry, by_expiry) { 482 bits = p->addr.af == AF_INET ? ipv4_masklen : ipv6_masklen; 483 addr_masklen_ntop(&p->addr, bits, s, sizeof(s)); 484 if (p->expiry < now) 485 logit("client %s %s (expired)", s, p->reason); 486 else { 487 logit("client %s %s (%.3f secs left)", s, p->reason, 488 p->expiry - now); 489 } 490 } 491} 492 493void 494srclimit_penalty_info(void) 495{ 496 srclimit_penalty_info_for_tree("ipv4", 497 &penalties_by_expiry4, npenalties4); 498 srclimit_penalty_info_for_tree("ipv6", 499 &penalties_by_expiry6, npenalties6); 500}