From patchwork Mon Aug 29 11:47:07 2016 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Sargun Dhillon X-Patchwork-Id: 9303691 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork.web.codeaurora.org (Postfix) with ESMTP id 62B1F607F0 for ; Mon, 29 Aug 2016 11:47:20 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 503B3285E2 for ; Mon, 29 Aug 2016 11:47:20 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id 445AD287B6; Mon, 29 Aug 2016 11:47:20 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-6.8 required=2.0 tests=BAYES_00,DKIM_SIGNED, RCVD_IN_DNSWL_HI,T_DKIM_INVALID autolearn=ham version=3.3.1 Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 7B6F4285E2 for ; Mon, 29 Aug 2016 11:47:18 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S932974AbcH2LrM (ORCPT ); Mon, 29 Aug 2016 07:47:12 -0400 Received: from mail-it0-f49.google.com ([209.85.214.49]:38088 "EHLO mail-it0-f49.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S932970AbcH2LrK (ORCPT ); Mon, 29 Aug 2016 07:47:10 -0400 Received: by mail-it0-f49.google.com with SMTP id g62so91834316ith.1 for ; Mon, 29 Aug 2016 04:47:10 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=sargun.me; s=google; h=date:from:to:cc:subject:message-id:mime-version:content-disposition :user-agent; bh=NWheW7tgATXRWb3z1ZECVrmBvVs8eZL6kmIhL7SJcbY=; b=cM4gzwwsJ70Fol6lAlYc8jPsIGS/DPs/B/Xtsp8RULE+1/GrwoMPdVLMitipLvulOp XvTDML1GOf9T6sCS4Zp4Tc/AWa6zLGvXFGxDcYv3iR5GN1m1I36oK/oSc7gDCfRNSa5A y4LukiY1GyGcQz87LxyW6IpP9jJihg3W4etgQ= X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20130820; h=x-gm-message-state:date:from:to:cc:subject:message-id:mime-version :content-disposition:user-agent; bh=NWheW7tgATXRWb3z1ZECVrmBvVs8eZL6kmIhL7SJcbY=; b=MB4cEMVqkCRTVFDUPYRVvc4YI6cBJIFvvOU2pVHwFqwI/MO/Rzpr/xCEVtZQ//cJfr 0uQvBmlEsQdywAr0cGYAh72KHWQImceiPAzElt6OirOfvw4x49jmjldG0QYbdHL3s4hW OzWDjRWnc8lDOrlmEvqmf1KKXbsIRg/48RR7r56ndkWu2jc7uRoSahIN8NW0afzp36jZ gg98IiZX43lonm26TRR4xXF48Jqo85xCllzJ4zKQWNoTVD8QqC1cVDHClJXNrz5dmOxn 31xq1d2aZoCik8wchiENsyAihZSSjvxpoP6kjUkFexBKFv7rluWmKVdU9HrJU25j17Bj fx7g== X-Gm-Message-State: AE9vXwMISlLCQf9Z3xwTr4At+jgM59CvUXM+2gzr9JQk1Y4wbDZMjNl7NJcA2lwZXG4O4A== X-Received: by 10.36.20.9 with SMTP id 9mr14453559itg.24.1472471229197; Mon, 29 Aug 2016 04:47:09 -0700 (PDT) Received: from ircssh.c.rugged-nimbus-611.internal (55.145.251.23.bc.googleusercontent.com. [23.251.145.55]) by smtp.gmail.com with ESMTPSA id j63sm5983320itj.19.2016.08.29.04.47.08 (version=TLS1_2 cipher=AES128-SHA bits=128/128); Mon, 29 Aug 2016 04:47:08 -0700 (PDT) Date: Mon, 29 Aug 2016 04:47:07 -0700 From: Sargun Dhillon To: netdev@vger.kernel.org Cc: cgroups@vger.kernel.org, linux-security-module@vger.kernel.org, daniel@iogearbox.net, ast@fb.com Subject: [net-next RFC v2 4/9] bpf, security: Add Checmate security LSM and BPF program type Message-ID: <20160829114705.GA20877@ircssh.c.rugged-nimbus-611.internal> MIME-Version: 1.0 Content-Disposition: inline User-Agent: Mutt/1.5.21 (2010-09-15) Sender: owner-linux-security-module@vger.kernel.org Precedence: bulk List-ID: X-Virus-Scanned: ClamAV using ClamSMTP This patch adds a minor LSM, Checmate. Checmate is a flexible programmable, extensible minor LSM that's coupled with cgroups and BPF. It is designed to enforce container-specific policies. It is also a cgroupv2 controller. By itself, it doesn't do very much, but in conjunction with a orchestrator complex policies can be installed on the cgroup hierarchy. These cgroup programs are tied to the kernel ABI version. If one tries to load a BPF program compiled against a different kernel version, an error will be thrown. Signed-off-by: Sargun Dhillon --- include/linux/cgroup_subsys.h | 4 + include/linux/checmate.h | 108 +++++++ include/uapi/linux/bpf.h | 1 + kernel/bpf/syscall.c | 2 +- security/Kconfig | 1 + security/Makefile | 2 + security/checmate/Kconfig | 11 + security/checmate/Makefile | 3 + security/checmate/checmate_bpf.c | 68 +++++ security/checmate/checmate_lsm.c | 610 +++++++++++++++++++++++++++++++++++++++ 10 files changed, 809 insertions(+), 1 deletion(-) create mode 100644 include/linux/checmate.h create mode 100644 security/checmate/Kconfig create mode 100644 security/checmate/Makefile create mode 100644 security/checmate/checmate_bpf.c create mode 100644 security/checmate/checmate_lsm.c diff --git a/include/linux/cgroup_subsys.h b/include/linux/cgroup_subsys.h index 0df0336a..fbb7aa7 100644 --- a/include/linux/cgroup_subsys.h +++ b/include/linux/cgroup_subsys.h @@ -56,6 +56,10 @@ SUBSYS(hugetlb) SUBSYS(pids) #endif +#if IS_ENABLED(CONFIG_SECURITY_CHECMATE) +SUBSYS(checmate) +#endif + /* * The following subsystems are not supported on the default hierarchy. */ diff --git a/include/linux/checmate.h b/include/linux/checmate.h new file mode 100644 index 0000000..4c4db4a --- /dev/null +++ b/include/linux/checmate.h @@ -0,0 +1,108 @@ +#ifndef _LINUX_CHECMATE_H_ +#define _LINUX_CHECMATE_H_ 1 +#include + +enum checmate_hook_num { + /* CONFIG_SECURITY_NET hooks */ + CHECMATE_HOOK_UNIX_STREAM_CONNECT, + CHECMATE_HOOK_UNIX_MAY_SEND, + CHECMATE_HOOK_SOCKET_CREATE, + CHECMATE_HOOK_SOCKET_POST_CREATE, + CHECMATE_HOOK_SOCKET_BIND, + CHECMATE_HOOK_SOCKET_CONNECT, + CHECMATE_HOOK_SOCKET_LISTEN, + CHECMATE_HOOK_SOCKET_ACCEPT, + CHECMATE_HOOK_SOCKET_SENDMSG, + CHECMATE_HOOK_SOCKET_RECVMSG, + CHECMATE_HOOK_SOCKET_GETSOCKNAME, + CHECMATE_HOOK_SOCKET_GETPEERNAME, + CHECMATE_HOOK_SOCKET_GETSOCKOPT, + CHECMATE_HOOK_SOCKET_SETSOCKOPT, + CHECMATE_HOOK_SOCKET_SHUTDOWN, + CHECMATE_HOOK_SOCKET_SOCK_RCV_SKB, + CHECMATE_HOOK_SK_FREE_SECURITY, + __CHECMATE_HOOK_MAX, +}; + +/* CONFIG_SECURITY_NET contexts */ +struct checmate_unix_stream_connect_ctx { + struct sock *sock; + struct sock *other; + struct sock *newsk; +}; + +struct checmate_unix_may_send_ctx { + struct socket *sock; + struct socket *other; +}; + +struct checmate_socket_create_ctx { + int family; + int type; + int protocol; + int kern; +}; + +struct checmate_socket_bind_ctx { + struct socket *sock; + struct sockaddr *address; + int addrlen; +}; + +struct checmate_socket_connect_ctx { + struct socket *sock; + struct sockaddr *address; + int addrlen; +}; + +struct checmate_socket_listen_ctx { + struct socket *sock; + int backlog; +}; + +struct checmate_socket_accept_ctx { + struct socket *sock; + struct socket *newsock; +}; + +struct checmate_socket_sendmsg_ctx { + struct socket *sock; + struct msghdr *msg; + int size; +}; + +struct checmate_socket_recvmsg_ctx { + struct socket *sock; + struct msghdr *msg; + int size; + int flags; +}; + +struct checmate_socket_sock_rcv_skb_ctx { + struct sock *sk; + struct sk_buff *skb; +}; + +struct checmate_sk_free_security_ctx { + struct sock *sk; +}; + +struct checmate_ctx { + int hook; + union { +/* CONFIG_SECURITY_NET contexts */ + struct checmate_unix_stream_connect_ctx unix_stream_connect; + struct checmate_unix_may_send_ctx unix_may_send; + struct checmate_socket_create_ctx socket_create; + struct checmate_socket_bind_ctx socket_bind; + struct checmate_socket_connect_ctx socket_connect; + struct checmate_socket_listen_ctx socket_listen; + struct checmate_socket_accept_ctx socket_accept; + struct checmate_socket_sendmsg_ctx socket_sendmsg; + struct checmate_socket_recvmsg_ctx socket_recvmsg; + struct checmate_socket_sock_rcv_skb_ctx socket_sock_rcv_skb; + struct checmate_sk_free_security_ctx sk_free_security; + }; +}; + +#endif /* _LINUX_CHECMATE_H_ */ diff --git a/include/uapi/linux/bpf.h b/include/uapi/linux/bpf.h index e4c5a1b..91bc92f 100644 --- a/include/uapi/linux/bpf.h +++ b/include/uapi/linux/bpf.h @@ -95,6 +95,7 @@ enum bpf_prog_type { BPF_PROG_TYPE_SCHED_ACT, BPF_PROG_TYPE_TRACEPOINT, BPF_PROG_TYPE_XDP, + BPF_PROG_TYPE_CHECMATE, }; #define BPF_PSEUDO_MAP_FD 1 diff --git a/kernel/bpf/syscall.c b/kernel/bpf/syscall.c index 228f962..6f4f7b0 100644 --- a/kernel/bpf/syscall.c +++ b/kernel/bpf/syscall.c @@ -741,7 +741,7 @@ static int bpf_prog_load(union bpf_attr *attr) if (attr->insn_cnt >= BPF_MAXINSNS) return -EINVAL; - if (type == BPF_PROG_TYPE_KPROBE && + if ((type == BPF_PROG_TYPE_KPROBE || type == BPF_PROG_TYPE_CHECMATE) && attr->kern_version != LINUX_VERSION_CODE) return -EINVAL; diff --git a/security/Kconfig b/security/Kconfig index df28f2b..c819539 100644 --- a/security/Kconfig +++ b/security/Kconfig @@ -152,6 +152,7 @@ source security/tomoyo/Kconfig source security/apparmor/Kconfig source security/loadpin/Kconfig source security/yama/Kconfig +source security/checmate/Kconfig source security/integrity/Kconfig diff --git a/security/Makefile b/security/Makefile index f2d71cd..6cc3342 100644 --- a/security/Makefile +++ b/security/Makefile @@ -8,6 +8,7 @@ subdir-$(CONFIG_SECURITY_SMACK) += smack subdir-$(CONFIG_SECURITY_TOMOYO) += tomoyo subdir-$(CONFIG_SECURITY_APPARMOR) += apparmor subdir-$(CONFIG_SECURITY_YAMA) += yama +subdir-$(CONFIG_SECURITY_CHECMATE) += checmate subdir-$(CONFIG_SECURITY_LOADPIN) += loadpin # always enable default capabilities @@ -25,6 +26,7 @@ obj-$(CONFIG_SECURITY_APPARMOR) += apparmor/ obj-$(CONFIG_SECURITY_YAMA) += yama/ obj-$(CONFIG_SECURITY_LOADPIN) += loadpin/ obj-$(CONFIG_CGROUP_DEVICE) += device_cgroup.o +obj-$(CONFIG_SECURITY_CHECMATE) += checmate/ # Object integrity file lists subdir-$(CONFIG_INTEGRITY) += integrity diff --git a/security/checmate/Kconfig b/security/checmate/Kconfig new file mode 100644 index 0000000..9dc76d1 --- /dev/null +++ b/security/checmate/Kconfig @@ -0,0 +1,11 @@ +config SECURITY_CHECMATE + bool "Checmate support" + depends on SECURITY && SOCK_CGROUP_DATA + default n + help + This selects Checmate, which is an LSM that works in conjuncion with + cgroups and BPF in order to provide programmable, flexible, and + extensible security policies. Further information can be found in + Documentation/security/Checmate.txt + + If you are unsure how to answer this question, answer N. diff --git a/security/checmate/Makefile b/security/checmate/Makefile new file mode 100644 index 0000000..c676773 --- /dev/null +++ b/security/checmate/Makefile @@ -0,0 +1,3 @@ +obj-$(CONFIG_SECURITY_CHECMATE) := checmate.o + +checmate-y := checmate_bpf.o checmate_lsm.o diff --git a/security/checmate/checmate_bpf.c b/security/checmate/checmate_bpf.c new file mode 100644 index 0000000..001225c --- /dev/null +++ b/security/checmate/checmate_bpf.c @@ -0,0 +1,68 @@ +/* + * Checmate Linux Security Module + * + * Copyright (C) 2016 Sargun Dhillon + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, as + * published by the Free Software Foundation. + * + */ + +#include +#include + +static const struct bpf_func_proto * +checmate_prog_func_proto(enum bpf_func_id func_id) +{ + switch (func_id) { + case BPF_FUNC_map_lookup_elem: + return &bpf_map_lookup_elem_proto; + case BPF_FUNC_map_update_elem: + return &bpf_map_update_elem_proto; + case BPF_FUNC_map_delete_elem: + return &bpf_map_delete_elem_proto; + case BPF_FUNC_probe_read: + return &bpf_probe_read_proto; + case BPF_FUNC_tail_call: + return &bpf_tail_call_proto; + case BPF_FUNC_get_current_pid_tgid: + return &bpf_get_current_pid_tgid_proto; + case BPF_FUNC_get_current_task: + return &bpf_get_current_task_proto; + case BPF_FUNC_get_current_uid_gid: + return &bpf_get_current_uid_gid_proto; + case BPF_FUNC_get_current_comm: + return &bpf_get_current_comm_proto; + case BPF_FUNC_trace_printk: + return bpf_get_trace_printk_proto(); + default: + return NULL; + } +} + +static bool checmate_prog_is_valid_access(int off, int size, + enum bpf_access_type type, + enum bpf_reg_type *reg_type) +{ + if (type != BPF_READ) + return false; + if (off < 0 || off >= sizeof(struct checmate_ctx)) + return false; + return true; +} + +static const struct bpf_verifier_ops checmate_prog_ops = { + .get_func_proto = checmate_prog_func_proto, + .is_valid_access = checmate_prog_is_valid_access, +}; + +static struct bpf_prog_type_list checmate_tl = { + .ops = &checmate_prog_ops, + .type = BPF_PROG_TYPE_CHECMATE, +}; + +void register_checmate_prog_ops(void) +{ + bpf_register_prog_type(&checmate_tl); +} diff --git a/security/checmate/checmate_lsm.c b/security/checmate/checmate_lsm.c new file mode 100644 index 0000000..ef8514d --- /dev/null +++ b/security/checmate/checmate_lsm.c @@ -0,0 +1,610 @@ +/* + * Checmate Linux Security Module + * + * Copyright (C) 2016 Sargun Dhillon + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, as + * published by the Free Software Foundation. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MAX_CHECMATE_INSTANCES 32 + +/* Global mutex for any Checmate hook manipulation operations */ +DEFINE_MUTEX(checmate_mutex); + +#define CHECMATE_CFTYPE(HOOK, NAME) \ + { \ + .name = NAME, \ + .private = HOOK, \ + .read_u64 = checmate_read_u64, \ + .write_s64 = checmate_write_s64, \ + .flags = CFTYPE_NOT_ON_ROOT \ + } + +extern void register_checmate_prog_ops(void); + +struct checmate_instance { + struct list_head list; + struct rcu_head rcu; + struct bpf_prog *prog; +}; + +struct checmate_hook { + struct list_head instances; + int count; +}; + +struct checmate_css { + struct cgroup_subsys_state css; + struct checmate_hook hooks[__CHECMATE_HOOK_MAX]; +}; + +static struct checmate_css *css_checmate(struct cgroup_subsys_state *css) +{ + return container_of(css, struct checmate_css, css); +} + +static struct checmate_css *parent_checmate(struct checmate_css *checmate) +{ + return css_checmate(checmate->css.parent); +} + +static struct cgroup_subsys_state * +checmate_css_alloc(struct cgroup_subsys_state *parent) +{ + struct checmate_css *checmate; + int i; + + checmate = kzalloc(sizeof(*checmate), GFP_KERNEL); + if (!checmate) + return ERR_PTR(-ENOMEM); + + for (i = 0; i < ARRAY_SIZE(checmate->hooks); i++) + INIT_LIST_HEAD(&checmate->hooks[i].instances); + + return &checmate->css; +} + +/* + * checmate_hook_free - Deallocate, and release resources for a given hook + * @hook: The hook + * + * Always succeeds. Only to be used when hook is out of use, and therefore + * doesn't use the RCU mechanism to cleanup he hook. Only use it for + * retirement of a hook. + */ +static void checmate_hook_free(struct checmate_hook *hook) +{ + struct checmate_instance *instance, *next; + + list_for_each_entry_safe(instance, next, &hook->instances, list) { + list_del(&instance->list); + bpf_prog_put(instance->prog); + kfree(instance); + } +} + +/* + * checmate_css_free - Callback for css_free + * @css: The cgroup_subsys_state to be freed + */ +static void checmate_css_free(struct cgroup_subsys_state *css) +{ + struct checmate_css *checmate = css_checmate(css); + int i; + + mutex_lock(&checmate_mutex); + for (i = 0; i < ARRAY_SIZE(checmate->hooks); i++) + checmate_hook_free(&checmate->hooks[i]); + + kfree(checmate); + mutex_unlock(&checmate_mutex); +} + +/* + * checmate_instance_add - Add BPF program instance to a Checmate hook + * @hook: The hook + * @prog: A checmate BPF program + * + * Checks if the program is already part of the hook, and only adds new + * programs. + * + * Returns 0 on success. -errno on failure. + * + * Requires that the Checmate mutex is held during the operation. + */ +static int checmate_instance_add(struct checmate_hook *hook, + struct bpf_prog *prog) +{ + struct checmate_instance *instance; + int rc = 0; + + if (hook->count >= MAX_CHECMATE_INSTANCES) + return -ENOSPC; + + list_for_each_entry(instance, &hook->instances, list) { + if (instance->prog == prog) { + bpf_prog_put(prog); + rc = -EEXIST; + goto err; + } + } + + instance = kmalloc(sizeof(*instance), GFP_KERNEL); + if (!instance) { + rc = -ENOMEM; + goto err; + } + + instance->prog = prog; + list_add_tail_rcu(&instance->list, &hook->instances); + hook->count++; + return rc; + +err: + bpf_prog_put(prog); + return rc; +} + +/* + * checmate_instance_cleanup_rcu - Cleans up a Checmate program instance + * @rp: rcu_head pointer to a Checmate instance + */ +static void checmate_instance_cleanup_rcu(struct rcu_head *rp) +{ + struct checmate_instance *instance; + + instance = container_of(rp, struct checmate_instance, rcu); + bpf_prog_put(instance->prog); + kfree(instance); +} + +/* + * checmate_instance_remove - Remove Checmate program instance from a hook + * @hook: The hook + * @prog: A Checmate BPF program referred to by the instance. + * + * Returns 0 on success. -errno on failure. + * + * Requires that the Checmate mutex is held during the operation. + */ +static int checmate_instance_remove(struct checmate_hook *hook, + struct bpf_prog *prog) +{ + struct checmate_instance *instance, *next; + int rc = -ENOENT; + + list_for_each_entry_safe(instance, next, &hook->instances, list) { + if (instance->prog == prog) { + list_del_rcu(&instance->list); + call_rcu(&instance->rcu, checmate_instance_cleanup_rcu); + rc = 0; + hook->count--; + break; + } + } + bpf_prog_put(prog); + + return rc; +} + +/* + * checmate_hook_reset - Remove all program instances from a Checmate hook + * @hook: The hook + * + * Always succeeds. + * + * Requires that the Checmate mutex is held during the operation. + */ +static void checmate_hook_reset(struct checmate_hook *hook) +{ + struct checmate_instance *instance, *next; + + list_for_each_entry_safe(instance, next, &hook->instances, list) { + list_del_rcu(&instance->list); + call_rcu(&instance->rcu, checmate_instance_cleanup_rcu); + } + hook->count = 0; +} + +/* + * checmate_write_s64 - Handle a write to the checmate cgroup control file + * @css: The given cgroup state that own's the hook + * @cft: The given cftype that is being referenced, used to get the hook id. + * @val: The bpf program fd that is involved in the operation, or 0. + * + * val == 0: Reset all programs in hook. + * val > 0: Add the given program. + * val < 0: Remove the given program. + * + * Returns 0 on success. -errno on failure. + */ +static int checmate_write_s64(struct cgroup_subsys_state *css, + struct cftype *cft, s64 val) +{ + struct checmate_css *checmate = css_checmate(css); + struct checmate_hook *hook; + struct bpf_prog *prog; + int rc = 0; + + hook = &checmate->hooks[cft->private]; + mutex_lock(&checmate_mutex); + if (val == 0) { + checmate_hook_reset(hook); + goto out; + } + + /* If we're not resetting, we have to load, and check the program */ + prog = bpf_prog_get_type(abs(val), BPF_PROG_TYPE_CHECMATE); + if (IS_ERR(prog)) + return PTR_ERR(prog); + + if (val > 0) + rc = checmate_instance_add(hook, prog); + else + rc = checmate_instance_remove(hook, prog); + +out: + mutex_unlock(&checmate_mutex); + return rc; +} + +/* + * checmate_read_u64 - Read the number of programs loaded into a given hook + * @css: The given cgroup state that own's the hook + * @cft: The given cftype that is being referenced, used to get the hook id. + * + * + * Returns number of programs loaded into hook. Always succeeds. + */ +static u64 checmate_read_u64(struct cgroup_subsys_state *css, + struct cftype *cft) +{ + struct checmate_css *checmate = css_checmate(css); + struct checmate_hook *hook; + + hook = &checmate->hooks[cft->private]; + return hook->count; +} + +static struct cftype checmate_files[] = { +#ifdef CONFIG_SECURITY_NETWORK + CHECMATE_CFTYPE(CHECMATE_HOOK_UNIX_STREAM_CONNECT, + "unix_stream_connect"), + CHECMATE_CFTYPE(CHECMATE_HOOK_UNIX_MAY_SEND, + "unix_may_send"), + CHECMATE_CFTYPE(CHECMATE_HOOK_SOCKET_CREATE, "socket_create"), + CHECMATE_CFTYPE(CHECMATE_HOOK_SOCKET_BIND, "socket_bind"), + CHECMATE_CFTYPE(CHECMATE_HOOK_SOCKET_CONNECT, "socket_connect"), + CHECMATE_CFTYPE(CHECMATE_HOOK_SOCKET_LISTEN, "socket_listen"), + CHECMATE_CFTYPE(CHECMATE_HOOK_SOCKET_ACCEPT, "socket_accept"), + CHECMATE_CFTYPE(CHECMATE_HOOK_SOCKET_SENDMSG, "socket_sendmsg"), + CHECMATE_CFTYPE(CHECMATE_HOOK_SOCKET_RECVMSG, "socket_recvmsg"), + CHECMATE_CFTYPE(CHECMATE_HOOK_SOCKET_SHUTDOWN, "socket_shutdown"), + CHECMATE_CFTYPE(CHECMATE_HOOK_SOCKET_SOCK_RCV_SKB, + "socket_sock_rcv_skb"), + CHECMATE_CFTYPE(CHECMATE_HOOK_SK_FREE_SECURITY, "sk_free_security"), +#endif /* CONFIG_SECURITY_NETWORK */ + {} +}; + +struct cgroup_subsys checmate_cgrp_subsys = { + .css_alloc = checmate_css_alloc, + .css_free = checmate_css_free, + .dfl_cftypes = checmate_files, +}; + +/* + * check_checmate_filters - Run all the BPF programs associated with a hook + * @css: A pointer to the Checmate css + * @ctx: A pointer to the Checmate ctx + * + * Return 0 on success, on first hook returning non-0, the error is returned + * to the caller. + */ +static int checmate_check_filters(struct checmate_css *checmate, + struct checmate_ctx *ctx) +{ + struct checmate_instance *instance; + struct checmate_hook *hook; + int rc = 0; + + hook = &checmate->hooks[ctx->hook]; + + rcu_read_lock(); + list_for_each_entry_rcu(instance, &hook->instances, list) { + rc = BPF_PROG_RUN(instance->prog, (void *)ctx); + if (rc) + break; + } + rcu_read_unlock(); + + return rc; +} + +/* + * call_bpf_int_hook - Walk the cgroup hierarchy, running filters up the chain + * @hook: The Hook ID + * @css: A pointer to the Checmate css + * @cgrp: A pointer to the cgroup we're in, may be null or err + * + * Return 0 on success, on first hook erroring, the error is returned + * to the caller. + * + * Requires that the context struct is populated before passing, but + * the actual ctx->hook number is set by the function. + */ +static int call_bpf_int_hook(int hook, struct cgroup_subsys_state *css, + struct checmate_ctx *ctx) +{ + struct checmate_css *checmate; + int rc = 0; + + /* Fail open if we can't find the css / cgroup */ + if (unlikely(IS_ERR_OR_NULL(css))) + goto out; + + ctx->hook = hook; + + for (checmate = css_checmate(css); parent_checmate(checmate); + checmate = parent_checmate(checmate)) { + rc = checmate_check_filters(checmate, ctx); + if (rc) + break; + } + +out: + return rc; +} + +/* + * call_bpf_void_hook - Run all the BPF programs associated with a hook + * Wrapper around call_bpf_int_hook. + */ +static void call_bpf_void_hook(int hook, struct cgroup_subsys_state *css, + struct checmate_ctx *ctx) +{ + call_bpf_int_hook(hook, css, ctx); +} + +/* + * css_from_sk - Get the Checmate CSS for an sk + * @sk: The struct sock we're trying to get the CSS for. + * + * Return Checmate CSS on success, or NULL / ERR_PTR on failure. It will try + * to return the effective CSS. + */ +static struct cgroup_subsys_state *css_from_sk(struct sock *sk) +{ + struct cgroup_subsys_state *css; + struct cgroup *cgrp; + + if (!sk_fullsock(sk)) + return ERR_PTR(-EINVAL); + cgrp = sock_cgroup_ptr(&sk->sk_cgrp_data); + + rcu_read_lock(); + do { + css = rcu_dereference(cgrp->subsys[checmate_cgrp_id]); + if (css) + goto out; + cgrp = cgroup_parent(cgrp); + } while (cgrp); + +out: + rcu_read_unlock(); + + return css; +} + +/* + * css_from_sock - Get the Checmate CSS for a socket + * @sock: The struct socket we're trying to get the CSS for. + * + * Return CSS on success. NULL / ERR_PTR on failure. It's a wrapper around + * css_from_sk. + */ +static struct cgroup_subsys_state *css_from_sock(struct socket *sock) +{ + struct sock *sk; + + sk = sock->sk; + if (!sk) + return ERR_PTR(-ENOENT); + + return css_from_sk(sk); +} + +/* + * css_from_sock - Get the checmate CSS for the current task context. + * + * Return CSS success on success. ERR_PTR on failure. It checks to see if it's + * being called from an interrupt as well. + */ +static struct cgroup_subsys_state *css_from_current(void) +{ + struct cgroup_subsys_state *css; + + if (unlikely(in_interrupt())) + return ERR_PTR(-ENOENT); + + rcu_read_lock(); + css = task_css(current, checmate_cgrp_id); + rcu_read_unlock(); + + return css; +} + +/* Checmate hooks */ +#ifdef CONFIG_SECURITY_NETWORK +static int checmate_unix_stream_connect(struct sock *sock, struct sock *other, + struct sock *newsk) +{ + struct cgroup_subsys_state *css; + struct checmate_ctx ctx; + + css = css_from_sk(sock); + ctx.unix_stream_connect.sock = sock; + ctx.unix_stream_connect.other = other; + ctx.unix_stream_connect.newsk = newsk; + return call_bpf_int_hook(CHECMATE_HOOK_UNIX_STREAM_CONNECT, css, &ctx); +} + +static int checmate_unix_may_send(struct socket *sock, struct socket *other) +{ + struct cgroup_subsys_state *css; + struct checmate_ctx ctx; + + css = css_from_sock(sock); + ctx.unix_may_send.sock = sock; + ctx.unix_may_send.other = other; + return call_bpf_int_hook(CHECMATE_HOOK_UNIX_MAY_SEND, css, &ctx); +} + +static int checmate_socket_create(int family, int type, int protocol, int kern) +{ + struct cgroup_subsys_state *css; + struct checmate_ctx ctx; + + css = css_from_current(); + ctx.socket_create.family = family; + ctx.socket_create.type = type; + ctx.socket_create.protocol = protocol; + ctx.socket_create.kern = kern; + return call_bpf_int_hook(CHECMATE_HOOK_SOCKET_CREATE, css, &ctx); +} + +static int checmate_socket_bind(struct socket *sock, struct sockaddr *address, + int addrlen) +{ + struct cgroup_subsys_state *css; + struct checmate_ctx ctx; + + css = css_from_sock(sock); + ctx.socket_bind.sock = sock; + ctx.socket_bind.address = address; + ctx.socket_bind.addrlen = addrlen; + return call_bpf_int_hook(CHECMATE_HOOK_SOCKET_BIND, css, &ctx); +} + +static int checmate_socket_connect(struct socket *sock, + struct sockaddr *address, int addrlen) +{ + struct cgroup_subsys_state *css; + struct checmate_ctx ctx; + + css = css_from_sock(sock); + ctx.socket_connect.sock = sock; + ctx.socket_connect.address = address; + ctx.socket_connect.addrlen = addrlen; + return call_bpf_int_hook(CHECMATE_HOOK_SOCKET_CONNECT, css, &ctx); +} + +static int checmate_socket_listen(struct socket *sock, int backlog) +{ + struct cgroup_subsys_state *css; + struct checmate_ctx ctx; + + css = css_from_sock(sock); + ctx.socket_listen.sock = sock; + ctx.socket_listen.backlog = backlog; + return call_bpf_int_hook(CHECMATE_HOOK_SOCKET_LISTEN, css, &ctx); +} + +static int checmate_socket_accept(struct socket *sock, struct socket *newsock) +{ + struct cgroup_subsys_state *css; + struct checmate_ctx ctx; + + css = css_from_sock(sock); + ctx.socket_accept.sock = sock; + ctx.socket_accept.newsock = newsock; + return call_bpf_int_hook(CHECMATE_HOOK_SOCKET_ACCEPT, css, &ctx); +} + +static int checmate_socket_sendmsg(struct socket *sock, struct msghdr *msg, + int size) +{ + struct cgroup_subsys_state *css; + struct checmate_ctx ctx; + + css = css_from_sock(sock); + ctx.socket_sendmsg.sock = sock; + ctx.socket_sendmsg.msg = msg; + ctx.socket_sendmsg.size = size; + return call_bpf_int_hook(CHECMATE_HOOK_SOCKET_SENDMSG, css, &ctx); +} + +static int checmate_socket_recvmsg(struct socket *sock, struct msghdr *msg, + int size, int flags) +{ + struct cgroup_subsys_state *css; + struct checmate_ctx ctx; + + css = css_from_sock(sock); + ctx.socket_recvmsg.sock = sock; + ctx.socket_recvmsg.msg = msg; + ctx.socket_recvmsg.size = size; + ctx.socket_recvmsg.flags = flags; + return call_bpf_int_hook(CHECMATE_HOOK_SOCKET_RECVMSG, css, &ctx); +} + +static int checmate_socket_sock_rcv_skb(struct sock *sk, struct sk_buff *skb) +{ + struct cgroup_subsys_state *css; + struct checmate_ctx ctx; + + css = css_from_sk(sk); + ctx.socket_sock_rcv_skb.sk = sk; + ctx.socket_sock_rcv_skb.skb = skb; + return call_bpf_int_hook(CHECMATE_HOOK_SOCKET_SOCK_RCV_SKB, css, &ctx); +} + +static void checmate_sk_free_security(struct sock *sk) +{ + struct cgroup_subsys_state *css; + struct checmate_ctx ctx; + + css = css_from_sk(sk); + ctx.sk_free_security.sk = sk; + return call_bpf_void_hook(CHECMATE_HOOK_SK_FREE_SECURITY, css, &ctx); +} + +#endif /* CONFIG_SECURITY_NETWORK */ + +static struct security_hook_list checmate_hooks[] = { +#ifdef CONFIG_SECURITY_NETWORK + LSM_HOOK_INIT(unix_stream_connect, checmate_unix_stream_connect), + LSM_HOOK_INIT(unix_may_send, checmate_unix_may_send), + LSM_HOOK_INIT(socket_create, checmate_socket_create), + LSM_HOOK_INIT(socket_bind, checmate_socket_bind), + LSM_HOOK_INIT(socket_connect, checmate_socket_connect), + LSM_HOOK_INIT(socket_listen, checmate_socket_listen), + LSM_HOOK_INIT(socket_accept, checmate_socket_accept), + LSM_HOOK_INIT(socket_sendmsg, checmate_socket_sendmsg), + LSM_HOOK_INIT(socket_recvmsg, checmate_socket_recvmsg), + LSM_HOOK_INIT(socket_sock_rcv_skb, checmate_socket_sock_rcv_skb), + LSM_HOOK_INIT(sk_free_security, checmate_sk_free_security), +#endif /* CONFIG_SECURITY_NETWORK */ +}; + +static int __init checmate_setup(void) +{ + pr_info("Checmate activating.\n"); + register_checmate_prog_ops(); + security_add_hooks(checmate_hooks, ARRAY_SIZE(checmate_hooks)); + return 0; +} +late_initcall(checmate_setup);