Index | Thread | Search

From:
Alexandr Nedvedicky <sashan@fastmail.net>
Subject:
pf(4): pfr_insert_kentry() always needs PF_LOCK()
To:
tech@openbsd.org
Cc:
bluhm@openbsd.org, dlg@openbsd.org
Date:
Wed, 30 Sep 2026 12:28:00 +0200

Download raw body.

Thread
  • Alexandr Nedvedicky:

    pf(4): pfr_insert_kentry() always needs PF_LOCK()

Hello,

this is almost identical diff I've sent to bugs@ [1].
The change I'd like to commit adds PF_ASSERT_LOCKED() to
pfr_insert_kentry() function.

the diff fixes the issue which got introduced in 2018 by my
commit here:

    revision 1.1074
    date: 2018/09/11 07:53:38;  author: sashan;  state: Exp;  lines: +117 -33;\
	commitid: L4l6Ihj5YoSZO1NA;
    - moving state look up outside of PF_LOCK()

    this change adds a pf_state_lock rw-lock, which protects consistency
    of state table in PF. The code delivered in this change is guarded
    by 'WITH_PF_LOCK', which is still undefined. People, who are willing
    to experiment and want to run it must do two things:

	    - compile kernel with -DWITH_PF_LOCK
	    - bump NET_TASKQ from 1 to ... sky is the limit,
	      (just select some sensible value for number of tasks your
	      system is able to handle)

    OK bluhm@

the thing is the pfr_insert_kentry() assumes it's being called under PF_LOCK(),
but this is not true when we deal with overload action. pf(4) holds no
lock when adding offending address to overload table. on this code path
the pf(4) grabs reader lock on state table, grabs reference to state and
drops the state lock. all further processing runs with no global pf(40 locks.

it's interesting the bug was sitting there waiting to bite for 8 years.

I think the diff below is safe enough to commit even it is late in release
cycle. I plan to bring improved diff after release. The improved diff
does:
    - shrinks the PF_LOCK() scope just to cover pfr_insert_kentry()

    - adds mtx_lock()/mtx_unlock() on state mutex to pf_get_src_node()
    so source node list traversal is safe. The diff below should take
    care of it because purging state grabs the PF_LOCK() these days.
    the snippet here comes from pf_purged_expired_states()

	2354         rw_enter_write(&pf_state_list.pfs_rwl);
	2355         PF_LOCK();
	2356         PF_STATE_ENTER_WRITE();
	2357         SLIST_FOREACH(st, &gcl, gc_list) {
	2358                 if (st->timeout != PFTM_UNLINKED)
	2359                         pf_remove_state(st);
	2360
	2361                 pf_free_state(st);
	2362         }
	2363         PF_STATE_EXIT_WRITE();
	2364         PF_UNLOCK();
	2365         rw_exit_write(&pf_state_list.pfs_rwl);

    pf_remove_state() calls to pf_src_tree_remove_state() where the
    list of source nodes (the same list pf(4) traverses in pf_get_src_node()
    gets purged.

The follow up change I plan to commit is larger, thus more risky IMO to go for
it now.

OK to commit diff below? or leave the bug to be fixed with next release.

thanks and
regards
sashan


[1] https://marc.info/?l=openbsd-bugs&m=179071202654415&w=2

--------8<---------------8<---------------8<------------------8<--------
diff --git a/sys/net/pf.c b/sys/net/pf.c
index b465a77b487..e6c67842a07 100644
--- a/sys/net/pf.c
+++ b/sys/net/pf.c
@@ -757,9 +757,12 @@ pf_src_connlimit(struct pf_state **stp)
 	int			 bad = 0;
 	struct pf_src_node	*sn;
 	u_int32_t		 sn_conn;
+	int			 rv = 0;
+
+	PF_LOCK();

 	if ((sn = pf_get_src_node((*stp), PF_SN_NONE)) == NULL)
-		return (0);
+		goto done;

 	/*
 	 * Note: conn limit is bumped on SYN_SENT->ESTBLISHED
@@ -784,7 +787,7 @@ pf_src_connlimit(struct pf_state **stp)
 	}

 	if (!bad)
-		return (0);
+		goto done;

 	if ((*stp)->rule.ptr->overload_tbl) {
 		struct pfr_addr p;
@@ -822,6 +825,7 @@ pf_src_connlimit(struct pf_state **stp)
 			struct pf_state *st;

 			pf_status.lcounters[LCNT_OVERLOAD_FLUSH]++;
+			PF_STATE_ENTER_READ();
 			RBT_FOREACH(st, pf_state_tree_id, &tree_id) {
 				sk = st->key[PF_SK_WIRE];
 				/*
@@ -844,6 +848,7 @@ pf_src_connlimit(struct pf_state **stp)
 					killed++;
 				}
 			}
+			PF_STATE_EXIT_READ();
 			if (pf_status.debug >= LOG_NOTICE)
 				addlog(", %u states killed", killed);
 		}
@@ -854,7 +859,12 @@ pf_src_connlimit(struct pf_state **stp)
 	/* kill this state */
 	pf_update_state_timeout(*stp, PFTM_PURGE);
 	pf_set_protostate(*stp, PF_PEER_BOTH, TCPS_CLOSED);
-	return (1);
+
+	rv = 1;
+done:
+	PF_UNLOCK();
+
+	return (rv);
 }

 int
diff --git a/sys/net/pf_table.c b/sys/net/pf_table.c
index a33fa296497..f83cada05cc 100644
--- a/sys/net/pf_table.c
+++ b/sys/net/pf_table.c
@@ -1145,6 +1145,8 @@ pfr_insert_kentry(struct pfr_ktable *kt, struct pfr_addr *ad, time_t tzero)
 	struct pfr_kentry	*p;
 	int			 rv;

+	PF_ASSERT_LOCKED();
+
 	p = pfr_lookup_addr(kt, ad, 1);
 	if (p != NULL)
 		return (0);