pf_sourcelim_add() in sys/net/pf_ioctl.c has two bugs in the error
path when the second RBT_INSERT (into pf_sourcelim_nm_tree_inactive)
fails.
1) Lines 1579-1580 attempt to undo the first insert but do the
wrong operation on the wrong tree:
RBT_INSERT(pf_sourcelim_nm_tree,
&pf_sourcelim_nm_tree_inactive, pfsrlim);
This is a no-op (the nm_tree insert already failed because a
duplicate exists). It should remove from the id_tree where
the first insert succeeded:
RBT_REMOVE(pf_sourcelim_id_tree,
&pf_sourcelim_id_tree_inactive, pfsrlim);
pf_statelim_add() in the same file has the correct pattern
at lines 1046-1047. Possible copy/paste error.
After the failed cleanup, pool_put at line 1596 frees the
entry while it is still linked in pf_sourcelim_id_tree_inactive.
Any subsequent DIOCADDSOURCELIM traverses the tree and
dereferences the freed node.
2) The unlock error path does not detach the overload table
attached at lines 1561-1564. The entry is never added to
the TAILQ (line 1585 is skipped), so pf_sourcelim_rollback()
will not find it either. The table reference leaks.
Both bugs introduced on 2025-11-11 (dlg, "introduce source and
state limiters in pf").
Tested on OpenBSD 7.9-beta amd64: DIOCADDSOURCELIM with duplicate
name then subsequent inserts. Stock kernel: freed node left in
tree (UAF on next traversal). Patched kernel: 20 inserts after
the error all succeed, rollback clean, no kernel messages.
Index: sys/net/pf_ioctl.c
===================================================================
--- sys/net/pf_ioctl.c
+++ sys/net/pf_ioctl.c
@@ -1576,8 +1576,8 @@ pf_sourcelim_add(const struct pfioc_sour
if (RBT_INSERT(pf_sourcelim_nm_tree,
&pf_sourcelim_nm_tree_inactive, pfsrlim) != NULL) {
- RBT_INSERT(pf_sourcelim_nm_tree,
- &pf_sourcelim_nm_tree_inactive, pfsrlim);
+ RBT_REMOVE(pf_sourcelim_id_tree,
+ &pf_sourcelim_id_tree_inactive, pfsrlim);
error = EBUSY;
goto unlock;
}
@@ -1590,6 +1590,8 @@ pf_sourcelim_add(const struct pfioc_sour
return (0);
unlock:
+ if (pfsrlim->pfsrlim_overload.table != NULL)
+ pfr_detach_table(pfsrlim->pfsrlim_overload.table);
PF_UNLOCK();
NET_UNLOCK();
free: