Re: pf: u_int32_t conn counter underflow in pf_src_tree_remove_state()

Alexander Bluhm <[email protected]>
Newsgroups gmane.os.openbsd.tech
Message-ID <[email protected]>
On Sat, Aug 15, 2026 at 12:10:03PM +0200, Alexandr Nedvedicky wrote:
> Hello,
> 
> resending patch from bugs [1], the mail is part of this thread [2].
> 
> the pf(4) needs to use aotmic ops to bump connection counter
> at source node. This happens in function pf_src_connlimit() which
> is called on behalf of pf_test_state() via pf_tcp_track_*() functions.
> the pf_test_state() itself is being called from pf_test() here:
> 
> 8511
> 8512                 PF_STATE_ENTER_READ();
> 8513                 action = pf_find_state(&pd, &key, &st);
> 8514                 st = pf_state_ref(st);
> 8515                 PF_STATE_EXIT_READ();
> 8516
> 8517                 /* check for syncookies if tcp ack and no active state */
> ....
> 8546
> 8547                 if (action == PF_MATCH)
> 8548                         action = pf_test_state(&pd, &st, &reason);
> 
> the thing is that pf_test_state() uses a reference to state. the function
> itself is running without locks. It may happen two packets try to update
> connection counter at the same source node entry. If that happens in parallel
> then counter may miss update. This later leads to underflow.
> 
> The reporter (Mr. Janak Trivedi) confirms patch below works. I think he replied
> off-list.
> 
> OK to commit diff below?

Is it sufficient to have one unlocked atomic operation together
with a locked non-atomic?

thread A pf_src_connlimit
thread B pf_src_tree_remove_state

B PF_LOCK
B read sn->conn
A atomic increment sn->conn
B write sn->conn - 1
B PF_UNLOCK

This does not work, you need atomic everywhere.

> +     atomic_inc_int((int *)&sn->conn);

Do we need the cast?  Address of u_int32_t should work with
atomic_inc_int().

The overflow check is reading the sn->conn value independent from the
atomic increment.  Something like this is better:

	uint32_t sn_conn;

	sn_conn = atomic_inc_int_nv(&sn->conn);

	if ((*stp)->rule.ptr->max_src_conn &&
	    (*stp)->rule.ptr->max_src_conn < sn_conn) {
		pf_status.lcounters[LCNT_SRCCONN]++;
		bad++;
	}

bluhm

> 
> thanks and
> regards
> sashan
> 
> [1] https://marc.info/?l=openbsd-bugs&m=178627901979767&w=2
> 
> [2] https://marc.info/?t=178601035500001&r=1&w=2
> 
> --------8<---------------8<---------------8<------------------8<--------
> diff --git a/sys/net/pf.c b/sys/net/pf.c
> index 0fd00c0dbf3..ca28eb0b98f 100644
> --- a/sys/net/pf.c
> +++ b/sys/net/pf.c
> @@ -760,7 +760,13 @@ pf_src_connlimit(struct pf_state **stp)
>  	if ((sn = pf_get_src_node((*stp), PF_SN_NONE)) == NULL)
>  		return (0);
>  
> -	sn->conn++;
> +	/*
> +	 * Note: conn limit is bumped on SYN_SENT->ESTBLISHED
> +	 * state transition. packet does not hold any locks
> +	 * when running here, therefore atomic is needed.
> +	 */
> +	atomic_inc_int((int *)&sn->conn);
> +
>  	(*stp)->src.tcp_est = 1;
>  	pf_add_threshold(&sn->conn_rate);
>  
> @@ -2051,10 +2057,16 @@ pf_src_tree_remove_state(struct pf_state *st)
>  	u_int32_t		 timeout;
>  	struct pf_sn_item	*sni;
>  
> +	PF_ASSERT_LOCKED();
> +
>  	while ((sni = SLIST_FIRST(&st->src_nodes)) != NULL) {
>  		SLIST_REMOVE_HEAD(&st->src_nodes, next);
> -		if (st->src.tcp_est)
> +		if (st->src.tcp_est) {
> +			/*
> +			 * atomic not needed here, because of PF_LOCK()
> +			 */
>  			--sni->sn->conn;
> +		}
>  		if (--sni->sn->states == 0) {
>  			timeout = st->rule.ptr->timeout[PFTM_SRC_NODE];
>  			if (!timeout)
> 
>
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.