Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions debian/changelog
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
libcap2 (1:2.75-10deepin1) unstable; urgency=medium

* Apply patches from upstream:
- Address a potential TOCTOU race condition in cap_set_file()
Fixes: CVE-2026-4878

-- Tianyu Chen <sweetyfish@deepin.org> Thu, 09 Apr 2026 14:34:21 +0800

libcap2 (1:2.75-10) unstable; urgency=medium

* d/rules: Fix typo in override_dh_auto_clean
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
From: "Andrew G. Morgan" <morgan@kernel.org>
Date: Thu, 12 Mar 2026 07:38:05 -0700
Subject: Address a potential TOCTOU race condition in cap_set_file().

This issue was researched and reported by Ali Raza (@locus-x64). It
has been assigned CVE-2026-4878.

The finding is that while cap_set_file() checks if a file is a regular
file before applying or removing a capability attribute, a small
window existed after that check when the filepath could be overwritten
either with new content or a symlink to some other file. To do this
would imply that the caller of cap_set_file() was directing it to a
directory over which a local attacker has write access, and performed
the operation frequently enough that an attacker had a non-negligible
chance of exploiting the race condition. The code now locks onto the
intended file, eliminating the race condition.

Signed-off-by: Andrew G. Morgan <morgan@kernel.org>
---
libcap/cap_file.c | 69 ++++++++++++++++++++++++++++++++++++++++++++++--------
progs/quicktest.sh | 14 ++++++++++-
2 files changed, 72 insertions(+), 11 deletions(-)

diff --git a/libcap/cap_file.c b/libcap/cap_file.c
index 0bc07f7..f02bf9f 100644
--- a/libcap/cap_file.c
+++ b/libcap/cap_file.c
@@ -8,8 +8,13 @@
#define _DEFAULT_SOURCE
#endif

+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE
+#endif
+
#include <sys/types.h>
#include <byteswap.h>
+#include <fcntl.h>
#include <sys/stat.h>
#include <unistd.h>

@@ -322,26 +327,70 @@ int cap_set_file(const char *filename, cap_t cap_d)
struct vfs_ns_cap_data rawvfscap;
int sizeofcaps;
struct stat buf;
+ char fdpath[64];
+ int fd, ret;
+
+ _cap_debug("setting filename capabilities");
+ fd = open(filename, O_RDONLY|O_NOFOLLOW);
+ if (fd >= 0) {
+ ret = cap_set_fd(fd, cap_d);
+ close(fd);
+ return ret;
+ }

- if (lstat(filename, &buf) != 0) {
- _cap_debug("unable to stat file [%s]", filename);
+ /*
+ * Attempting to set a file capability on a file the process can't
+ * read the content of. This is considered a non-standard use case
+ * and the following (slower) code is complicated because it is
+ * trying to avoid a TOCTOU race condition.
+ */
+
+ fd = open(filename, O_PATH|O_NOFOLLOW);
+ if (fd < 0) {
+ _cap_debug("cannot find file at path [%s]", filename);
+ return -1;
+ }
+ if (fstat(fd, &buf) != 0) {
+ _cap_debug("unable to stat file [%s] descriptor %d",
+ filename, fd);
+ close(fd);
return -1;
}
if (S_ISLNK(buf.st_mode) || !S_ISREG(buf.st_mode)) {
- _cap_debug("file [%s] is not a regular file", filename);
+ _cap_debug("file [%s] descriptor %d for non-regular file",
+ filename, fd);
+ close(fd);
errno = EINVAL;
return -1;
}

- if (cap_d == NULL) {
- _cap_debug("removing filename capabilities");
- return removexattr(filename, XATTR_NAME_CAPS);
+ /*
+ * While the fd remains open, this named file is locked to the
+ * origin regular file. The size of the fdpath variable is
+ * sufficient to support a 160+ bit number.
+ */
+ if (snprintf(fdpath, sizeof(fdpath), "/proc/self/fd/%d", fd)
+ >= sizeof(fdpath)) {
+ _cap_debug("file descriptor too large %d", fd);
+ errno = EINVAL;
+ ret = -1;
+
+ } else if (cap_d == NULL) {
+ _cap_debug("dropping file caps on [%s] via [%s]",
+ filename, fdpath);
+ ret = removexattr(fdpath, XATTR_NAME_CAPS);
+
} else if (_fcaps_save(&rawvfscap, cap_d, &sizeofcaps) != 0) {
- return -1;
- }
+ _cap_debug("problem converting cap_d to vfscap format");
+ ret = -1;

- _cap_debug("setting filename capabilities");
- return setxattr(filename, XATTR_NAME_CAPS, &rawvfscap, sizeofcaps, 0);
+ } else {
+ _cap_debug("setting filename capabilities");
+ ret = setxattr(fdpath, XATTR_NAME_CAPS, &rawvfscap,
+ sizeofcaps, 0);
+ }
+ close(fd);
+ return ret;
}

/*
diff --git a/progs/quicktest.sh b/progs/quicktest.sh
index e6c48e6..5dc72f9 100755
--- a/progs/quicktest.sh
+++ b/progs/quicktest.sh
@@ -148,7 +148,19 @@ pass_capsh --caps="cap_setpcap=p" --inh=cap_chown --current
pass_capsh --strict --caps="cap_chown=p" --inh=cap_chown --current

# change the way the capability is obtained (make it inheritable)
+chmod 0000 ./privileged
./setcap cap_setuid,cap_setgid=ei ./privileged
+if [ $? -ne 0 ]; then
+ echo "FAILED to set file capability"
+ exit 1
+fi
+chmod 0755 ./privileged
+ln -s privileged unprivileged
+./setcap -r ./unprivileged
+if [ $? -eq 0 ]; then
+ echo "FAILED by removing a capability from a symlinked file"
+ exit 1
+fi

# Note, the bounding set (edited with --drop) only limits p
# capabilities, not i's.
@@ -246,7 +258,7 @@ EOF
pass_capsh --iab='!%cap_chown,^cap_setpcap,cap_setuid'
fail_capsh --mode=PURE1E --iab='!%cap_chown,^cap_setuid'
fi
-/bin/rm -f ./privileged
+/bin/rm -f ./privileged ./unprivileged

echo "testing namespaced file caps"

1 change: 1 addition & 0 deletions debian/patches/series
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ Glibc-needs-a-constant-to-be-defined-for-puts-to-work.patch
Add-support-for-some-less-mainstream-architectures.patch
Extend-support-further-to-__m68k__-and-possibly-__sparc__.patch
Be-more-systematic-using-the-kernel-signal-handler-APIs.patch
Address-a-potential-TOCTOU-race-condition-in-cap_set_file.patch
Loading