From 56f82a540724523352f2f940069e26a514cec125 Mon Sep 17 00:00:00 2001
From: Chris Hanson <cmhanson@eschatologist.net>
Date: Sat, 18 Apr 2026 16:30:18 -0700
Subject: [PATCH] Add posix_spawn_file_actions_addclosefrom_np

Add support for a closefrom(3) action to posix_spawn_file_actions_t.
This can be used to ensure all but specific low-numbered descriptors are
closed in the child process.

Rationale: In a large or complex application or one using many
libraries, the caller of posix_spawn(3) can't necessarily have complete
knowledge of the state of all descriptors that could potentially be
inherited by the child process.
---
 distrib/sets/lists/base/shl.mi                |  4 +-
 distrib/sets/lists/debug/shl.mi               |  4 +-
 doc/CHANGES                                   |  2 +
 include/spawn.h                               |  4 ++
 lib/libc/gen/posix_spawn.3                    |  1 +
 .../gen/posix_spawn_file_actions_addopen.3    | 39 +++++++++++++++++--
 lib/libc/gen/posix_spawn_file_actions_init.3  |  1 +
 lib/libc/gen/posix_spawn_fileactions.c        | 26 +++++++++++++
 lib/libc/include/namespace.h                  |  4 ++
 lib/libc/shlib_version                        |  2 +-
 sys/kern/kern_exec.c                          | 10 ++++-
 sys/sys/spawn.h                               | 10 ++++-
 .../lib/libc/gen/posix_spawn/h_fileactions.c  | 11 ++++++
 .../lib/libc/gen/posix_spawn/t_fileactions.c  | 25 +++++++++++-
 14 files changed, 131 insertions(+), 12 deletions(-)

diff --git distrib/sets/lists/base/shl.mi distrib/sets/lists/base/shl.mi
index dc61b3a5efa9..1655e5c40147 100644
--- distrib/sets/lists/base/shl.mi
+++ distrib/sets/lists/base/shl.mi
@@ -22,7 +22,7 @@
 ./lib/libblocklist.so.0.1			base-sys-shlib		dynamicroot
 ./lib/libc.so					base-sys-shlib		dynamicroot
 ./lib/libc.so.12				base-sys-shlib		dynamicroot
-./lib/libc.so.12.224				base-sys-shlib		dynamicroot
+./lib/libc.so.12.225				base-sys-shlib		dynamicroot
 ./lib/libcrypt.so				base-sys-shlib		dynamicroot
 ./lib/libcrypt.so.1				base-sys-shlib		dynamicroot
 ./lib/libcrypt.so.1.0				base-sys-shlib		dynamicroot
@@ -267,7 +267,7 @@
 ./usr/lib/libc++.so.1.0				base-sys-shlib		compatfile,libcxx
 ./usr/lib/libc.so				base-sys-shlib		compatfile
 ./usr/lib/libc.so.12				base-sys-shlib		compatfile
-./usr/lib/libc.so.12.224			base-sys-shlib		compatfile
+./usr/lib/libc.so.12.225			base-sys-shlib		compatfile
 ./usr/lib/libcbor.so				base-sys-shlib		compatfile
 ./usr/lib/libcbor.so.0				base-sys-shlib		compatfile
 ./usr/lib/libcbor.so.0.5			base-sys-shlib		compatfile
diff --git distrib/sets/lists/debug/shl.mi distrib/sets/lists/debug/shl.mi
index f29c98ef9319..b27607ae7876 100644
--- distrib/sets/lists/debug/shl.mi
+++ distrib/sets/lists/debug/shl.mi
@@ -3,7 +3,7 @@
 ./usr/libdata/debug/lib						base-sys-usr	debug,dynamicroot,compatdir
 ./usr/libdata/debug/lib/libavl.so.0.0.debug			comp-zfs-debug	debug,dynamicroot,zfs
 ./usr/libdata/debug/lib/libblocklist.so.0.1.debug		comp-sys-debug	debug,dynamicroot
-./usr/libdata/debug/lib/libc.so.12.224.debug			comp-sys-debug	debug,dynamicroot
+./usr/libdata/debug/lib/libc.so.12.225.debug			comp-sys-debug	debug,dynamicroot
 ./usr/libdata/debug/lib/libcrypt.so.1.0.debug			comp-sys-debug	debug,dynamicroot
 ./usr/libdata/debug/lib/libcrypto.so.12.0.debug			comp-sys-debug	debug,dynamicroot,openssl=10
 ./usr/libdata/debug/lib/libcrypto.so.14.1.debug			comp-sys-debug	debug,dynamicroot,openssl=11
@@ -89,7 +89,7 @@
 ./usr/libdata/debug/usr/lib/libbsdmalloc.so.0.1.debug		comp-sys-debug	debug,compatfile
 ./usr/libdata/debug/usr/lib/libbz2.so.1.1.debug			comp-sys-debug	debug,compatfile
 ./usr/libdata/debug/usr/lib/libc++.so.1.0.debug			comp-sys-debug	debug,compatfile,libcxx
-./usr/libdata/debug/usr/lib/libc.so.12.224.debug		comp-sys-debug	debug,compatfile
+./usr/libdata/debug/usr/lib/libc.so.12.225.debug		comp-sys-debug	debug,compatfile
 ./usr/libdata/debug/usr/lib/libcbor.so.0.5.debug		comp-sys-debug	debug,compatfile
 ./usr/libdata/debug/usr/lib/libcom_err.so.8.0.debug		comp-krb5-debug	debug,compatfile,kerberos
 ./usr/libdata/debug/usr/lib/libcrypt.so.1.0.debug		comp-sys-debug	debug,compatfile
diff --git doc/CHANGES doc/CHANGES
index 5678c01952ad..d93db8886d5d 100644
--- doc/CHANGES
+++ doc/CHANGES
@@ -238,6 +238,8 @@ Changes from NetBSD 11.0 to NetBSD 12.0:
 	tzdata: Updated to 2026b (using 2026bgtz) [kre 20260429]
 	pthread(3): add pthread_main_np(3) [wiz 20260501]
 	zstd(1): import 1.5.7 [christos 20260501]
+	posix_spawn(3): Add posix_spawn_file_actions_addclosefrom_np
+		function from FreeBSD. [cmh 20260418]
 	libarchive: Import libarchive-3.8.7. [christos 20260503]
 	acpi(4): Updated ACPICA to 20260408. [christos 20260503]
 	byacc: Update to 20260126. [christos 20260503]
diff --git include/spawn.h include/spawn.h
index 32cdd85ca27f..d5963cdb1942 100644
--- include/spawn.h
+++ include/spawn.h
@@ -60,6 +60,10 @@ int posix_spawn_file_actions_addchdir(posix_spawn_file_actions_t * __restrict,
         const char * __restrict);
 int posix_spawn_file_actions_addfchdir(posix_spawn_file_actions_t *, int);
 
+#if defined(_NETBSD_SOURCE)
+int posix_spawn_file_actions_addclosefrom_np(posix_spawn_file_actions_t *, int);
+#endif
+
 /*
  * Spawn attributes
  */
diff --git lib/libc/gen/posix_spawn.3 lib/libc/gen/posix_spawn.3
index 6ca4a59a0810..379da1708fd6 100644
--- lib/libc/gen/posix_spawn.3
+++ lib/libc/gen/posix_spawn.3
@@ -444,6 +444,7 @@ is returned.
 .Xr vfork 2 ,
 .Xr posix_spawn_file_actions_addchdir 3 ,
 .Xr posix_spawn_file_actions_addclose 3 ,
+.Xr posix_spawn_file_actions_addclosefrom_np 3 ,
 .Xr posix_spawn_file_actions_adddup2 3 ,
 .Xr posix_spawn_file_actions_addfchdir 3 ,
 .Xr posix_spawn_file_actions_addopen 3 ,
diff --git lib/libc/gen/posix_spawn_file_actions_addopen.3 lib/libc/gen/posix_spawn_file_actions_addopen.3
index 9a23d6f9e899..1f9fc26a8790 100644
--- lib/libc/gen/posix_spawn_file_actions_addopen.3
+++ lib/libc/gen/posix_spawn_file_actions_addopen.3
@@ -36,14 +36,15 @@
 .\"
 .\" $FreeBSD: src/lib/libc/gen/posix_spawn_file_actions_addopen.3,v 1.2.2.1.4.1 2010/06/14 02:09:06 kensmith Exp $
 .\"
-.Dd February 2, 2014
+.Dd April 18, 2026
 .Dt POSIX_SPAWN_FILE_ACTIONS_ADDOPEN 3
 .Os
 .Sh NAME
 .Nm posix_spawn_file_actions_addopen ,
 .Nm posix_spawn_file_actions_adddup2 ,
-.Nm posix_spawn_file_actions_addclose
-.Nd "add open, dup2 or close action to spawn file actions object"
+.Nm posix_spawn_file_actions_addclose ,
+.Nm posix_spawn_file_actions_addclosefrom_np
+.Nd "add open, dup2, close, or closefrom action to spawn file actions object"
 .Sh LIBRARY
 .Lb libc
 .Sh SYNOPSIS
@@ -54,8 +55,10 @@
 .Fn posix_spawn_file_actions_adddup2 "posix_spawn_file_actions_t * file_actions" "int fildes" "int newfildes"
 .Ft int
 .Fn posix_spawn_file_actions_addclose "posix_spawn_file_actions_t * file_actions" "int fildes"
+.Ft int
+.Fn posix_spawn_file_actions_addclosefrom_np "posix_spawn_file_actions_t * file_actions" "int fildes"
 .Sh DESCRIPTION
-These functions add an open, dup2 or close action to a spawn
+These functions add an open, dup2, close, or closefrom action to a spawn
 file actions object.
 .Pp
 A spawn file actions object is of type
@@ -140,6 +143,21 @@ close(fildes)
 .Pp
 had been called) when a new process is spawned using this file actions
 object.
+.Pp
+The
+.Fn posix_spawn_file_actions_addclosefrom_np
+function adds a closefrom action to the object
+referenced by
+.Fa file_actions
+that causes the file descriptor
+.Fa filedes
+and all higher file descriptors to be closed (as if
+.Bd -literal -offset indent
+closefrom(filedes)
+.Ed
+.Pp
+had been called) when a new process is spawned using this file actions
+object.
 .Sh RETURN VALUES
 Upon successful completion, these functions return zero;
 otherwise, an error number is returned to indicate the error.
@@ -164,6 +182,7 @@ Insufficient memory exists to add to the spawn file actions object.
 .Xr close 2 ,
 .Xr dup2 2 ,
 .Xr open 2 ,
+.Xr closefrom 3 ,
 .Xr posix_spawn 3 ,
 .Xr posix_spawn_file_actions_destroy 3 ,
 .Xr posix_spawn_file_actions_init 3 ,
@@ -176,6 +195,10 @@ and
 .Fn posix_spawn_file_actions_addclose
 functions conform to
 .St -p1003.1-2001 .
+.Pp
+The
+.Fn posix_spawn_file_actions_addclosefrom_np
+function is a non-standard extension.
 .Sh HISTORY
 The
 .Fn posix_spawn_file_actions_addopen ,
@@ -186,5 +209,13 @@ functions first appeared in
 .Fx 8.0
 and imported for
 .Nx 6.0 .
+.Pp
+The implementation of
+.Fn posix_spawn_file_actions_addclosefrom_np
+is inspired by FreeBSD's
+.Fn posix_spawn_file_actions_addclosefrom_np
+and first appeared in
+.Nx 12.0 .
 .Sh AUTHORS
 .An Ed Schouten Aq Mt ed@FreeBSD.org
+.An Chris Hanson Aq Mt cmhanson@eschatologist.net
diff --git lib/libc/gen/posix_spawn_file_actions_init.3 lib/libc/gen/posix_spawn_file_actions_init.3
index d33a09c267b3..366a68d45d16 100644
--- lib/libc/gen/posix_spawn_file_actions_init.3
+++ lib/libc/gen/posix_spawn_file_actions_init.3
@@ -89,6 +89,7 @@ Insufficient memory exists to initialize the spawn file actions object.
 .Sh SEE ALSO
 .Xr posix_spawn 3 ,
 .Xr posix_spawn_file_actions_addclose 3 ,
+.Xr posix_spawn_file_actions_addclosefrom_np 3 ,
 .Xr posix_spawn_file_actions_adddup2 3 ,
 .Xr posix_spawn_file_actions_addopen 3 ,
 .Xr posix_spawnp 3
diff --git lib/libc/gen/posix_spawn_fileactions.c lib/libc/gen/posix_spawn_fileactions.c
index 1ff45c97faa5..a9335013d9e8 100644
--- lib/libc/gen/posix_spawn_fileactions.c
+++ lib/libc/gen/posix_spawn_fileactions.c
@@ -40,6 +40,11 @@ __RCSID("$NetBSD: posix_spawn_fileactions.c,v 1.5 2021/11/07 14:34:30 christos E
 
 #define MIN_SIZE	16
 
+#ifdef __weak_alias
+__weak_alias(posix_spawn_file_actions_addclosefrom_np,
+	     _posix_spawn_file_actions_addclosefrom_np)
+#endif
+
 /*
  * File descriptor actions
  */
@@ -215,3 +220,24 @@ posix_spawn_file_actions_addfchdir(posix_spawn_file_actions_t *fa, int fildes)
 
 	return 0;
 }
+
+int
+posix_spawn_file_actions_addclosefrom_np(posix_spawn_file_actions_t *fa,
+    int fildes)
+{
+	unsigned int i;
+	int error;
+
+	if (fildes < 0)
+		return EBADF;
+
+	error = posix_spawn_file_actions_getentry(fa, &i);
+	if (error)
+		return error;
+
+	fa->fae[i].fae_action = FAE_CLOSEFROM;
+	fa->fae[i].fae_fildes = fildes;
+	fa->len++;
+
+	return 0;
+}
diff --git lib/libc/include/namespace.h lib/libc/include/namespace.h
index 6ac8288b9123..424a7779ea22 100644
--- lib/libc/include/namespace.h
+++ lib/libc/include/namespace.h
@@ -935,6 +935,10 @@
 #define dladdr			__dladdr
 #define fmtcheck		__fmtcheck
 
+/* posix_spawn */
+#define posix_spawn_file_actions_addclosefrom_np \
+	_posix_spawn_file_actions_addclosefrom_np
+
 /* RB trees */
 #define	rb_tree_init		_rb_tree_init
 #define	rb_tree_find_node	_rb_tree_find_node
diff --git lib/libc/shlib_version lib/libc/shlib_version
index 7bcdd8dcbcb5..ad43eb5ef62d 100644
--- lib/libc/shlib_version
+++ lib/libc/shlib_version
@@ -55,4 +55,4 @@
 # - remove tzsetwall(3), upstream has removed it
 # - move *rand48* to libcompat
 major=12
-minor=224
+minor=225
diff --git sys/kern/kern_exec.c sys/kern/kern_exec.c
index 0d1b498ab303..0621f4d66ea8 100644
--- sys/kern/kern_exec.c
+++ sys/kern/kern_exec.c
@@ -2154,7 +2154,7 @@ handle_posix_spawn_file_actions(struct posix_spawn_file_actions *actions)
 {
 	struct lwp *l = curlwp;
 	register_t retval;
-	int error = 0, newfd;
+	int error = 0, newfd, fd;
 
 	if (actions == NULL)
 		return 0;
@@ -2201,6 +2201,14 @@ handle_posix_spawn_file_actions(struct posix_spawn_file_actions *actions)
 		case FAE_FCHDIR:
 			error = do_sys_fchdir(l, fae->fae_fildes, &retval);
 			break;
+		case FAE_CLOSEFROM:
+			/*
+			 * as above, ignore failures from close().
+			 */
+			for (fd = fae->fae_fildes; fd <= l->l_fd->fd_lastfile; fd++)
+				if (fd_getfile(fd) != NULL)
+					fd_close(fd);
+			break;
 		}
 		if (error)
 			return error;
diff --git sys/sys/spawn.h sys/sys/spawn.h
index b04db01bd03c..ecca5c9ad541 100644
--- sys/sys/spawn.h
+++ sys/sys/spawn.h
@@ -47,7 +47,15 @@ struct posix_spawnattr {
 	sigset_t		sa_sigmask;
 };
 
-enum fae_action { FAE_OPEN, FAE_DUP2, FAE_CLOSE, FAE_CHDIR, FAE_FCHDIR };
+enum fae_action {
+	FAE_OPEN,
+	FAE_DUP2,
+	FAE_CLOSE,
+	FAE_CHDIR,
+	FAE_FCHDIR,
+	FAE_CLOSEFROM,
+};
+
 typedef struct posix_spawn_file_actions_entry {
 	enum fae_action fae_action;
 
diff --git tests/lib/libc/gen/posix_spawn/h_fileactions.c tests/lib/libc/gen/posix_spawn/h_fileactions.c
index c658600bbf1c..0d9637b7d1ec 100644
--- tests/lib/libc/gen/posix_spawn/h_fileactions.c
+++ tests/lib/libc/gen/posix_spawn/h_fileactions.c
@@ -100,6 +100,17 @@ main(int argc, char **argv)
 		fprintf(stderr, "%s: stat results differ\n", getprogname());
 		res = EXIT_FAILURE;
 	}
+	/* file descs 8 and 9 should be closed (via addclosefrom_np) */
+	if (read(8, buf, BUFSIZE) != -1 || errno != EBADF) {
+		fprintf(stderr, "%s: filedesc 8 is not closed\n",
+		    getprogname());
+		res = EXIT_FAILURE;
+	}
+	if (read(9, buf, BUFSIZE) != -1 || errno != EBADF) {
+		fprintf(stderr, "%s: filedesc 9 is not closed\n",
+		    getprogname());
+		res = EXIT_FAILURE;
+	}
 
 	return res;
 }
diff --git tests/lib/libc/gen/posix_spawn/t_fileactions.c tests/lib/libc/gen/posix_spawn/t_fileactions.c
index 57e27a0ada46..c8763ebcc42d 100644
--- tests/lib/libc/gen/posix_spawn/t_fileactions.c
+++ tests/lib/libc/gen/posix_spawn/t_fileactions.c
@@ -274,7 +274,7 @@ ATF_TC_HEAD(t_spawn_fileactions, tc)
 }
 ATF_TC_BODY(t_spawn_fileactions, tc)
 {
-	int fd1, fd2, fd3, status;
+	int fd1, fd2, fd3, fd4, fd5, fd6, fd7, status;
 	pid_t pid;
 	char * const args[2] = { __UNCONST("h_fileactions"), NULL };
 	char helper[FILENAME_MAX];
@@ -297,6 +297,29 @@ ATF_TC_BODY(t_spawn_fileactions, tc)
 	RZ(posix_spawn_file_actions_addopen(&fa, 6, "/dev/null", O_RDWR, 0));
 	RZ(posix_spawn_file_actions_adddup2(&fa, 1, 7));
 
+	/*
+	 * Open descriptors 6 & 7 so we can get to 8 & 9 for testing
+	 * closefrom_np, but close 6 & 7 before the spawn since the
+	 * child process will get them via the open and dup2 actions
+	 * above.
+	 */
+	RL(fd4 = open("/dev/null", O_RDONLY));
+	ATF_REQUIRE(fd4 == 6);
+
+	RL(fd5 = open("/dev/null", O_RDONLY));
+	ATF_REQUIRE(fd5 == 7);
+
+	RL(fd6 = open("/dev/null", O_RDONLY));
+	ATF_REQUIRE(fd6 == 8);
+
+	RL(fd7 = open("/dev/null", O_RDONLY));
+	ATF_REQUIRE(fd7 == 9);
+
+	close(fd4);
+	close(fd5);
+
+	RZ(posix_spawn_file_actions_addclosefrom_np(&fa, 8));
+
 	snprintf(helper, sizeof helper, "%s/h_fileactions",
 	    atf_tc_get_config_var(tc, "srcdir"));
 	RZ(posix_spawn(&pid, helper, &fa, NULL, args, NULL));
-- 
2.54.0

