From 4b9c68b9a3a5297050985b2cbcb74752051edf98 Mon Sep 17 00:00:00 2001 From: "Dmitry V. Levin" Date: Fri, 5 Dec 2014 00:21:23 +0000 Subject: [PATCH] Implement full decoding of 64-bit capabilities 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 | 124 +++++++++++++++++++++++++++++++----------- tests/Makefile.am | 3 + tests/caps.awk | 25 +++++++++ tests/caps.c | 19 +++++++ tests/caps.test | 26 +++++++++ xlat/capabilities1.in | 7 +++ 6 files changed, 171 insertions(+), 33 deletions(-) create mode 100644 tests/caps.awk create mode 100644 tests/caps.c create mode 100755 tests/caps.test create mode 100644 xlat/capabilities1.in diff --git a/capability.c b/capability.c index 3c7666fc..af4dffeb 100644 --- a/capability.c +++ b/capability.c @@ -1,5 +1,6 @@ #include "defs.h" +/* these constants are the same as in */ enum { CAP_CHOWN, CAP_DAC_OVERRIDE, @@ -37,6 +38,19 @@ enum { #include "xlat/capabilities.h" +/* these constants are CAP_TO_INDEX'ed constants from */ +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 */ 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; } diff --git a/tests/Makefile.am b/tests/Makefile.am index aef6a56e..0784f24e 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -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 index 00000000..9f992800 --- /dev/null +++ b/tests/caps.awk @@ -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 index 00000000..918d6a8e --- /dev/null +++ b/tests/caps.c @@ -0,0 +1,19 @@ +#include + +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 index 00000000..ac0a85da --- /dev/null +++ b/tests/caps.test @@ -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 index 00000000..bf787a06 --- /dev/null +++ b/xlat/capabilities1.in @@ -0,0 +1,7 @@ +#unconditional +1<