]> granicus.if.org Git - strace/commitdiff
Implement full decoding of 64-bit capabilities
authorDmitry V. Levin <ldv@altlinux.org>
Fri, 5 Dec 2014 00:21:23 +0000 (00:21 +0000)
committerDmitry V. Levin <ldv@altlinux.org>
Sat, 6 Dec 2014 03:53:12 +0000 (03:53 +0000)
Unlike v1 capabilities which are 32-bit, v2 and v3 are 64-bit, but
before this change only lower 32 capability bits were decoded for
v2 and v3.

* xlat/capabilities1.in: New file.
* capability.c: Define v2/v3 CAP_* constants.
Include xlat/capabilities1.h.
(get_cap_header): New function.
(print_cap_header): Update to use get_cap_header result.
(print_cap_data): Decoder higher capability bits for v2 and v3.
(sys_capget, sys_capset): Use get_cap_header, update print_cap_header
and print_cap_data calls.
* tests/caps.c: New file.
* tests/caps.awk: New file.
* tests/caps.test: New test.
* tests/Makefile.am (CHECK_PROGRAMS): Add caps.
(TESTS): Add caps.test.
(EXTRA_DIST): Add caps.awk.

capability.c
tests/Makefile.am
tests/caps.awk [new file with mode: 0644]
tests/caps.c [new file with mode: 0644]
tests/caps.test [new file with mode: 0755]
xlat/capabilities1.in [new file with mode: 0644]

index 3c7666fc202177852a7c6de183c7efbbd6930447..af4dffeb0b98e89e446b4560810736e9c8f7fb49 100644 (file)
@@ -1,5 +1,6 @@
 #include "defs.h"
 
+/* these constants are the same as in <linux/capability.h> */
 enum {
        CAP_CHOWN,
        CAP_DAC_OVERRIDE,
@@ -37,6 +38,19 @@ enum {
 
 #include "xlat/capabilities.h"
 
+/* these constants are CAP_TO_INDEX'ed constants from <linux/capability.h> */
+enum {
+       CAP_MAC_OVERRIDE,
+       CAP_MAC_ADMIN,
+       CAP_SYSLOG,
+       CAP_WAKE_ALARM,
+       CAP_BLOCK_SUSPEND,
+       CAP_AUDIT_READ
+};
+
+#include "xlat/capabilities1.h"
+
+/* these constants are the same as in <linux/capability.h> */
 enum {
        _LINUX_CAPABILITY_VERSION_1 = 0x19980330,
        _LINUX_CAPABILITY_VERSION_2 = 0x20071026,
@@ -56,58 +70,101 @@ typedef struct user_cap_data_struct {
        uint32_t inheritable;
 } *cap_user_data_t;
 
-static void
-print_cap_header(struct tcb *tcp, unsigned long addr)
+static cap_user_header_t
+get_cap_header(struct tcb *tcp, unsigned long addr)
 {
-       union { cap_user_header_t p; long *a; char *c; } arg;
-       long a[sizeof(*arg.p) / sizeof(long) + 1];
-       arg.a = a;
+       static struct user_cap_header_struct header;
+
+       if (!addr || !verbose(tcp))
+               return NULL;
 
-       if (!addr)
+       if (umove(tcp, addr, &header) < 0)
+               return NULL;
+
+       return &header;
+}
+
+static void
+print_cap_header(struct tcb *tcp, unsigned long addr, cap_user_header_t h)
+{
+       if (!addr) {
                tprints("NULL");
-       else if (!verbose(tcp) ||
-                umoven(tcp, addr, sizeof(*arg.p), arg.c) < 0)
+               return;
+       }
+
+       if (!h) {
                tprintf("%#lx", addr);
-       else {
-               tprints("{");
-               printxval(cap_version, arg.p->version,
-                         "_LINUX_CAPABILITY_VERSION_???");
-               tprintf(", %d}", arg.p->pid);
+               return;
+       }
+
+       tprints("{");
+       printxval(cap_version, h->version,
+                 "_LINUX_CAPABILITY_VERSION_???");
+       tprintf(", %d}", h->pid);
+}
+
+static void
+print_cap_bits(const uint32_t lo, const uint32_t hi)
+{
+       if (lo || !hi)
+               printflags(capabilities, lo, "CAP_???");
+
+       if (hi) {
+               if (lo)
+                       tprints("|");
+               printflags(capabilities1, hi, "CAP_???");
        }
 }
 
 static void
-print_cap_data(struct tcb *tcp, unsigned long addr)
+print_cap_data(struct tcb *tcp, unsigned long addr, const cap_user_header_t h)
 {
-       union { cap_user_data_t p; long *a; char *c; } arg;
-       long a[sizeof(*arg.p) / sizeof(long) + 1];
-       arg.a = a;
+       struct user_cap_data_struct data[2];
+       unsigned int len;
 
-       if (!addr)
+       if (!addr) {
                tprints("NULL");
-       else if (!verbose(tcp) ||
-                (exiting(tcp) && syserror(tcp)) ||
-                umoven(tcp, addr, sizeof(*arg.p), arg.c) < 0)
+               return;
+       }
+
+       if (!h || !verbose(tcp) ||
+           (exiting(tcp) && syserror(tcp))) {
                tprintf("%#lx", addr);
-       else {
-               tprints("{");
-               printflags(capabilities, arg.p->effective, "CAP_???");
-               tprints(", ");
-               printflags(capabilities, arg.p->permitted, "CAP_???");
-               tprints(", ");
-               printflags(capabilities, arg.p->inheritable, "CAP_???");
-               tprints("}");
+               return;
        }
+
+       if (_LINUX_CAPABILITY_VERSION_2 == h->version ||
+           _LINUX_CAPABILITY_VERSION_3 == h->version)
+               len = 2;
+       else
+               len = 1;
+
+       if (umoven(tcp, addr, len * sizeof(data[0]), (char *) data) < 0) {
+               tprintf("%#lx", addr);
+               return;
+       }
+
+       tprints("{");
+       print_cap_bits(data[0].effective, len > 1 ? data[1].effective : 0);
+       tprints(", ");
+       print_cap_bits(data[0].permitted, len > 1 ? data[1].permitted : 0);
+       tprints(", ");
+       print_cap_bits(data[0].inheritable, len > 1 ? data[1].inheritable : 0);
+       tprints("}");
 }
 
 int
 sys_capget(struct tcb *tcp)
 {
+       cap_user_header_t h;
+
        if (entering(tcp)) {
-               print_cap_header(tcp, tcp->u_arg[0]);
+               h = get_cap_header(tcp, tcp->u_arg[0]);
+               print_cap_header(tcp, tcp->u_arg[0], h);
                tprints(", ");
        } else {
-               print_cap_data(tcp, tcp->u_arg[1]);
+               h = syserror(tcp) ? NULL : get_cap_header(tcp, tcp->u_arg[0]);
+               print_cap_data(tcp, tcp->u_arg[1], h);
        }
        return 0;
 }
@@ -116,9 +173,10 @@ int
 sys_capset(struct tcb *tcp)
 {
        if (entering(tcp)) {
-               print_cap_header(tcp, tcp->u_arg[0]);
+               cap_user_header_t h = get_cap_header(tcp, tcp->u_arg[0]);
+               print_cap_header(tcp, tcp->u_arg[0], h);
                tprints(", ");
-               print_cap_data(tcp, tcp->u_arg[1]);
+               print_cap_data(tcp, tcp->u_arg[1], h);
        }
        return 0;
 }
index aef6a56e50c94cb6f253af64f6167cfa8ac0cfa3..0784f24eae119a1eade1c8e032f8fd1275887e42 100644 (file)
@@ -4,6 +4,7 @@ AM_CFLAGS = $(WARN_CFLAGS)
 
 check_PROGRAMS = \
        inet-accept-connect-send-recv \
+       caps \
        mmsg \
        net-accept-connect \
        netlink_inet_diag \
@@ -23,6 +24,7 @@ TESTS = \
        ptrace_setoptions.test \
        strace-f.test \
        qual_syscall.test \
+       caps.test \
        getdents.test \
        scm_rights-fd.test \
        sigaction.test \
@@ -44,6 +46,7 @@ net-fd.log: net.log
 TEST_LOG_COMPILER = $(srcdir)/run.sh
 
 EXTRA_DIST = init.sh run.sh \
+            caps.awk \
             getdents.awk \
             mmsg.expected \
             net-yy-accept.awk \
diff --git a/tests/caps.awk b/tests/caps.awk
new file mode 100644 (file)
index 0000000..9f99280
--- /dev/null
@@ -0,0 +1,25 @@
+BEGIN {
+  fail = 0
+  lines = 3
+  cap = "(0|CAP_[A-Z_]+(\\|CAP_[A-Z_]+)*)"
+  capget = "^capget\\({_LINUX_CAPABILITY_VERSION_3, 0}, {" cap ", " cap ", " cap "}\\) = 0$"
+}
+
+NR == 1 {if (match($0, capget)) next}
+
+NR == 2 && $0 == "capset({_LINUX_CAPABILITY_VERSION_3, 0}, {CAP_DAC_OVERRIDE|CAP_WAKE_ALARM, CAP_DAC_READ_SEARCH|CAP_BLOCK_SUSPEND, 0}) = -1 EPERM (Operation not permitted)" {next}
+
+NR == lines && $0 == "+++ exited with 0 +++" {next}
+
+{
+  print "Line " NR " does not match."
+  fail = 1
+  exit 1
+}
+
+END {
+  if (fail == 0 && NR != lines) {
+    print "Expected " lines " lines, found " NR " line(s)."
+    exit 1
+  }
+}
diff --git a/tests/caps.c b/tests/caps.c
new file mode 100644 (file)
index 0000000..918d6a8
--- /dev/null
@@ -0,0 +1,19 @@
+#include <errno.h>
+
+extern int capget(int *, int *);
+extern int capset(int *, const int *);
+
+int
+main(void)
+{
+       int unused[6];
+       const int data[] = { 2, 4, 0, 8, 16, 0 };
+       const int v3 = 0x20080522;
+       int head[] = { v3, 0 };
+
+       if (capget(head, unused) || head[0] != v3 ||
+           capset(head, data) == 0 || errno != EPERM)
+               return 77;
+
+       return 0;
+}
diff --git a/tests/caps.test b/tests/caps.test
new file mode 100755 (executable)
index 0000000..ac0a85d
--- /dev/null
@@ -0,0 +1,26 @@
+#!/bin/sh
+
+# Check capget/capset syscalls decoding.
+
+. "${srcdir=.}/init.sh"
+
+check_prog awk
+
+./caps || {
+       if [ $? -eq 77 ]; then
+               framework_skip_ 'capget/capset syscalls do not behave as expected'
+       else
+               fail_ 'caps failed'
+       fi
+}
+
+args="-e trace=capget,capset ./caps"
+$STRACE -o "$LOG" $args || {
+       cat "$LOG"
+       fail_ "$STRACE $args failed"
+}
+
+awk -f "$srcdir"/caps.awk "$LOG" ||
+       { cat "$LOG"; fail_ 'unexpected output'; }
+
+exit 0
diff --git a/xlat/capabilities1.in b/xlat/capabilities1.in
new file mode 100644 (file)
index 0000000..bf787a0
--- /dev/null
@@ -0,0 +1,7 @@
+#unconditional
+1<<CAP_MAC_OVERRIDE
+1<<CAP_MAC_ADMIN
+1<<CAP_SYSLOG
+1<<CAP_WAKE_ALARM
+1<<CAP_BLOCK_SUSPEND
+1<<CAP_AUDIT_READ