git: e2db1c9a88e8 - releng/15.1 - file: Add filecaps_intersect() and cap_rights_intersect()

From: Mark Johnston <markj_at_FreeBSD.org>
Date: Tue, 29 Sep 2026 16:00:06 UTC
The branch releng/15.1 has been updated by markj:

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

commit e2db1c9a88e89e0ee5d2eed0acaebb9283d1294e
Author:     Mark Johnston <markj@FreeBSD.org>
AuthorDate: 2026-09-28 14:43:23 +0000
Commit:     Mark Johnston <markj@FreeBSD.org>
CommitDate: 2026-09-29 01:01:48 +0000

    file: Add filecaps_intersect() and cap_rights_intersect()
    
    These routines let one compute the intersection of two sets of filecaps
    or capability rights, just as filecaps_merge() and cap_rights_merge()
    compute the union.  This will be useful in an upcoming patch.
    
    filecaps_intersect() is complex due to the need to merge sets of ioctls.
    For now this is implemented with a dumb nested loop on the basis that
    ioctl lists are typically short enough that this is fine.  It may be
    better to instead sort the two lists first and step through them
    together.
    
    No functional change intended.
    
    Approved by:    so
    Security:       FreeBSD-SA-26:66.jail
    Reviewed by:    kib
    Sponsored by:   The FreeBSD Foundation
    Differential Revision:  https://reviews.freebsd.org/D59885
---
 lib/libc/capability/cap_rights_init.3 | 16 ++++++++++--
 sys/kern/kern_descrip.c               | 49 +++++++++++++++++++++++++++++++++++
 sys/kern/subr_capability.c            | 23 ++++++++++++++++
 sys/sys/capsicum.h                    |  1 +
 sys/sys/filedesc.h                    |  1 +
 5 files changed, 88 insertions(+), 2 deletions(-)

diff --git a/lib/libc/capability/cap_rights_init.3 b/lib/libc/capability/cap_rights_init.3
index 98b50f653f2c..1dbb76686283 100644
--- a/lib/libc/capability/cap_rights_init.3
+++ b/lib/libc/capability/cap_rights_init.3
@@ -35,6 +35,7 @@
 .Nm cap_rights_is_set ,
 .Nm cap_rights_is_empty ,
 .Nm cap_rights_is_valid ,
+.Nm cap_rights_intersect ,
 .Nm cap_rights_merge ,
 .Nm cap_rights_remove ,
 .Nm cap_rights_contains
@@ -56,6 +57,8 @@
 .Ft bool
 .Fn cap_rights_is_valid "const cap_rights_t *rights"
 .Ft cap_rights_t *
+.Fn cap_rights_intersect "cap_rights_t *dst" "const cap_rights_t *src"
+.Ft cap_rights_t *
 .Fn cap_rights_merge "cap_rights_t *dst" "const cap_rights_t *src"
 .Ft cap_rights_t *
 .Fn cap_rights_remove "cap_rights_t *dst" "const cap_rights_t *src"
@@ -133,6 +136,14 @@ function verifies if the given
 structure is valid.
 .Pp
 The
+.Fn cap_rights_intersect
+function clears all capability rights from the
+.Fa dst
+structure that are not present in the
+.Fa src
+structure, leaving only the rights common to both.
+.Pp
+The
 .Fn cap_rights_merge
 function merges all capability rights present in the
 .Fa src
@@ -173,9 +184,10 @@ structure given in the
 argument.
 .Pp
 The
-.Fn cap_rights_merge
-and
+.Fn cap_rights_merge ,
 .Fn cap_rights_remove
+and
+.Fn cap_rights_intersect
 functions return pointer to the
 .Vt cap_rights_t
 structure given in the
diff --git a/sys/kern/kern_descrip.c b/sys/kern/kern_descrip.c
index e9ee752712f0..522e824a98d5 100644
--- a/sys/kern/kern_descrip.c
+++ b/sys/kern/kern_descrip.c
@@ -1945,6 +1945,55 @@ filecaps_full(const struct filecaps *fcaps)
 	    fcaps->fc_fcntls == CAP_FCNTL_ALL && fcaps->fc_nioctls == -1);
 }
 
+/*
+ * Find the intersection of two filecaps structures and store the result in the
+ * first structure.  This is a destructive operation on the src structure.
+ */
+void
+filecaps_intersect(struct filecaps *src, struct filecaps *dst)
+{
+
+	cap_rights_intersect(&dst->fc_rights, &src->fc_rights);
+	dst->fc_fcntls &= src->fc_fcntls;
+	if (dst->fc_nioctls == -1) {
+		dst->fc_ioctls = src->fc_ioctls;
+		dst->fc_nioctls = src->fc_nioctls;
+		src->fc_ioctls = NULL;
+	} else if (src->fc_nioctls != -1) {
+		int count;
+
+		/*
+		 * ioctl lists are usually short, so this dumb merge is fine.
+		 * We could alternately sort both lists and walk them in
+		 * parallel.
+		 */
+		count = 0;
+		for (int i = 0; i < dst->fc_nioctls; i++) {
+			bool found;
+
+			found = false;
+			for (int j = 0; j < src->fc_nioctls; j++) {
+				if (dst->fc_ioctls[i] == src->fc_ioctls[j]) {
+					count++;
+					found = true;
+					break;
+				}
+			}
+			if (!found) {
+				if (i != dst->fc_nioctls - 1)
+					dst->fc_ioctls[i] =
+					    dst->fc_ioctls[dst->fc_nioctls - 1];
+				dst->fc_nioctls--;
+				i--;
+			}
+		}
+		dst->fc_nioctls = count;
+	}
+	if (dst->fc_nioctls == 0)
+		filecaps_free_ioctl(dst);
+	filecaps_free(src);
+}
+
 static u_long *
 filecaps_free_prep(struct filecaps *fcaps)
 {
diff --git a/sys/kern/subr_capability.c b/sys/kern/subr_capability.c
index 6e23525186ea..e4e6f3316dc0 100644
--- a/sys/kern/subr_capability.c
+++ b/sys/kern/subr_capability.c
@@ -314,6 +314,29 @@ cap_rights_is_valid(const cap_rights_t *rights)
 	return (true);
 }
 
+cap_rights_t *
+cap_rights_intersect(cap_rights_t *dst, const cap_rights_t *src)
+{
+	unsigned int i, n;
+
+	assert(CAPVER(dst) == CAP_RIGHTS_VERSION_00);
+	assert(CAPVER(src) == CAP_RIGHTS_VERSION_00);
+	assert(CAPVER(dst) == CAPVER(src));
+	assert(cap_rights_is_valid(src));
+	assert(cap_rights_is_valid(dst));
+
+	n = CAPARSIZE(dst);
+	assert(n >= CAPARSIZE_MIN && n <= CAPARSIZE_MAX);
+
+	for (i = 0; i < n; i++)
+		dst->cr_rights[i] &= src->cr_rights[i] | ~0x01FFFFFFFFFFFFFFULL;
+
+	assert(cap_rights_is_valid(src));
+	assert(cap_rights_is_valid(dst));
+
+	return (dst);
+}
+
 cap_rights_t *
 cap_rights_merge(cap_rights_t *dst, const cap_rights_t *src)
 {
diff --git a/sys/sys/capsicum.h b/sys/sys/capsicum.h
index 3847c4c73e75..a6811e2de9b6 100644
--- a/sys/sys/capsicum.h
+++ b/sys/sys/capsicum.h
@@ -344,6 +344,7 @@ bool __cap_rights_is_set(const cap_rights_t *rights, ...);
 bool cap_rights_is_empty(const cap_rights_t *rights);
 
 bool cap_rights_is_valid(const cap_rights_t *rights);
+cap_rights_t *cap_rights_intersect(cap_rights_t *dst, const cap_rights_t *src);
 cap_rights_t *cap_rights_merge(cap_rights_t *dst, const cap_rights_t *src);
 cap_rights_t *cap_rights_remove(cap_rights_t *dst, const cap_rights_t *src);
 
diff --git a/sys/sys/filedesc.h b/sys/sys/filedesc.h
index 2f9dedec1905..b2c4be9874c1 100644
--- a/sys/sys/filedesc.h
+++ b/sys/sys/filedesc.h
@@ -245,6 +245,7 @@ bool	filecaps_copy(const struct filecaps *src, struct filecaps *dst,
 void	filecaps_move(struct filecaps *src, struct filecaps *dst);
 void	filecaps_free(struct filecaps *fcaps);
 bool	filecaps_full(const struct filecaps *fcaps);
+void	filecaps_intersect(struct filecaps *src, struct filecaps *dst);
 
 int	closef(struct file *fp, struct thread *td);
 void	closef_nothread(struct file *fp);