diff -ru a/configure.ac b/configure.ac
--- a/configure.ac
+++ b/configure.ac
@@ -59,6 +59,9 @@
AC_CHECK_HEADERS(pthread.h,
[AC_SEARCH_LIBS(pthread_atfork, pthread)],
[AC_MSG_WARN(pthread.h not found, disabling pthread_atfork.)])
+AC_CHECK_HEADERS(sys/vfs.h, [
+ AC_CHECK_HEADERS(linux/magic.h, [] [AC_MSG_WARN(linux/magic.h is required in order to verify procfs.)])
+ ], [AC_MSG_WARN(sys/vfs.h is required in order to verify procfs.)])
AC_C_CONST
AC_C_INLINE
diff -ru a/src/cap-ng.c b/src/cap-ng.c
--- a/src/cap-ng.c
+++ b/src/cap-ng.c
@@ -44,6 +44,10 @@
#ifdef HAVE_LINUX_SECUREBITS_H
#include <linux/securebits.h>
#endif
+#ifdef HAVE_LINUX_MAGIC_H
+#include <sys/vfs.h>
+#include <linux/magic.h>
+#endif
# define hidden __attribute__ ((visibility ("hidden")))
unsigned int last_cap hidden = 0;
@@ -168,6 +172,15 @@
m.state = CAPNG_NEW;
}
+static inline int test_cap(unsigned int cap)
+{
+ // prctl returns 0 or 1 for valid caps, -1 otherwise
+ return prctl(PR_CAPBSET_READ, cap) >= 0;
+}
+
+// The maximum cap value is determined by VFS_CAP_U32
+#define MAX_CAP_VALUE (VFS_CAP_U32 * sizeof(__le32) * 8)
+
static void init_lib(void) __attribute__ ((constructor));
static void init_lib(void)
{
@@ -178,8 +191,15 @@
if (last_cap == 0) {
int fd;
+ // Try to read last cap from procfs
fd = open("/proc/sys/kernel/cap_last_cap", O_RDONLY);
if (fd >= 0) {
+#ifdef HAVE_LINUX_MAGIC_H
+ struct statfs st;
+ // Bail out if procfs is invalid or fstatfs fails
+ if (fstatfs(fd, &st) || st.f_type != PROC_SUPER_MAGIC)
+ goto fail;
+#endif
char buf[8];
int num = read(fd, buf, sizeof(buf) - 1);
if (num > 0) {
@@ -189,10 +209,25 @@
if (errno == 0)
last_cap = val;
}
+fail:
close(fd);
}
- if (last_cap == 0)
- last_cap = CAP_LAST_CAP;
+ // Run a binary search over capabilities
+ if (last_cap == 0) {
+ // starting with last_cap=MAX_CAP_VALUE means we always know
+ // that cap1 is invalid after the first iteration
+ last_cap = MAX_CAP_VALUE;
+ unsigned int cap0 = 0, cap1 = MAX_CAP_VALUE;
+
+ while (cap0 < last_cap) {
+ if (test_cap(last_cap))
+ cap0 = last_cap;
+ else
+ cap1 = last_cap;
+
+ last_cap = (cap0 + cap1) / 2U;
+ }
+ }
}
}