diff mbox

[OpenWrt-Devel,procd,v3,5/7] jail: add capabilities support

Message ID 1440631607-63520-6-git-send-email-champetier.etienne@gmail.com
State Accepted
Headers show

Commit Message

Etienne Champetier Aug. 26, 2015, 11:26 p.m. UTC
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         |  18 +++++---
 jail/capabilities.c    | 116 +++++++++++++++++++++++++++++++++++++++++++++++++
 jail/capabilities.h    |  14 ++++++
 jail/jail.c            |  15 +++++--
 make_capabilities_h.sh |  10 +++++
 5 files changed, 164 insertions(+), 9 deletions(-)
 create mode 100644 jail/capabilities.c
 create mode 100644 jail/capabilities.h
 create mode 100755 make_capabilities_h.sh
diff mbox

Patch

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 805e2ed..cc1e4a5 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -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
index 0000000..b5ea965
--- /dev/null
+++ b/jail/capabilities.c
@@ -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
index 0000000..e6699e9
--- /dev/null
+++ b/jail/capabilities.h
@@ -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);
diff --git a/jail/jail.c b/jail/jail.c
index f8139b8..3d0830e 100644
--- a/jail/jail.c
+++ b/jail/jail.c
@@ -37,19 +37,21 @@ 
 #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
index 0000000..635e740
--- /dev/null
+++ b/make_capabilities_h.sh
@@ -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 "};"