CVE-2023-52556 (published 2024-03-01T17:15Z)

In OpenBSD 7.4 before errata 009, a race condition between pf(4)'s processing of packets and expiration of packet states may cause a kernel panic.\n


Weakness information:
cwe_id: NVD-CWE-noinfo
cwe_name: Insufficient Information
description: There is insufficient information about the issue to classify it; details are unkown or unspecified.
extended_description: Insufficient Information

Fix information:
repo_url: https://github.com/openbsd/src
commit_details: repo_url: https://github.com/openbsd/src
author: sashan
author_date: 2023-12-01 10:28:32+00:00
msg: Prevent race between pf_test() and pf_purge_expired_states().
Packets (callers to pf_test()) must alter pf_state::timeout
under protection of pf_state::mtx. We also have to make sure
the packet does not update pf_state::timeout when ::timeout
reaches PFTM_UNLINKED.

The first report came from Johan Huldtgren, but he is not
the single user who has noticed "st->timeout == PFTM_UNLINKED"
assert violation.

OK bluhm@
repository: repo_url: https://github.com/openbsd/src
repo_name: openbsd/src
description: Read-only git conversion of OpenBSD's official CVS src repository. Pull requests not accepted - send diffs to the tech@ mailing list.
repo_language: C
owner: openbsd
file_changes: filename: pf.c
change_type: ModificationType.MODIFY
diff: @@ -1,4 +1,4 @@
-/*	$OpenBSD: pf.c,v 1.1188 2023/10/10 16:26:06 bluhm Exp $ */
+/*	$OpenBSD: pf.c,v 1.1189 2023/12/01 10:28:32 sashan Exp $ */
 
 /*
  * Copyright (c) 2001 Daniel Hartmeier
@@ -469,6 +469,15 @@ pf_state_list_remove(struct pf_state_list *pfs, struct pf_state *st)
 	pf_state_unref(st); /* list no longer references the state */
 }
 
+void
+pf_update_state_timeout(struct pf_state *st, int to)
+{
+	mtx_enter(&st->mtx);
+	if (st->timeout != PFTM_UNLINKED)
+		st->timeout = to;
+	mtx_leave(&st->mtx);
+}
+
 int
 pf_src_connlimit(struct pf_state **stp)
 {
@@ -549,7 +558,7 @@ pf_src_connlimit(struct pf_state **stp)
 				    ((*stp)->rule.ptr->flush &
 				    PF_FLUSH_GLOBAL ||
 				    (*stp)->rule.ptr == st->rule.ptr)) {
-					st->timeout = PFTM_PURGE;
+					pf_update_state_timeout(st, PFTM_PURGE);
 					pf_set_protostate(st, PF_PEER_BOTH,
 					    TCPS_CLOSED);
 					killed++;
@@ -563,7 +572,7 @@ pf_src_connlimit(struct pf_state **stp)
 	}
 
 	/* kill this state */
-	(*stp)->timeout = PFTM_PURGE;
+	pf_update_state_timeout(*stp, PFTM_PURGE);
 	pf_set_protostate(*stp, PF_PEER_BOTH, TCPS_CLOSED);
 	return (1);
 }
@@ -1758,10 +1767,13 @@ pf_remove_state(struct pf_state *st)
 {
 	PF_ASSERT_LOCKED();
 
-	if (st->timeout == PFTM_UNLINKED)
+	mtx_enter(&st->mtx);
+	if (st->timeout == PFTM_UNLINKED) {
+		mtx_leave(&st->mtx);
 		return;
-
+	}
 	st->timeout = PFTM_UNLINKED;
+	mtx_leave(&st->mtx);
 
 	/* handle load balancing related tasks */
 	pf_postprocess_addr(st);
@@ -1816,7 +1828,8 @@ pf_remove_divert_state(struct pf_state_key *sk)
 				    sist->dst.state < TCPS_FIN_WAIT_2) {
 					pf_set_protostate(sist, PF_PEER_BOTH,
 					    TCPS_TIME_WAIT);
-					sist->timeout = PFTM_TCP_CLOSED;
+					pf_update_state_timeout(sist,
+					    PFTM_TCP_CLOSED);
 					sist->expire = getuptime();
 				}
 				sist->state_flags |= PFSTATE_INP_UNLINKED;
@@ -5036,18 +5049,18 @@ pf_tcp_track_full(struct pf_pdesc *pd, struct pf_state **stp, u_short *reason,
 		(*stp)->expire = getuptime();
 		if (src->state >= TCPS_FIN_WAIT_2 &&
 		    dst->state >= TCPS_FIN_WAIT_2)
-			(*stp)->timeout = PFTM_TCP_CLOSED;
+			pf_update_state_timeout(*stp, PFTM_TCP_CLOSED);
 		else if (src->state >= TCPS_CLOSING &&
 		    dst->state >= TCPS_CLOSING)
-			(*stp)->timeout = PFTM_TCP_FIN_WAIT;
+			pf_update_state_timeout(*stp, PFTM_TCP_FIN_WAIT);
 		else if (src->state < TCPS_ESTABLISHED ||
 		    dst->state < TCPS_ESTABLISHED)
-			(*stp)->timeout = PFTM_TCP_OPENING;
+			pf_update_state_timeout(*stp, PFTM_TCP_OPENING);
 		else if (src->state >= TCPS_CLOSING ||
 		    dst->state >= TCPS_CLOSING)
-			(*stp)->timeout = PFTM_TCP_CLOSING;
+			pf_update_state_timeout(*stp, PFTM_TCP_CLOSING);
 		else
-			(*stp)->timeout = PFTM_TCP_ESTABLISHED;
+			pf_update_state_timeout(*stp, PFTM_TCP_ESTABLISHED);
 
 		/* Fall through to PASS packet */
 	} else if ((dst->state < TCPS_SYN_SENT ||
@@ -5229,18 +5242,18 @@ pf_tcp_track_sloppy(struct pf_pdesc *pd, struct pf_state **stp,
 	(*stp)->expire = getuptime();
 	if (src->state >= TCPS_FIN_WAIT_2 &&
 	    dst->state >= TCPS_FIN_WAIT_2)
-		(*stp)->timeout = PFTM_TCP_CLOSED;
+		pf_update_state_timeout(*stp, PFTM_TCP_CLOSED);
 	else if (src->state >= TCPS_CLOSING &&
 	    dst->state >= TCPS_CLOSING)
-		(*stp)->timeout = PFTM_TCP_FIN_WAIT;
+		pf_update_state_timeout(*stp, PFTM_TCP_FIN_WAIT);
 	else if (src->state < TCPS_ESTABLISHED ||
 	    dst->state < TCPS_ESTABLISHED)
-		(*stp)->timeout = PFTM_TCP_OPENING;
+		pf_update_state_timeout(*stp, PFTM_TCP_OPENING);
 	else if (src->state >= TCPS_CLOSING ||
 	    dst->state >= TCPS_CLOSING)
-		(*stp)->timeout = PFTM_TCP_CLOSING;
+		pf_update_state_timeout(*stp, PFTM_TCP_CLOSING);
 	else
-		(*stp)->timeout = PFTM_TCP_ESTABLISHED;
+		pf_update_state_timeout(*stp, PFTM_TCP_ESTABLISHED);
 
 	return (PF_PASS);
 }
@@ -5377,7 +5390,7 @@ pf_test_state(struct pf_pdesc *pd, struct pf_state **stp, u_short *reason)
 					addlog("\n");
 				}
 				/* XXX make sure it's the same direction ?? */
-				(*stp)->timeout = PFTM_PURGE;
+				pf_update_state_timeout(*stp, PFTM_PURGE);
 				pf_state_unref(*stp);
 				*stp = NULL;
 				pf_mbuf_link_inpcb(pd->m, inp);
@@ -5417,9 +5430,9 @@ pf_test_state(struct pf_pdesc *pd, struct pf_state **stp, u_short *reason)
 		(*stp)->expire = getuptime();
 		if (src->state == PFUDPS_MULTIPLE &&
 		    dst->state == PFUDPS_MULTIPLE)
-			(*stp)->timeout = PFTM_UDP_MULTIPLE;
+			pf_update_state_timeout(*stp, PFTM_UDP_MULTIPLE);
 		else
-			(*stp)->timeout = PFTM_UDP_SINGLE;
+			pf_update_state_timeout(*stp, PFTM_UDP_SINGLE);
 		break;
 	default:
 		/* update states */
@@ -5432,9 +5445,9 @@ pf_test_state(struct pf_pdesc *pd, struct pf_state **stp, u_short *reason)
 		(*stp)->expire = getuptime();
 		if (src->state == PFOTHERS_MULTIPLE &&
 		    dst->state == PFOTHERS_MULTIPLE)
-			(*stp)->timeout = PFTM_OTHER_MULTIPLE;
+			pf_update_state_timeout(*stp, PFTM_OTHER_MULTIPLE);
 		else
-			(*stp)->timeout = PFTM_OTHER_SINGLE;
+			pf_update_state_timeout(*stp, PFTM_OTHER_SINGLE);
 		break;
 	}
 
@@ -5585,7 +5598,7 @@ pf_test_state_icmp(struct pf_pdesc *pd, struct pf_state **stp,
 			return (ret);
 
 		(*stp)->expire = getuptime();
-		(*stp)->timeout = PFTM_ICMP_ERROR_REPLY;
+		pf_update_state_timeout(*stp, PFTM_ICMP_ERROR_REPLY);
 
 		/* translate source/destination address, if necessary */
 		if ((*stp)->key[PF_SK_WIRE] != (*stp)->key[PF_SK_STACK]) {

programming_language: C