jail: add capabilities support
authorEtienne CHAMPETIER <champetier.etienne@gmail.com>
Wed, 26 Aug 2015 23:26:45 +0000 (23:26 +0000)
committerJohn Crispin <blogic@openwrt.org>
Wed, 7 Oct 2015 09:07:54 +0000 (11:07 +0200)
If there is one or more capabilities in cap.keep,
drop all capabilities not in cap.keep.
Always drop all capabalities in cap.drop

exemple json syntax:
{
"cap.keep": [
        "cap_net_raw"
],
"cap.drop": []
}

Signed-off-by: Etienne CHAMPETIER <champetier.etienne@gmail.com>
CMakeLists.txt
jail/capabilities.c [new file with mode: 0644]
jail/capabilities.h [new file with mode: 0644]
jail/jail.c
make_capabilities_h.sh [new file with mode: 0755]

index 805e2ed8f5ea02c66268eeccf2ec8d1893987afa..cc1e4a586a564bd5a97fb2afddfe76aaac70440c 100644 (file)
@@ -67,7 +67,14 @@ ADD_CUSTOM_COMMAND(
        COMMAND ./make_syscall_h.sh ${CMAKE_C_COMPILER} > ./syscall-names.h
        DEPENDS ./make_syscall_h.sh
 )
-ADD_CUSTOM_TARGET(headers DEPENDS syscall-names.h)
+ADD_CUSTOM_TARGET(syscall-names-h DEPENDS syscall-names.h)
+
+ADD_CUSTOM_COMMAND(
+       OUTPUT capabilities-names.h
+       COMMAND ./make_capabilities_h.sh ${CMAKE_C_COMPILER} > ./capabilities-names.h
+       DEPENDS ./make_capabilities_h.sh
+)
+ADD_CUSTOM_TARGET(capabilities-names-h DEPENDS capabilities-names.h)
 
 IF(SECCOMP_SUPPORT)
 ADD_LIBRARY(preload-seccomp SHARED jail/preload.c jail/seccomp.c)
@@ -75,15 +82,16 @@ TARGET_LINK_LIBRARIES(preload-seccomp dl ubox blobmsg_json)
 INSTALL(TARGETS preload-seccomp
        LIBRARY DESTINATION lib
 )
-ADD_DEPENDENCIES(preload-seccomp headers)
+ADD_DEPENDENCIES(preload-seccomp syscall-names-h)
 endif()
 
 IF(JAIL_SUPPORT)
-ADD_EXECUTABLE(ujail jail/jail.c jail/elf.c)
-TARGET_LINK_LIBRARIES(ujail ubox)
+ADD_EXECUTABLE(ujail jail/jail.c jail/elf.c jail/capabilities.c)
+TARGET_LINK_LIBRARIES(ujail ubox blobmsg_json)
 INSTALL(TARGETS ujail
        RUNTIME DESTINATION sbin
 )
+ADD_DEPENDENCIES(ujail capabilities-names-h)
 endif()
 
 IF(UTRACE_SUPPORT)
@@ -92,7 +100,7 @@ TARGET_LINK_LIBRARIES(utrace ubox ${json} blobmsg_json)
 INSTALL(TARGETS utrace
        RUNTIME DESTINATION sbin
 )
-ADD_DEPENDENCIES(utrace headers)
+ADD_DEPENDENCIES(utrace syscall-names-h)
 
 ADD_LIBRARY(preload-trace SHARED trace/preload.c)
 TARGET_LINK_LIBRARIES(preload-trace dl)
diff --git a/jail/capabilities.c b/jail/capabilities.c
new file mode 100644 (file)
index 0000000..b5ea965
--- /dev/null
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2015 Etienne CHAMPETIER <champetier.etienne@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1
+ * as published by the Free Software Foundation
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#define _GNU_SOURCE 1
+#include <syslog.h>
+#include <sys/prctl.h>
+
+#include <libubox/blobmsg.h>
+#include <libubox/blobmsg_json.h>
+
+#include "log.h"
+#include "../capabilities-names.h"
+#include "capabilities.h"
+
+static int find_capabilities(const char *name)
+{
+       int i;
+
+       for (i = 0; i <= CAP_LAST_CAP; i++)
+               if (capabilities_names[i] && !strcmp(capabilities_names[i], name))
+                       return i;
+
+       return -1;
+}
+
+int drop_capabilities(const char *file)
+{
+       enum {
+               CAP_KEEP,
+               CAP_DROP,
+               __CAP_MAX
+       };
+       static const struct blobmsg_policy policy[__CAP_MAX] = {
+               [CAP_KEEP] = { .name = "cap.keep", .type = BLOBMSG_TYPE_ARRAY },
+               [CAP_DROP] = { .name = "cap.drop", .type = BLOBMSG_TYPE_ARRAY },
+       };
+       struct blob_buf b = { 0 };
+       struct blob_attr *tb[__CAP_MAX];
+       struct blob_attr *cur;
+       int rem, cap;
+       char *name;
+       uint64_t capdrop = 0LLU;
+
+       DEBUG("dropping capabilities\n");
+
+       blob_buf_init(&b, 0);
+       if (!blobmsg_add_json_from_file(&b, file)) {
+               ERROR("failed to load %s\n", file);
+               return -1;
+       }
+
+       blobmsg_parse(policy, __CAP_MAX, tb, blob_data(b.head), blob_len(b.head));
+       if (!tb[CAP_KEEP] && !tb[CAP_DROP]) {
+               ERROR("failed to parse %s\n", file);
+               return -1;
+       }
+
+       blobmsg_for_each_attr(cur, tb[CAP_KEEP], rem) {
+               name = blobmsg_get_string(cur);
+               if (!name) {
+                       ERROR("invalid capability name in cap.keep\n");
+                       return -1;
+               }
+               cap = find_capabilities(name);
+               if (cap == -1) {
+                       ERROR("unknown capability %s in cap.keep\n", name);
+                       return -1;
+               }
+               capdrop |= (1LLU << cap);
+       }
+
+       if (capdrop == 0LLU) {
+               DEBUG("cap.keep empty -> only dropping capabilities from cap.drop (blacklist)\n");
+               capdrop = 0xffffffffffffffffLLU;
+       } else {
+               DEBUG("cap.keep has at least one capability -> dropping every capabilities not in cap.keep (whitelist)\n");
+       }
+
+       blobmsg_for_each_attr(cur, tb[CAP_DROP], rem) {
+               name = blobmsg_get_string(cur);
+               if (!name) {
+                       ERROR("invalid capability name in cap.drop\n");
+                       return -1;
+               }
+               cap = find_capabilities(name);
+               if (cap == -1) {
+                       ERROR("unknown capability %s in cap.drop\n", name);
+                       return -1;
+               }
+               capdrop &= ~(1LLU << cap);
+       }
+
+       for (cap = 0; cap <= CAP_LAST_CAP; cap++) {
+               if ( (capdrop & (1LLU << cap)) == 0) {
+                       DEBUG("dropping capability %s (%d)\n", capabilities_names[cap], cap);
+                       if (prctl(PR_CAPBSET_DROP, cap, 0, 0, 0)) {
+                               ERROR("prctl(PR_CAPBSET_DROP, %d) failed: %s\n", cap, strerror(errno));
+                               return errno;
+                       }
+               } else {
+                       DEBUG("keeping capability %s (%d)\n", capabilities_names[cap], cap);
+               }
+       }
+
+       return 0;
+}
diff --git a/jail/capabilities.h b/jail/capabilities.h
new file mode 100644 (file)
index 0000000..e6699e9
--- /dev/null
@@ -0,0 +1,14 @@
+/*
+ * Copyright (C) 2015 Etienne CHAMPETIER <champetier.etienne@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1
+ * as published by the Free Software Foundation
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+int drop_capabilities(const char *file);
index f8139b8b745504573496cba74b7c095a71d745ad..3d0830e98010d792712c14828a8b2f13d00cdda3 100644 (file)
 #include <sched.h>
 
 #include "elf.h"
+#include "capabilities.h"
 
 #include <libubox/utils.h>
 #include <libubox/list.h>
 #include <libubox/uloop.h>
 
 #define STACK_SIZE     (1024 * 1024)
-#define OPT_ARGS       "P:S:n:r:w:d:psulo"
+#define OPT_ARGS       "P:S:C:n:r:w:d:psulo"
 
 static struct {
        char *path;
        char *name;
        char **jail_argv;
        char *seccomp;
+       char *capabilities;
        int procfs;
        int ronly;
        int sysfs;
@@ -243,6 +245,7 @@ static void usage(void)
        fprintf(stderr, "ujail <options> -- <binary> <params ...>\n");
        fprintf(stderr, "  -P <path>\tpath where the jail will be staged\n");
        fprintf(stderr, "  -S <file>\tseccomp filter\n");
+       fprintf(stderr, "  -C <file>\tcapabilities drop config\n");
        fprintf(stderr, "  -n <name>\tthe name of the jail\n");
        fprintf(stderr, "  -r <file>\treadonly files that should be staged\n");
        fprintf(stderr, "  -w <file>\twriteable files that should be staged\n");
@@ -255,7 +258,7 @@ static void usage(void)
        fprintf(stderr, "\nWarning: by default root inside the jail is the same\n\
 and he has the same powers as root outside the jail,\n\
 thus he can escape the jail and/or break stuff.\n\
-Please use an appropriate seccomp filter (-S) to restrict his powers\n");
+Please use an appropriate seccomp/capabilities filter (-S/-C) to restrict his powers\n");
 }
 
 static int spawn_jail(void *arg)
@@ -273,8 +276,8 @@ static int spawn_jail(void *arg)
        if (!envp)
                exit(EXIT_FAILURE);
 
-       //TODO: drop capabilities() here
-       //prctl(PR_CAPBSET_DROP, ..., 0, 0, 0);
+       if (opts.capabilities && drop_capabilities(opts.capabilities))
+               exit(EXIT_FAILURE);
 
        INFO("exec-ing %s\n", *opts.jail_argv);
        execve(*opts.jail_argv, opts.jail_argv, envp);
@@ -354,6 +357,10 @@ int main(int argc, char **argv)
                        opts.seccomp = optarg;
                        add_extra(optarg, 1);
                        break;
+               case 'C':
+                       opts.capabilities = optarg;
+                       add_extra(optarg, 1);
+                       break;
                case 'P':
                        opts.path = optarg;
                        break;
diff --git a/make_capabilities_h.sh b/make_capabilities_h.sh
new file mode 100755 (executable)
index 0000000..635e740
--- /dev/null
@@ -0,0 +1,10 @@
+#!/bin/sh
+
+CC=$1
+[ -n "$TARGET_CC_NOCACHE" ] && CC=$TARGET_CC_NOCACHE
+
+echo "#include <linux/capability.h>"
+echo "static const char *capabilities_names[] = {"
+echo "#include <linux/capability.h>" | ${CC} -E -dM - | grep '#define CAP' | grep -vE '(CAP_TO|CAP_LAST_CAP)' | \
+       awk '{print $3" "$2}' | sort -n | awk '{print "   ["$1"]\t= \""tolower($2)"\","}'
+echo "};"