From patchwork Tue Aug 25 23:00:43 2015 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Etienne Champetier X-Patchwork-Id: 510667 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Received: from arrakis.dune.hu (arrakis.dune.hu [78.24.191.176]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by ozlabs.org (Postfix) with ESMTPS id C82381402A3 for ; Wed, 26 Aug 2015 09:05:21 +1000 (AEST) Authentication-Results: ozlabs.org; dkim=fail reason="signature verification failed" (2048-bit key; unprotected) header.d=gmail.com header.i=@gmail.com header.b=nCeuaKiC; dkim-atps=neutral Received: from arrakis.dune.hu (localhost [127.0.0.1]) by arrakis.dune.hu (Postfix) with ESMTP id 3349B28BFC6; Wed, 26 Aug 2015 01:01:33 +0200 (CEST) X-Spam-Checker-Version: SpamAssassin 3.3.2 (2011-06-06) on arrakis.dune.hu X-Spam-Level: X-Spam-Status: No, score=-1.5 required=5.0 tests=BAYES_00,FREEMAIL_FROM, T_DKIM_INVALID autolearn=unavailable version=3.3.2 Received: from arrakis.dune.hu (localhost [127.0.0.1]) by arrakis.dune.hu (Postfix) with ESMTP id 8DD812805DD for ; Wed, 26 Aug 2015 01:00:49 +0200 (CEST) X-policyd-weight: using cached result; rate: -8.5 Received: from mail-wi0-f170.google.com (mail-wi0-f170.google.com [209.85.212.170]) by arrakis.dune.hu (Postfix) with ESMTPS for ; Wed, 26 Aug 2015 01:00:31 +0200 (CEST) Received: by widdq5 with SMTP id dq5so28114909wid.0 for ; Tue, 25 Aug 2015 16:01:24 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113; h=from:to:cc:subject:date:message-id:in-reply-to:references; bh=leYcaZlnu0Mfd13KMDjWjug/zFHUFUNr2jnssjuCvPg=; b=nCeuaKiCr+4MKuFCH+B9ak7psuCW8I6qFDD7cPmu8kP1tct9cJj0fI2DQ56up6wAZZ bEllsuvkKFyaBcgvnWSH8KWenM/JhJ+9bbbMvlhvHyym5ifpR3Lozfk7eDtdFFIyrZ4R AQMrIqgHKOw5iwxXLouEWG1JHdNABC1ghBtUg0jwEhi4ix5aB2bardccmRrpiNKJFzjh +Dr5HPWn4Z6o4oQnr+wOOQFlZwS8IWtLodgi44KXlGwxeqSyCwxijuRdjD/FGifVquZi e8OwP0n7EFhGCxTFMrLhcJ+Ise66rR/eR3CaeXXBhRxxpgJWohmb3VpvCn2M0ceLzc0p YlfA== X-Received: by 10.194.2.9 with SMTP id 9mr52148518wjq.95.1440543684648; Tue, 25 Aug 2015 16:01:24 -0700 (PDT) Received: from ubuntu1404.lxcnattst (ns623510.ovh.net. [5.135.134.9]) by smtp.gmail.com with ESMTPSA id o3sm989177wjx.16.2015.08.25.16.01.23 (version=TLS1_2 cipher=ECDHE-RSA-AES128-SHA bits=128/128); Tue, 25 Aug 2015 16:01:23 -0700 (PDT) From: Etienne CHAMPETIER To: openwrt-devel@lists.openwrt.org, John Crispin Date: Tue, 25 Aug 2015 23:00:43 +0000 Message-Id: <1440543643-43546-6-git-send-email-champetier.etienne@gmail.com> X-Mailer: git-send-email 1.9.1 In-Reply-To: <1440543643-43546-1-git-send-email-champetier.etienne@gmail.com> References: <1440543643-43546-1-git-send-email-champetier.etienne@gmail.com> Subject: [OpenWrt-Devel] [PATCH procd v2 5/5] jail: add capabilities support X-BeenThere: openwrt-devel@lists.openwrt.org X-Mailman-Version: 2.1.15 Precedence: list List-Id: OpenWrt Development List List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , MIME-Version: 1.0 Errors-To: openwrt-devel-bounces@lists.openwrt.org Sender: "openwrt-devel" 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 --- 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 --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 + * + * 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 +#include + +#include +#include + +#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 + * + * 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 487d18f..cfaefdd 100644 --- a/jail/jail.c +++ b/jail/jail.c @@ -37,19 +37,21 @@ #include #include "elf.h" +#include "capabilities.h" #include #include #include #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 -- \n"); fprintf(stderr, " -P \tpath where the jail will be staged\n"); fprintf(stderr, " -S \tseccomp filter\n"); + fprintf(stderr, " -C \tcapabilities drop config\n"); fprintf(stderr, " -n \tthe name of the jail\n"); fprintf(stderr, " -r \treadonly files that should be staged\n"); fprintf(stderr, " -w \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 " +echo "static const char *capabilities_names[] = {" +echo "#include " | ${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 "};"