git: 674932048a15 - main - authpf: pass the privileged Netlink socket to pfctl

From: Kristof Provost <kp_at_FreeBSD.org>
Date: Fri, 30 Oct 2026 14:08:09 UTC
The branch main has been updated by kp:

URL: https://cgit.FreeBSD.org/src/commit/?id=674932048a15203b9f45242be17b2ea10feb0d02

commit 674932048a15203b9f45242be17b2ea10feb0d02
Author:     Avinash Duduskar <avinash.duduskar@gmail.com>
AuthorDate: 2026-10-26 14:38:11 +0000
Commit:     Kristof Provost <kp@FreeBSD.org>
CommitDate: 2026-10-30 14:07:10 +0000

    authpf: pass the privileged Netlink socket to pfctl
    
    authpf opens its pf handle as root and drops privileges before it runs
    pfctl to load the per-user ruleset. The child inherits the /dev/pf
    descriptor through -p /dev/fd/N, but since pf's control path moved to
    Netlink, pfctl opens a Netlink socket of its own, which carries the
    unprivileged credentials, and the first privileged command on it fails:
    every authpf login ends with "pfctl exited abnormally" on 15.x.
    
    Pass the socket authpf already holds with the new -l flag, next to the
    device. Clear FD_CLOEXEC on both descriptors before the exec so the
    handoff does not depend on how libpfctl happened to open them.
    
    The socket already reached the child: neither libpfctl nor snl opens
    its descriptors close-on-exec and authpf closes nothing before the exec,
    so since pf moved to Netlink the unprivileged pfctl has inherited it
    unused. Passing its number makes the handoff deliberate. The socket
    carries root's credentials for every generic Netlink family, so the
    child could reach pflow, carp and the kernel RPC family through it, not
    only pf; it already had full control of pf through /dev/pf.
    
    PR:             291981
    Signed-off-by:  Avinash Duduskar <avinash.duduskar@gmail.com>
    Reviewed by:    kp
    Pull Request:   https://github.com/freebsd/freebsd-src/pull/2508
---
 contrib/pf/authpf/authpf.c | 35 ++++++++++++++++++++++++-----------
 1 file changed, 24 insertions(+), 11 deletions(-)

diff --git a/contrib/pf/authpf/authpf.c b/contrib/pf/authpf/authpf.c
index b835737c5ee2..35e3a865bd51 100644
--- a/contrib/pf/authpf/authpf.c
+++ b/contrib/pf/authpf/authpf.c
@@ -748,16 +748,17 @@ static int
 change_filter(int add, const char *l_user, const char *ip_src)
 {
 	char	*fdpath = NULL, *userstr = NULL, *ipstr = NULL;
-	char	*rsn = NULL, *fn = NULL;
+	char	*rsn = NULL, *fn = NULL, *nlfdstr = NULL;
 	pid_t	pid;
 	gid_t   gid;
 	int	s;
 
 	if (add) {
 		struct stat sb;
-		char *pargv[13] = {
-			"pfctl", "-p", "/dev/pf", "-q", "-a", "anchor/ruleset",
-			"-D", "user_id=X", "-D", "user_ip=X", "-f", "file", NULL
+		char *pargv[15] = {
+			"pfctl", "-p", "/dev/pf", "-l", "nlfd", "-q", "-a",
+			"anchor/ruleset", "-D", "user_id=X", "-D", "user_ip=X",
+			"-f", "file", NULL
 		};
 
 		if (l_user == NULL || !l_user[0] || ip_src == NULL || !ip_src[0]) {
@@ -769,6 +770,8 @@ change_filter(int add, const char *l_user, const char *ip_src)
 			goto no_mem;
 		if (asprintf(&fdpath, "/dev/fd/%d", pfctl_fd(pfh)) == -1)
 			goto no_mem;
+		if (asprintf(&nlfdstr, "%d", pfctl_nl_fd(pfh)) == -1)
+			goto no_mem;
 		if (asprintf(&ipstr, "user_ip=%s", ip_src) == -1)
 			goto no_mem;
 		if (asprintf(&userstr, "user_id=%s", l_user) == -1)
@@ -782,15 +785,16 @@ change_filter(int add, const char *l_user, const char *ip_src)
 				goto no_mem;
 		}
 		pargv[2] = fdpath;
-		pargv[5] = rsn;
-		pargv[7] = userstr;
+		pargv[4] = nlfdstr;
+		pargv[7] = rsn;
+		pargv[9] = userstr;
 		if (user_ip) {
-			pargv[9] = ipstr;
-			pargv[11] = fn;
+			pargv[11] = ipstr;
+			pargv[13] = fn;
 		} else {
-			pargv[8] = "-f";
-			pargv[9] = fn;
-			pargv[10] = NULL;
+			pargv[10] = "-f";
+			pargv[11] = fn;
+			pargv[12] = NULL;
 		}
 
 		switch (pid = fork()) {
@@ -803,6 +807,14 @@ change_filter(int add, const char *l_user, const char *ip_src)
 			if (setregid(gid, gid) == -1) {
 				err(1, "setregid");
 			}
+			/*
+			 * pfctl inherits both descriptors; do not rely on how
+			 * libpfctl happened to open them.
+			 */
+			if (fcntl(pfctl_fd(pfh), F_SETFD, 0) == -1 ||
+			    fcntl(pfctl_nl_fd(pfh), F_SETFD, 0) == -1) {
+				err(1, "fcntl");
+			}
 			execvp(PATH_PFCTL, pargv);
 			warn("exec of %s failed", PATH_PFCTL);
 			_exit(1);
@@ -829,6 +841,7 @@ no_mem:
 	syslog(LOG_ERR, "malloc failed");
 error:
 	free(fdpath);
+	free(nlfdstr);
 	free(rsn);
 	free(userstr);
 	free(ipstr);