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 Tue, Aug 18, 2026 at 05:01:58PM +0200, Alexandr Nedvedicky wrote:
> Hello,
> 
> On Tue, Aug 18, 2026 at 04:16:51PM +0200, Alexander Bluhm wrote:
> > 
> > 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.
> 
>     of course you are right. I kind of forgot about race
>     when there are two states where when one does transition
>     to ESTABLSIHED state while the other state is being purged.
> 
> > 
> > > +     atomic_inc_int((int *)&sn->conn);
> > 
> > Do we need the cast?  Address of u_int32_t should work with
> > atomic_inc_int().
> 
>     perhaps not. I dropped the cast here.
> 
> > 
> > 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);
>     do you mind if I do '(u_int32_t)atomic_inc_int_nv(..);'

The compiler does the right thing.  The cast is unnecessary.

> updated diff is below.

OK bluhm@

> thanks and
> regards
> sashan
> 
> --------8<---------------8<-----------------8<--------
> diff --git a/sys/net/pf.c b/sys/net/pf.c
> index 0fd00c0dbf3..5ce7c1226a1 100644
> --- a/sys/net/pf.c
> +++ b/sys/net/pf.c
> @@ -756,16 +756,23 @@ pf_src_connlimit(struct pf_state **stp)
>  {
>  	int			 bad = 0;
>  	struct pf_src_node	*sn;
> +	u_int32_t		 sn_conn;
>  
>  	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.
> +	 */
> +	sn_conn = (u_int32_t) atomic_inc_int_nv(&sn->conn);
> +
>  	(*stp)->src.tcp_est = 1;
>  	pf_add_threshold(&sn->conn_rate);
>  
>  	if ((*stp)->rule.ptr->max_src_conn &&
> -	    (*stp)->rule.ptr->max_src_conn < sn->conn) {
> +	    (*stp)->rule.ptr->max_src_conn < sn_conn) {
>  		pf_status.lcounters[LCNT_SRCCONN]++;
>  		bad++;
>  	}
> @@ -2054,7 +2061,7 @@ pf_src_tree_remove_state(struct pf_state *st)
>  	while ((sni = SLIST_FIRST(&st->src_nodes)) != NULL) {
>  		SLIST_REMOVE_HEAD(&st->src_nodes, next);
>  		if (st->src.tcp_est)
> -			--sni->sn->conn;
> +			atomic_dec_int(&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.