DF-0631 / fix.diff
diff --git a/sys/net/ipfw3/ip_fw3.h b/sys/net/ipfw3/ip_fw3.h --- a/sys/net/ipfw3/ip_fw3.h +++ b/sys/net/ipfw3/ip_fw3.h @@ -469,6 +469,9 @@ extern ip_fw_ctl_t *ip_fw_ctl_x_ptr; extern ip_fw_dn_io_t *ip_fw_dn_io_ptr; +/* DF-0631: invoked on rule deletion to drop states caching the rule ptr */ +extern void (*ip_fw3_state_remove_rule_ptr)(struct ip_fw *); + #define IPFW_TABLES_MAX 32 #define IPFW_USR_F_NORULE 0x01 diff --git a/sys/net/ipfw3/ip_fw3.c b/sys/net/ipfw3/ip_fw3.c --- a/sys/net/ipfw3/ip_fw3.c +++ b/sys/net/ipfw3/ip_fw3.c @@ -91,6 +91,11 @@ MALLOC_DEFINE(M_IPFW3, "IPFW3", "ipfw3 module"); +/* DF-0631: set by ipfw3_basic; called from rule-deletion dispatches to drop + * states caching a soon-to-be-freed rule pointer. Defined here (module) so + * ipfw3_basic can resolve it via its existing MODULE_DEPEND on ipfw3. */ +void (*ip_fw3_state_remove_rule_ptr)(struct ip_fw *); + #define MAX_MODULE 10 #define MAX_OPCODE_PER_MODULE 100 @@ -784,6 +789,8 @@ the_rule = rule; rule = rule->next; + if (ip_fw3_state_remove_rule_ptr) + ip_fw3_state_remove_rule_ptr(the_rule); kfree(the_rule, M_IPFW3); } @@ -830,6 +837,8 @@ rule = ctx->rules; while (rule!=NULL) { if (rule->rulenum == dmsg->rulenum) { + if (ip_fw3_state_remove_rule_ptr) + ip_fw3_state_remove_rule_ptr(rule); ip_fw3_delete_rule(ctx, prev, rule); break; } diff --git a/sys/net/ipfw3_basic/ip_fw3_state.c b/sys/net/ipfw3_basic/ip_fw3_state.c --- a/sys/net/ipfw3_basic/ip_fw3_state.c +++ b/sys/net/ipfw3_basic/ip_fw3_state.c @@ -357,6 +357,39 @@ return 0; } +/* DF-0631: sweep states caching a rule ptr about to be freed. */ +void ip_fw3_state_remove_rule(struct ip_fw *rule); + +void +ip_fw3_state_remove_rule(struct ip_fw *rule) +{ + struct ipfw3_state_context *state_ctx = fw3_state_ctx[mycpuid]; + struct ipfw3_state *s, *tmp; + + /* + * DF-0631: every dynamic state caches a raw, unrefcounted pointer to + * its creating rule (s->stub). Rule deletion must drop those states + * on the owning CPU before the rule slab is freed, otherwise a later + * state match restores the dangling pointer and ip_fw3 dereferences + * and writes freed kernel memory. This runs from the per-CPU rule + * deletion dispatches, so it sweeps only the current CPU's trees. + */ +#define SWEEP_TREE(tree) \ + RB_FOREACH_SAFE(s, fw3_state_tree, &state_ctx->tree, tmp) { \ + if (s->stub == rule) { \ + RB_REMOVE(fw3_state_tree, &state_ctx->tree, s); \ + kfree(s, M_IPFW3_STATE); \ + } \ + } + SWEEP_TREE(rb_tcp_in); + SWEEP_TREE(rb_tcp_out); + SWEEP_TREE(rb_udp_in); + SWEEP_TREE(rb_udp_out); + SWEEP_TREE(rb_icmp_in); + SWEEP_TREE(rb_icmp_out); +#undef SWEEP_TREE +} + void ip_fw3_state_flush_dispatch(netmsg_t nmsg) { @@ -697,6 +730,7 @@ struct netmsg_base msg; ip_fw3_ctl_state_ptr = ip_fw3_ctl_state_sockopt; + ip_fw3_state_remove_rule_ptr = ip_fw3_state_remove_rule; callout_init_mp(&ip_fw3_state_cleanup_callout); callout_reset(&ip_fw3_state_cleanup_callout, sysctl_var_cleanup_interval * hz, |