Changeset View
Changeset View
Standalone View
Standalone View
sys/netpfil/pf/pf_norm.c
Show First 20 Lines • Show All 50 Lines • ▼ Show 20 Lines | |||||
#include <netinet/in.h> | #include <netinet/in.h> | ||||
#include <netinet/ip.h> | #include <netinet/ip.h> | ||||
#include <netinet/ip_var.h> | #include <netinet/ip_var.h> | ||||
#include <netinet6/ip6_var.h> | #include <netinet6/ip6_var.h> | ||||
#include <netinet6/scope6_var.h> | #include <netinet6/scope6_var.h> | ||||
#include <netinet/tcp.h> | #include <netinet/tcp.h> | ||||
#include <netinet/tcp_fsm.h> | #include <netinet/tcp_fsm.h> | ||||
#include <netinet/tcp_seq.h> | #include <netinet/tcp_seq.h> | ||||
#include <netinet/sctp_constants.h> | |||||
#include <netinet/sctp_header.h> | |||||
#ifdef INET6 | #ifdef INET6 | ||||
#include <netinet/ip6.h> | #include <netinet/ip6.h> | ||||
#endif /* INET6 */ | #endif /* INET6 */ | ||||
struct pf_frent { | struct pf_frent { | ||||
TAILQ_ENTRY(pf_frent) fr_next; | TAILQ_ENTRY(pf_frent) fr_next; | ||||
struct mbuf *fe_m; | struct mbuf *fe_m; | ||||
▲ Show 20 Lines • Show All 1,957 Lines • ▼ Show 20 Lines | case TCPOPT_MAXSEG: | ||||
} | } | ||||
break; | break; | ||||
default: | default: | ||||
break; | break; | ||||
} | } | ||||
} | } | ||||
return (0); | return (0); | ||||
} | |||||
static int | |||||
pf_scan_sctp(struct mbuf *m, int ipoff, int off, struct pf_pdesc *pd) | |||||
{ | |||||
struct sctp_chunkhdr ch = { }; | |||||
int chunk_off = sizeof(struct sctphdr); | |||||
int chunk_start; | |||||
tuexen: Does this loop provide a way for a DOS attack? Assume an attacker sends a lot of packets… | |||||
while (off + chunk_off < pd->tot_len) { | |||||
if (!pf_pull_hdr(m, off + chunk_off, &ch, sizeof(ch), NULL, | |||||
NULL, pd->af)) | |||||
return (PF_DROP); | |||||
Not Done Inline ActionsIf an attacker sends a chunk with chunk_length == 0, doesn't this results in an endless loop? tuexen: If an attacker sends a chunk with chunk_length == 0, doesn't this results in an endless loop? | |||||
chunk_start = chunk_off; | |||||
chunk_off += roundup(ntohs(ch.chunk_length), 4); | |||||
switch (ch.chunk_type) { | |||||
case SCTP_INITIATION: { | |||||
struct sctp_init_chunk init; | |||||
if (!pf_pull_hdr(m, off + chunk_start, &init, | |||||
sizeof(init), NULL, NULL, pd->af)) | |||||
return (PF_DROP); | |||||
/* | |||||
* RFC 9620, Section 3.3.2, "The Initiate Tag is allowed to have | |||||
* any value except 0." | |||||
*/ | |||||
if (init.init.initiate_tag == 0) | |||||
return (PF_DROP); | |||||
if (init.init.num_inbound_streams == 0) | |||||
return (PF_DROP); | |||||
if (init.init.num_outbound_streams == 0) | |||||
return (PF_DROP); | |||||
if (ntohl(init.init.a_rwnd) < SCTP_MIN_RWND) | |||||
return (PF_DROP); | |||||
/* | |||||
* RFC 9260, Section 3.1, INIT chunks MUST have zero | |||||
* verification tag. | |||||
*/ | |||||
if (pd->hdr.sctp.v_tag != 0) | |||||
return (PF_DROP); | |||||
pd->sctp_flags |= PFDESC_SCTP_INIT; | |||||
break; | |||||
} | |||||
case SCTP_INITIATION_ACK: | |||||
pd->sctp_flags |= PFDESC_SCTP_INIT_ACK; | |||||
break; | |||||
case SCTP_ABORT_ASSOCIATION: | |||||
pd->sctp_flags |= PFDESC_SCTP_ABORT; | |||||
break; | |||||
case SCTP_SHUTDOWN: | |||||
case SCTP_SHUTDOWN_ACK: | |||||
case SCTP_SHUTDOWN_COMPLETE: | |||||
pd->sctp_flags |= PFDESC_SCTP_SHUTDOWN; | |||||
break; | |||||
case SCTP_COOKIE_ECHO: | |||||
case SCTP_COOKIE_ACK: | |||||
pd->sctp_flags |= PFDESC_SCTP_COOKIE; | |||||
break; | |||||
case SCTP_DATA: | |||||
pd->sctp_flags |= PFDESC_SCTP_DATA; | |||||
break; | |||||
default: | |||||
pd->sctp_flags |= PFDESC_SCTP_OTHER; | |||||
break; | |||||
} | |||||
} | |||||
/* Validate chunk lengths vs. packet length. */ | |||||
if (off + chunk_off != pd->tot_len) | |||||
return (PF_DROP); | |||||
/* INIT chunk must always be the only one in a packet. */ | |||||
if ((pd->sctp_flags & PFDESC_SCTP_INIT) && | |||||
(pd->sctp_flags & ~PFDESC_SCTP_INIT)) | |||||
return (PF_DROP); | |||||
return (PF_PASS); | |||||
} | |||||
int | |||||
pf_normalize_sctp(int dir, struct pfi_kkif *kif, struct mbuf *m, int ipoff, | |||||
int off, void *h, struct pf_pdesc *pd) | |||||
{ | |||||
struct pf_krule *r, *rm = NULL; | |||||
struct sctphdr *sh = &pd->hdr.sctp; | |||||
u_short reason; | |||||
sa_family_t af = pd->af; | |||||
int srs; | |||||
PF_RULES_RASSERT(); | |||||
/* Unconditionally scan the SCTP packet, because we need to look for | |||||
* things like shutdown and asconf chunks. */ | |||||
if (pf_scan_sctp(m, ipoff, off, pd) != PF_PASS) | |||||
goto sctp_drop; | |||||
r = TAILQ_FIRST(pf_main_ruleset.rules[PF_RULESET_SCRUB].active.ptr); | |||||
/* Check if there any scrub rules. Lack of scrub rules means enforced | |||||
* packet normalization operation just like in OpenBSD. */ | |||||
srs = (r != NULL); | |||||
while (r != NULL) { | |||||
pf_counter_u64_add(&r->evaluations, 1); | |||||
if (pfi_kkif_match(r->kif, kif) == r->ifnot) | |||||
r = r->skip[PF_SKIP_IFP].ptr; | |||||
else if (r->direction && r->direction != dir) | |||||
r = r->skip[PF_SKIP_DIR].ptr; | |||||
else if (r->af && r->af != af) | |||||
r = r->skip[PF_SKIP_AF].ptr; | |||||
else if (r->proto && r->proto != pd->proto) | |||||
Not Done Inline ActionsThe same applies to INIT ACK and SHUTDOWN COMPLETE chunks. Why is this not tested? tuexen: The same applies to INIT ACK and SHUTDOWN COMPLETE chunks. Why is this not tested? | |||||
r = r->skip[PF_SKIP_PROTO].ptr; | |||||
else if (PF_MISMATCHAW(&r->src.addr, pd->src, af, | |||||
r->src.neg, kif, M_GETFIB(m))) | |||||
r = r->skip[PF_SKIP_SRC_ADDR].ptr; | |||||
else if (r->src.port_op && !pf_match_port(r->src.port_op, | |||||
r->src.port[0], r->src.port[1], sh->src_port)) | |||||
r = r->skip[PF_SKIP_SRC_PORT].ptr; | |||||
else if (PF_MISMATCHAW(&r->dst.addr, pd->dst, af, | |||||
r->dst.neg, NULL, M_GETFIB(m))) | |||||
r = r->skip[PF_SKIP_DST_ADDR].ptr; | |||||
else if (r->dst.port_op && !pf_match_port(r->dst.port_op, | |||||
r->dst.port[0], r->dst.port[1], sh->dest_port)) | |||||
r = r->skip[PF_SKIP_DST_PORT].ptr; | |||||
else { | |||||
rm = r; | |||||
break; | |||||
} | |||||
} | |||||
if (srs) { | |||||
/* With scrub rules present SCTP normalization happens only | |||||
* if one of rules has matched and it's not a "no scrub" rule */ | |||||
if (rm == NULL || rm->action == PF_NOSCRUB) | |||||
return (PF_PASS); | |||||
pf_counter_u64_critical_enter(); | |||||
pf_counter_u64_add_protected(&r->packets[dir == PF_OUT], 1); | |||||
pf_counter_u64_add_protected(&r->bytes[dir == PF_OUT], pd->tot_len); | |||||
pf_counter_u64_critical_exit(); | |||||
} | |||||
/* Verify we're a multiple of 4 bytes long */ | |||||
if ((pd->tot_len - off - sizeof(struct sctphdr)) % 4) | |||||
goto sctp_drop; | |||||
/* INIT chunk needs to be the only chunk */ | |||||
if (pd->sctp_flags & PFDESC_SCTP_INIT) | |||||
if (pd->sctp_flags & ~PFDESC_SCTP_INIT) | |||||
goto sctp_drop; | |||||
return (PF_PASS); | |||||
sctp_drop: | |||||
REASON_SET(&reason, PFRES_NORM); | |||||
if (rm != NULL && r->log) | |||||
PFLOG_PACKET(kif, m, AF_INET, dir, reason, r, NULL, NULL, pd, | |||||
1); | |||||
return (PF_DROP); | |||||
} | } | ||||
u_int16_t | u_int16_t | ||||
pf_rule_to_scrub_flags(u_int32_t rule_flags) | pf_rule_to_scrub_flags(u_int32_t rule_flags) | ||||
{ | { | ||||
/* | /* | ||||
* Translate pf_krule->rule_flag to pf_krule->scrub_flags. | * Translate pf_krule->rule_flag to pf_krule->scrub_flags. | ||||
* The pf_scrub_ip functions have been adapted to the new style of pass | * The pf_scrub_ip functions have been adapted to the new style of pass | ||||
▲ Show 20 Lines • Show All 75 Lines • Show Last 20 Lines |
Does this loop provide a way for a DOS attack? Assume an attacker sends a lot of packets containing, for example, (1500 - 12) / 4 = 372 chunks?