]> git.ipfire.org Git - thirdparty/util-linux.git/commitdiff
lib/caputils: add fall back for last cap using prctl.
authorÉrico Rolim <erico.erc@gmail.com>
Sun, 15 Nov 2020 04:34:41 +0000 (01:34 -0300)
committerEmil Velikov <emil.l.velikov@gmail.com>
Tue, 9 Feb 2021 23:40:17 +0000 (23:40 +0000)
This allows the rest of the programs using cap_last_cap to trust the
value returned by it, since it will either be obtained from procfs
(straight from kernel) or with prctl.

Also checked if the file under /proc is actually mounted in a procfs.

(cherry picked from commit 5d95818757941bc609e5aeec5e2218f7d35a6e19)

lib/caputils.c

index 17e9c01bb014fd8dd3d58d2081fd0f51cc5d18d1..669330c50bd4a92ea7f6a18a1802b7c369c2bf09 100644 (file)
  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  */
 
+#include <sys/prctl.h>
 #include <stdio.h>
+#include <limits.h>
 
 #include "caputils.h"
+#include "procutils.h"
 #include "pathnames.h"
 
+static int test_cap(unsigned int cap)
+{
+       /* prctl returns 0 or 1 for valid caps, -1 otherwise */
+       return prctl(PR_CAPBSET_READ, cap, 0, 0, 0) >= 0;
+}
+
 int cap_last_cap(void)
 {
-       /* CAP_LAST_CAP is untrustworthy. */
-       static int ret = -1;
-       int matched;
+       static int cap = -1;
        FILE *f;
 
-       if (ret != -1)
-               return ret;
+       if (cap != -1)
+               return cap;
 
+       /* try to read value from kernel, check that the path is
+        * indeed in a procfs mount */
        f = fopen(_PATH_PROC_CAPLASTCAP, "r");
-       if (!f) {
-               ret = CAP_LAST_CAP;     /* guess */
-               return ret;
+       if (f) {
+               int matched = 0;
+
+               if (proc_is_procfs(fileno(f))) {
+                       matched = fscanf(f, "%d", &cap);
+               }
+               fclose(f);
+
+               /* we check if the cap after this one really isn't valid */
+               if (matched == 1 && cap < INT_MAX && !test_cap(cap + 1))
+                       return cap;
        }
 
-       matched = fscanf(f, "%d", &ret);
-       fclose(f);
+       /* if it wasn't possible to read the file in /proc,
+        * fall back to binary search over capabilities */
 
-       if (matched != 1)
-               ret = CAP_LAST_CAP;     /* guess */
+       /* starting with cap=INT_MAX means we always know
+        * that cap1 is invalid after the first iteration */
+       unsigned int cap0 = 0, cap1 = INT_MAX;
+       cap = INT_MAX;
+       while ((int)cap0 < cap) {
+               if (test_cap(cap)) {
+                       cap0 = cap;
+               } else {
+                       cap1 = cap;
+               }
+               cap = (cap0 + cap1) / 2U;
+       }
 
-       return ret;
+       return cap;
 }