diff options
| author | Alexei Starovoitov <ast@kernel.org> | 2026-09-06 15:50:31 -0700 |
|---|---|---|
| committer | Alexei Starovoitov <ast@kernel.org> | 2026-09-06 16:17:40 -0700 |
| commit | 1b7415bf70be95b9a1e7e87d544867881065613f (patch) | |
| tree | 546f3eae52bfdce557498530a6d999445cb02699 /security | |
| parent | b0ca562e0d05cf7afedcfa71c9aed180b72bcc97 (diff) | |
| parent | df2908090cda368b01ff43709f51890076c56157 (diff) | |
Merge git://git.kernel.org/pub/scm/linux/kernel/git/bpf/bpf 7.3-rc2
Cross-merge BPF and other fixes after downstream PR.
Conflicts:
kernel/bpf/backtrack.c
include/linux/bpf_verifier.h
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
Diffstat (limited to 'security')
69 files changed, 4015 insertions, 2181 deletions
diff --git a/security/Kconfig.hardening b/security/Kconfig.hardening index 6923036e1a2f..81c81ad983ad 100644 --- a/security/Kconfig.hardening +++ b/security/Kconfig.hardening @@ -278,7 +278,7 @@ config CC_HAS_RANDSTRUCT choice prompt "Randomize layout of sensitive kernel structures" - default RANDSTRUCT_FULL if COMPILE_TEST && (GCC_PLUGINS || CC_HAS_RANDSTRUCT) + default RANDSTRUCT_FULL if !(RUST_IS_AVAILABLE && HAVE_RUST) && COMPILE_TEST && (GCC_PLUGINS || CC_HAS_RANDSTRUCT) default RANDSTRUCT_NONE help If you enable this, the layouts of structures that are entirely diff --git a/security/apparmor/Kconfig b/security/apparmor/Kconfig index 1e3bd44643da..a1f3749bdfd0 100644 --- a/security/apparmor/Kconfig +++ b/security/apparmor/Kconfig @@ -11,7 +11,7 @@ config SECURITY_APPARMOR This enables the AppArmor security module. Required userspace tools (if they are not included in your distribution) and further information may be found at - http://apparmor.wiki.kernel.org + https://apparmor.wiki.kernel.org If you are unsure how to answer this question, answer N. @@ -93,6 +93,18 @@ config SECURITY_APPARMOR_EXPORT_BINARY also increases policy load time. This option is required for checkpoint and restore support, and debugging of loaded policy. +config SECURITY_APPARMOR_COMPRESSED_POLICY + bool "Allow loading policy in a compressed format" + depends on SECURITY_APPARMOR + select ZSTD_DECOMPRESS + default y + help + This option allows loading policy from userspace in a + compressed format. This allows for userspace to not have to + decrompress caches before loading policy, and also allows + for less kernel memory to be used when "exporting the raw + binary policy" is enabled. + config SECURITY_APPARMOR_PARANOID_LOAD bool "Perform full verification of loaded policy" depends on SECURITY_APPARMOR diff --git a/security/apparmor/Makefile b/security/apparmor/Makefile index 12fb419714c0..8aed0d7ed6f5 100644 --- a/security/apparmor/Makefile +++ b/security/apparmor/Makefile @@ -6,7 +6,7 @@ obj-$(CONFIG_SECURITY_APPARMOR) += apparmor.o apparmor-y := apparmorfs.o audit.o capability.o task.o ipc.o lib.o match.o \ path.o domain.o policy.o policy_unpack.o procattr.o lsm.o \ resource.o secid.o file.o policy_ns.o label.o mount.o net.o \ - policy_compat.o af_unix.o + policy_compat.o af_unix.o af_inet.o apparmor-$(CONFIG_SECURITY_APPARMOR_HASH) += crypto.o obj-$(CONFIG_SECURITY_APPARMOR_KUNIT_TEST) += apparmor_policy_unpack_test.o diff --git a/security/apparmor/af_inet.c b/security/apparmor/af_inet.c new file mode 100644 index 000000000000..516c9016537c --- /dev/null +++ b/security/apparmor/af_inet.c @@ -0,0 +1,565 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * AppArmor security module + * + * This file contains AppArmor inet fine grained mediation + * + * Copyright 2024 Canonical Ltd. + * + */ + +#include <net/tcp_states.h> + +#include "include/audit.h" +#include "include/af_inet.h" +#include "include/apparmor.h" +#include "include/file.h" +#include "include/label.h" +#include "include/net.h" +#include "include/path.h" +#include "include/policy.h" +#include "include/cred.h" + + +static inline aa_state_t RULE_MEDIATES_SK(struct aa_ruleset *rules, + const struct sock *sk) +{ + return RULE_MEDIATES_NET(rules); +} + + +enum addr_type { + ADDR_LOCAL = 0, + ADDR_LOCAL_PRIV = 1, + ADDR_REMOTE = 2, +}; + +struct match_addr { + const char *addrp; + enum addr_type addrtype; + int len; + __be16 port; +}; + +struct stored_match_addr { + union { + struct sockaddr addr; + struct sockaddr_in addr4; + struct sockaddr_in6 addr6; + }; + int addrlen; + struct match_addr maddr; +}; + +static void set_ad_create(struct apparmor_audit_data *ad, + int family, int type, int protocol) +{ + ad->common.u.net->family = family; + ad->net.type = type; + ad->net.protocol = protocol; +} + +static int set_ad_addr(struct apparmor_audit_data *ad, + u16 family, bool source, struct match_addr *maddr) +{ + ad->common.u.net->family = family; + + if (source) { + ad->common.u.net->sport = maddr->port; + if (maddr->addrp) { + if (family == AF_INET) + /* ad.u.net->v4info.saddr = addr4->sin_addr.s_addr; */ + ad->common.u.net->v4info.saddr = *(__be32 *)maddr->addrp; + else + /* ad.u.net->v4info.saddr = addr6->sin6_addr.s6_addr; */ + ad->common.u.net->v6info.saddr = *(struct in6_addr *)maddr->addrp; + } + } else { + ad->common.u.net->dport = maddr->port; + if (maddr->addrp) { + if (family == AF_INET) + /* ad.u.net->v4info.saddr = addr4->sin_addr.s_addr; */ + ad->common.u.net->v4info.daddr = *(__be32 *)maddr->addrp; + else + /* ad.u.net->v4info.saddr = addr6->sin6_addr.s6_addr; */ + ad->common.u.net->v6info.daddr = *(struct in6_addr *)maddr->addrp; + } + } + return 0; +} + +/* returns 0 on success +* raw_port - if set raw_port (protocol) when SOCK_RAW */ +static int map_addr(struct sockaddr *addr, int addrlen, u16 raw_port, + enum addr_type addrtype, struct match_addr *maddr, + struct apparmor_audit_data *ad) +{ + struct sockaddr_in *addr4 = NULL; + struct sockaddr_in6 *addr6 = NULL; + + AA_BUG(!addr); + AA_BUG(!maddr); + + maddr->addrtype = addrtype; + if (!addr || addrlen < offsetofend(struct sockaddr, sa_family)) { + maddr->addrp = NULL; + maddr->port = 0; + maddr->len = 0; + return 0; + } + + /* + * its possibly to have sk->sk_family == PF_INET6 and + * addr->sa_family == AF_INET. sk_family is used for socket + * mediation, sa_family for when we have address ... + */ + switch (addr->sa_family) { + case AF_INET: + addr4 = (struct sockaddr_in *)addr; + if (addrlen < sizeof(struct sockaddr_in)) + return -EINVAL; + maddr->port = addr4->sin_port; + maddr->addrp = (char *)&addr4->sin_addr.s_addr; + maddr->len = 4; + break; + case AF_INET6: + addr6 = (struct sockaddr_in6 *)addr; + if (addrlen < SIN6_LEN_RFC2133) + return -EINVAL; + maddr->port = addr6->sin6_port; + maddr->addrp = (char *)&addr6->sin6_addr.s6_addr; + maddr->len = 16; + break; + default: + return -EAFNOSUPPORT; + } + /* per ip spec, && sk->sk_type == SOCK_RAW*/ + if (raw_port && addrtype != ADDR_REMOTE) + maddr->port = htons(raw_port); + if (ad) + set_ad_addr(ad, addr->sa_family, addrtype != ADDR_REMOTE, maddr); + + return 0; +} + +/* -ENOTCONN if not connected */ +static int map_sock_addr(struct socket *sock, enum addr_type addrtype, + struct stored_match_addr *maddr, + struct apparmor_audit_data *ad) +{ + /* do we need early bailout for !family ... */ + maddr->addrlen = sock->ops->getname(sock, (struct sockaddr *) &maddr->addr, addrtype != ADDR_REMOTE ? 0 : 1); + if (maddr->addrlen == -ENOTCONN) { + maddr->addrlen = 0; + return map_addr(NULL, 0, 0, addrtype, &maddr->maddr, ad); + } else if (maddr->addrlen < 0) + return maddr->addrlen; + return map_addr(&maddr->addr, maddr->addrlen, 0, addrtype, + &maddr->maddr, ad); +} + +/* TODO: combine with connect map addr */ +/* TODO: raw_port */ +static int bind_map_addr(const struct sock *sk, struct sockaddr *addr, + int addrlen, + struct match_addr *maddr, + struct apparmor_audit_data *ad) +{ + struct sockaddr_in *addr4 = NULL; + struct sockaddr_in6 *addr6 = NULL; + u16 family; + + AA_BUG(!sk); + AA_BUG(!addr); + AA_BUG(!maddr); + + if (addrlen < offsetofend(struct sockaddr, sa_family)) + return -EINVAL; + + maddr->addrtype = ADDR_LOCAL; + /* + * its possibly to have sk->sk_family == PF_INET6 and + * addr->sa_family == AF_INET. sk_family is used for socket + * mediation, sa_family for when we have address ... + */ + family = addr->sa_family; + switch (addr->sa_family) { + case AF_UNSPEC: + if (sk->sk_family == PF_INET6) { + /* Length check from inet6_bind_sk() */ + if (addrlen < SIN6_LEN_RFC2133) + return -EINVAL; + /* Family check from __inet6_bind() */ + return -EAFNOSUPPORT; + } + /* see __inet_bind(), we only want to allow + * AF_UNSPEC if the address is INADDR_ANY + */ + addr4 = (struct sockaddr_in *)addr; + if (addr4->sin_addr.s_addr != htonl(INADDR_ANY)) + return -EAFNOSUPPORT; + family = AF_INET; + fallthrough; + case AF_INET: + addr4 = (struct sockaddr_in *)addr; + if (addrlen < sizeof(struct sockaddr_in)) + return -EINVAL; + maddr->port = addr4->sin_port; + maddr->addrp = (char *)&addr4->sin_addr.s_addr; + maddr->len = 4; + break; + case AF_INET6: + addr6 = (struct sockaddr_in6 *)addr; + if (addrlen < SIN6_LEN_RFC2133) + return -EINVAL; + maddr->port = addr6->sin6_port; + maddr->addrp = (char *)&addr6->sin6_addr.s6_addr; + maddr->len = 16; + break; + default: + return -EAFNOSUPPORT; + } + + if (ad) + set_ad_addr(ad, family, true, maddr); + + return 0; +} + + +static inline int profile_sk_perm(struct aa_profile *profile, u32 request, + const struct sock *sk, + struct match_addr *maddr, + struct apparmor_audit_data *ad) +{ + AA_BUG(!profile); + AA_BUG(!sk); + + return aa_profile_af_sk_perm(profile, ad, request, sk); +} + +/* no kernel_t bailout */ +static int profile_create_perm(struct aa_profile *profile, int family, + int type, int protocol, + struct apparmor_audit_data *ad) +{ + AA_BUG(!profile); + + return aa_profile_af_perm(profile, ad, AA_MAY_CREATE, family, type, + protocol); +} + + +/* sendmsg/rcvmsg/connect */ +static int profile_remote_perm(struct aa_profile *profile, + const struct sock *sk, + u32 request, struct match_addr *raddr, + struct match_addr *laddr, + struct apparmor_audit_data *ad) +{ + AA_BUG(!profile); + AA_BUG(!sk); + AA_BUG(!raddr); + AA_BUG(!laddr); + AA_BUG(sk->sk_family != PF_INET && sk->sk_family != PF_INET6, + "family=%d", sk->sk_family); + + return aa_profile_af_sk_perm(profile, ad, request, sk); +} + +static int profile_bind_perm(struct aa_profile *profile, + const struct sock *sk, + struct match_addr *maddr, + struct apparmor_audit_data *ad) +{ + return aa_profile_af_sk_perm(profile, ad, AA_MAY_BIND, sk); + +} + +static int profile_listen_perm(struct aa_profile *profile, + const struct sock *sk, + struct match_addr *maddr, int backlog, + struct apparmor_audit_data *ad) +{ + AA_BUG(!profile); + AA_BUG(!sk); + AA_BUG(!maddr); + AA_BUG(sk->sk_family != PF_INET && sk->sk_family != PF_INET6, + "family=%d", sk->sk_family); + + return aa_profile_af_sk_perm(profile, ad, AA_MAY_LISTEN, sk); +} + +static inline int profile_accept_perm(struct aa_profile *profile, + const struct sock *sk, + struct match_addr *maddr, + const struct sock *newsk, + struct apparmor_audit_data *ad) +{ + AA_BUG(!profile); + AA_BUG(!sk); + /* AA_BUG(!newsk); newsk can be null here, since not using atm ... */ + AA_BUG(!maddr); + AA_BUG(sk->sk_family != PF_INET && sk->sk_family != PF_INET6, + "family=%d", sk->sk_family); + + return aa_profile_af_sk_perm(profile, ad, AA_MAY_ACCEPT, sk); +} + +/* getopt/setopt */ +static int profile_opt_perm(struct aa_profile *profile, u32 request, + const struct sock *sk, struct match_addr *maddr, + int level, int optname, + struct apparmor_audit_data *ad) +{ + AA_BUG(!profile); + AA_BUG(!sk); + AA_BUG(!maddr); + AA_BUG(sk->sk_family != PF_INET && sk->sk_family != PF_INET6, + "family=%d", sk->sk_family); + + return aa_profile_af_sk_perm(profile, ad, request, sk); +} + +/* ---------------------------------------------------------------------- */ + +// TODO: cleanup init to use recursion, so we can have N init fns, in 1 macro +// TODO: lift DEFINE_AUDIT out of macro into init fn??? + +/* no kernel_t bailout */ +#define label_sk_has_perm2(CRED, LABEL, SOCKSK, OP, REQUEST, PROFILE, AAD, XXXX, YYYY, CALLBACKFN) \ +({ \ + int __EERROR = 0; \ + if (label_mediates(LABEL, AA_CLASS_NET)) { \ + struct aa_profile *PROFILE; \ + DEFINE_AUDIT_SK(AAD, OP, CRED, SOCKSK); \ + (AAD).subj_cred = (CRED); \ + (AAD).request = (REQUEST); \ + __EERROR = (XXXX); \ + if (__EERROR == 0) { \ + __EERROR = (YYYY); \ + if (__EERROR == 0) { \ + __EERROR = fn_for_each(LABEL, PROFILE, \ + (CALLBACKFN)); \ + } \ + } \ + } \ + __EERROR; \ +}) + +/* no kernel_t bailout */ +#define label_sk_has_perm(CRED, LABEL, SOCKSK, OP, REQUEST, PROFILE, AAD, CALLBACKFN) \ + label_sk_has_perm2(CRED, LABEL, SOCKSK, OP, REQUEST, PROFILE, AAD, \ + 0, 0, CALLBACKFN) + +/* no kernel_t bailout */ +#define label_sk_has_perm1(CRED, LABEL, SOCKSK, OP, REQUEST, PROFILE, AAD, XXXX, CALLBACKFN) \ + label_sk_has_perm2(CRED, LABEL, SOCKSK, OP, REQUEST, PROFILE, AAD, \ + XXXX, 0, CALLBACKFN) + + +/* Early bailout for kernel_t - 2 init args before callback */ +#define sk_has_perm2(SOCKSK, OP, REQUEST, PROFILE, AAD, XXXXY, YYYYX, CALLBACKFN) \ +({ \ + struct aa_label *__label; \ + struct aa_sk_ctx *__ctx = aa_sock(SOCKSK); \ + int __ERROR = 0; \ + bool __needput; \ + if (rcu_access_pointer(__ctx->label) != kernel_t) { \ + \ + __label = begin_current_label_crit_section(&__needput); \ + __ERROR = label_sk_has_perm2(current_cred(), __label, SOCKSK, OP, REQUEST, PROFILE, AAD, XXXXY, YYYYX, CALLBACKFN); \ + end_current_label_crit_section(__label, __needput); \ + } \ + __ERROR; \ +}) + +/* Early bailout for kernel_t - no init args before callback */ +#define sk_has_perm(SOCKSK, OP, REQUEST, PROFILE, AAD, CALLBACKFN) \ + sk_has_perm2(SOCKSK, OP, REQUEST, PROFILE, AAD, 0, 0, CALLBACKFN) + + +/* Early bailout for kernel_t - 1 init arg before callback */ +#define sk_has_perm1(SOCKSK, OP, REQUEST, PROFILE, AAD, XXXXY, CALLBACKFN) \ + sk_has_perm2(SOCKSK, OP, REQUEST, PROFILE, AAD, XXXXY, 0, CALLBACKFN) + + + +/* no kernel_t early bailout */ +/* NOTE: already lifted label_mediates into lsm.c */ +int aa_inet_create_perm(struct aa_label *label, int family, int type, + int protocol) +{ + struct aa_profile *profile; + int error = 0; + DEFINE_AUDIT_NET(ad, OP_CREATE, current_cred(), NULL, family, type, + protocol); + + ad.subj_cred = current_cred(); + set_ad_create(&ad, family, type, protocol); + error = fn_for_each(label, profile, + profile_create_perm(profile, family, type, + protocol, &ad)); + + return error; +} + +int aa_inet_bind_perm(struct socket *sock, struct sockaddr *addr, + int addrlen) +{ + struct match_addr maddr; + + return sk_has_perm1(sock->sk, OP_BIND, AA_MAY_BIND, profile, ad, + bind_map_addr(sock->sk, addr, addrlen, &maddr, + &ad), + profile_bind_perm(profile, sock->sk, &maddr, &ad)); +} + +int aa_inet_connect_perm(struct socket *sock, struct sockaddr *addr, + int addrlen) +{ + struct stored_match_addr laddr; + struct match_addr raddr; + + /* disconnect socket */ + if (addrlen < offsetofend(struct sockaddr, sa_family)) + return -EINVAL; + if (addr->sa_family == AF_UNSPEC) + return 0; + + /* do we need early bailout for !family ... */ + return sk_has_perm2(sock->sk, OP_CONNECT, AA_MAY_CONNECT, profile, ad, + map_sock_addr(sock, ADDR_LOCAL, &laddr, &ad), + map_addr(addr, addrlen, 0, ADDR_REMOTE, &raddr, + &ad), + profile_remote_perm(profile, sock->sk, + AA_MAY_CONNECT, &raddr, + &laddr.maddr, &ad)); +} + +int aa_inet_listen_perm(struct socket *sock, int backlog) +{ + struct stored_match_addr maddr; + + /* do we need early bailout for !family ... */ + return sk_has_perm1(sock->sk, OP_LISTEN, AA_MAY_LISTEN, profile, ad, + map_sock_addr(sock, ADDR_LOCAL, &maddr, &ad), + profile_listen_perm(profile, sock->sk, &maddr.maddr, + backlog, &ad)); +} + +/* ability of sock to connect, not peer address binding */ +int aa_inet_accept_perm(struct socket *sock, struct socket *newsock) +{ + struct stored_match_addr maddr; + int error; + + error = sk_has_perm1(sock->sk, OP_ACCEPT, AA_MAY_ACCEPT, profile, ad, + map_sock_addr(sock, ADDR_LOCAL, &maddr, &ad), + profile_accept_perm(profile, sock->sk, + &maddr.maddr, + newsock->sk, &ad)); + + /* selinux updates inode - need to investigate this more */ + return error; +} + +/* sendmsg, recvmsg. */ +int aa_inet_msg_perm(const char *op, u32 request, struct socket *sock, + struct msghdr *msg, int size) +{ + struct stored_match_addr laddr; + struct match_addr raddr; + + /* do we need early bailout for !family ... */ + return sk_has_perm2(sock->sk, op, request, profile, ad, + map_sock_addr(sock, ADDR_LOCAL, &laddr, &ad), + map_addr(msg->msg_name, msg->msg_namelen, 0, + ADDR_REMOTE, &raddr, &ad), + profile_remote_perm(profile, sock->sk, request, + &raddr, &laddr.maddr, &ad)); +} + +/* getopt, setopt */ +int aa_inet_opt_perm(const char *op, u32 request, struct socket *sock, + int level, int optname) +{ + struct stored_match_addr maddr; + + return sk_has_perm1(sock->sk, op, request, profile, ad, + map_sock_addr(sock, ADDR_LOCAL, &maddr, &ad), + profile_opt_perm(profile, request, sock->sk, + &maddr.maddr, level, optname, &ad)); +} + +static int inet_label_sock_perm(const struct cred *cred, struct aa_label *label, + const char *op, u32 request, + struct socket *sock) +{ + struct stored_match_addr maddr; + + return label_sk_has_perm1(cred, label, sock->sk, op, request, profile, + ad, + map_sock_addr(sock, ADDR_LOCAL, &maddr, &ad), + profile_sk_perm(profile, request, sock->sk, + &maddr.maddr, &ad)); +} + +/* revalidation, get/set attr/getsockname/peername */ +int aa_inet_sock_perm(const char *op, u32 request, struct socket *sock) +{ + struct aa_sk_ctx *ctx = aa_sock(sock->sk); + struct aa_label *label; + bool needput; + int error; + + if (rcu_access_pointer(ctx->label) == kernel_t) + return 0; + + label = begin_current_label_crit_section(&needput); + error = inet_label_sock_perm(current_cred(), label, op, request, sock); + end_current_label_crit_section(label, needput); + + return error; +} + +int aa_inet_file_perm(const struct cred *subj_cred, struct aa_label *label, + const char *op, u32 request, struct socket *sock) +{ + u32 sk_req = request & ~NET_PEER_MASK; + struct stored_match_addr laddr; + const struct sock *sk = sock->sk; + int error = 0; + + AA_BUG(!label); + AA_BUG(!sock); + AA_BUG(!sock->sk); + AA_BUG(sk->sk_family != PF_INET && sk->sk_family != PF_INET6, + "family=%d", sk->sk_family); + + /* access to the local sock */ + error = label_sk_has_perm1(subj_cred, label, sock->sk, op, request, + profile, ad, + map_sock_addr(sock, ADDR_LOCAL, &laddr, &ad), + profile_sk_perm(profile, sk_req, sock->sk, &laddr.maddr, + &ad)); + + if (!error) { + struct stored_match_addr raddr; + + /* TODO: have ad here: instead of in CB so we do have to redo */ + error = map_sock_addr(sock, ADDR_REMOTE, &raddr, NULL); + if (!error && raddr.maddr.addrp) { + error = label_sk_has_perm1(subj_cred, label, sock->sk, + op, request, profile, ad, + set_ad_addr(&ad, raddr.addr.sa_family, + false, &raddr.maddr), + profile_remote_perm(profile, sock->sk, + request, + &raddr.maddr, + &laddr.maddr, &ad)); + } + } + + return error; +} diff --git a/security/apparmor/af_unix.c b/security/apparmor/af_unix.c index b9b22edae202..b908e744818c 100644 --- a/security/apparmor/af_unix.c +++ b/security/apparmor/af_unix.c @@ -20,6 +20,7 @@ #include "include/apparmor.h" #include "include/file.h" #include "include/label.h" +#include "include/net.h" #include "include/path.h" #include "include/policy.h" #include "include/cred.h" @@ -66,7 +67,7 @@ static int unix_fs_perm(const char *op, u32 mask, const struct cred *subj_cred, #define SHUTDOWN_ADDR "\x03" /* path addr is shutdown and cleared */ #define FS_ADDR "/" /* path addr in fs */ -static aa_state_t match_addr(struct aa_dfa *dfa, aa_state_t state, +static aa_state_t match_addr(const struct aa_dfa *dfa, aa_state_t state, struct sockaddr_un *addr, int addrlen) { if (addr) @@ -172,7 +173,7 @@ static aa_state_t match_to_peer(struct aa_policydb *policy, aa_state_t state, static aa_state_t match_label(struct aa_profile *profile, struct aa_ruleset *rule, aa_state_t state, u32 request, struct aa_profile *peer, - struct aa_perms *p, + const struct aa_perms *p, struct apparmor_audit_data *ad) { AA_BUG(!profile); @@ -208,7 +209,7 @@ static int profile_create_perm(struct aa_profile *profile, int family, AA_BUG(!profile); AA_BUG(profile_unconfined(profile)); - state = RULE_MEDIATES_v9NET(rules); + state = RULE_MEDIATES_UNIX(rules); if (state) { state = aa_match_to_prot(rules->policy, state, AA_MAY_CREATE, PF_UNIX, type, protocol, NULL, @@ -234,7 +235,7 @@ static int profile_sk_perm(struct aa_profile *profile, AA_BUG(!sk); AA_BUG(profile_unconfined(profile)); - state = RULE_MEDIATES_v9NET(rules); + state = RULE_MEDIATES_UNIX(rules); if (state) { if (is_unix_fs(sk)) return unix_fs_perm(ad->op, request, ad->subj_cred, @@ -263,7 +264,7 @@ static int profile_bind_perm(struct aa_profile *profile, struct sock *sk, AA_BUG(!ad); AA_BUG(profile_unconfined(profile)); - state = RULE_MEDIATES_v9NET(rules); + state = RULE_MEDIATES_UNIX(rules); if (state) { if (is_unix_addr_fs(ad->net.addr, ad->net.addrlen)) /* under v7-9 fs hook handles bind */ @@ -294,7 +295,7 @@ static int profile_listen_perm(struct aa_profile *profile, struct sock *sk, AA_BUG(!ad); AA_BUG(profile_unconfined(profile)); - state = RULE_MEDIATES_v9NET(rules); + state = RULE_MEDIATES_UNIX(rules); if (state) { __be16 b = cpu_to_be16(backlog); @@ -331,7 +332,7 @@ static int profile_accept_perm(struct aa_profile *profile, AA_BUG(!ad); AA_BUG(profile_unconfined(profile)); - state = RULE_MEDIATES_v9NET(rules); + state = RULE_MEDIATES_UNIX(rules); if (state) { if (is_unix_fs(sk)) return unix_fs_perm(ad->op, AA_MAY_ACCEPT, @@ -361,7 +362,7 @@ static int profile_opt_perm(struct aa_profile *profile, u32 request, AA_BUG(!ad); AA_BUG(profile_unconfined(profile)); - state = RULE_MEDIATES_v9NET(rules); + state = RULE_MEDIATES_UNIX(rules); if (state) { __be16 b = cpu_to_be16(optname); if (is_unix_fs(sk)) @@ -402,7 +403,7 @@ static int profile_peer_perm(struct aa_profile *profile, u32 request, AA_BUG(!peer_label); AA_BUG(!ad); - state = RULE_MEDIATES_v9NET(rules); + state = RULE_MEDIATES_UNIX(rules); if (state) { struct aa_profile *peerp; @@ -462,13 +463,14 @@ static int aa_unix_label_sk_perm(const struct cred *subj_cred, int aa_unix_sock_perm(const char *op, u32 request, struct socket *sock) { struct aa_label *label; + bool needput; int error; - label = begin_current_label_crit_section(); + label = begin_current_label_crit_section(&needput); error = aa_unix_label_sk_perm(current_cred(), label, op, request, sock->sk, is_unix_fs(sock->sk) ? &unix_sk(sock->sk)->path : NULL); - end_current_label_crit_section(label); + end_current_label_crit_section(label, needput); return error; } @@ -489,13 +491,14 @@ int aa_unix_bind_perm(struct socket *sock, struct sockaddr *addr, { struct aa_profile *profile; struct aa_label *label; + bool needput; int error = 0; error = valid_addr(addr, addrlen); if (error) return error; - label = begin_current_label_crit_section(); + label = begin_current_label_crit_section(&needput); /* fs bind is handled by mknod */ if (!unconfined(label)) { DEFINE_AUDIT_SK(ad, OP_BIND, current_cred(), sock->sk); @@ -506,7 +509,7 @@ int aa_unix_bind_perm(struct socket *sock, struct sockaddr *addr, error = fn_for_each_confined(label, profile, profile_bind_perm(profile, sock->sk, &ad)); } - end_current_label_crit_section(label); + end_current_label_crit_section(label, needput); return error; } @@ -528,9 +531,10 @@ int aa_unix_listen_perm(struct socket *sock, int backlog) { struct aa_profile *profile; struct aa_label *label; + bool needput; int error = 0; - label = begin_current_label_crit_section(); + label = begin_current_label_crit_section(&needput); if (!unconfined(label)) { DEFINE_AUDIT_SK(ad, OP_LISTEN, current_cred(), sock->sk); @@ -538,7 +542,7 @@ int aa_unix_listen_perm(struct socket *sock, int backlog) profile_listen_perm(profile, sock->sk, backlog, &ad)); } - end_current_label_crit_section(label); + end_current_label_crit_section(label, needput); return error; } @@ -549,16 +553,17 @@ int aa_unix_accept_perm(struct socket *sock, struct socket *newsock) { struct aa_profile *profile; struct aa_label *label; + bool needput; int error = 0; - label = begin_current_label_crit_section(); + label = begin_current_label_crit_section(&needput); if (!unconfined(label)) { DEFINE_AUDIT_SK(ad, OP_ACCEPT, current_cred(), sock->sk); error = fn_for_each_confined(label, profile, profile_accept_perm(profile, sock->sk, &ad)); } - end_current_label_crit_section(label); + end_current_label_crit_section(label, needput); return error; } @@ -582,9 +587,10 @@ int aa_unix_opt_perm(const char *op, u32 request, struct socket *sock, { struct aa_profile *profile; struct aa_label *label; + bool needput; int error = 0; - label = begin_current_label_crit_section(); + label = begin_current_label_crit_section(&needput); if (!unconfined(label)) { DEFINE_AUDIT_SK(ad, op, current_cred(), sock->sk); @@ -592,7 +598,7 @@ int aa_unix_opt_perm(const char *op, u32 request, struct socket *sock, profile_opt_perm(profile, request, sock->sk, optname, &ad)); } - end_current_label_crit_section(label); + end_current_label_crit_section(label, needput); return error; } diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c index 56155d7d5b2f..5b42140e12e8 100644 --- a/security/apparmor/apparmorfs.c +++ b/security/apparmor/apparmorfs.c @@ -23,6 +23,7 @@ #include <linux/fs_context.h> #include <linux/poll.h> #include <linux/zstd.h> +#include <linux/string.h> #include <uapi/linux/major.h> #include <uapi/linux/magic.h> @@ -33,6 +34,7 @@ #include "include/crypto.h" #include "include/ipc.h" #include "include/label.h" +#include "include/net.h" #include "include/lib.h" #include "include/policy.h" #include "include/policy_ns.h" @@ -482,6 +484,126 @@ static struct aa_loaddata *aa_simple_write_to_buffer(const char __user *userbuf, return data; } +#if defined(CONFIG_SECURITY_APPARMOR_COMPRESSED_POLICY) || \ + defined(CONFIG_SECURITY_APPARMOR_EXPORT_BINARY) +static int decompress_zstd(char *src, size_t slen, char *dst, size_t dlen) +{ + if (slen < dlen) { + const size_t wksp_len = zstd_dctx_workspace_bound(); + zstd_dctx *ctx; + void *wksp; + size_t out_len; + int ret = 0; + + wksp = kvzalloc(wksp_len, GFP_KERNEL); + if (!wksp) { + ret = -ENOMEM; + goto cleanup; + } + ctx = zstd_init_dctx(wksp, wksp_len); + if (ctx == NULL) { + ret = -ENOMEM; + goto cleanup; + } + out_len = zstd_decompress_dctx(ctx, dst, dlen, src, slen); + if (zstd_is_error(out_len)) { + ret = -EINVAL; + goto cleanup; + } +cleanup: + kvfree(wksp); + return ret; + } + + if (dlen < slen) + return -EINVAL; + memcpy(dst, src, slen); + return 0; +} +#endif + + +#ifdef CONFIG_SECURITY_APPARMOR_COMPRESSED_POLICY +/** + * aa_get_data_from_compressed - common routine for getting compressed policy + * from user and get both compressed and uncompressed version. + * @userbuf: user buffer to copy data from (NOT NULL) + * @buffer_size: size of user buffer + * @pos: position write is at in the file (NOT NULL) + * @compressed_data: Ptr on compressed data. *compressed_data is allocated there + * + * Returns: kernel buffer containing copy of user buffer data or an + * ERR_PTR on failure. + */ +static struct aa_loaddata *aa_get_data_from_compressed(const char __user *userbuf, + size_t buffer_size, + loff_t *pos, + char **compressed_data) +{ + struct aa_loaddata *data; + zstd_frame_header header; + int error; + + if (!userbuf || !pos) + return ERR_PTR(-EINVAL); + if (*pos) + return ERR_PTR(-ESPIPE); + + *compressed_data = kvmalloc(buffer_size, GFP_KERNEL); + if (!*compressed_data) + return ERR_PTR(-ENOMEM); + if (copy_from_user(*compressed_data, userbuf, buffer_size)) { + error = -EFAULT; + goto fail; + } + error = zstd_get_frame_header(&header, *compressed_data, buffer_size); + if (error || header.frameContentSize == ZSTD_CONTENTSIZE_UNKNOWN || + header.frameContentSize == ZSTD_CONTENTSIZE_ERROR) { + error = -EINVAL; + goto fail; + } + + data = aa_loaddata_alloc(header.frameContentSize); + if (IS_ERR(data)) { + error = PTR_ERR(data); + goto fail; + } + + // We then decompress the data + error = decompress_zstd(*compressed_data, buffer_size, data->data, + header.frameContentSize); + if (error) + goto fail_decompress; + + data->size = header.frameContentSize; + return data; + +fail_decompress: + aa_put_i_loaddata(data); +fail: + kvfree(*compressed_data); + return ERR_PTR(error); + +} +#else +static struct aa_loaddata *aa_get_data_from_compressed(const char __user *userbuf __always_unused, + size_t buffer_size __always_unused, + loff_t *pos __always_unused, + char **compressed_data __always_unused) +{ + return ERR_PTR(-EINVAL); +} +#endif /* CONFIG_SECURITY_APPARMOR_COMPRESSED_POLICY */ + +struct aa_user_hdr { + uint8_t version; + uint8_t compress_level; + uint8_t padding[6]; /* force 8-byte alignment */ +} __packed __aligned(8); + +#define aa_hdr_magic "\x04\x08\x00\x76\x65\x72\x73\x69\x6f\x6e\x00\x02" +#define aa_hdr_magic_size 12 + static ssize_t policy_update(u32 mask, const char __user *buf, size_t size, loff_t *pos, struct aa_ns *ns, const struct cred *ocred) @@ -489,8 +611,13 @@ static ssize_t policy_update(u32 mask, const char __user *buf, size_t size, struct aa_loaddata *data; struct aa_label *label; ssize_t error; + char *compressed_data = NULL; + __le32 magic_le; + bool is_compressed; + u8 aahdr[aa_hdr_magic_size]; + bool needput; - label = begin_current_label_crit_section(); + label = begin_current_label_crit_section(&needput); /* high level check about policy management - fine grained in * below after unpack @@ -499,17 +626,59 @@ static ssize_t policy_update(u32 mask, const char __user *buf, size_t size, if (error) goto end_section; - data = aa_simple_write_to_buffer(buf, size, size, pos); - error = PTR_ERR(data); + /* If the policy is userspace compressed we start by decompressing it + * to make the required checks (computing hash, verifying profile, ...) + * + * Getting a userspace-compressed version then decompressing it in the + * kernel actually makes sense since zstd decompression is ~3.5x faster + * than compression. This also allow to increase the compression level. + */ + + if (size >= sizeof(__le32) && + !copy_from_user(&magic_le, buf, sizeof(magic_le)) && + le32_to_cpu(magic_le) == ZSTD_MAGICNUMBER) { + is_compressed = true; + } else if (size >= sizeof(struct aa_user_hdr) + sizeof(__le32) && + !copy_from_user(&magic_le, + buf + sizeof(struct aa_user_hdr), + sizeof(magic_le)) && + le32_to_cpu(magic_le) == ZSTD_MAGICNUMBER) { + is_compressed = true; + /* skip the userspace header if present */ + buf += sizeof(struct aa_user_hdr); + size -= sizeof(struct aa_user_hdr); + } else if (size >= sizeof(struct aa_user_hdr) + + aa_hdr_magic_size && + !copy_from_user(&aahdr, + buf + sizeof(struct aa_user_hdr), + aa_hdr_magic_size) && + memcmp(&aahdr, aa_hdr_magic, aa_hdr_magic_size) == 0) { + /* uncompressed blob with user hdr */ + buf += sizeof(struct aa_user_hdr); + size -= sizeof(struct aa_user_hdr); + is_compressed = false; + } else { + is_compressed = false; + } + if (is_compressed) { + + data = aa_get_data_from_compressed(buf, size, pos, &compressed_data); + error = PTR_ERR(data); + } else { + data = aa_simple_write_to_buffer(buf, size, size, pos); + error = PTR_ERR(data); + } + if (!IS_ERR(data)) { - error = aa_replace_profiles(ns, label, mask, data); + error = aa_replace_profiles(ns, label, mask, data, + compressed_data, size); /* put pcount, which will put count and free if no * profiles referencing it. */ aa_put_profile_loaddata(data); } end_section: - end_current_label_crit_section(label); + end_current_label_crit_section(label, needput); return error; } @@ -549,6 +718,7 @@ static const struct file_operations aa_fs_profile_replace = { .llseek = default_llseek, }; + /* .remove file hook fn to remove loaded policy */ static ssize_t profile_remove(struct file *f, const char __user *buf, size_t size, loff_t *pos) @@ -557,8 +727,9 @@ static ssize_t profile_remove(struct file *f, const char __user *buf, struct aa_label *label; ssize_t error; struct aa_ns *ns = get_ns_common_ref(f->f_inode->i_private); + bool needput; - label = begin_current_label_crit_section(); + label = begin_current_label_crit_section(&needput); /* high level check about policy management - fine grained in * below after unpack */ @@ -580,7 +751,7 @@ static ssize_t profile_remove(struct file *f, const char __user *buf, aa_put_profile_loaddata(data); } out: - end_current_label_crit_section(label); + end_current_label_crit_section(label, needput); aa_put_ns(ns); return error; } @@ -685,7 +856,8 @@ static const struct file_operations aa_fs_ns_revision_fops = { .release = ns_revision_release, }; -static void profile_query_cb(struct aa_profile *profile, struct aa_perms *perms, +static void profile_query_cb(const struct aa_profile *profile, + struct aa_perms *perms, const char *match_str, size_t match_len) { struct aa_ruleset *rules = profile->label.rules[0]; @@ -755,6 +927,7 @@ static ssize_t query_data(char *buf, size_t buf_len, struct aa_data *data; u32 bytes, blocks; __le32 outle32; + bool needput; if (!query_len) return -EINVAL; /* need a query */ @@ -768,9 +941,9 @@ static ssize_t query_data(char *buf, size_t buf_len, if (buf_len < sizeof(bytes) + sizeof(blocks)) return -EINVAL; /* not enough space */ - curr = begin_current_label_crit_section(); + curr = begin_current_label_crit_section(&needput); label = aa_label_parse(curr, query, GFP_KERNEL, false, false); - end_current_label_crit_section(curr); + end_current_label_crit_section(curr, needput); if (IS_ERR(label)) return PTR_ERR(label); @@ -846,6 +1019,7 @@ static ssize_t query_label(char *buf, size_t buf_len, size_t label_name_len, match_len; struct aa_perms perms; struct label_it i; + bool needput; if (!query_len) return -EINVAL; @@ -864,9 +1038,9 @@ static ssize_t query_label(char *buf, size_t buf_len, match_str = label_name + label_name_len + 1; match_len = query_len - label_name_len - 1; - curr = begin_current_label_crit_section(); + curr = begin_current_label_crit_section(&needput); label = aa_label_parse(curr, label_name, GFP_KERNEL, false, false); - end_current_label_crit_section(curr); + end_current_label_crit_section(curr, needput); if (IS_ERR(label)) return PTR_ERR(label); @@ -1234,10 +1408,11 @@ static const struct file_operations seq_ns_ ##NAME ##_fops = { \ static int seq_ns_stacked_show(struct seq_file *seq, void *v) { struct aa_label *label; + bool needput; - label = begin_current_label_crit_section(); + label = begin_current_label_crit_section(&needput); seq_printf(seq, "%s\n", str_yes_no(label->size > 1)); - end_current_label_crit_section(label); + end_current_label_crit_section(label, needput); return 0; } @@ -1248,8 +1423,9 @@ static int seq_ns_nsstacked_show(struct seq_file *seq, void *v) struct aa_profile *profile; struct label_it it; int count = 1; + bool needput; - label = begin_current_label_crit_section(); + label = begin_current_label_crit_section(&needput); if (label->size > 1) { label_for_each(it, label, profile) @@ -1260,7 +1436,7 @@ static int seq_ns_nsstacked_show(struct seq_file *seq, void *v) } seq_printf(seq, "%s\n", str_yes_no(count > 1)); - end_current_label_crit_section(label); + end_current_label_crit_section(label, needput); return 0; } @@ -1268,23 +1444,32 @@ static int seq_ns_nsstacked_show(struct seq_file *seq, void *v) static int seq_ns_level_show(struct seq_file *seq, void *v) { struct aa_label *label; + bool needput; - label = begin_current_label_crit_section(); + label = begin_current_label_crit_section(&needput); seq_printf(seq, "%d\n", labels_ns(label)->level); - end_current_label_crit_section(label); + end_current_label_crit_section(label, needput); return 0; } static int seq_ns_name_show(struct seq_file *seq, void *v) { - struct aa_label *label = begin_current_label_crit_section(); + bool needput; + struct aa_label *label = begin_current_label_crit_section(&needput); + seq_printf(seq, "%s\n", labels_ns(label)->base.name); - end_current_label_crit_section(label); + end_current_label_crit_section(label, needput); return 0; } +SEQ_NS_FOPS(stacked); +SEQ_NS_FOPS(nsstacked); +SEQ_NS_FOPS(level); +SEQ_NS_FOPS(name); + +#ifdef CONFIG_SECURITY_APPARMOR_EXPORT_BINARY static int seq_ns_compress_min_show(struct seq_file *seq, void *v) { seq_printf(seq, "%d\n", AA_MIN_CLEVEL); @@ -1297,16 +1482,11 @@ static int seq_ns_compress_max_show(struct seq_file *seq, void *v) return 0; } -SEQ_NS_FOPS(stacked); -SEQ_NS_FOPS(nsstacked); -SEQ_NS_FOPS(level); -SEQ_NS_FOPS(name); SEQ_NS_FOPS(compress_min); SEQ_NS_FOPS(compress_max); /* policy/raw_data/ * file ops */ -#ifdef CONFIG_SECURITY_APPARMOR_EXPORT_BINARY #define SEQ_RAWDATA_FOPS(NAME) \ static int seq_rawdata_ ##NAME ##_open(struct inode *inode, struct file *file)\ { \ @@ -1396,41 +1576,6 @@ SEQ_RAWDATA_FOPS(revision); SEQ_RAWDATA_FOPS(hash); SEQ_RAWDATA_FOPS(compressed_size); -static int decompress_zstd(char *src, size_t slen, char *dst, size_t dlen) -{ - if (slen < dlen) { - const size_t wksp_len = zstd_dctx_workspace_bound(); - zstd_dctx *ctx; - void *wksp; - size_t out_len; - int ret = 0; - - wksp = kvzalloc(wksp_len, GFP_KERNEL); - if (!wksp) { - ret = -ENOMEM; - goto cleanup; - } - ctx = zstd_init_dctx(wksp, wksp_len); - if (ctx == NULL) { - ret = -ENOMEM; - goto cleanup; - } - out_len = zstd_decompress_dctx(ctx, dst, dlen, src, slen); - if (zstd_is_error(out_len)) { - ret = -EINVAL; - goto cleanup; - } -cleanup: - kvfree(wksp); - return ret; - } - - if (dlen < slen) - return -EINVAL; - memcpy(dst, src, slen); - return 0; -} - static ssize_t rawdata_read(struct file *file, char __user *buf, size_t size, loff_t *ppos) { @@ -1935,11 +2080,12 @@ static struct dentry *ns_mkdir_op(struct mnt_idmap *idmap, struct inode *dir, /* TODO: improve permission check */ struct aa_label *label; int error; + bool needput; - label = begin_current_label_crit_section(); + label = begin_current_label_crit_section(&needput); error = aa_may_manage_policy(current_cred(), label, NULL, NULL, AA_MAY_LOAD_POLICY); - end_current_label_crit_section(label); + end_current_label_crit_section(label, needput); if (error) return ERR_PTR(error); @@ -1984,12 +2130,13 @@ static int ns_rmdir_op(struct inode *dir, struct dentry *dentry) struct aa_ns *ns, *parent; /* TODO: improve permission check */ struct aa_label *label; + bool needput; int error; - label = begin_current_label_crit_section(); + label = begin_current_label_crit_section(&needput); error = aa_may_manage_policy(current_cred(), label, NULL, NULL, AA_MAY_LOAD_POLICY); - end_current_label_crit_section(label); + end_current_label_crit_section(label, needput); if (error) return error; @@ -2483,6 +2630,10 @@ static struct aa_sfs_entry aa_sfs_entry_policy[] = { AA_SFS_FILE_STRING("permstable32", PERMS32STR), AA_SFS_FILE_U64("state32", 1), AA_SFS_DIR("unconfined_restrictions", aa_sfs_entry_unconfined), +#ifdef CONFIG_SECURITY_APPARMOR_COMPRESSED_POLICY + AA_SFS_FILE_BOOLEAN("compressed_load", 1), + AA_SFS_FILE_BOOLEAN("extended_policy_header", 1), +#endif { } }; @@ -2547,8 +2698,10 @@ static struct aa_sfs_entry aa_sfs_entry_apparmor[] = { AA_SFS_FILE_FOPS(".ns_level", 0444, &seq_ns_level_fops), AA_SFS_FILE_FOPS(".ns_name", 0444, &seq_ns_name_fops), AA_SFS_FILE_FOPS("profiles", 0444, &aa_sfs_profiles_fops), +#ifdef CONFIG_SECURITY_APPARMOR_EXPORT_BINARY AA_SFS_FILE_FOPS("raw_data_compression_level_min", 0444, &seq_ns_compress_min_fops), AA_SFS_FILE_FOPS("raw_data_compression_level_max", 0444, &seq_ns_compress_max_fops), +#endif AA_SFS_DIR("features", aa_sfs_entry_features), { } }; diff --git a/security/apparmor/audit.c b/security/apparmor/audit.c index 4a60b6fda75f..5752bb93aaaa 100644 --- a/security/apparmor/audit.c +++ b/security/apparmor/audit.c @@ -20,6 +20,7 @@ const char *const audit_mode_names[] = { "normal", "quiet_denied", + "quiet.allowed", "quiet", "noquiet", "all" @@ -52,7 +53,7 @@ static const char *const aa_class_names[] = { "unknown", "unknown", "net", - "unknown", + "netv9", "label", "posix_mqueue", "io_uring", @@ -67,7 +68,7 @@ static const char *const aa_class_names[] = { "unknown", "unknown", "unknown", - "unknown", + "netv9_packet", "X", "dbus", }; @@ -139,6 +140,17 @@ static void audit_pre(struct audit_buffer *ab, void *va) } } +int aa_select_audit_type(u32 denied, const struct aa_perms *perms) +{ + if (likely(!denied)) + return AUDIT_APPARMOR_AUDIT; + else if (denied & perms->kill) + return AUDIT_APPARMOR_KILL; + else if (denied == (denied & perms->complain)) + return AUDIT_APPARMOR_ALLOWED; + return AUDIT_APPARMOR_DENIED; +} + /** * aa_audit_msg - Log a message to the audit subsystem * @type: audit type for the message @@ -152,6 +164,28 @@ void aa_audit_msg(int type, struct apparmor_audit_data *ad, common_lsm_audit(&ad->common, audit_pre, cb); } +int aa_audit_perm_error(struct aa_label *label, u32 request, int error, + struct apparmor_audit_data *ad, + void (*cb)(struct audit_buffer *, void *)) +{ + int type = aa_select_audit_type(request, &nullperms); + + if (ad) { + struct aa_profile *profile; + struct label_it i; + + ad->request = request; + ad->denied = request; + ad->error = error; + label_for_each_confined(i, label, profile) { + ad->subj_label = &profile->label; + aa_audit_msg(type, ad, cb); + } + } + + return error; +} + /** * aa_audit - Log a profile based audit event to the audit subsystem * @type: audit type for the message @@ -192,7 +226,7 @@ int aa_audit(int type, struct aa_profile *profile, aa_audit_msg(type, ad, cb); if (ad->type == AUDIT_APPARMOR_KILL) - (void)send_sig_info(profile->signal, NULL, + send_sig_info(profile->signal, SEND_SIG_NOINFO, ad->common.type == LSM_AUDIT_DATA_TASK && ad->common.u.tsk ? ad->common.u.tsk : current); diff --git a/security/apparmor/capability.c b/security/apparmor/capability.c index b9ea6bc45c1a..2f3c6c84358d 100644 --- a/security/apparmor/capability.c +++ b/security/apparmor/capability.c @@ -191,7 +191,7 @@ int aa_capable(const struct cred *subj_cred, struct aa_label *label, return error; } -kernel_cap_t aa_profile_capget(struct aa_profile *profile) +kernel_cap_t aa_profile_capget(const struct aa_profile *profile) { struct aa_ruleset *rules = profile->label.rules[0]; aa_state_t state; diff --git a/security/apparmor/domain.c b/security/apparmor/domain.c index d6958eb00e30..af9e8431e3ea 100644 --- a/security/apparmor/domain.c +++ b/security/apparmor/domain.c @@ -24,6 +24,7 @@ #include "include/domain.h" #include "include/file.h" #include "include/ipc.h" +#include "include/lib.h" #include "include/match.h" #include "include/path.h" #include "include/policy.h" @@ -90,8 +91,8 @@ out: * If a subns profile is not to be matched should be prescreened with * visibility test. */ -static inline aa_state_t match_component(struct aa_profile *profile, - struct aa_profile *tp, +static inline aa_state_t match_component(const struct aa_profile *profile, + const struct aa_profile *tp, bool stack, aa_state_t state) { struct aa_ruleset *rules = profile->label.rules[0]; @@ -126,7 +127,7 @@ static inline aa_state_t match_component(struct aa_profile *profile, * @perms should be preinitialized with allperms OR a previous permission * check to be stacked. */ -static int label_compound_match(struct aa_profile *profile, +static int label_compound_match(const struct aa_profile *profile, struct aa_label *label, bool stack, aa_state_t state, bool inview, u32 request, struct aa_perms *perms) @@ -188,7 +189,7 @@ fail: * @perms should be preinitialized with allperms OR a previous permission * check to be stacked. */ -static int label_components_match(struct aa_profile *profile, +static int label_components_match(const struct aa_profile *profile, struct aa_label *label, bool stack, aa_state_t start, bool inview, u32 request, struct aa_perms *perms) @@ -252,7 +253,7 @@ fail: * * Returns: the state the match finished in, may be the none matching state */ -static int label_match(struct aa_profile *profile, struct aa_label *label, +static int label_match(const struct aa_profile *profile, struct aa_label *label, bool stack, aa_state_t state, bool inview, u32 request, struct aa_perms *perms) { @@ -286,7 +287,7 @@ static int label_match(struct aa_profile *profile, struct aa_label *label, * currently only matches full label A//&B//&C or individual components A, B, C * not arbitrary combinations. Eg. A//&B, C */ -static int change_profile_perms(struct aa_profile *profile, +static int change_profile_perms(const struct aa_profile *profile, struct aa_label *target, bool stack, u32 request, aa_state_t start, struct aa_perms *perms) @@ -303,28 +304,31 @@ static int change_profile_perms(struct aa_profile *profile, /** * aa_xattrs_match - check whether a file matches the xattrs defined in profile - * @bprm: binprm struct for the process to validate + * @path: path for file being matched (NOT NULL) * @profile: profile to match against (NOT NULL) * @state: state to start match in * * Returns: number of extended attributes that matched, or < 0 on error */ -static int aa_xattrs_match(const struct linux_binprm *bprm, - struct aa_profile *profile, aa_state_t state) +static int aa_xattrs_match(const struct path *path, + const struct aa_profile *profile, aa_state_t state) { + AA_BUG(!path); + AA_BUG(!profile); + int i; struct dentry *d; char *value = NULL; - struct aa_attachment *attach = &profile->attach; + const struct aa_attachment *attach = &profile->attach; int size, value_size = 0, ret = attach->xattr_count; - if (!bprm || !attach->xattr_count) + if (!attach->xattr_count) return 0; might_sleep(); /* transition from exec match to xattr set */ state = aa_dfa_outofband_transition(attach->xmatch->dfa, state); - d = bprm->file->f_path.dentry; + d = path->dentry; for (i = 0; i < attach->xattr_count; i++) { size = vfs_getxattr_alloc(&nop_mnt_idmap, d, attach->xattrs[i], @@ -372,7 +376,7 @@ out: /** * find_attach - do attachment search for unconfined processes - * @bprm: binprm structure of transitioning task + * @path: path of file in question (NOT NULL) * @ns: the current namespace (NOT NULL) * @head: profile list to walk (NOT NULL) * @name: to match against (NOT NULL) @@ -387,7 +391,7 @@ out: * * Returns: label or NULL if no match found */ -static struct aa_label *find_attach(const struct linux_binprm *bprm, +static struct aa_label *find_attach(const struct path *path, struct aa_ns *ns, struct list_head *head, const char *name, const char **info) { @@ -395,6 +399,7 @@ static struct aa_label *find_attach(const struct linux_binprm *bprm, bool conflict = false; struct aa_profile *profile, *candidate = NULL; + AA_BUG(!path); AA_BUG(!name); AA_BUG(!head); @@ -434,13 +439,13 @@ restart: if (count < candidate_len) continue; - if (bprm && attach->xattr_count) { + if (attach->xattr_count) { long rev = READ_ONCE(ns->revision); if (!aa_get_profile_not0(profile)) goto restart; rcu_read_unlock(); - ret = aa_xattrs_match(bprm, profile, + ret = aa_xattrs_match(path, profile, state); rcu_read_lock(); aa_put_profile(profile); @@ -556,7 +561,7 @@ struct aa_label *x_table_lookup(struct aa_profile *profile, u32 xindex, /** * x_to_label - get target label for a given xindex * @profile: current profile (NOT NULL) - * @bprm: binprm structure of transitioning task + * @path: path of file in question * @name: name to lookup (NOT NULL) * @xindex: index into x transition table * @lookupname: returns: name used in lookup if one was specified (NOT NULL) @@ -567,7 +572,7 @@ struct aa_label *x_table_lookup(struct aa_profile *profile, u32 xindex, * Returns: refcounted label or NULL if not found available */ static struct aa_label *x_to_label(struct aa_profile *profile, - const struct linux_binprm *bprm, + const struct path *path, const char *name, u32 xindex, const char **lookupname, const char **info) @@ -598,11 +603,11 @@ static struct aa_label *x_to_label(struct aa_profile *profile, case AA_X_NAME: if (xindex & AA_X_CHILD) /* released by caller */ - new = find_attach(bprm, ns, &profile->base.profiles, + new = find_attach(path, ns, &profile->base.profiles, name, info); else /* released by caller */ - new = find_attach(bprm, ns, &ns->base.profiles, + new = find_attach(path, ns, &ns->base.profiles, name, info); *lookupname = name; break; @@ -690,7 +695,7 @@ static struct aa_label *profile_transition(const struct cred *subj_cred, } if (profile_unconfined(profile)) { - new = find_attach(bprm, profile->ns, + new = find_attach(&bprm->file->f_path, profile->ns, &profile->ns->base.profiles, name, &info); /* info set -> something unusual that we should report * Currently this is only conflicting attachments, but other @@ -720,8 +725,8 @@ static struct aa_label *profile_transition(const struct cred *subj_cred, state = aa_str_perms(rules->file, state, name, cond, &perms); if (perms.allow & MAY_EXEC) { /* exec permission determine how to transition */ - new = x_to_label(profile, bprm, name, perms.xindex, &target, - &info); + new = x_to_label(profile, &bprm->file->f_path, name, + perms.xindex, &target, &info); if (new && new->proxy == profile->label.proxy && info) { /* Force audit on conflicting attachment fallback * Because perms is never used again after this audit @@ -874,6 +879,52 @@ static struct aa_label *label_merge_wrap(struct aa_label *a, struct aa_label *b, return label; } +static bool is_profile_priv_restricted_to_stack(const struct cred *subj_cred, + struct aa_profile *profile) +{ + if (profile_unconfined(profile) && profile == profile->ns->unconfined && + aa_unprivileged_unconfined_restricted && + /* cap_capable returns false (0) if true, hence true here means + * doesn't have capability and the stack will be restricted + */ + cap_capable(current_cred(), &init_user_ns, CAP_MAC_OVERRIDE, + CAP_OPT_NOAUDIT)) + return true; + return false; +} + +static const char *stack_msg = "change_profile unprivileged unconfined converted to stacking"; + +static struct aa_label *priv_restricted_transition(const struct cred *subj_cred, + struct aa_profile *profile, + const char *op, u32 request, + const char *name, + struct aa_label *transition, + gfp_t gfp) +{ + if (!is_profile_priv_restricted_to_stack(subj_cred, profile)) + return aa_get_newest_label(transition); + + /* transition allowed but only via stack */ + struct aa_label *target = label_merge_wrap(&profile->label, + transition, gfp); + if (IS_ERR_OR_NULL(target)) + return target; + + /* doing this here is less than optimal but good enough until the + * fs mediation rework lands + */ + struct aa_perms perms = { + .allow = request, + .audit = request, + }; + aa_audit_file(subj_cred, profile, &perms, op, + request, name, NULL, target, + subj_cred->euid, stack_msg, 0); + + return target; +} + static struct aa_label *handle_onexec(const struct cred *subj_cred, struct aa_label *label, struct aa_label *onexec, bool stack, @@ -902,7 +953,10 @@ static struct aa_label *handle_onexec(const struct cred *subj_cred, new = fn_label_build_in_scope(label, profile, GFP_KERNEL, stack ? label_merge_wrap(&profile->label, onexec, GFP_KERNEL) - : aa_get_newest_label(onexec), + : priv_restricted_transition(subj_cred, profile, + OP_CHANGE_ONEXEC, AA_MAY_ONEXEC, + bprm->filename, onexec, + GFP_KERNEL), profile_transition(subj_cred, profile, bprm, buffer, cond, unsafe)); AA_BUG(!new); @@ -1084,8 +1138,8 @@ static struct aa_label *build_change_hat(const struct cred *subj_cred, if (!hat) { error = -ENOENT; if (COMPLAIN_MODE(profile)) { - hat = aa_new_learning_profile(profile, true, name, - GFP_KERNEL); + hat = __aa_new_learning_profile(profile, true, name, + GFP_KERNEL); if (!hat) { info = "failed null profile create"; error = -ENOMEM; @@ -1123,6 +1177,7 @@ static struct aa_label *change_hat(const struct cred *subj_cred, bool sibling = false; const char *name, *info = NULL; int i, error; + bool needput = false; AA_BUG(!label); AA_BUG(!hats); @@ -1135,7 +1190,6 @@ static struct aa_label *change_hat(const struct cred *subj_cred, * the profiles and label, we can rely on the namespaces being live * and avoid incrementing their refcounts while grabbing the lock. */ - label = aa_get_label(label); ns = labels_ns(label); retry: @@ -1143,15 +1197,19 @@ retry: if (label_is_stale(label)) { new = aa_get_newest_label(label); new_ns = labels_ns(new); + + if (needput) + /* aa_put_label() is safe to call when under lock */ + aa_put_label(label); + label = new; + needput = true; + /* check if replaced with label in parent ns, and lock there */ if (new_ns != ns) { - aa_put_label(new); mutex_unlock(&ns->lock); ns = new_ns; - label = new; + /* retry will bottom out at the root of the tree */ goto retry; } - aa_put_label(label); - label = new; } if (PROFILE_IS_HAT(labels_profile(label))) @@ -1162,7 +1220,8 @@ retry: name = hats[i]; label_for_each_in_scope(it, labels_ns(label), label, profile) { if (sibling && PROFILE_IS_HAT(profile)) { - root = aa_get_profile(profile->parent); + root = aa_get_profile(rcu_dereference_protected(profile->parent, + mutex_is_locked(&ns->lock))); } else if (!sibling && !PROFILE_IS_HAT(profile)) { root = aa_get_profile(profile); } else { /* conflicting change type */ @@ -1223,6 +1282,8 @@ fail: } } mutex_unlock(&ns->lock); + if (needput) + aa_put_label(label); return ERR_PTR(error); build: @@ -1233,7 +1294,8 @@ build: mutex_unlock(&ns->lock); AA_BUG(!new); /* return new label or error ptr */ - + if (needput) + aa_put_label(label); return new; } @@ -1406,8 +1468,6 @@ static int change_profile_perms_wrapper(const char *op, const char *name, return error; } -static const char *stack_msg = "change_profile unprivileged unconfined converted to stacking"; - /** * aa_change_profile - perform a one-way profile transition * @fqname: name of profile may include namespace (NOT NULL) @@ -1467,28 +1527,6 @@ int aa_change_profile(const char *fqname, int flags) op = OP_CHANGE_PROFILE; } - /* This should move to a per profile test. Requires pushing build - * into callback - */ - if (!stack && unconfined(label) && - label == &labels_ns(label)->unconfined->label && - aa_unprivileged_unconfined_restricted && - /* TODO: refactor so this check is a fn */ - cap_capable(current_cred(), &init_user_ns, CAP_MAC_OVERRIDE, - CAP_OPT_NOAUDIT)) { - /* regardless of the request in this case apparmor - * stacks against unconfined so admin set policy can't be - * by-passed - */ - stack = true; - perms.audit = request; - (void) fn_for_each_in_scope(label, profile, - aa_audit_file(subj_cred, profile, &perms, op, - request, auditname, NULL, target, - GLOBAL_ROOT_UID, stack_msg, 0)); - perms.audit = 0; - } - if (*fqname == '&') { stack = true; /* don't have label_parse() do stacking */ @@ -1559,7 +1597,10 @@ check: /* stacking is always a subset, so only check the nonstack case */ if (!stack) { new = fn_label_build_in_scope(label, profile, GFP_KERNEL, - aa_get_label(target), + priv_restricted_transition(subj_cred, profile, + op, request, + auditname, target, + GFP_KERNEL), aa_get_label(&profile->label)); AA_BUG(!new); if (IS_ERR(new)) diff --git a/security/apparmor/file.c b/security/apparmor/file.c index c9d55fe1086f..3e74b613db32 100644 --- a/security/apparmor/file.c +++ b/security/apparmor/file.c @@ -93,11 +93,13 @@ static void file_audit_cb(struct audit_buffer *ab, void *va) * Returns: %0 or error on failure */ int aa_audit_file(const struct cred *subj_cred, - struct aa_profile *profile, struct aa_perms *perms, + struct aa_profile *profile, const struct aa_perms *perms, const char *op, u32 request, const char *name, const char *target, struct aa_label *tlabel, kuid_t ouid, const char *info, int error) { + u32 quiet = perms->quiet; + u32 complain = perms->complain; int type = AUDIT_APPARMOR_AUTO; DEFINE_AUDIT_DATA(ad, LSM_AUDIT_DATA_TASK, AA_CLASS_FILE, op); @@ -112,6 +114,8 @@ int aa_audit_file(const struct cred *subj_cred, ad.error = error; ad.common.u.tsk = NULL; + if (COMPLAIN_MODE(profile)) + complain |= ~(perms->allow | perms->deny); if (likely(!ad.error)) { u32 mask = perms->audit; @@ -132,11 +136,14 @@ int aa_audit_file(const struct cred *subj_cred, if (ad.request & perms->kill) type = AUDIT_APPARMOR_KILL; + if (AUDIT_MODE(profile) == AUDIT_QUIET_ALLOWED) + quiet |= complain | perms->allow; + /* quiet known rejects, assumes quiet and kill do not overlap */ - if ((ad.request & perms->quiet) && + if ((ad.request & quiet) && AUDIT_MODE(profile) != AUDIT_NOQUIET && AUDIT_MODE(profile) != AUDIT_ALL) - ad.request &= ~perms->quiet; + ad.request &= ~quiet; if (!ad.request) return ad.error; @@ -232,7 +239,7 @@ int __aa_path_perm(const char *op, const struct cred *subj_cred, int e = 0; if (profile_unconfined(profile) || - ((flags & PATH_SOCK_COND) && !RULE_MEDIATES_v9NET(rules))) + ((flags & PATH_SOCK_COND) && !RULE_MEDIATES_UNIX(rules))) return 0; aa_str_perms(rules->file, rules->file->start[AA_CLASS_FILE], name, cond, perms); diff --git a/security/apparmor/include/af_inet.h b/security/apparmor/include/af_inet.h new file mode 100644 index 000000000000..601ab44bdfe1 --- /dev/null +++ b/security/apparmor/include/af_inet.h @@ -0,0 +1,36 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * AppArmor security module + * + * This file contains AppArmor inet/inet6 fine grained mediation + * + * Copyright 2024 Canonical Ltd. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2 of the + * License. + */ +#ifndef __AA_AF_INET_H +#define __AA_AF_INET_H + +#include "label.h" + +int aa_inet_sock_perm(const char *op, u32 request, struct socket *sock); +int aa_inet_create_perm(struct aa_label *label, int family, int type, + int protocol); +int aa_inet_bind_perm(struct socket *sock, struct sockaddr *address, + int addrlen); +int aa_inet_connect_perm(struct socket *sock, struct sockaddr *address, + int addrlen); +int aa_inet_listen_perm(struct socket *sock, int backlog); +int aa_inet_accept_perm(struct socket *sock, struct socket *newsock); +int aa_inet_msg_perm(const char *op, u32 request, struct socket *sock, + struct msghdr *msg, int size); +int aa_inet_opt_perm(const char *op, u32 request, struct socket *sock, int level, + int optname); +int aa_inet_file_perm(const struct cred *subj_cred, + struct aa_label *label, const char *op, u32 request, + struct socket *sock); + +#endif /* __AA_AF_INET_H */ diff --git a/security/apparmor/include/apparmor.h b/security/apparmor/include/apparmor.h index cc6e3df1bc62..cb06b2f55d49 100644 --- a/security/apparmor/include/apparmor.h +++ b/security/apparmor/include/apparmor.h @@ -36,6 +36,7 @@ #define AA_CLASS_NS 21 #define AA_CLASS_IO_URING 22 +#define AA_CLASS_NETV9_SKB 30 #define AA_CLASS_X 31 #define AA_CLASS_DBUS 32 diff --git a/security/apparmor/include/apparmorfs.h b/security/apparmor/include/apparmorfs.h index 33243d11fd10..c6c8fcde728f 100644 --- a/security/apparmor/include/apparmorfs.h +++ b/security/apparmor/include/apparmorfs.h @@ -11,6 +11,9 @@ #ifndef __AA_APPARMORFS_H #define __AA_APPARMORFS_H +#include <linux/init.h> +#include <linux/types.h> + extern struct path aa_null; enum aa_sfs_type { diff --git a/security/apparmor/include/audit.h b/security/apparmor/include/audit.h index aa00b34404f9..987ed4441fd3 100644 --- a/security/apparmor/include/audit.h +++ b/security/apparmor/include/audit.h @@ -21,13 +21,14 @@ #include "label.h" extern const char *const audit_mode_names[]; -#define AUDIT_MAX_INDEX 5 enum audit_mode { AUDIT_NORMAL, /* follow normal auditing of accesses */ AUDIT_QUIET_DENIED, /* quiet all denied access messages */ + AUDIT_QUIET_ALLOWED, /* quiet all allowed access messages */ AUDIT_QUIET, /* quiet all messages */ AUDIT_NOQUIET, /* do not quiet audit messages */ - AUDIT_ALL /* audit all accesses */ + AUDIT_ALL, /* audit all accesses */ + AUDIT_MODE_NAMES_COUNT /* Must be last entry */ }; enum audit_type { @@ -183,6 +184,8 @@ struct apparmor_audit_data { .common.apparmor_audit_data = &NAME, \ }; +int aa_select_audit_type(u32 denied, const struct aa_perms *perms); + void aa_audit_msg(int type, struct apparmor_audit_data *ad, void (*cb) (struct audit_buffer *, void *)); int aa_audit(int type, struct aa_profile *profile, @@ -196,6 +199,9 @@ int aa_audit(int type, struct aa_profile *profile, (AD)->error; \ }) +int aa_audit_perm_error(struct aa_label *label, u32 request, int error, + struct apparmor_audit_data *ad, + void (*cb)(struct audit_buffer *, void *)); static inline int complain_error(int error) { diff --git a/security/apparmor/include/capability.h b/security/apparmor/include/capability.h index 1ddcec2d1160..89a9c75d8f44 100644 --- a/security/apparmor/include/capability.h +++ b/security/apparmor/include/capability.h @@ -11,6 +11,7 @@ #ifndef __AA_CAPABILITY_H #define __AA_CAPABILITY_H +#include <linux/capability.h> #include <linux/sched.h> #include "apparmorfs.h" @@ -36,7 +37,7 @@ struct aa_caps { extern struct aa_sfs_entry aa_sfs_entry_caps[]; -kernel_cap_t aa_profile_capget(struct aa_profile *profile); +kernel_cap_t aa_profile_capget(const struct aa_profile *profile); int aa_capable(const struct cred *subj_cred, struct aa_label *label, int cap, unsigned int opts); diff --git a/security/apparmor/include/cred.h b/security/apparmor/include/cred.h index 2b6098149b15..056e031b7b8a 100644 --- a/security/apparmor/include/cred.h +++ b/security/apparmor/include/cred.h @@ -177,12 +177,14 @@ static inline void __end_current_label_crit_section(struct aa_label *label, /** * end_current_label_crit_section - put a reference found with begin_current_label.. * @label: label reference to put + * @needput: output: bool set by __begin_current_label_crit_section * * Should only be used with a reference obtained with * begin_current_label_crit_section and never used in situations where the * task cred may be updated */ -static inline void end_current_label_crit_section(struct aa_label *label) +static inline void end_current_label_crit_section(struct aa_label *label, + bool needput) { if (label != aa_current_raw_label()) aa_put_label(label); @@ -208,28 +210,22 @@ static inline struct aa_label *__begin_current_label_crit_section(bool *needput) /** * begin_current_label_crit_section - current's confining label and update it + * @needput: store whether the label needs to be put when ending crit section * * Returns: up to date confining label or the ns unconfined label (NOT NULL) * - * Not safe to call inside locks - * * The returned reference must be put with end_current_label_crit_section() - * This must NOT be used if the task cred could be updated within the + * This should NOT be used if the task cred could be updated within the * critical section between begin_current_label_crit_section() .. * end_current_label_crit_section() */ -static inline struct aa_label *begin_current_label_crit_section(void) +static inline struct aa_label *begin_current_label_crit_section(bool *needput) { struct aa_label *label = aa_current_raw_label(); - might_sleep(); - - if (label_is_stale(label)) { - label = aa_get_newest_label(label); - if (aa_replace_current_label(label) == 0) - /* task cred will keep the reference */ - aa_put_label(label); - } + label = __begin_current_label_crit_section(needput); + if (*needput) + aa_schedule_stale_label_replacement(); return label; } diff --git a/security/apparmor/include/file.h b/security/apparmor/include/file.h index ef60f99bc5ae..1614c07fc53e 100644 --- a/security/apparmor/include/file.h +++ b/security/apparmor/include/file.h @@ -72,7 +72,7 @@ struct path_cond { #define COMBINED_PERM_MASK(X) ((X).allow | (X).audit | (X).quiet | (X).kill) int aa_audit_file(const struct cred *cred, - struct aa_profile *profile, struct aa_perms *perms, + struct aa_profile *profile, const struct aa_perms *perms, const char *op, u32 request, const char *name, const char *target, struct aa_label *tlabel, kuid_t ouid, const char *info, int error); diff --git a/security/apparmor/include/label.h b/security/apparmor/include/label.h index b5a722a47fd2..2c869b7dec97 100644 --- a/security/apparmor/include/label.h +++ b/security/apparmor/include/label.h @@ -23,7 +23,7 @@ struct aa_ruleset; #define LOCAL_VEC_ENTRIES 8 #define DEFINE_VEC(T, V) \ - struct aa_ ## T *(_ ## V ## _localtmp)[LOCAL_VEC_ENTRIES]; \ + struct aa_ ## T *(_ ## V ## _localtmp)[LOCAL_VEC_ENTRIES + 1]; \ struct aa_ ## T **(V) #define vec_setup(T, V, N, GFP) \ @@ -31,10 +31,10 @@ struct aa_ruleset; if ((N) <= LOCAL_VEC_ENTRIES) { \ typeof(N) i; \ (V) = (_ ## V ## _localtmp); \ - for (i = 0; i < (N); i++) \ + for (i = 0; i <= (N); i++) \ (V)[i] = NULL; \ } else \ - (V) = kzalloc(sizeof(struct aa_ ## T *) * (N), (GFP)); \ + (V) = kzalloc_objs(struct aa_ ## T *, (N) + 1, (GFP)); \ (V) ? 0 : -ENOMEM; \ }) @@ -165,7 +165,7 @@ do { \ #define labels_profile(X) ((X)->vec[(X)->size - 1]) -int aa_label_next_confined(struct aa_label *l, int i); +int aa_label_next_confined(const struct aa_label *l, int i); /* for each profile in a label */ #define label_for_each(I, L, P) \ @@ -246,12 +246,12 @@ int aa_label_next_confined(struct aa_label *l, int i); #define fn_for_each_not_in_set(L1, L2, P, FN) \ fn_for_each2_XXX((L1), (L2), P, FN, _not_in_set) -static inline bool label_mediates(struct aa_label *L, unsigned char C) +static inline bool label_mediates(const struct aa_label *L, unsigned char C) { return (L)->mediates & (((u64) 1) << (C)); } -static inline bool label_mediates_safe(struct aa_label *L, unsigned char C) +static inline bool label_mediates_safe(const struct aa_label *L, unsigned char C) { if (C > AA_CLASS_LAST) return false; @@ -268,11 +268,12 @@ void aa_label_kref(struct kref *kref); bool aa_label_init(struct aa_label *label, int size, gfp_t gfp); struct aa_label *aa_label_alloc(int size, struct aa_proxy *proxy, gfp_t gfp); -bool aa_label_is_subset(struct aa_label *set, struct aa_label *sub); -bool aa_label_is_unconfined_subset(struct aa_label *set, struct aa_label *sub); +bool aa_label_is_subset(const struct aa_label *set, const struct aa_label *sub); +bool aa_label_is_unconfined_subset(const struct aa_label *set, + const struct aa_label *sub); struct aa_profile *__aa_label_next_not_in_set(struct label_it *I, - struct aa_label *set, - struct aa_label *sub); + const struct aa_label *set, + const struct aa_label *sub); bool aa_label_remove(struct aa_label *label); struct aa_label *aa_label_insert(struct aa_labelset *ls, struct aa_label *l); bool aa_label_replace(struct aa_label *old, struct aa_label *new); @@ -280,8 +281,8 @@ bool aa_label_make_newest(struct aa_labelset *ls, struct aa_label *old, struct aa_label *new); struct aa_profile *aa_label_next_in_merge(struct label_it *I, - struct aa_label *a, - struct aa_label *b); + const struct aa_label *a, + const struct aa_label *b); struct aa_label *aa_label_find_merge(struct aa_label *a, struct aa_label *b); struct aa_label *aa_label_merge(struct aa_label *a, struct aa_label *b, gfp_t gfp); @@ -342,7 +343,7 @@ static inline const char *aa_label_str_split(const char *str) struct aa_perms; struct aa_ruleset; -int aa_label_match(struct aa_profile *profile, struct aa_ruleset *rules, +int aa_label_match(const struct aa_profile *profile, struct aa_ruleset *rules, struct aa_label *label, aa_state_t state, bool subns, u32 request, struct aa_perms *perms); @@ -462,8 +463,8 @@ static inline void aa_put_label(struct aa_label *l) } /* wrapper fn to indicate semantics of the check */ -static inline bool __aa_subj_label_is_cached(struct aa_label *subj_label, - struct aa_label *obj_label) +static inline bool __aa_subj_label_is_cached(const struct aa_label *subj_label, + const struct aa_label *obj_label) { return aa_label_is_subset(obj_label, subj_label); } diff --git a/security/apparmor/include/lib.h b/security/apparmor/include/lib.h index e3c8cb044a90..475dd71fbe40 100644 --- a/security/apparmor/include/lib.h +++ b/security/apparmor/include/lib.h @@ -136,7 +136,7 @@ static inline bool aa_strneq(const char *str, const char *sub, int len) * character which is not used in standard matching and is only * used to separate pairs. */ -static inline aa_state_t aa_dfa_null_transition(struct aa_dfa *dfa, +static inline aa_state_t aa_dfa_null_transition(const struct aa_dfa *dfa, aa_state_t start) { /* the null transition only needs the string's null terminator byte */ @@ -338,8 +338,8 @@ __do_cleanup: \ __new_ = (FN); \ } \ __done: \ - if (PTR_ERR(__new_)) \ - AA_DEBUG(DEBUG_LABEL, "label build failed\n"); \ + if (IS_ERR(__new_)) \ + AA_DEBUG(DEBUG_LABEL, "label build failed %ld\n", PTR_ERR(__new_)); \ (__new_); \ }) diff --git a/security/apparmor/include/match.h b/security/apparmor/include/match.h index 7accb1c39849..f7bd7855f1bd 100644 --- a/security/apparmor/include/match.h +++ b/security/apparmor/include/match.h @@ -125,16 +125,18 @@ static inline size_t table_size(size_t len, size_t el_size) #define aa_state_t unsigned int -struct aa_dfa *aa_dfa_unpack(void *blob, size_t size, int flags); -aa_state_t aa_dfa_match_len(struct aa_dfa *dfa, aa_state_t start, +struct aa_dfa *aa_dfa_unpack(const void *blob, size_t size, int flags); +aa_state_t aa_dfa_match_len(const struct aa_dfa *dfa, aa_state_t start, const char *str, int len); -aa_state_t aa_dfa_match(struct aa_dfa *dfa, aa_state_t start, +aa_state_t aa_dfa_match(const struct aa_dfa *dfa, aa_state_t start, const char *str); -aa_state_t aa_dfa_next(struct aa_dfa *dfa, aa_state_t state, const char c); -aa_state_t aa_dfa_outofband_transition(struct aa_dfa *dfa, aa_state_t state); -aa_state_t aa_dfa_match_until(struct aa_dfa *dfa, aa_state_t start, +aa_state_t aa_dfa_next(const struct aa_dfa *dfa, aa_state_t state, + const char c); +aa_state_t aa_dfa_outofband_transition(const struct aa_dfa *dfa, + aa_state_t state); +aa_state_t aa_dfa_match_until(const struct aa_dfa *dfa, aa_state_t start, const char *str, const char **retpos); -aa_state_t aa_dfa_matchn_until(struct aa_dfa *dfa, aa_state_t start, +aa_state_t aa_dfa_matchn_until(const struct aa_dfa *dfa, aa_state_t start, const char *str, int n, const char **retpos); void aa_dfa_free_kref(struct kref *kref); @@ -152,7 +154,7 @@ struct match_workbuf N = { \ .len = 0, \ } -aa_state_t aa_dfa_leftmatch(struct aa_dfa *dfa, aa_state_t start, +aa_state_t aa_dfa_leftmatch(const struct aa_dfa *dfa, aa_state_t start, const char *str, unsigned int *count); /** diff --git a/security/apparmor/include/net.h b/security/apparmor/include/net.h index 0d0b0ce42723..375341929cb6 100644 --- a/security/apparmor/include/net.h +++ b/security/apparmor/include/net.h @@ -86,7 +86,7 @@ extern struct aa_sfs_entry aa_sfs_entry_network[]; extern struct aa_sfs_entry aa_sfs_entry_networkv9[]; int aa_do_perms(struct aa_profile *profile, struct aa_policydb *policy, - aa_state_t state, u32 request, struct aa_perms *p, + aa_state_t state, u32 request, const struct aa_perms *p, struct apparmor_audit_data *ad); /* passing in state returned by XXX_mediates_AF() */ aa_state_t aa_match_to_prot(struct aa_policydb *policy, aa_state_t state, @@ -102,13 +102,14 @@ int aa_af_perm(const struct cred *subj_cred, struct aa_label *label, static inline int aa_profile_af_sk_perm(struct aa_profile *profile, struct apparmor_audit_data *ad, u32 request, - struct sock *sk) + const struct sock *sk) { return aa_profile_af_perm(profile, ad, request, sk->sk_family, sk->sk_type, sk->sk_protocol); } -int aa_sk_perm(const char *op, u32 request, struct sock *sk); - +int aa_sk_perm(const char *op, u32 request, const struct sock *sk); +int aa_label_sk_perm(const struct cred *subj_cred, struct aa_label *label, + const char *op, u32 request, const struct sock *sk); int aa_sock_file_perm(const struct cred *subj_cred, struct aa_label *label, const char *op, u32 request, struct file *file); diff --git a/security/apparmor/include/path.h b/security/apparmor/include/path.h index 8bb915d48dc7..250812a77e11 100644 --- a/security/apparmor/include/path.h +++ b/security/apparmor/include/path.h @@ -11,6 +11,9 @@ #ifndef __AA_PATH_H #define __AA_PATH_H +#include <linux/path.h> +#include <linux/types.h> + enum path_flags { PATH_IS_DIR = 0x1, /* path is a directory */ PATH_SOCK_COND = 0x2, diff --git a/security/apparmor/include/perms.h b/security/apparmor/include/perms.h index 37a3781b99a0..ee25eb8e78e4 100644 --- a/security/apparmor/include/perms.h +++ b/security/apparmor/include/perms.h @@ -96,8 +96,8 @@ struct aa_perms { #define AA_INDEX_NONE 0 #define ALL_PERMS_MASK 0xffffffff -extern struct aa_perms nullperms; -extern struct aa_perms allperms; +extern const struct aa_perms nullperms; +extern const struct aa_perms allperms; /** * aa_perms_accum_raw - accumulate perms with out masking off overlapping perms @@ -105,7 +105,7 @@ extern struct aa_perms allperms; * @addend: perms struct to add to @accum */ static inline void aa_perms_accum_raw(struct aa_perms *accum, - struct aa_perms *addend) + const struct aa_perms *addend) { accum->deny |= addend->deny; accum->allow &= addend->allow & ~addend->deny; @@ -132,7 +132,7 @@ static inline void aa_perms_accum_raw(struct aa_perms *accum, * @addend: perms struct to add to @accum */ static inline void aa_perms_accum(struct aa_perms *accum, - struct aa_perms *addend) + const struct aa_perms *addend) { accum->deny |= addend->deny; accum->allow &= addend->allow & ~accum->deny; @@ -206,14 +206,15 @@ void aa_audit_perm_names(struct audit_buffer *ab, const char * const *names, u32 mask); void aa_audit_perm_mask(struct audit_buffer *ab, u32 mask, const char *chrs, u32 chrsmask, const char * const *names, u32 namesmask); -void aa_apply_modes_to_perms(struct aa_profile *profile, +void aa_apply_modes_to_perms(const struct aa_profile *profile, struct aa_perms *perms); -void aa_perms_accum(struct aa_perms *accum, struct aa_perms *addend); -void aa_perms_accum_raw(struct aa_perms *accum, struct aa_perms *addend); -void aa_profile_match_label(struct aa_profile *profile, +void aa_perms_accum(struct aa_perms *accum, const struct aa_perms *addend); +void aa_perms_accum_raw(struct aa_perms *accum, const struct aa_perms *addend); +void aa_profile_match_label(const struct aa_profile *profile, struct aa_ruleset *rules, struct aa_label *label, int type, u32 request, struct aa_perms *perms); -int aa_check_perms(struct aa_profile *profile, struct aa_perms *perms, +int aa_check_perms(struct aa_profile *profile, const struct aa_perms *perms, u32 request, struct apparmor_audit_data *ad, void (*cb)(struct audit_buffer *, void *)); #endif /* __AA_PERM_H */ + diff --git a/security/apparmor/include/policy.h b/security/apparmor/include/policy.h index 3895f8774a3f..0dcbf8cf1029 100644 --- a/security/apparmor/include/policy.h +++ b/security/apparmor/include/policy.h @@ -26,7 +26,6 @@ #include "file.h" #include "lib.h" #include "label.h" -#include "net.h" #include "perms.h" #include "resource.h" @@ -37,7 +36,6 @@ extern int unprivileged_userns_apparmor_policy; extern int aa_unprivileged_unconfined_restricted; extern const char *const aa_profile_mode_names[]; -#define APPARMOR_MODE_NAMES_MAX_INDEX 4 #define PROFILE_MODE(_profile, _mode) \ ((aa_g_profile_mode == (_mode)) || \ @@ -76,6 +74,7 @@ enum profile_mode { APPARMOR_KILL, /* kill task on access violation */ APPARMOR_UNCONFINED, /* profile set to unconfined */ APPARMOR_USER, /* modified complain mode to userspace */ + PROFILE_MODE_NAMES_COUNT /* Must be last entry */ }; @@ -295,6 +294,9 @@ struct aa_profile *aa_alloc_profile(const char *name, struct aa_proxy *proxy, gfp_t gfp); struct aa_profile *aa_alloc_null(struct aa_profile *parent, const char *name, gfp_t gfp); +struct aa_profile *__aa_new_learning_profile(struct aa_profile *parent, + bool hat, const char *base, + gfp_t gfp); struct aa_profile *aa_new_learning_profile(struct aa_profile *parent, bool hat, const char *base, gfp_t gfp); void aa_free_profile(struct aa_profile *profile); @@ -305,7 +307,8 @@ struct aa_profile *aa_fqlookupn_profile(struct aa_label *base, const char *fqname, size_t n); ssize_t aa_replace_profiles(struct aa_ns *view, struct aa_label *label, - u32 mask, struct aa_loaddata *udata); + u32 mask, struct aa_loaddata *udata, + char *compressed_profile, size_t compressed_size); ssize_t aa_remove_profiles(struct aa_ns *view, struct aa_label *label, char *name, size_t size); void __aa_profile_list_release(struct list_head *head); @@ -351,10 +354,14 @@ static inline aa_state_t RULE_MEDIATES_NET(struct aa_ruleset *rules) /* fallback and check v7/8 if v9 is NOT mediated */ if (!state) state = RULE_MEDIATES(rules, AA_CLASS_NET); - return state; } +static inline aa_state_t RULE_MEDIATES_UNIX(struct aa_ruleset *rules) +{ + return RULE_MEDIATES_v9NET(rules); +} + void aa_compute_profile_mediates(struct aa_profile *profile); static inline bool profile_mediates(struct aa_profile *profile, @@ -429,7 +436,7 @@ static inline void aa_put_profile(struct aa_profile *p) kref_put(&p->label.count.count, aa_label_kref); } -static inline int AUDIT_MODE(struct aa_profile *profile) +static inline int AUDIT_MODE(const struct aa_profile *profile) { if (aa_g_audit != AUDIT_NORMAL) return aa_g_audit; diff --git a/security/apparmor/include/policy_unpack.h b/security/apparmor/include/policy_unpack.h index 4ea9b6479a3e..c01f6885dbe3 100644 --- a/security/apparmor/include/policy_unpack.h +++ b/security/apparmor/include/policy_unpack.h @@ -16,6 +16,7 @@ #include <linux/dcache.h> #include <linux/workqueue.h> +#include "lib.h" struct aa_load_ent { struct list_head list; @@ -128,7 +129,8 @@ struct aa_loaddata { char *data; }; -int aa_unpack(struct aa_loaddata *udata, struct list_head *lh, const char **ns); +int aa_unpack(struct aa_loaddata *udata, struct list_head *lh, const char **ns, + char *compressed_data, size_t compressed_size); /** * aa_get_i_loaddata - get a reference count from a counted data reference diff --git a/security/apparmor/include/procattr.h b/security/apparmor/include/procattr.h index 03dbfdb2f2c0..56acd1bdb634 100644 --- a/security/apparmor/include/procattr.h +++ b/security/apparmor/include/procattr.h @@ -11,6 +11,8 @@ #ifndef __AA_PROCATTR_H #define __AA_PROCATTR_H +#include "label.h" + int aa_getprocattr(struct aa_label *label, char **string, bool newline); int aa_setprocattr_changehat(char *args, size_t size, int flags); diff --git a/security/apparmor/include/task.h b/security/apparmor/include/task.h index b1aaaf60fa8b..a8030ed78ff2 100644 --- a/security/apparmor/include/task.h +++ b/security/apparmor/include/task.h @@ -10,6 +10,11 @@ #ifndef __AA_TASK_H #define __AA_TASK_H +#include <linux/sched.h> + +#include "audit.h" +#include "label.h" + static inline struct aa_task_ctx *task_ctx(struct task_struct *task) { return task->security + apparmor_blob_sizes.lbs_task; @@ -21,15 +26,22 @@ static inline struct aa_task_ctx *task_ctx(struct task_struct *task) * @onexec: profile to transition to on next exec (MAY BE NULL) * @previous: profile the task may return to (MAY BE NULL) * @token: magic value the task must know for returning to @previous_profile + * @label_replacement_tw: for aa_schedule_stale_label_replacement() + * @label_replacement_pending: is @label_replacement_tw pending? + * + * When changing this, check if aa_dup_task_ctx() needs to be updated. */ struct aa_task_ctx { struct aa_label *nnp; struct aa_label *onexec; struct aa_label *previous; u64 token; + struct callback_head label_replacement_tw; + bool label_replacement_pending; }; int aa_replace_current_label(struct aa_label *label); +void aa_schedule_stale_label_replacement(void); void aa_set_current_onexec(struct aa_label *label, bool stack); int aa_set_current_hat(struct aa_label *label, u64 token); int aa_restore_previous_label(u64 cookie); @@ -56,10 +68,10 @@ static inline void aa_free_task_ctx(struct aa_task_ctx *ctx) static inline void aa_dup_task_ctx(struct aa_task_ctx *new, const struct aa_task_ctx *old) { - *new = *old; - aa_get_label(new->nnp); - aa_get_label(new->previous); - aa_get_label(new->onexec); + new->nnp = aa_get_label(old->nnp); + new->onexec = aa_get_label(old->onexec); + new->previous = aa_get_label(old->previous); + new->token = old->token; } /** diff --git a/security/apparmor/label.c b/security/apparmor/label.c index 3fd384d8c41a..82742a471055 100644 --- a/security/apparmor/label.c +++ b/security/apparmor/label.c @@ -165,7 +165,8 @@ static int profile_cmp(struct aa_profile *a, struct aa_profile *b) * ==0 if @a == @b * >0 if @a > @b */ -static int vec_cmp(struct aa_profile **a, int an, struct aa_profile **b, int bn) +static int vec_cmp(struct aa_profile * const *a, int an, + struct aa_profile * const *b, int bn) { int i; @@ -473,7 +474,7 @@ fail: * ==0 if a == b * >0 if a > b */ -static int label_cmp(struct aa_label *a, struct aa_label *b) +static int label_cmp(const struct aa_label *a, const struct aa_label *b) { AA_BUG(!b); @@ -484,7 +485,7 @@ static int label_cmp(struct aa_label *a, struct aa_label *b) } /* helper fn for label_for_each_confined */ -int aa_label_next_confined(struct aa_label *label, int i) +int aa_label_next_confined(const struct aa_label *label, int i) { AA_BUG(!label); AA_BUG(i < 0); @@ -507,8 +508,8 @@ int aa_label_next_confined(struct aa_label *label, int i) * else NULL if @sub is a subset of @set */ struct aa_profile *__aa_label_next_not_in_set(struct label_it *I, - struct aa_label *set, - struct aa_label *sub) + const struct aa_label *set, + const struct aa_label *sub) { AA_BUG(!set); AA_BUG(!I); @@ -544,7 +545,7 @@ struct aa_profile *__aa_label_next_not_in_set(struct label_it *I, * Returns: true if @sub is subset of @set * else false */ -bool aa_label_is_subset(struct aa_label *set, struct aa_label *sub) +bool aa_label_is_subset(const struct aa_label *set, const struct aa_label *sub) { struct label_it i = { }; @@ -571,7 +572,8 @@ bool aa_label_is_subset(struct aa_label *set, struct aa_label *sub) * Returns: true if @sub is special_subset of @set * else false */ -bool aa_label_is_unconfined_subset(struct aa_label *set, struct aa_label *sub) +bool aa_label_is_unconfined_subset(const struct aa_label *set, + const struct aa_label *sub) { struct label_it i = { }; struct aa_profile *p; @@ -796,6 +798,44 @@ bool aa_label_remove(struct aa_label *label) return res; } +enum ls_lock_class { + AA_LS_LOCK_FIRST, + AA_LS_LOCK_SECOND, +}; + +#define write_lock_irqsave_nested(L, F, SC) write_lock_irqsave(L, F) + +static void ns_ls_double_lock(struct aa_ns *ns1, struct aa_ns *ns2, + unsigned long *flags) +{ + if (likely(ns1 == ns2)) { + write_lock_irqsave(&ns1->labels.lock, *flags); + return; + } + + /* ordered by namespace hierarchy (walked in nesting order in + * labels_update. If at the same level by address order + */ + if ((ns1->level > ns2->level) || + (ns1->level == ns2->level && ns1 > ns2)) + swap(ns1, ns2); + + write_lock_irqsave_nested(&ns1->labels.lock, *flags, AA_LS_LOCK_FIRST); + write_lock_nested(&ns2->labels.lock, AA_LS_LOCK_SECOND); +} + +static void ns_ls_double_unlock(struct aa_ns *ns1, struct aa_ns *ns2, + unsigned long flags) +{ + if (likely(ns1 == ns2)) { + write_unlock_irqrestore(&ns1->labels.lock, flags); + return; + } + /* order doesn't matter on unlock, except flags restore must be last */ + write_unlock(&ns2->labels.lock); + write_unlock_irqrestore(&ns1->labels.lock, flags); +} + /** * aa_label_replace - replace a label @old with a new version @new * @old: label to replace @@ -803,36 +843,34 @@ bool aa_label_remove(struct aa_label *label) * * Returns: true if @old was in tree and replaced * else @old was not in tree, and @new was not inserted + * + * replacement can involve two different labelsets so has to be + * handled very careful, as a double lock may be required. */ bool aa_label_replace(struct aa_label *old, struct aa_label *new) { + struct aa_ns *ons = labels_ns(old); + struct aa_ns *nns = labels_ns(new); unsigned long flags; bool res; - if (name_is_shared(old, new) && labels_ns(old) == labels_ns(new)) { - write_lock_irqsave(&labels_set(old)->lock, flags); + ns_ls_double_lock(ons, nns, &flags); + if (ons == nns && name_is_shared(old, new)) { if (old->proxy != new->proxy) __proxy_share(old, new); else __aa_proxy_redirect(old, new); res = __label_replace(old, new); - write_unlock_irqrestore(&labels_set(old)->lock, flags); } else { struct aa_label *l; - struct aa_labelset *ls = labels_set(old); - write_lock_irqsave(&ls->lock, flags); + /* will redirect old proxy to new */ res = __label_remove(old, new); - if (labels_ns(old) != labels_ns(new)) { - write_unlock_irqrestore(&ls->lock, flags); - ls = labels_set(new); - write_lock_irqsave(&ls->lock, flags); - } - l = __label_insert(ls, new, true); + l = __label_insert(&nns->labels, new, true); res = (l == new); - write_unlock_irqrestore(&ls->lock, flags); aa_put_label(l); } + ns_ls_double_unlock(ons, nns, flags); return res; } @@ -955,8 +993,8 @@ struct aa_label *aa_label_insert(struct aa_labelset *ls, struct aa_label *label) * else null if no more profiles */ struct aa_profile *aa_label_next_in_merge(struct label_it *I, - struct aa_label *a, - struct aa_label *b) + const struct aa_label *a, + const struct aa_label *b) { AA_BUG(!a); AA_BUG(!b); @@ -1254,9 +1292,9 @@ out: * If a subns profile is not to be matched should be prescreened with * visibility test. */ -static inline aa_state_t match_component(struct aa_profile *profile, +static inline aa_state_t match_component(const struct aa_profile *profile, struct aa_ruleset *rules, - struct aa_profile *tp, + const struct aa_profile *tp, aa_state_t state) { const char *ns_name; @@ -1288,7 +1326,7 @@ static inline aa_state_t match_component(struct aa_profile *profile, * @perms should be preinitialized with allperms OR a previous permission * check to be stacked. */ -static int label_compound_match(struct aa_profile *profile, +static int label_compound_match(const struct aa_profile *profile, struct aa_ruleset *rules, struct aa_label *label, aa_state_t state, bool inview, u32 request, @@ -1344,7 +1382,7 @@ fail: * @perms should be preinitialized with allperms OR a previous permission * check to be stacked. */ -static int label_components_match(struct aa_profile *profile, +static int label_components_match(const struct aa_profile *profile, struct aa_ruleset *rules, struct aa_label *label, aa_state_t start, bool inview, u32 request, @@ -1403,7 +1441,7 @@ fail: * * Returns: the state the match finished in, may be the none matching state */ -int aa_label_match(struct aa_profile *profile, struct aa_ruleset *rules, +int aa_label_match(const struct aa_profile *profile, struct aa_ruleset *rules, struct aa_label *label, aa_state_t state, bool inview, u32 request, struct aa_perms *perms) { @@ -1743,10 +1781,7 @@ void aa_label_xaudit(struct audit_buffer *ab, struct aa_ns *ns, str = (char *) label->hname; len = strlen(str); } - if (audit_string_contains_control(str, len)) - audit_log_n_hex(ab, str, len); - else - audit_log_n_string(ab, str, len); + audit_log_n_untrustedstring(ab, str, len); kfree(name); } diff --git a/security/apparmor/lib.c b/security/apparmor/lib.c index e41ff57798b2..73b88bde40bb 100644 --- a/security/apparmor/lib.c +++ b/security/apparmor/lib.c @@ -20,8 +20,8 @@ #include "include/perms.h" #include "include/policy.h" -struct aa_perms nullperms; -struct aa_perms allperms = { .allow = ALL_PERMS_MASK, +const struct aa_perms nullperms; +const struct aa_perms allperms = { .allow = ALL_PERMS_MASK, .quiet = ALL_PERMS_MASK, .hide = ALL_PERMS_MASK }; @@ -30,7 +30,7 @@ struct val_table_ent { int value; }; -static struct val_table_ent debug_values_table[] = { +static const struct val_table_ent debug_values_table[] = { { "N", DEBUG_NONE }, { "none", DEBUG_NONE }, { "n", DEBUG_NONE }, @@ -49,10 +49,11 @@ static struct val_table_ent debug_values_table[] = { { NULL, 0 } }; -static struct val_table_ent *val_table_find_ent(struct val_table_ent *table, - const char *name, size_t len) +static const struct val_table_ent * +val_table_find_ent(const struct val_table_ent *table, + const char *name, size_t len) { - struct val_table_ent *entry; + const struct val_table_ent *entry; for (entry = table; entry->str != NULL; entry++) { if (strncmp(entry->str, name, len) == 0 && @@ -64,7 +65,7 @@ static struct val_table_ent *val_table_find_ent(struct val_table_ent *table, int aa_parse_debug_params(const char *str) { - struct val_table_ent *ent; + const struct val_table_ent *ent; const char *next; int val = 0; @@ -360,8 +361,16 @@ void aa_audit_perm_mask(struct audit_buffer *ab, u32 mask, const char *chrs, * * TODO: split into profile and ns based flags for when accumulating perms */ -void aa_apply_modes_to_perms(struct aa_profile *profile, struct aa_perms *perms) +void aa_apply_modes_to_perms(const struct aa_profile *profile, + struct aa_perms *perms) { + if (KILL_MODE(profile)) + perms->kill = ~perms->allow; + else if (COMPLAIN_MODE(profile)) + perms->complain |= ~(perms->allow | perms->deny); + else if (USER_MODE(profile)) + perms->prompt |= ~(perms->allow | perms->deny); + switch (AUDIT_MODE(profile)) { case AUDIT_ALL: perms->audit = ALL_PERMS_MASK; @@ -373,19 +382,15 @@ void aa_apply_modes_to_perms(struct aa_profile *profile, struct aa_perms *perms) perms->audit = 0; fallthrough; case AUDIT_QUIET_DENIED: - perms->quiet = ALL_PERMS_MASK; + perms->quiet |= ~perms->allow; + break; + case AUDIT_QUIET_ALLOWED: + perms->quiet |= perms->complain | perms->allow; break; } - - if (KILL_MODE(profile)) - perms->kill = ALL_PERMS_MASK; - else if (COMPLAIN_MODE(profile)) - perms->complain = ALL_PERMS_MASK; - else if (USER_MODE(profile)) - perms->prompt = ALL_PERMS_MASK; } -void aa_profile_match_label(struct aa_profile *profile, +void aa_profile_match_label(const struct aa_profile *profile, struct aa_ruleset *rules, struct aa_label *label, int type, u32 request, struct aa_perms *perms) @@ -417,11 +422,11 @@ void aa_profile_match_label(struct aa_profile *profile, * error code will indicate whether there was an explicit deny * with a positive value. */ -int aa_check_perms(struct aa_profile *profile, struct aa_perms *perms, +int aa_check_perms(struct aa_profile *profile, const struct aa_perms *perms, u32 request, struct apparmor_audit_data *ad, void (*cb)(struct audit_buffer *, void *)) { - int type, error; + int error; u32 denied = request & (~perms->allow | perms->deny); if (likely(!denied)) { @@ -430,18 +435,10 @@ int aa_check_perms(struct aa_profile *profile, struct aa_perms *perms, if (!request || !ad) return 0; - type = AUDIT_APPARMOR_AUDIT; error = 0; } else { error = -EACCES; - if (denied & perms->kill) - type = AUDIT_APPARMOR_KILL; - else if (denied == (denied & perms->complain)) - type = AUDIT_APPARMOR_ALLOWED; - else - type = AUDIT_APPARMOR_DENIED; - if (denied == (denied & perms->hide)) error = -ENOENT; @@ -450,6 +447,8 @@ int aa_check_perms(struct aa_profile *profile, struct aa_perms *perms, return error; } + int type = aa_select_audit_type(denied, perms); + if (ad) { ad->subj_label = &profile->label; ad->request = request; @@ -482,6 +481,8 @@ bool aa_policy_init(struct aa_policy *policy, const char *prefix, char *hname; size_t hname_sz; + INIT_LIST_HEAD(&policy->list); + INIT_LIST_HEAD(&policy->profiles); hname_sz = (prefix ? strlen(prefix) + 2 : 0) + strlen(name) + 1; /* freed by policy_free */ hname = aa_str_alloc(hname_sz, gfp); @@ -494,8 +495,6 @@ bool aa_policy_init(struct aa_policy *policy, const char *prefix, policy->hname = hname; /* base.name is a substring of fqname */ policy->name = basename(policy->hname); - INIT_LIST_HEAD(&policy->list); - INIT_LIST_HEAD(&policy->profiles); return true; } diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c index 88d12e89d115..d502ad0ac26f 100644 --- a/security/apparmor/lsm.c +++ b/security/apparmor/lsm.c @@ -28,6 +28,7 @@ #include <uapi/linux/lsm.h> #include "include/af_unix.h" +#include "include/af_inet.h" #include "include/apparmor.h" #include "include/apparmorfs.h" #include "include/audit.h" @@ -367,15 +368,16 @@ static int apparmor_path_link(struct dentry *old_dentry, const struct path *new_ { struct aa_label *label; int error = 0; + bool needput; if (!path_mediated_fs(old_dentry)) return 0; - label = begin_current_label_crit_section(); + label = begin_current_label_crit_section(&needput); if (!unconfined(label)) error = aa_path_link(current_cred(), label, old_dentry, new_dir, new_dentry); - end_current_label_crit_section(label); + end_current_label_crit_section(label, needput); return error; } @@ -386,13 +388,14 @@ static int apparmor_path_rename(const struct path *old_dir, struct dentry *old_d { struct aa_label *label; int error = 0; + bool needput; if (!path_mediated_fs(old_dentry)) return 0; if ((flags & RENAME_EXCHANGE) && !path_mediated_fs(new_dentry)) return 0; - label = begin_current_label_crit_section(); + label = begin_current_label_crit_section(&needput); if (!unconfined(label)) { struct mnt_idmap *idmap = mnt_idmap(old_dir->mnt); vfsuid_t vfsuid; @@ -438,7 +441,7 @@ static int apparmor_path_rename(const struct path *old_dir, struct dentry *old_d AA_MAY_CREATE, &cond); } - end_current_label_crit_section(label); + end_current_label_crit_section(label, needput); return error; } @@ -505,11 +508,12 @@ static int apparmor_file_open(struct file *file) static int apparmor_file_alloc_security(struct file *file) { struct aa_file_ctx *ctx = file_ctx(file); - struct aa_label *label = begin_current_label_crit_section(); + bool needput; + struct aa_label *label = begin_current_label_crit_section(&needput); spin_lock_init(&ctx->lock); rcu_assign_pointer(ctx->label, aa_get_label(label)); - end_current_label_crit_section(label); + end_current_label_crit_section(label, needput); return 0; } @@ -524,11 +528,12 @@ static void apparmor_file_free_security(struct file *file) static int common_file_perm(const char *op, struct file *file, u32 mask) { struct aa_label *label; + bool needput; int error = 0; - label = begin_current_label_crit_section(); + label = begin_current_label_crit_section(&needput); error = aa_file_perm(op, current_cred(), label, file, mask, false); - end_current_label_crit_section(label); + end_current_label_crit_section(label, needput); return error; } @@ -849,6 +854,7 @@ static int do_setattr(u64 attr, void *value, size_t size) char *command, *largs = NULL, *args = value; size_t arg_size; int error; + bool needput; DEFINE_AUDIT_DATA(ad, LSM_AUDIT_DATA_NONE, AA_CLASS_NONE, OP_SETPROCATTR); @@ -906,7 +912,7 @@ out: return error; fail: - ad.subj_label = begin_current_label_crit_section(); + ad.subj_label = begin_current_label_crit_section(&needput); if (attr == LSM_ATTR_CURRENT) ad.info = "current"; else if (attr == LSM_ATTR_EXEC) @@ -915,7 +921,7 @@ fail: ad.info = "invalid"; ad.error = error = -EINVAL; aa_audit_msg(AUDIT_APPARMOR_DENIED, &ad, NULL); - end_current_label_crit_section(ad.subj_label); + end_current_label_crit_section(ad.subj_label, needput); goto out; } @@ -1046,18 +1052,19 @@ static int apparmor_userns_create(const struct cred *cred) struct aa_label *label; struct aa_profile *profile; int error = 0; + bool needput; DEFINE_AUDIT_DATA(ad, LSM_AUDIT_DATA_TASK, AA_CLASS_NS, OP_USERNS_CREATE); ad.subj_cred = current_cred(); - label = begin_current_label_crit_section(); + label = begin_current_label_crit_section(&needput); if (!unconfined(label)) { error = fn_for_each(label, profile, aa_profile_ns_perm(profile, &ad, AA_USERNS_CREATE)); } - end_current_label_crit_section(label); + end_current_label_crit_section(label, needput); return error; } @@ -1251,23 +1258,32 @@ static int apparmor_socket_create(int family, int type, int protocol, int kern) { struct aa_label *label; int error = 0; + bool needput; AA_BUG(in_interrupt()); if (kern) return 0; - label = begin_current_label_crit_section(); + label = begin_current_label_crit_section(&needput); if (!unconfined(label)) { - if (family == PF_UNIX) + switch (family) { + case PF_UNIX: error = aa_unix_create_perm(label, family, type, protocol); - else + break; + case PF_INET: + case PF_INET6: + error = aa_inet_create_perm(label, family, type, + protocol); + break; + default: error = aa_af_perm(current_cred(), label, OP_CREATE, AA_MAY_CREATE, family, type, protocol); + } } - end_current_label_crit_section(label); + end_current_label_crit_section(label, needput); return error; } @@ -1314,9 +1330,10 @@ static int apparmor_socket_socketpair(struct socket *socka, struct aa_sk_ctx *a_ctx = aa_sock(socka->sk); struct aa_sk_ctx *b_ctx = aa_sock(sockb->sk); struct aa_label *label; + bool needput; /* socks not live yet - initial values set in sk_alloc */ - label = begin_current_label_crit_section(); + label = begin_current_label_crit_section(&needput); if (rcu_access_pointer(a_ctx->label) != label) { AA_BUG("a_ctx != label"); aa_put_label(rcu_dereference_protected(a_ctx->label, true)); @@ -1332,7 +1349,7 @@ static int apparmor_socket_socketpair(struct socket *socka, /* unix socket pairs by-pass unix_stream_connect */ unix_connect_peers(a_ctx, b_ctx); } - end_current_label_crit_section(label); + end_current_label_crit_section(label, needput); return 0; } @@ -1359,8 +1376,13 @@ static int apparmor_socket_bind(struct socket *sock, AA_BUG(!address); AA_BUG(in_interrupt()); - if (sock->sk->sk_family == PF_UNIX) + switch (sock->sk->sk_family) { + case PF_UNIX: return aa_unix_bind_perm(sock, address, addrlen); + case PF_INET: + case PF_INET6: + return aa_inet_bind_perm(sock, address, addrlen); + } return aa_sk_perm(OP_BIND, AA_MAY_BIND, sock->sk); } @@ -1373,8 +1395,13 @@ static int apparmor_socket_connect(struct socket *sock, AA_BUG(in_interrupt()); /* PF_UNIX goes through unix_stream_connect && unix_may_send */ - if (sock->sk->sk_family == PF_UNIX) + switch (sock->sk->sk_family) { + case PF_UNIX: return 0; + case PF_INET: + case PF_INET6: + return aa_inet_connect_perm(sock, address, addrlen); + } return aa_sk_perm(OP_CONNECT, AA_MAY_CONNECT, sock->sk); } @@ -1384,8 +1411,13 @@ static int apparmor_socket_listen(struct socket *sock, int backlog) AA_BUG(!sock->sk); AA_BUG(in_interrupt()); - if (sock->sk->sk_family == PF_UNIX) + switch (sock->sk->sk_family) { + case PF_UNIX: return aa_unix_listen_perm(sock, backlog); + case PF_INET: + case PF_INET6: + return aa_inet_listen_perm(sock, backlog); + } return aa_sk_perm(OP_LISTEN, AA_MAY_LISTEN, sock->sk); } @@ -1400,8 +1432,13 @@ static int apparmor_socket_accept(struct socket *sock, struct socket *newsock) AA_BUG(!newsock); AA_BUG(in_interrupt()); - if (sock->sk->sk_family == PF_UNIX) + switch (sock->sk->sk_family) { + case PF_UNIX: return aa_unix_accept_perm(sock, newsock); + case PF_INET: + case PF_INET6: + return aa_inet_accept_perm(sock, newsock); + } return aa_sk_perm(OP_ACCEPT, AA_MAY_ACCEPT, sock->sk); } @@ -1414,8 +1451,14 @@ static int aa_sock_msg_perm(const char *op, u32 request, struct socket *sock, AA_BUG(in_interrupt()); /* PF_UNIX goes through unix_may_send */ - if (sock->sk->sk_family == PF_UNIX) + switch (sock->sk->sk_family) { + case PF_UNIX: return 0; + case PF_INET: + case PF_INET6: + return aa_inet_msg_perm(op, request, sock, msg, size); + } + return aa_sk_perm(op, request, sock->sk); } @@ -1434,7 +1477,8 @@ static int apparmor_socket_sendmsg(struct socket *sock, (sk_is_tcp(sock->sk) || (sk_is_inet(sock->sk) && sock->sk->sk_type == SOCK_STREAM && sock->sk->sk_protocol == IPPROTO_MPTCP))) - error = aa_sk_perm(OP_CONNECT, AA_MAY_CONNECT, sock->sk); + error = aa_sock_msg_perm(OP_CONNECT, AA_MAY_CONNECT, sock, + msg, size); return error; } @@ -1452,8 +1496,13 @@ static int aa_sock_perm(const char *op, u32 request, struct socket *sock) AA_BUG(!sock->sk); AA_BUG(in_interrupt()); - if (sock->sk->sk_family == PF_UNIX) + switch (sock->sk->sk_family) { + case PF_UNIX: return aa_unix_sock_perm(op, request, sock); + case PF_INET: + case PF_INET6: + return aa_inet_sock_perm(op, request, sock); + } return aa_sk_perm(op, request, sock->sk); } @@ -1475,8 +1524,13 @@ static int aa_sock_opt_perm(const char *op, u32 request, struct socket *sock, AA_BUG(!sock->sk); AA_BUG(in_interrupt()); - if (sock->sk->sk_family == PF_UNIX) + switch (sock->sk->sk_family) { + case PF_UNIX: return aa_unix_opt_perm(op, request, sock, level, optname); + case PF_INET: + case PF_INET6: + return aa_inet_opt_perm(op, request, sock, level, optname); + } return aa_sk_perm(op, request, sock->sk); } @@ -1499,42 +1553,6 @@ static int apparmor_socket_shutdown(struct socket *sock, int how) return aa_sock_perm(OP_SHUTDOWN, AA_MAY_SHUTDOWN, sock); } -#ifdef CONFIG_NETWORK_SECMARK -/** - * apparmor_socket_sock_rcv_skb - check perms before associating skb to sk - * @sk: sk to associate @skb with - * @skb: skb to check for perms - * - * Note: can not sleep may be called with locks held - * - * don't want protocol specific in __skb_recv_datagram() - * to deny an incoming connection socket_sock_rcv_skb() - */ -static int apparmor_socket_sock_rcv_skb(struct sock *sk, struct sk_buff *skb) -{ - struct aa_sk_ctx *ctx = aa_sock(sk); - int error; - - if (!skb->secmark) - return 0; - - /* - * If reach here before socket_post_create hook is called, in which - * case label is null, drop the packet. - */ - if (!rcu_access_pointer(ctx->label)) - return -EACCES; - - rcu_read_lock(); - error = apparmor_secmark_check(rcu_dereference(ctx->label), OP_RECVMSG, - AA_MAY_RECEIVE, skb->secmark, sk); - rcu_read_unlock(); - - return error; -} -#endif - - static struct aa_label *sk_peer_get_label(struct sock *sk) { struct aa_sk_ctx *ctx = aa_sock(sk); @@ -1563,13 +1581,14 @@ static int apparmor_socket_getpeersec_stream(struct socket *sock, int slen, error = 0; struct aa_label *label; struct aa_label *peer; + bool needput; peer = sk_peer_get_label(sock->sk); if (IS_ERR(peer)) { error = PTR_ERR(peer); goto done; } - label = begin_current_label_crit_section(); + label = begin_current_label_crit_section(&needput); slen = aa_label_asxprint(&name, labels_ns(label), peer, FLAG_SHOW_MODE | FLAG_VIEW_SUBNS | FLAG_HIDDEN_UNCONFINED, GFP_KERNEL); @@ -1590,7 +1609,7 @@ done_len: error = -EFAULT; done_put: - end_current_label_crit_section(label); + end_current_label_crit_section(label, needput); aa_put_label(peer); done: kfree(name); @@ -1634,6 +1653,39 @@ static void apparmor_sock_graft(struct sock *sk, struct socket *parent) } #ifdef CONFIG_NETWORK_SECMARK +/** + * apparmor_socket_sock_rcv_skb - check perms before associating skb to sk + * @sk: sk to associate @skb with + * @skb: skb to check for perms + * + * Note: can not sleep may be called with locks held + * + * don't want protocol specific in __skb_recv_datagram() + * to deny an incoming connection socket_sock_rcv_skb() + */ +static int apparmor_socket_sock_rcv_skb(struct sock *sk, struct sk_buff *skb) +{ + struct aa_sk_ctx *ctx = aa_sock(sk); + int error; + + if (!skb->secmark) + return 0; + + /* + * If reach here before socket_post_create hook is called, in which + * case label is null, drop the packet. + */ + if (!rcu_access_pointer(ctx->label)) + return -EACCES; + + rcu_read_lock(); + error = apparmor_secmark_check(rcu_dereference(ctx->label), OP_RECVMSG, + AA_MAY_RECEIVE, skb->secmark, sk); + rcu_read_unlock(); + + return error; +} + static int apparmor_inet_conn_request(const struct sock *sk, struct sk_buff *skb, struct request_sock *req) { @@ -1652,6 +1704,51 @@ static int apparmor_inet_conn_request(const struct sock *sk, struct sk_buff *skb } #endif +#if defined(CONFIG_NETFILTER) && defined(CONFIG_NETWORK_SECMARK) +static unsigned int apparmor_ip_postroute(void *priv, + struct sk_buff *skb, + const struct nf_hook_state *state) +{ + struct aa_sk_ctx *ctx; + struct sock *sk; + int error; + + if (!skb->secmark) + return NF_ACCEPT; + + sk = skb_to_full_sk(skb); + if (sk == NULL) + return NF_ACCEPT; + + ctx = aa_sock(sk); + rcu_read_lock(); + error = apparmor_secmark_check(rcu_dereference(ctx->label), OP_SENDMSG, + AA_MAY_SEND, skb->secmark, sk); + rcu_read_unlock(); + if (!error) + return NF_ACCEPT; + + return NF_DROP_ERR(-ECONNREFUSED); +} + +static const struct nf_hook_ops apparmor_nf_ops[] = { + { + .hook = apparmor_ip_postroute, + .pf = NFPROTO_IPV4, + .hooknum = NF_INET_POST_ROUTING, + .priority = NF_IP_PRI_SELINUX_FIRST, + }, +#if IS_ENABLED(CONFIG_IPV6) + { + .hook = apparmor_ip_postroute, + .pf = NFPROTO_IPV6, + .hooknum = NF_INET_POST_ROUTING, + .priority = NF_IP6_PRI_SELINUX_FIRST, + }, +#endif +}; +#endif + /* * The cred blob is a pointer to, not an instance of, an aa_label. */ @@ -2093,7 +2190,7 @@ static int param_set_audit(const char *val, const struct kernel_param *kp) if (apparmor_initialized && !aa_current_policy_admin_capable(NULL)) return -EPERM; - i = match_string(audit_mode_names, AUDIT_MAX_INDEX, val); + i = match_string(audit_mode_names, AUDIT_MODE_NAMES_COUNT, val); if (i < 0) return -EINVAL; @@ -2121,7 +2218,7 @@ static int param_set_mode(const char *val, const struct kernel_param *kp) if (apparmor_initialized && !aa_current_policy_admin_capable(NULL)) return -EPERM; - i = match_string(aa_profile_mode_names, APPARMOR_MODE_NAMES_MAX_INDEX, + i = match_string(aa_profile_mode_names, PROFILE_MODE_NAMES_COUNT, val); if (i < 0) return -EINVAL; @@ -2369,51 +2466,8 @@ static inline int apparmor_init_sysctl(void) } #endif /* CONFIG_SYSCTL */ -#if defined(CONFIG_NETFILTER) && defined(CONFIG_NETWORK_SECMARK) -static unsigned int apparmor_ip_postroute(void *priv, - struct sk_buff *skb, - const struct nf_hook_state *state) -{ - struct aa_sk_ctx *ctx; - struct sock *sk; - int error; - - if (!skb->secmark) - return NF_ACCEPT; - - sk = skb_to_full_sk(skb); - if (sk == NULL) - return NF_ACCEPT; - - ctx = aa_sock(sk); - rcu_read_lock(); - error = apparmor_secmark_check(rcu_dereference(ctx->label), OP_SENDMSG, - AA_MAY_SEND, skb->secmark, sk); - rcu_read_unlock(); - if (!error) - return NF_ACCEPT; - - return NF_DROP_ERR(-ECONNREFUSED); - -} - -static const struct nf_hook_ops apparmor_nf_ops[] = { - { - .hook = apparmor_ip_postroute, - .pf = NFPROTO_IPV4, - .hooknum = NF_INET_POST_ROUTING, - .priority = NF_IP_PRI_SELINUX_FIRST, - }, -#if IS_ENABLED(CONFIG_IPV6) - { - .hook = apparmor_ip_postroute, - .pf = NFPROTO_IPV6, - .hooknum = NF_INET_POST_ROUTING, - .priority = NF_IP6_PRI_SELINUX_FIRST, - }, -#endif -}; +#if defined(CONFIG_NETFILTER) && defined(CONFIG_NETWORK_SECMARK) static int __net_init apparmor_nf_register(struct net *net) { return nf_register_net_hooks(net, apparmor_nf_ops, @@ -2446,16 +2500,16 @@ static int __init apparmor_nf_ip_init(void) } #endif -static char nulldfa_src[] __aligned(8) = { +static const char nulldfa_src[] __aligned(8) = { #include "nulldfa.in" }; -static struct aa_dfa *nulldfa; +static struct aa_dfa *nulldfa __ro_after_init; static char stacksplitdfa_src[] __aligned(8) = { #include "stacksplitdfa.in" }; -struct aa_dfa *stacksplitdfa; -struct aa_policydb *nullpdb; +struct aa_dfa *stacksplitdfa __ro_after_init; +struct aa_policydb *nullpdb __ro_after_init; static int __init aa_setup_dfa_engine(void) { diff --git a/security/apparmor/match.c b/security/apparmor/match.c index d43ff34d705c..7713484f6a36 100644 --- a/security/apparmor/match.c +++ b/security/apparmor/match.c @@ -31,7 +31,7 @@ * * NOTE: must be freed by kvfree (not kfree) */ -static struct table_header *unpack_table(char *blob, size_t bsize) +static struct table_header *unpack_table(const char *blob, size_t bsize) { struct table_header *table = ERR_PTR(-EPROTO); struct table_header th; @@ -151,7 +151,7 @@ out: * * Returns: %0 else error code on failure to verify */ -static int verify_dfa(struct aa_dfa *dfa) +static int verify_dfa(const struct aa_dfa *dfa) { size_t i, state_count, trans_count; int error = -EPROTO; @@ -312,11 +312,11 @@ static struct table_header *remap_data16_to_data32(struct table_header *old) * * Returns: an unpacked dfa ready for matching or ERR_PTR on failure */ -struct aa_dfa *aa_dfa_unpack(void *blob, size_t size, int flags) +struct aa_dfa *aa_dfa_unpack(const void *blob, size_t size, int flags) { int hsize; int error = -ENOMEM; - char *data = blob; + const char *data = blob; struct table_header *table = NULL; struct aa_dfa *dfa = kzalloc_obj(struct aa_dfa); if (!dfa) @@ -467,7 +467,7 @@ do { \ * * Returns: final state reached after input is consumed */ -aa_state_t aa_dfa_match_len(struct aa_dfa *dfa, aa_state_t start, +aa_state_t aa_dfa_match_len(const struct aa_dfa *dfa, aa_state_t start, const char *str, int len) { u32 *def = DEFAULT_TABLE(dfa); @@ -512,7 +512,8 @@ aa_state_t aa_dfa_match_len(struct aa_dfa *dfa, aa_state_t start, * * Returns: final state reached after input is consumed */ -aa_state_t aa_dfa_match(struct aa_dfa *dfa, aa_state_t start, const char *str) +aa_state_t aa_dfa_match(const struct aa_dfa *dfa, aa_state_t start, + const char *str) { u32 *def = DEFAULT_TABLE(dfa); u32 *base = BASE_TABLE(dfa); @@ -555,7 +556,8 @@ aa_state_t aa_dfa_match(struct aa_dfa *dfa, aa_state_t start, const char *str) * * Returns: state reach after input @c */ -aa_state_t aa_dfa_next(struct aa_dfa *dfa, aa_state_t state, const char c) +aa_state_t aa_dfa_next(const struct aa_dfa *dfa, aa_state_t state, + const char c) { u32 *def = DEFAULT_TABLE(dfa); u32 *base = BASE_TABLE(dfa); @@ -573,7 +575,8 @@ aa_state_t aa_dfa_next(struct aa_dfa *dfa, aa_state_t state, const char c) return state; } -aa_state_t aa_dfa_outofband_transition(struct aa_dfa *dfa, aa_state_t state) +aa_state_t aa_dfa_outofband_transition(const struct aa_dfa *dfa, + aa_state_t state) { u32 *def = DEFAULT_TABLE(dfa); u32 *base = BASE_TABLE(dfa); @@ -603,8 +606,8 @@ aa_state_t aa_dfa_outofband_transition(struct aa_dfa *dfa, aa_state_t state) * * Returns: final state reached after input is consumed */ -aa_state_t aa_dfa_match_until(struct aa_dfa *dfa, aa_state_t start, - const char *str, const char **retpos) +aa_state_t aa_dfa_match_until(const struct aa_dfa *dfa, aa_state_t start, + const char *str, const char **retpos) { u32 *def = DEFAULT_TABLE(dfa); u32 *base = BASE_TABLE(dfa); @@ -664,8 +667,8 @@ aa_state_t aa_dfa_match_until(struct aa_dfa *dfa, aa_state_t start, * * Returns: final state reached after input is consumed */ -aa_state_t aa_dfa_matchn_until(struct aa_dfa *dfa, aa_state_t start, - const char *str, int n, const char **retpos) +aa_state_t aa_dfa_matchn_until(const struct aa_dfa *dfa, aa_state_t start, + const char *str, int n, const char **retpos) { u32 *def = DEFAULT_TABLE(dfa); u32 *base = BASE_TABLE(dfa); @@ -740,9 +743,9 @@ static bool is_loop(struct match_workbuf *wb, aa_state_t state, return false; } -static aa_state_t leftmatch_fb(struct aa_dfa *dfa, aa_state_t start, - const char *str, struct match_workbuf *wb, - unsigned int *count) +static aa_state_t leftmatch_fb(const struct aa_dfa *dfa, aa_state_t start, + const char *str, struct match_workbuf *wb, + unsigned int *count) { u32 *def = DEFAULT_TABLE(dfa); u32 *base = BASE_TABLE(dfa); @@ -821,7 +824,7 @@ out: * * Returns: final state reached after input is consumed */ -aa_state_t aa_dfa_leftmatch(struct aa_dfa *dfa, aa_state_t start, +aa_state_t aa_dfa_leftmatch(const struct aa_dfa *dfa, aa_state_t start, const char *str, unsigned int *count) { DEFINE_MATCH_WB(wb); diff --git a/security/apparmor/mount.c b/security/apparmor/mount.c index 2f5d918832c1..4ed7b9136beb 100644 --- a/security/apparmor/mount.c +++ b/security/apparmor/mount.c @@ -24,6 +24,10 @@ #include "include/policy.h" +#define DEFINE_AUDIT_MOUNT(NAME, OP, CRED) \ + DEFINE_AUDIT_DATA(NAME, LSM_AUDIT_DATA_NONE, AA_CLASS_MOUNT, OP);\ + NAME.subj_cred = (CRED) + static void audit_mnt_flags(struct audit_buffer *ab, unsigned long flags) { if (flags & MS_RDONLY) @@ -114,77 +118,6 @@ static void audit_cb(struct audit_buffer *ab, void *va) } /** - * audit_mount - handle the auditing of mount operations - * @subj_cred: cred of the subject - * @profile: the profile being enforced (NOT NULL) - * @op: operation being mediated (NOT NULL) - * @name: name of object being mediated (MAYBE NULL) - * @src_name: src_name of object being mediated (MAYBE_NULL) - * @type: type of filesystem (MAYBE_NULL) - * @trans: name of trans (MAYBE NULL) - * @flags: filesystem independent mount flags - * @data: filesystem mount flags - * @request: permissions requested - * @perms: the permissions computed for the request (NOT NULL) - * @info: extra information message (MAYBE NULL) - * @error: 0 if operation allowed else failure error code - * - * Returns: %0 or error on failure - */ -static int audit_mount(const struct cred *subj_cred, - struct aa_profile *profile, const char *op, - const char *name, const char *src_name, - const char *type, const char *trans, - unsigned long flags, const void *data, u32 request, - struct aa_perms *perms, const char *info, int error) -{ - int audit_type = AUDIT_APPARMOR_AUTO; - DEFINE_AUDIT_DATA(ad, LSM_AUDIT_DATA_NONE, AA_CLASS_MOUNT, op); - - if (likely(!error)) { - u32 mask = perms->audit; - - if (unlikely(AUDIT_MODE(profile) == AUDIT_ALL)) - mask = 0xffff; - - /* mask off perms that are not being force audited */ - request &= mask; - - if (likely(!request)) - return 0; - audit_type = AUDIT_APPARMOR_AUDIT; - } else { - /* only report permissions that were denied */ - request = request & ~perms->allow; - - if (request & perms->kill) - audit_type = AUDIT_APPARMOR_KILL; - - /* quiet known rejects, assumes quiet and kill do not overlap */ - if ((request & perms->quiet) && - AUDIT_MODE(profile) != AUDIT_NOQUIET && - AUDIT_MODE(profile) != AUDIT_ALL) - request &= ~perms->quiet; - - if (!request) - return error; - } - - ad.subj_cred = subj_cred; - ad.name = name; - ad.mnt.src_name = src_name; - ad.mnt.type = type; - ad.mnt.trans = trans; - ad.mnt.flags = flags; - if (data && (perms->audit & AA_AUDIT_DATA)) - ad.mnt.data = data; - ad.info = info; - ad.error = error; - - return aa_audit(audit_type, profile, &ad, audit_cb); -} - -/** * match_mnt_flags - Do an ordered match on mount flags * @dfa: dfa to match against * @state: state to start in @@ -196,7 +129,7 @@ static int audit_mount(const struct cred *subj_cred, * * Returns: next state after flags match */ -static aa_state_t match_mnt_flags(struct aa_dfa *dfa, aa_state_t state, +static aa_state_t match_mnt_flags(const struct aa_dfa *dfa, aa_state_t state, unsigned long flags) { unsigned int i; @@ -278,7 +211,7 @@ static int do_match_mnt(struct aa_policydb *policy, aa_state_t start, } -static int path_flags(struct aa_profile *profile, const struct path *path) +static int path_flags(const struct aa_profile *profile, const struct path *path) { AA_BUG(!profile); AA_BUG(!path); @@ -289,7 +222,6 @@ static int path_flags(struct aa_profile *profile, const struct path *path) /** * match_mnt_path_str - handle path matching for mount - * @subj_cred: cred of confined subject * @profile: the confining profile * @mntpath: for the mntpnt (NOT NULL) * @buffer: buffer to be used to lookup mntpath @@ -299,18 +231,19 @@ static int path_flags(struct aa_profile *profile, const struct path *path) * @data: fs mount data (MAYBE NULL) * @binary: whether @data is binary * @devinfo: error str if (IS_ERR(@devname)) + * @ad: apparmor audit data structure * * Returns: 0 on success else error */ -static int match_mnt_path_str(const struct cred *subj_cred, - struct aa_profile *profile, +static int match_mnt_path_str(struct aa_profile *profile, const struct path *mntpath, char *buffer, const char *devname, const char *type, unsigned long flags, void *data, bool binary, - const char *devinfo) + const char *devinfo, + struct apparmor_audit_data *ad) { struct aa_perms perms = { }; - const char *mntpnt = NULL, *info = NULL; + const char *mntpnt = NULL; struct aa_ruleset *rules = profile->label.rules[0]; int pos, error; @@ -321,36 +254,37 @@ static int match_mnt_path_str(const struct cred *subj_cred, if (!RULE_MEDIATES(rules, AA_CLASS_MOUNT)) return 0; + ad->mnt.type = type; + error = aa_path_name(mntpath, path_flags(profile, mntpath), buffer, - &mntpnt, &info, profile->disconnected); + &mntpnt, &ad->info, profile->disconnected); if (error) - goto audit; + return aa_audit_perm_error(&profile->label, AA_MAY_MOUNT, + error, ad, audit_cb); + ad->name = mntpnt; + if (IS_ERR(devname)) { error = PTR_ERR(devname); - devname = NULL; - info = devinfo; - goto audit; + ad->info = devinfo; + return aa_audit_perm_error(&profile->label, AA_MAY_MOUNT, + error, ad, audit_cb); } + ad->mnt.src_name = devname; - error = -EACCES; pos = do_match_mnt(rules->policy, rules->policy->start[AA_CLASS_MOUNT], mntpnt, devname, type, flags, data, binary, &perms); - if (pos) { - info = mnt_info_table[pos]; - goto audit; - } - error = 0; + if (pos) + ad->info = mnt_info_table[pos]; -audit: - return audit_mount(subj_cred, profile, OP_MOUNT, mntpnt, devname, - type, NULL, - flags, data, AA_MAY_MOUNT, &perms, info, error); + aa_apply_modes_to_perms(profile, &perms); + if (data && !binary && (perms.audit & AA_AUDIT_DATA)) + ad->mnt.data = data; + return aa_check_perms(profile, &perms, AA_MAY_MOUNT, ad, audit_cb); } /** * match_mnt - handle path matching for mount - * @subj_cred: cred of the subject * @profile: the confining profile * @path: for the mntpnt (NOT NULL) * @buffer: buffer to be used to lookup mntpath @@ -360,14 +294,14 @@ audit: * @flags: mount flags to match * @data: fs mount data (MAYBE NULL) * @binary: whether @data is binary + * @ad: apparmor audit data structure * * Returns: 0 on success else error */ -static int match_mnt(const struct cred *subj_cred, - struct aa_profile *profile, const struct path *path, +static int match_mnt(struct aa_profile *profile, const struct path *path, char *buffer, const struct path *devpath, char *devbuffer, const char *type, unsigned long flags, void *data, - bool binary) + bool binary, struct apparmor_audit_data *ad) { const char *devname = NULL, *info = NULL; struct aa_ruleset *rules = profile->label.rules[0]; @@ -387,8 +321,8 @@ static int match_mnt(const struct cred *subj_cred, devname = ERR_PTR(error); } - return match_mnt_path_str(subj_cred, profile, path, buffer, devname, - type, flags, data, binary, info); + return match_mnt_path_str(profile, path, buffer, devname, + type, flags, data, binary, info, ad); } int aa_remount(const struct cred *subj_cred, @@ -399,6 +333,8 @@ int aa_remount(const struct cred *subj_cred, char *buffer = NULL; bool binary; int error; + DEFINE_AUDIT_MOUNT(ad, OP_MOUNT, subj_cred); + ad.mnt.flags = flags; AA_BUG(!label); AA_BUG(!path); @@ -408,10 +344,9 @@ int aa_remount(const struct cred *subj_cred, buffer = aa_get_buffer(false); if (!buffer) return -ENOMEM; - error = fn_for_each_confined(label, profile, - match_mnt(subj_cred, profile, path, buffer, NULL, - NULL, NULL, - flags, data, binary)); + error = fn_for_each(label, profile, + match_mnt(profile, path, buffer, NULL, NULL, NULL, + flags, data, binary, &ad)); aa_put_buffer(buffer); return error; @@ -425,6 +360,7 @@ int aa_bind_mount(const struct cred *subj_cred, char *buffer = NULL, *old_buffer = NULL; struct path old_path; int error; + DEFINE_AUDIT_MOUNT(ad, OP_MOUNT, subj_cred); AA_BUG(!label); AA_BUG(!path); @@ -433,10 +369,12 @@ int aa_bind_mount(const struct cred *subj_cred, return -EINVAL; flags &= MS_REC | MS_BIND; + ad.mnt.flags = flags; error = kern_path(dev_name, LOOKUP_FOLLOW|LOOKUP_AUTOMOUNT, &old_path); if (error) - return error; + return aa_audit_perm_error(label, AA_MAY_MOUNT, error, &ad, + audit_cb); buffer = aa_get_buffer(false); old_buffer = aa_get_buffer(false); @@ -444,9 +382,9 @@ int aa_bind_mount(const struct cred *subj_cred, if (!buffer || !old_buffer) goto out; - error = fn_for_each_confined(label, profile, - match_mnt(subj_cred, profile, path, buffer, &old_path, - old_buffer, NULL, flags, NULL, false)); + error = fn_for_each(label, profile, + match_mnt(profile, path, buffer, &old_path, + old_buffer, NULL, flags, NULL, false, &ad)); out: aa_put_buffer(buffer); aa_put_buffer(old_buffer); @@ -462,6 +400,7 @@ int aa_mount_change_type(const struct cred *subj_cred, struct aa_profile *profile; char *buffer = NULL; int error; + DEFINE_AUDIT_MOUNT(ad, OP_MOUNT, subj_cred); AA_BUG(!label); AA_BUG(!path); @@ -469,14 +408,14 @@ int aa_mount_change_type(const struct cred *subj_cred, /* These are the flags allowed by do_change_type() */ flags &= (MS_REC | MS_SILENT | MS_SHARED | MS_PRIVATE | MS_SLAVE | MS_UNBINDABLE); + ad.mnt.flags = flags; buffer = aa_get_buffer(false); if (!buffer) return -ENOMEM; - error = fn_for_each_confined(label, profile, - match_mnt(subj_cred, profile, path, buffer, NULL, - NULL, NULL, - flags, NULL, false)); + error = fn_for_each(label, profile, + match_mnt(profile, path, buffer, NULL, NULL, NULL, + flags, NULL, false, &ad)); aa_put_buffer(buffer); return error; @@ -489,6 +428,8 @@ int aa_move_mount(const struct cred *subj_cred, struct aa_profile *profile; char *to_buffer = NULL, *from_buffer = NULL; int error; + DEFINE_AUDIT_MOUNT(ad, OP_MOUNT, subj_cred); + ad.mnt.flags = MS_MOVE; AA_BUG(!label); AA_BUG(!from_path); @@ -503,10 +444,10 @@ int aa_move_mount(const struct cred *subj_cred, if (!our_mnt(from_path->mnt)) /* moving a mount detached from the namespace */ from_path = NULL; - error = fn_for_each_confined(label, profile, - match_mnt(subj_cred, profile, to_path, to_buffer, - from_path, from_buffer, - NULL, MS_MOVE, NULL, false)); + error = fn_for_each(label, profile, + match_mnt(profile, to_path, to_buffer, from_path, + from_buffer, NULL, MS_MOVE, NULL, false, + &ad)); out: aa_put_buffer(to_buffer); aa_put_buffer(from_buffer); @@ -542,6 +483,8 @@ int aa_new_mount(const struct cred *subj_cred, struct aa_label *label, int error; int requires_dev = 0; struct path tmp_path, *dev_path = NULL; + DEFINE_AUDIT_MOUNT(ad, OP_MOUNT, subj_cred); + ad.mnt.flags = flags; AA_BUG(!label); AA_BUG(!path); @@ -578,15 +521,15 @@ int aa_new_mount(const struct cred *subj_cred, struct aa_label *label, error = -ENOMEM; goto out; } - error = fn_for_each_confined(label, profile, - match_mnt(subj_cred, profile, path, buffer, - dev_path, dev_buffer, - type, flags, data, binary)); + error = fn_for_each(label, profile, + match_mnt(profile, path, buffer, dev_path, + dev_buffer, type, flags, data, + binary, &ad)); } else { - error = fn_for_each_confined(label, profile, - match_mnt_path_str(subj_cred, profile, path, - buffer, dev_name, - type, flags, data, binary, NULL)); + error = fn_for_each(label, profile, + match_mnt_path_str(profile, path, buffer, + dev_name, type, flags, data, + binary, NULL, &ad)); } out: @@ -598,13 +541,12 @@ out: return error; } -static int profile_umount(const struct cred *subj_cred, - struct aa_profile *profile, const struct path *path, - char *buffer) +static int profile_umount(struct aa_profile *profile, const struct path *path, + char *buffer, struct apparmor_audit_data *ad) { struct aa_ruleset *rules = profile->label.rules[0]; struct aa_perms perms = { }; - const char *name = NULL, *info = NULL; + const char *name = NULL; aa_state_t state; int error; @@ -614,22 +556,23 @@ static int profile_umount(const struct cred *subj_cred, if (!RULE_MEDIATES(rules, AA_CLASS_MOUNT)) return 0; + /* TODO: lift path_name, need to separate profile path_flags from + * the lookup + */ error = aa_path_name(path, path_flags(profile, path), buffer, &name, - &info, profile->disconnected); + &ad->info, profile->disconnected); if (error) - goto audit; + return aa_audit_perm_error(&profile->label, AA_MAY_UMOUNT, + error, ad, audit_cb); + ad->name = name; state = aa_dfa_match(rules->policy->dfa, rules->policy->start[AA_CLASS_MOUNT], name); perms = *aa_lookup_perms(rules->policy, state); - if (AA_MAY_UMOUNT & ~perms.allow) - error = -EACCES; -audit: - return audit_mount(subj_cred, profile, OP_UMOUNT, name, NULL, NULL, - NULL, 0, NULL, - AA_MAY_UMOUNT, &perms, info, error); + aa_apply_modes_to_perms(profile, &perms); + return aa_check_perms(profile, &perms, AA_MAY_UMOUNT, ad, audit_cb); } int aa_umount(const struct cred *subj_cred, struct aa_label *label, @@ -639,6 +582,7 @@ int aa_umount(const struct cred *subj_cred, struct aa_label *label, char *buffer = NULL; int error; struct path path = { .mnt = mnt, .dentry = mnt->mnt_root }; + DEFINE_AUDIT_MOUNT(ad, OP_UMOUNT, subj_cred); AA_BUG(!label); AA_BUG(!mnt); @@ -647,8 +591,8 @@ int aa_umount(const struct cred *subj_cred, struct aa_label *label, if (!buffer) return -ENOMEM; - error = fn_for_each_confined(label, profile, - profile_umount(subj_cred, profile, &path, buffer)); + error = fn_for_each(label, profile, + profile_umount(profile, &path, buffer, &ad)); aa_put_buffer(buffer); return error; @@ -658,16 +602,15 @@ int aa_umount(const struct cred *subj_cred, struct aa_label *label, * * Returns: label for transition or ERR_PTR. Does not return NULL */ -static struct aa_label *build_pivotroot(const struct cred *subj_cred, - struct aa_profile *profile, +static struct aa_label *build_pivotroot(struct aa_profile *profile, const struct path *new_path, char *new_buffer, const struct path *old_path, - char *old_buffer) + char *old_buffer, + struct apparmor_audit_data *ad) { struct aa_ruleset *rules = profile->label.rules[0]; - const char *old_name, *new_name = NULL, *info = NULL; - const char *trans_name = NULL; + const char *old_name, *new_name = NULL; struct aa_perms perms = { }; aa_state_t state; int error; @@ -681,36 +624,40 @@ static struct aa_label *build_pivotroot(const struct cred *subj_cred, return aa_get_newest_label(&profile->label); error = aa_path_name(old_path, path_flags(profile, old_path), - old_buffer, &old_name, &info, + old_buffer, &old_name, &ad->info, profile->disconnected); if (error) - goto audit; + goto err; + ad->mnt.src_name = old_name; error = aa_path_name(new_path, path_flags(profile, new_path), - new_buffer, &new_name, &info, + new_buffer, &new_name, &ad->info, profile->disconnected); if (error) - goto audit; + goto err; + ad->name = new_name; - error = -EACCES; state = aa_dfa_match(rules->policy->dfa, rules->policy->start[AA_CLASS_MOUNT], new_name); state = aa_dfa_null_transition(rules->policy->dfa, state); state = aa_dfa_match(rules->policy->dfa, state, old_name); perms = *aa_lookup_perms(rules->policy, state); + /* todo: allow pivotroot to specify a transition other than profile */ + ad->mnt.trans = profile->label.hname; - if (AA_MAY_PIVOTROOT & perms.allow) - error = 0; + aa_apply_modes_to_perms(profile, &perms); + error = aa_check_perms(profile, &perms, AA_MAY_PIVOTROOT, ad, audit_cb); -audit: - error = audit_mount(subj_cred, profile, OP_PIVOTROOT, new_name, - old_name, - NULL, trans_name, 0, NULL, AA_MAY_PIVOTROOT, - &perms, info, error); +out: if (error) return ERR_PTR(error); return aa_get_newest_label(&profile->label); + +err: + error = aa_audit_perm_error(&profile->label, AA_MAY_PIVOTROOT, error, + ad, audit_cb); + goto out; } int aa_pivotroot(const struct cred *subj_cred, struct aa_label *label, @@ -719,8 +666,9 @@ int aa_pivotroot(const struct cred *subj_cred, struct aa_label *label, { struct aa_profile *profile; struct aa_label *target = NULL; - char *old_buffer = NULL, *new_buffer = NULL, *info = NULL; + char *old_buffer = NULL, *new_buffer = NULL; int error; + DEFINE_AUDIT_MOUNT(ad, OP_PIVOTROOT, subj_cred); AA_BUG(!label); AA_BUG(!old_path); @@ -732,9 +680,8 @@ int aa_pivotroot(const struct cred *subj_cred, struct aa_label *label, if (!old_buffer || !new_buffer) goto out; target = fn_label_build(label, profile, GFP_KERNEL, - build_pivotroot(subj_cred, profile, new_path, - new_buffer, - old_path, old_buffer)); + build_pivotroot(profile, new_path, new_buffer, + old_path, old_buffer, &ad)); AA_BUG(!target); if (!IS_ERR(target)) { error = aa_replace_current_label(target); @@ -742,7 +689,7 @@ int aa_pivotroot(const struct cred *subj_cred, struct aa_label *label, goto fail; aa_put_label(target); } else - /* already audited error */ + /* already audited error in build_pivotroot */ error = PTR_ERR(target); out: aa_put_buffer(old_buffer); @@ -751,14 +698,12 @@ out: return error; fail: - /* TODO: add back in auditing of new_name and old_name */ - error = fn_for_each(label, profile, - audit_mount(subj_cred, profile, OP_PIVOTROOT, - NULL /*new_name */, - NULL /* old_name */, - NULL, NULL, - 0, target->hname, AA_MAY_PIVOTROOT, &nullperms, info, - error)); + /* TODO: add back in auditing of new_name and old_name, + * needs lifting of name lookup out of profile cb + */ + ad.mnt.trans = target->hname; + error = aa_audit_perm_error(label, AA_MAY_PIVOTROOT, error, &ad, + audit_cb); aa_put_label(target); goto out; } diff --git a/security/apparmor/net.c b/security/apparmor/net.c index cf590dd08540..a333e6aff926 100644 --- a/security/apparmor/net.c +++ b/security/apparmor/net.c @@ -9,6 +9,7 @@ */ #include "include/af_unix.h" +#include "include/af_inet.h" #include "include/apparmor.h" #include "include/audit.h" #include "include/cred.h" @@ -133,12 +134,12 @@ void audit_net_cb(struct audit_buffer *ab, void *va) audit_log_format(ab, " protocol=%d", ad->net.protocol); if (ad->request & NET_PERMS_MASK) { - audit_log_format(ab, " requested_mask="); + audit_log_format(ab, " requested="); aa_audit_perm_mask(ab, ad->request, NULL, 0, net_mask_names, NET_PERMS_MASK); if (ad->denied & NET_PERMS_MASK) { - audit_log_format(ab, " denied_mask="); + audit_log_format(ab, " denied="); aa_audit_perm_mask(ab, ad->denied, NULL, 0, net_mask_names, NET_PERMS_MASK); } @@ -166,7 +167,7 @@ void audit_net_cb(struct audit_buffer *ab, void *va) /* standard permission lookup pattern - supports early bailout */ int aa_do_perms(struct aa_profile *profile, struct aa_policydb *policy, aa_state_t state, u32 request, - struct aa_perms *p, struct apparmor_audit_data *ad) + const struct aa_perms *p, struct apparmor_audit_data *ad) { struct aa_perms perms; @@ -198,7 +199,7 @@ static struct aa_perms *early_match(struct aa_policydb *policy, return p; } -static aa_state_t aa_dfa_match_be16(struct aa_dfa *dfa, aa_state_t state, +static aa_state_t aa_dfa_match_be16(const struct aa_dfa *dfa, aa_state_t state, u16 data) { __be16 buffer = cpu_to_be16(data); @@ -261,14 +262,15 @@ int aa_profile_af_perm(struct aa_profile *profile, AA_BUG(type < 0 || type >= SOCK_MAX); AA_BUG(profile_unconfined(profile)); - if (profile_unconfined(profile)) - return 0; state = RULE_MEDIATES_NET(rules); - if (!state) - return 0; - state = aa_match_to_prot(rules->policy, state, request, family, type, - protocol, &p, &ad->info); - return aa_do_perms(profile, rules->policy, state, request, p, ad); + if (state) { + state = aa_match_to_prot(rules->policy, state, request, family, + type, protocol, &p, &ad->info); + return aa_do_perms(profile, rules->policy, state, request, p, + ad); + } /* else */ + + return 0; } int aa_af_perm(const struct cred *subj_cred, struct aa_label *label, @@ -282,10 +284,8 @@ int aa_af_perm(const struct cred *subj_cred, struct aa_label *label, type, protocol)); } -static int aa_label_sk_perm(const struct cred *subj_cred, - struct aa_label *label, - const char *op, u32 request, - struct sock *sk) +int aa_label_sk_perm(const struct cred *subj_cred, struct aa_label *label, + const char *op, u32 request, const struct sock *sk) { struct aa_sk_ctx *ctx = aa_sock(sk); int error = 0; @@ -299,24 +299,26 @@ static int aa_label_sk_perm(const struct cred *subj_cred, ad.subj_cred = subj_cred; error = fn_for_each_confined(label, profile, - aa_profile_af_sk_perm(profile, &ad, request, sk)); + aa_profile_af_perm(profile, &ad, request, sk->sk_family, + sk->sk_type, sk->sk_protocol)); } return error; } -int aa_sk_perm(const char *op, u32 request, struct sock *sk) +int aa_sk_perm(const char *op, u32 request, const struct sock *sk) { struct aa_label *label; + bool needput; int error; AA_BUG(!sk); AA_BUG(in_interrupt()); /* TODO: switch to begin_current_label ???? */ - label = begin_current_label_crit_section(); + label = begin_current_label_crit_section(&needput); error = aa_label_sk_perm(current_cred(), label, op, request, sk); - end_current_label_crit_section(label); + end_current_label_crit_section(label, needput); return error; } @@ -333,8 +335,13 @@ int aa_sock_file_perm(const struct cred *subj_cred, struct aa_label *label, if (!sock || !sock->sk) return 0; - if (sock->sk->sk_family == PF_UNIX) + switch (sock->sk->sk_family) { + case PF_UNIX: return aa_unix_file_perm(subj_cred, label, op, request, file); + case PF_INET: + case PF_INET6: + return aa_inet_file_perm(subj_cred, label, op, request, sock); + } return aa_label_sk_perm(subj_cred, label, op, request, sock->sk); } diff --git a/security/apparmor/policy.c b/security/apparmor/policy.c index 94b4a7e727cc..397f9ff78aeb 100644 --- a/security/apparmor/policy.c +++ b/security/apparmor/policy.c @@ -81,6 +81,7 @@ #include "include/file.h" #include "include/ipc.h" #include "include/match.h" +#include "include/net.h" #include "include/path.h" #include "include/policy.h" #include "include/policy_ns.h" @@ -719,7 +720,7 @@ struct aa_profile *aa_alloc_null(struct aa_profile *parent, const char *name, } /** - * aa_new_learning_profile - create or find a null-X learning profile + * __aa_new_learning_profile - create or find a null-X learning profile * @parent: profile that caused this profile to be created (NOT NULL) * @hat: true if the null- learning profile is a hat * @base: name to base the null profile off of @@ -736,8 +737,9 @@ struct aa_profile *aa_alloc_null(struct aa_profile *parent, const char *name, * * Returns: new refcounted profile else NULL on failure */ -struct aa_profile *aa_new_learning_profile(struct aa_profile *parent, bool hat, - const char *base, gfp_t gfp) +struct aa_profile *__aa_new_learning_profile(struct aa_profile *parent, + bool hat, const char *base, + gfp_t gfp) { struct aa_profile *p, *profile; const char *bname; @@ -745,6 +747,7 @@ struct aa_profile *aa_new_learning_profile(struct aa_profile *parent, bool hat, size_t name_sz; AA_BUG(!parent); + AA_BUG(!mutex_is_locked(&parent->ns->lock)); if (base) { name_sz = strlen(parent->base.hname) + 8 + strlen(base); @@ -778,7 +781,6 @@ name: if (hat) profile->label.flags |= FLAG_HAT; - mutex_lock_nested(&profile->ns->lock, profile->ns->level); p = __find_child(&parent->base.profiles, bname); if (p) { aa_free_profile(profile); @@ -786,7 +788,6 @@ name: } else { __add_profile(&parent->base.profiles, profile); } - mutex_unlock(&profile->ns->lock); /* refcount released by caller */ out: @@ -800,6 +801,18 @@ fail: return NULL; } +struct aa_profile *aa_new_learning_profile(struct aa_profile *parent, bool hat, + const char *base, gfp_t gfp) +{ + struct aa_profile *profile; + + mutex_lock_nested(&parent->ns->lock, parent->ns->level); + profile = __aa_new_learning_profile(parent, hat, base, gfp); + mutex_unlock(&parent->ns->lock); + + return profile; +} + /** * replacement_allowed - test to see if replacement is allowed * @profile: profile to test if it can be replaced (MAYBE NULL) @@ -1158,6 +1171,8 @@ static struct aa_profile *update_to_newest_parent(struct aa_profile *new) * @label: label that is attempting to load/replace policy * @mask: permission mask * @udata: serialized data stream (NOT NULL) + * @compressed_profile: The userspace-provided compressed profile. May be NULL + * @compressed_size: If compressed_data is not NULL, the compressed data size * * unpack and replace a profile on the profile list and uses of that profile * by any task creds via invalidating the old version of the profile, which @@ -1167,7 +1182,8 @@ static struct aa_profile *update_to_newest_parent(struct aa_profile *new) * Returns: size of data consumed else error code on failure. */ ssize_t aa_replace_profiles(struct aa_ns *policy_ns, struct aa_label *label, - u32 mask, struct aa_loaddata *udata) + u32 mask, struct aa_loaddata *udata, + char *compressed_profile, size_t compressed_size) { const char *ns_name = NULL, *info = NULL; struct aa_ns *ns = NULL; @@ -1180,7 +1196,7 @@ ssize_t aa_replace_profiles(struct aa_ns *policy_ns, struct aa_label *label, op = mask & AA_MAY_REPLACE_POLICY ? OP_PROF_REPL : OP_PROF_LOAD; aa_get_profile_loaddata(udata); /* released below */ - error = aa_unpack(udata, &lh, &ns_name); + error = aa_unpack(udata, &lh, &ns_name, compressed_profile, compressed_size); if (error) goto out; diff --git a/security/apparmor/policy_compat.c b/security/apparmor/policy_compat.c index 5fc16d56fbf4..94e4b781d33c 100644 --- a/security/apparmor/policy_compat.c +++ b/security/apparmor/policy_compat.c @@ -97,7 +97,7 @@ static u32 map_old_perms(u32 old) return new; } -static void compute_fperms_allow(struct aa_perms *perms, struct aa_dfa *dfa, +static void compute_fperms_allow(struct aa_perms *perms, const struct aa_dfa *dfa, aa_state_t state) { perms->allow |= AA_MAY_GETATTR; @@ -109,7 +109,7 @@ static void compute_fperms_allow(struct aa_perms *perms, struct aa_dfa *dfa, perms->allow |= AA_MAY_ONEXEC; } -static struct aa_perms compute_fperms_user(struct aa_dfa *dfa, +static struct aa_perms compute_fperms_user(const struct aa_dfa *dfa, aa_state_t state) { struct aa_perms perms = { }; @@ -124,7 +124,7 @@ static struct aa_perms compute_fperms_user(struct aa_dfa *dfa, return perms; } -static struct aa_perms compute_fperms_other(struct aa_dfa *dfa, +static struct aa_perms compute_fperms_other(const struct aa_dfa *dfa, aa_state_t state) { struct aa_perms perms = { }; @@ -147,7 +147,7 @@ static struct aa_perms compute_fperms_other(struct aa_dfa *dfa, * * Returns: remapped perm table */ -static struct aa_perms *compute_fperms(struct aa_dfa *dfa, +static struct aa_perms *compute_fperms(const struct aa_dfa *dfa, u32 *size) { aa_state_t state; @@ -171,7 +171,7 @@ static struct aa_perms *compute_fperms(struct aa_dfa *dfa, return table; } -static struct aa_perms *compute_xmatch_perms(struct aa_dfa *xmatch, +static struct aa_perms *compute_xmatch_perms(const struct aa_dfa *xmatch, u32 *size) { struct aa_perms *perms; @@ -207,7 +207,7 @@ static u32 map_xbits(u32 x) ((x & 0x7e) << 9); } -static struct aa_perms compute_perms_entry(struct aa_dfa *dfa, +static struct aa_perms compute_perms_entry(const struct aa_dfa *dfa, aa_state_t state, u32 version) { @@ -246,7 +246,7 @@ static struct aa_perms compute_perms_entry(struct aa_dfa *dfa, return perms; } -static struct aa_perms *compute_perms(struct aa_dfa *dfa, u32 version, +static struct aa_perms *compute_perms(const struct aa_dfa *dfa, u32 version, u32 *size) { unsigned int state; diff --git a/security/apparmor/policy_unpack.c b/security/apparmor/policy_unpack.c index d9dcff167c48..f1fc48e72d0e 100644 --- a/security/apparmor/policy_unpack.c +++ b/security/apparmor/policy_unpack.c @@ -25,6 +25,7 @@ #include "include/crypto.h" #include "include/file.h" #include "include/match.h" +#include "include/net.h" #include "include/path.h" #include "include/policy.h" #include "include/policy_unpack.h" @@ -730,7 +731,7 @@ static bool verify_tags(struct aa_tags_struct *tags, const char **info) /* count followed by count indexes into hdrs */ u32 cnt = tags->sets.table[i]; - if (i+cnt >= tags->sets.size) { + if ((u64)i + cnt >= tags->sets.size) { AA_DEBUG(DEBUG_UNPACK, "tagset too large %d+%d > sets.table[%d]", i, cnt, tags->sets.size); @@ -1482,7 +1483,7 @@ static int verify_header(struct aa_ext *e, int required, const char **ns) * @dfa: the dfa to check accept indexes are in range * @table_size: the permission table size the indexes should be within */ -static bool verify_dfa_accept_index(struct aa_dfa *dfa, int table_size) +static bool verify_dfa_accept_index(const struct aa_dfa *dfa, int table_size) { int i; for (i = 0; i < dfa->tables[YYTD_ID_ACCEPT]->td_lolen; i++) { @@ -1492,7 +1493,7 @@ static bool verify_dfa_accept_index(struct aa_dfa *dfa, int table_size) return true; } -static bool verify_perm(struct aa_perms *perm) +static bool verify_perm(const struct aa_perms *perm) { /* TODO: allow option to just force the perms into a valid state */ if (perm->allow & perm->deny) @@ -1717,6 +1718,8 @@ static int compress_loaddata(struct aa_loaddata *data) * @udata: user data copied to kmem (NOT NULL) * @lh: list to place unpacked profiles in a aa_repl_ws * @ns: Returns namespace profile is in if specified else NULL (NOT NULL) + * @compressed_data: The userspace-provided compressed data. May be NULL + * @compressed_size: If compressed_data is not NULL, the compressed data size * * Unpack user data and return refcounted allocated profile(s) stored in * @lh in order of discovery, with the list chain stored in base.list @@ -1725,12 +1728,12 @@ static int compress_loaddata(struct aa_loaddata *data) * Returns: profile(s) on @lh else error pointer if fails to unpack */ int aa_unpack(struct aa_loaddata *udata, struct list_head *lh, - const char **ns) + const char **ns, char *compressed_data, size_t compressed_size) { struct aa_load_ent *tmp, *ent; struct aa_profile *profile = NULL; char *ns_name = NULL; - int error; + int error = 0; struct aa_ext e = { .start = udata->data, .end = udata->data + udata->size, @@ -1783,10 +1786,23 @@ int aa_unpack(struct aa_loaddata *udata, struct list_head *lh, } if (aa_g_export_binary) { - error = compress_loaddata(udata); + /* Do we have userspace-compressed data? */ + if (compressed_data) { + kvfree(udata->data); + udata->data = compressed_data; + udata->compressed_size = compressed_size; + compressed_data = NULL; /* consumed */ + + } else + error = compress_loaddata(udata); + if (error) goto fail; + } else if (compressed_data) { + kvfree(compressed_data); + compressed_data = NULL; } + return 0; fail_profile: @@ -1794,6 +1810,8 @@ fail_profile: aa_put_profile(profile); fail: + if (compressed_data) + kvfree(compressed_data); list_for_each_entry_safe(ent, tmp, lh, list) { list_del_init(&ent->list); aa_load_ent_free(ent); diff --git a/security/apparmor/task.c b/security/apparmor/task.c index b9fb3738124e..e16ff4130bc2 100644 --- a/security/apparmor/task.c +++ b/security/apparmor/task.c @@ -14,6 +14,7 @@ #include <linux/gfp.h> #include <linux/ptrace.h> +#include <linux/task_work.h> #include "include/path.h" #include "include/audit.h" @@ -89,6 +90,32 @@ int aa_replace_current_label(struct aa_label *label) return 0; } +static void aa_replace_stale_label_tw_func(struct callback_head *tw) +{ + struct aa_task_ctx *ctx = task_ctx(current); + struct aa_label *label; + + ctx->label_replacement_pending = false; + label = aa_current_raw_label(); + if (!label_is_stale(label)) + return; + label = aa_get_newest_label(label); + aa_replace_current_label(label); + aa_put_label(label); +} + +/* replace the current task's stale label on syscall return */ +void aa_schedule_stale_label_replacement(void) +{ + struct aa_task_ctx *ctx = task_ctx(current); + + if (ctx->label_replacement_pending) + return; + init_task_work(&ctx->label_replacement_tw, aa_replace_stale_label_tw_func); + if (task_work_add(current, &ctx->label_replacement_tw, TWA_RESUME) == 0) + ctx->label_replacement_pending = true; +} + /** * aa_set_current_onexec - set the tasks change_profile to happen onexec diff --git a/security/integrity/ima/Kconfig b/security/integrity/ima/Kconfig index b3a9f86809b0..72654cf797cd 100644 --- a/security/integrity/ima/Kconfig +++ b/security/integrity/ima/Kconfig @@ -46,12 +46,16 @@ config IMA_KEXEC config IMA_MEASURE_PCR_IDX int + prompt "PCR Index for Aggregate" if EXPERT range 8 14 default 10 help IMA_MEASURE_PCR_IDX determines the TPM PCR register index that IMA uses to maintain the integrity aggregate of the - measurement list. If unsure, use the default 10. + measurement list. Most attestation tooling expects PCR 10. + + The default is almost always what you want. Only change this + if you know what you are doing. config IMA_LSM_RULES bool diff --git a/security/integrity/ima/ima_appraise.c b/security/integrity/ima/ima_appraise.c index ced2e131b061..b280488e15fc 100644 --- a/security/integrity/ima/ima_appraise.c +++ b/security/integrity/ima/ima_appraise.c @@ -748,6 +748,8 @@ static int validate_hash_algo(struct dentry *dentry, return -EACCES; path = dentry_path(dentry, pathbuf, PATH_MAX); + if (IS_ERR(path)) + path = NULL; integrity_audit_msg(AUDIT_INTEGRITY_DATA, d_inode(dentry), path, "set_data", errmsg, -EACCES, 0); diff --git a/security/integrity/ima/ima_policy.c b/security/integrity/ima/ima_policy.c index f79d07bb63c6..68d9a5e6c232 100644 --- a/security/integrity/ima/ima_policy.c +++ b/security/integrity/ima/ima_policy.c @@ -165,7 +165,10 @@ static struct ima_rule_entry dont_measure_rules[] __ro_after_init = { {.action = DONT_MEASURE, .fsmagic = CGROUP2_SUPER_MAGIC, .flags = IMA_FSMAGIC}, {.action = DONT_MEASURE, .fsmagic = NSFS_MAGIC, .flags = IMA_FSMAGIC}, - {.action = DONT_MEASURE, .fsmagic = EFIVARFS_MAGIC, .flags = IMA_FSMAGIC} + {.action = DONT_MEASURE, .fsmagic = EFIVARFS_MAGIC, + .flags = IMA_FSMAGIC}, + {.action = DONT_MEASURE, .fsmagic = CONFIGFS_MAGIC, + .flags = IMA_FSMAGIC} }; static struct ima_rule_entry original_measurement_rules[] __ro_after_init = { @@ -211,6 +214,8 @@ static struct ima_rule_entry default_appraise_rules[] __ro_after_init = { {.action = DONT_APPRAISE, .fsmagic = EFIVARFS_MAGIC, .flags = IMA_FSMAGIC}, {.action = DONT_APPRAISE, .fsmagic = CGROUP_SUPER_MAGIC, .flags = IMA_FSMAGIC}, {.action = DONT_APPRAISE, .fsmagic = CGROUP2_SUPER_MAGIC, .flags = IMA_FSMAGIC}, + {.action = DONT_APPRAISE, .fsmagic = CONFIGFS_MAGIC, + .flags = IMA_FSMAGIC}, #ifdef CONFIG_IMA_WRITE_POLICY {.action = APPRAISE, .func = POLICY_CHECK, .flags = IMA_FUNC | IMA_DIGSIG_REQUIRED}, diff --git a/security/integrity/ima/ima_queue.c b/security/integrity/ima/ima_queue.c index f89f0ca3d4ed..0f1b7e4113c4 100644 --- a/security/integrity/ima/ima_queue.c +++ b/security/integrity/ima/ima_queue.c @@ -75,8 +75,7 @@ static struct hlist_head *ima_alloc_replace_htable(void) struct hlist_head *old_htable, *new_htable; /* Initializing to zeros is equivalent to call HLIST_HEAD_INIT. */ - new_htable = kcalloc(IMA_MEASURE_HTABLE_SIZE, sizeof(struct hlist_head), - GFP_KERNEL); + new_htable = kzalloc_objs(struct hlist_head, IMA_MEASURE_HTABLE_SIZE); if (!new_htable) return ERR_PTR(-ENOMEM); diff --git a/security/keys/trusted-keys/trusted_tpm1.c b/security/keys/trusted-keys/trusted_tpm1.c index 13513819991e..bf0bf7f36970 100644 --- a/security/keys/trusted-keys/trusted_tpm1.c +++ b/security/keys/trusted-keys/trusted_tpm1.c @@ -18,7 +18,6 @@ #include <keys/trusted-type.h> #include <linux/key-type.h> #include <linux/tpm.h> -#include <linux/tpm_command.h> #include <keys/trusted_tpm.h> @@ -317,9 +316,8 @@ static int TSS_checkhmac2(unsigned char *buffer, * For key specific tpm requests, we will generate and send our * own TPM command packets using the drivers send function. */ -static int trusted_tpm_send(unsigned char *cmd, size_t buflen) +static int trusted_tpm_send(struct tpm_buf *buf) { - struct tpm_buf buf; int rc; if (!chip) @@ -329,12 +327,9 @@ static int trusted_tpm_send(unsigned char *cmd, size_t buflen) if (rc) return rc; - buf.flags = 0; - buf.length = buflen; - buf.data = cmd; - dump_tpm_buf(cmd); - rc = tpm_transmit_cmd(chip, &buf, 4, "sending data"); - dump_tpm_buf(cmd); + dump_tpm_buf(buf->data); + rc = tpm_transmit_cmd(chip, buf, 4, "sending data"); + dump_tpm_buf(buf->data); if (rc > 0) /* TPM error */ @@ -380,7 +375,7 @@ static int osap(struct tpm_buf *tb, struct osapsess *s, tpm_buf_append_u32(tb, handle); tpm_buf_append(tb, ononce, TPM_NONCE_SIZE); - ret = trusted_tpm_send(tb->data, tb->length); + ret = trusted_tpm_send(tb); if (ret < 0) return ret; @@ -404,7 +399,7 @@ static int oiap(struct tpm_buf *tb, uint32_t *handle, unsigned char *nonce) return -ENODEV; tpm_buf_reset(tb, TPM_TAG_RQU_COMMAND, TPM_ORD_OIAP); - ret = trusted_tpm_send(tb->data, tb->length); + ret = trusted_tpm_send(tb); if (ret < 0) return ret; @@ -513,7 +508,7 @@ static int tpm_seal(struct tpm_buf *tb, uint16_t keytype, tpm_buf_append_u8(tb, cont); tpm_buf_append(tb, td->pubauth, SHA1_DIGEST_SIZE); - ret = trusted_tpm_send(tb->data, tb->length); + ret = trusted_tpm_send(tb); if (ret < 0) goto out; @@ -604,7 +599,7 @@ static int tpm_unseal(struct tpm_buf *tb, tpm_buf_append_u8(tb, cont); tpm_buf_append(tb, authdata2, SHA1_DIGEST_SIZE); - ret = trusted_tpm_send(tb->data, tb->length); + ret = trusted_tpm_send(tb); if (ret < 0) { pr_info("authhmac failed (%d)\n", ret); return ret; @@ -631,23 +626,23 @@ static int tpm_unseal(struct tpm_buf *tb, static int key_seal(struct trusted_key_payload *p, struct trusted_key_options *o) { - struct tpm_buf tb; int ret; - ret = tpm_buf_init(&tb, 0, 0); - if (ret) - return ret; + struct tpm_buf *tb __free(kfree) = kzalloc(TPM_BUFSIZE, GFP_KERNEL); + if (!tb) + return -ENOMEM; + + tpm_buf_init(tb, TPM_BUFSIZE); /* include migratable flag at end of sealed key */ p->key[p->key_len] = p->migratable; - ret = tpm_seal(&tb, o->keytype, o->keyhandle, o->keyauth, + ret = tpm_seal(tb, o->keytype, o->keyhandle, o->keyauth, p->key, p->key_len + 1, p->blob, &p->blob_len, o->blobauth, o->pcrinfo, o->pcrinfo_len); if (ret < 0) pr_info("srkseal failed (%d)\n", ret); - tpm_buf_destroy(&tb); return ret; } @@ -657,14 +652,15 @@ static int key_seal(struct trusted_key_payload *p, static int key_unseal(struct trusted_key_payload *p, struct trusted_key_options *o) { - struct tpm_buf tb; int ret; - ret = tpm_buf_init(&tb, 0, 0); - if (ret) - return ret; + struct tpm_buf *tb __free(kfree) = kzalloc(TPM_BUFSIZE, GFP_KERNEL); + if (!tb) + return -ENOMEM; + + tpm_buf_init(tb, TPM_BUFSIZE); - ret = tpm_unseal(&tb, o->keyhandle, o->keyauth, p->blob, p->blob_len, + ret = tpm_unseal(tb, o->keyhandle, o->keyauth, p->blob, p->blob_len, o->blobauth, p->key, &p->key_len); if (ret < 0) pr_info("srkunseal failed (%d)\n", ret); @@ -672,7 +668,6 @@ static int key_unseal(struct trusted_key_payload *p, /* pull migratable flag out of sealed key */ p->migratable = p->key[--p->key_len]; - tpm_buf_destroy(&tb); return ret; } @@ -987,9 +982,9 @@ err_put: static void trusted_tpm_exit(void) { if (chip) { + unregister_key_type(&key_type_trusted); put_device(&chip->dev); kfree(digests); - unregister_key_type(&key_type_trusted); } } diff --git a/security/keys/trusted-keys/trusted_tpm2.c b/security/keys/trusted-keys/trusted_tpm2.c index 6340823f8b53..67225dd562a9 100644 --- a/security/keys/trusted-keys/trusted_tpm2.c +++ b/security/keys/trusted-keys/trusted_tpm2.c @@ -9,7 +9,6 @@ #include <linux/string.h> #include <linux/err.h> #include <linux/tpm.h> -#include <linux/tpm_command.h> #include <keys/trusted-type.h> #include <keys/trusted_tpm.h> @@ -234,7 +233,8 @@ int tpm2_seal_trusted(struct tpm_chip *chip, struct trusted_key_options *options) { off_t offset = TPM_HEADER_SIZE; - struct tpm_buf buf, sized; + struct tpm_buf *buf __free(kfree) = NULL; + struct tpm_buf *sized __free(kfree) = NULL; int blob_len = 0; int hash; u32 flags; @@ -255,97 +255,100 @@ int tpm2_seal_trusted(struct tpm_chip *chip, if (rc) goto out_put; - rc = tpm_buf_init(&buf, TPM2_ST_SESSIONS, TPM2_CC_CREATE); - if (rc) { + buf = kzalloc(TPM_BUFSIZE, GFP_KERNEL); + if (!buf) { + rc = -ENOMEM; tpm2_end_auth_session(chip); goto out_put; } - rc = tpm_buf_init_sized(&sized); - if (rc) { - tpm_buf_destroy(&buf); + tpm_buf_init(buf, TPM_BUFSIZE); + tpm_buf_reset(buf, TPM2_ST_SESSIONS, TPM2_CC_CREATE); + + sized = kzalloc(TPM_BUFSIZE, GFP_KERNEL); + if (!sized) { + rc = -ENOMEM; tpm2_end_auth_session(chip); goto out_put; } - rc = tpm_buf_append_name(chip, &buf, options->keyhandle, NULL); + tpm_buf_init_sized(sized, TPM_BUFSIZE); + + rc = tpm_buf_append_name(chip, buf, options->keyhandle, NULL); if (rc) goto out; - tpm_buf_append_hmac_session(chip, &buf, TPM2_SA_DECRYPT, + tpm_buf_append_hmac_session(chip, buf, TPM2_SA_DECRYPT, options->keyauth, TPM_DIGEST_SIZE); /* sensitive */ - tpm_buf_append_u16(&sized, options->blobauth_len); + tpm_buf_append_u16(sized, options->blobauth_len); if (options->blobauth_len) - tpm_buf_append(&sized, options->blobauth, options->blobauth_len); + tpm_buf_append(sized, options->blobauth, options->blobauth_len); - tpm_buf_append_u16(&sized, payload->key_len); - tpm_buf_append(&sized, payload->key, payload->key_len); - tpm_buf_append(&buf, sized.data, sized.length); + tpm_buf_append_u16(sized, payload->key_len); + tpm_buf_append(sized, payload->key, payload->key_len); + tpm_buf_append(buf, sized->data, sized->length); /* public */ - tpm_buf_reset_sized(&sized); - tpm_buf_append_u16(&sized, TPM_ALG_KEYEDHASH); - tpm_buf_append_u16(&sized, hash); + tpm_buf_reset_sized(sized); + tpm_buf_append_u16(sized, TPM_ALG_KEYEDHASH); + tpm_buf_append_u16(sized, hash); /* key properties */ flags = 0; flags |= options->policydigest_len ? 0 : TPM2_OA_USER_WITH_AUTH; flags |= payload->migratable ? 0 : (TPM2_OA_FIXED_TPM | TPM2_OA_FIXED_PARENT); - tpm_buf_append_u32(&sized, flags); + tpm_buf_append_u32(sized, flags); /* policy */ - tpm_buf_append_u16(&sized, options->policydigest_len); + tpm_buf_append_u16(sized, options->policydigest_len); if (options->policydigest_len) - tpm_buf_append(&sized, options->policydigest, options->policydigest_len); + tpm_buf_append(sized, options->policydigest, options->policydigest_len); /* public parameters */ - tpm_buf_append_u16(&sized, TPM_ALG_NULL); - tpm_buf_append_u16(&sized, 0); + tpm_buf_append_u16(sized, TPM_ALG_NULL); + tpm_buf_append_u16(sized, 0); - tpm_buf_append(&buf, sized.data, sized.length); + tpm_buf_append(buf, sized->data, sized->length); /* outside info */ - tpm_buf_append_u16(&buf, 0); + tpm_buf_append_u16(buf, 0); /* creation PCR */ - tpm_buf_append_u32(&buf, 0); + tpm_buf_append_u32(buf, 0); - if (buf.flags & TPM_BUF_OVERFLOW) { + if (buf->flags & TPM_BUF_INVALID) { rc = -E2BIG; tpm2_end_auth_session(chip); goto out; } - rc = tpm_buf_fill_hmac_session(chip, &buf); + rc = tpm_buf_fill_hmac_session(chip, buf); if (rc) goto out; - rc = tpm_transmit_cmd(chip, &buf, 4, "sealing data"); - rc = tpm_buf_check_hmac_response(chip, &buf, rc); + rc = tpm_transmit_cmd(chip, buf, 4, "sealing data"); + rc = tpm_buf_check_hmac_response(chip, buf, rc); if (rc) goto out; - blob_len = tpm_buf_read_u32(&buf, &offset); - if (blob_len > MAX_BLOB_SIZE || buf.flags & TPM_BUF_BOUNDARY_ERROR) { + blob_len = tpm_buf_read_u32(buf, &offset); + if (blob_len > MAX_BLOB_SIZE || buf->flags & TPM_BUF_INVALID) { rc = -E2BIG; goto out; } - if (buf.length - offset < blob_len) { + if (buf->length - offset < blob_len) { rc = -EFAULT; goto out; } - blob_len = tpm2_key_encode(payload, options, &buf.data[offset], blob_len); + blob_len = tpm2_key_encode(payload, options, &buf->data[offset], blob_len); if (blob_len < 0) rc = blob_len; out: - tpm_buf_destroy(&sized); - tpm_buf_destroy(&buf); - if (!rc) payload->blob_len = blob_len; @@ -373,7 +376,7 @@ static int tpm2_load_cmd(struct tpm_chip *chip, u32 *blob_handle) { u8 *blob_ref __free(kfree) = NULL; - struct tpm_buf buf; + struct tpm_buf *buf __free(kfree) = NULL; unsigned int private_len; unsigned int public_len; unsigned int blob_len; @@ -427,39 +430,38 @@ static int tpm2_load_cmd(struct tpm_chip *chip, if (rc) return rc; - rc = tpm_buf_init(&buf, TPM2_ST_SESSIONS, TPM2_CC_LOAD); - if (rc) { + buf = kzalloc(TPM_BUFSIZE, GFP_KERNEL); + if (!buf) { tpm2_end_auth_session(chip); - return rc; + return -ENOMEM; } - rc = tpm_buf_append_name(chip, &buf, options->keyhandle, NULL); + tpm_buf_init(buf, TPM_BUFSIZE); + tpm_buf_reset(buf, TPM2_ST_SESSIONS, TPM2_CC_LOAD); + + rc = tpm_buf_append_name(chip, buf, options->keyhandle, NULL); if (rc) - goto out; + return rc; - tpm_buf_append_hmac_session(chip, &buf, 0, options->keyauth, + tpm_buf_append_hmac_session(chip, buf, 0, options->keyauth, TPM_DIGEST_SIZE); - tpm_buf_append(&buf, blob, blob_len); + tpm_buf_append(buf, blob, blob_len); - if (buf.flags & TPM_BUF_OVERFLOW) { - rc = -E2BIG; + if (buf->flags & TPM_BUF_INVALID) { tpm2_end_auth_session(chip); - goto out; + return -E2BIG; } - rc = tpm_buf_fill_hmac_session(chip, &buf); + rc = tpm_buf_fill_hmac_session(chip, buf); if (rc) - goto out; + return rc; - rc = tpm_transmit_cmd(chip, &buf, 4, "loading blob"); - rc = tpm_buf_check_hmac_response(chip, &buf, rc); + rc = tpm_transmit_cmd(chip, buf, 4, "loading blob"); + rc = tpm_buf_check_hmac_response(chip, buf, rc); if (!rc) *blob_handle = be32_to_cpup( - (__be32 *) &buf.data[TPM_HEADER_SIZE]); - -out: - tpm_buf_destroy(&buf); + (__be32 *)&buf->data[TPM_HEADER_SIZE]); return tpm_ret_to_err(rc); } @@ -482,7 +484,7 @@ static int tpm2_unseal_cmd(struct tpm_chip *chip, u32 blob_handle) { struct tpm_header *head; - struct tpm_buf buf; + struct tpm_buf *buf __free(kfree) = NULL; u16 data_len; int offset; u8 *data; @@ -492,18 +494,21 @@ static int tpm2_unseal_cmd(struct tpm_chip *chip, if (rc) return rc; - rc = tpm_buf_init(&buf, TPM2_ST_SESSIONS, TPM2_CC_UNSEAL); - if (rc) { + buf = kzalloc(TPM_BUFSIZE, GFP_KERNEL); + if (!buf) { tpm2_end_auth_session(chip); - return rc; + return -ENOMEM; } - rc = tpm_buf_append_name(chip, &buf, blob_handle, NULL); + tpm_buf_init(buf, TPM_BUFSIZE); + tpm_buf_reset(buf, TPM2_ST_SESSIONS, TPM2_CC_UNSEAL); + + rc = tpm_buf_append_name(chip, buf, blob_handle, NULL); if (rc) - goto out; + return rc; if (!options->policyhandle) { - tpm_buf_append_hmac_session(chip, &buf, TPM2_SA_ENCRYPT, + tpm_buf_append_hmac_session(chip, buf, TPM2_SA_ENCRYPT, options->blobauth, options->blobauth_len); } else { @@ -518,39 +523,36 @@ static int tpm2_unseal_cmd(struct tpm_chip *chip, * could repeat our actions with the exfiltrated * password. */ - tpm2_buf_append_auth(&buf, options->policyhandle, + tpm2_buf_append_auth(buf, options->policyhandle, NULL /* nonce */, 0, 0, options->blobauth, options->blobauth_len); if (tpm2_chip_auth(chip)) { - tpm_buf_append_hmac_session(chip, &buf, TPM2_SA_ENCRYPT, NULL, 0); + tpm_buf_append_hmac_session(chip, buf, TPM2_SA_ENCRYPT, + NULL, 0); } else { - offset = buf.handles * 4 + TPM_HEADER_SIZE; - head = (struct tpm_header *)buf.data; - if (tpm_buf_length(&buf) == offset) + offset = buf->handles * 4 + TPM_HEADER_SIZE; + head = (struct tpm_header *)buf->data; + if (tpm_buf_length(buf) == offset) head->tag = cpu_to_be16(TPM2_ST_NO_SESSIONS); } } - rc = tpm_buf_fill_hmac_session(chip, &buf); + rc = tpm_buf_fill_hmac_session(chip, buf); if (rc) - goto out; + return rc; - rc = tpm_transmit_cmd(chip, &buf, 6, "unsealing"); - rc = tpm_buf_check_hmac_response(chip, &buf, rc); + rc = tpm_transmit_cmd(chip, buf, 6, "unsealing"); + rc = tpm_buf_check_hmac_response(chip, buf, rc); if (!rc) { data_len = be16_to_cpup( - (__be16 *) &buf.data[TPM_HEADER_SIZE + 4]); - if (data_len < MIN_KEY_SIZE || data_len > MAX_KEY_SIZE) { - rc = -EFAULT; - goto out; - } + (__be16 *)&buf->data[TPM_HEADER_SIZE + 4]); + if (data_len < MIN_KEY_SIZE || data_len > MAX_KEY_SIZE) + return -EFAULT; - if (tpm_buf_length(&buf) < TPM_HEADER_SIZE + 6 + data_len) { - rc = -EFAULT; - goto out; - } - data = &buf.data[TPM_HEADER_SIZE + 6]; + if (tpm_buf_length(buf) < TPM_HEADER_SIZE + 6 + data_len) + return -EFAULT; + data = &buf->data[TPM_HEADER_SIZE + 6]; if (payload->old_format) { /* migratable flag is at the end of the key */ @@ -567,8 +569,6 @@ static int tpm2_unseal_cmd(struct tpm_chip *chip, } } -out: - tpm_buf_destroy(&buf); return tpm_ret_to_err(rc); } diff --git a/security/landlock/Kconfig b/security/landlock/Kconfig index 3f1493402052..7aeac29160e8 100644 --- a/security/landlock/Kconfig +++ b/security/landlock/Kconfig @@ -21,6 +21,11 @@ config SECURITY_LANDLOCK you should also prepend "landlock," to the content of CONFIG_LSM to enable Landlock at boot time. +config SECURITY_LANDLOCK_LOG + bool + depends on SECURITY_LANDLOCK + default y if AUDIT || TRACEPOINTS + config SECURITY_LANDLOCK_KUNIT_TEST bool "KUnit tests for Landlock" if !KUNIT_ALL_TESTS depends on KUNIT=y diff --git a/security/landlock/Makefile b/security/landlock/Makefile index ffa7646d99f3..2711f4876939 100644 --- a/security/landlock/Makefile +++ b/security/landlock/Makefile @@ -8,11 +8,15 @@ landlock-y := \ cred.o \ task.o \ fs.o \ - tsync.o + tsync.o \ + domain.o landlock-$(CONFIG_INET) += net.o -landlock-$(CONFIG_AUDIT) += \ +landlock-$(CONFIG_SECURITY_LANDLOCK_LOG) += \ id.o \ - audit.o \ - domain.o + log.o + +landlock-$(CONFIG_AUDIT) += audit.o + +landlock-$(CONFIG_TRACEPOINTS) += trace.o diff --git a/security/landlock/access.h b/security/landlock/access.h index d926078bf0a5..bbbb41f41147 100644 --- a/security/landlock/access.h +++ b/security/landlock/access.h @@ -19,7 +19,7 @@ /* * All access rights that are denied by default whether they are handled or not - * by a ruleset/layer. This must be ORed with all ruleset->access_masks[] + * by a ruleset/layer. This must be ORed with all domain->handled_masks[] * entries when we need to get the absolute handled access masks, see * landlock_upgrade_handled_access_masks(). */ @@ -74,13 +74,13 @@ struct layer_mask { * @access: The unfulfilled access rights for this layer. */ access_mask_t access : LANDLOCK_NUM_ACCESS_MAX; -#ifdef CONFIG_AUDIT +#ifdef CONFIG_SECURITY_LANDLOCK_LOG /** * @quiet: Whether we have encountered a rule with the quiet flag for * this layer. Used to control logging. */ access_mask_t quiet : 1; -#endif /* CONFIG_AUDIT */ +#endif /* CONFIG_SECURITY_LANDLOCK_LOG */ } __packed __aligned(sizeof(access_mask_t)); /* diff --git a/security/landlock/audit.c b/security/landlock/audit.c index 50536c568526..e02963834e48 100644 --- a/security/landlock/audit.c +++ b/security/landlock/audit.c @@ -5,9 +5,9 @@ * Copyright © 2023-2025 Microsoft Corporation */ -#include <kunit/test.h> #include <linux/audit.h> #include <linux/bitops.h> +#include <linux/landlock.h> #include <linux/lsm_audit.h> #include <linux/pid.h> #include <uapi/linux/landlock.h> @@ -18,40 +18,30 @@ #include "cred.h" #include "domain.h" #include "limits.h" -#include "ruleset.h" - -static const char *const fs_access_strings[] = { - [BIT_INDEX(LANDLOCK_ACCESS_FS_EXECUTE)] = "fs.execute", - [BIT_INDEX(LANDLOCK_ACCESS_FS_WRITE_FILE)] = "fs.write_file", - [BIT_INDEX(LANDLOCK_ACCESS_FS_READ_FILE)] = "fs.read_file", - [BIT_INDEX(LANDLOCK_ACCESS_FS_READ_DIR)] = "fs.read_dir", - [BIT_INDEX(LANDLOCK_ACCESS_FS_REMOVE_DIR)] = "fs.remove_dir", - [BIT_INDEX(LANDLOCK_ACCESS_FS_REMOVE_FILE)] = "fs.remove_file", - [BIT_INDEX(LANDLOCK_ACCESS_FS_MAKE_CHAR)] = "fs.make_char", - [BIT_INDEX(LANDLOCK_ACCESS_FS_MAKE_DIR)] = "fs.make_dir", - [BIT_INDEX(LANDLOCK_ACCESS_FS_MAKE_REG)] = "fs.make_reg", - [BIT_INDEX(LANDLOCK_ACCESS_FS_MAKE_SOCK)] = "fs.make_sock", - [BIT_INDEX(LANDLOCK_ACCESS_FS_MAKE_FIFO)] = "fs.make_fifo", - [BIT_INDEX(LANDLOCK_ACCESS_FS_MAKE_BLOCK)] = "fs.make_block", - [BIT_INDEX(LANDLOCK_ACCESS_FS_MAKE_SYM)] = "fs.make_sym", - [BIT_INDEX(LANDLOCK_ACCESS_FS_REFER)] = "fs.refer", - [BIT_INDEX(LANDLOCK_ACCESS_FS_TRUNCATE)] = "fs.truncate", - [BIT_INDEX(LANDLOCK_ACCESS_FS_IOCTL_DEV)] = "fs.ioctl_dev", - [BIT_INDEX(LANDLOCK_ACCESS_FS_RESOLVE_UNIX)] = "fs.resolve_unix", -}; +#include "log.h" + +/* + * Access-right and scope names are built from the lists shared with the trace + * events (see <linux/landlock.h>). The designated initializer places each name + * at its bit index, so the lookup stays O(1) and does not depend on the entry + * order. log_blockers() adds the "fs."/"net."/"scope." category prefix. + */ +#define _LANDLOCK_NAME_ENTRY(mask, name) [BIT_INDEX(mask)] = name + +static const char *const fs_access_strings[] = { _LANDLOCK_ACCESS_FS_NAMES }; static_assert(ARRAY_SIZE(fs_access_strings) == LANDLOCK_NUM_ACCESS_FS); -static const char *const net_access_strings[] = { - [BIT_INDEX(LANDLOCK_ACCESS_NET_BIND_TCP)] = "net.bind_tcp", - [BIT_INDEX(LANDLOCK_ACCESS_NET_CONNECT_TCP)] = "net.connect_tcp", - [BIT_INDEX(LANDLOCK_ACCESS_NET_BIND_UDP)] = "net.bind_udp", - [BIT_INDEX(LANDLOCK_ACCESS_NET_CONNECT_SEND_UDP)] = - "net.connect_send_udp", -}; +static const char *const net_access_strings[] = { _LANDLOCK_ACCESS_NET_NAMES }; static_assert(ARRAY_SIZE(net_access_strings) == LANDLOCK_NUM_ACCESS_NET); +static const char *const scope_strings[] = { _LANDLOCK_SCOPE_NAMES }; + +static_assert(ARRAY_SIZE(scope_strings) == LANDLOCK_NUM_SCOPE); + +#undef _LANDLOCK_NAME_ENTRY + static __attribute_const__ const char * get_blocker(const enum landlock_request_type type, const unsigned long access_bit) @@ -63,7 +53,7 @@ get_blocker(const enum landlock_request_type type, case LANDLOCK_REQUEST_FS_CHANGE_TOPOLOGY: WARN_ON_ONCE(access_bit != -1); - return "fs.change_topology"; + return "change_topology"; case LANDLOCK_REQUEST_FS_ACCESS: if (WARN_ON_ONCE(access_bit >= ARRAY_SIZE(fs_access_strings))) @@ -77,32 +67,63 @@ get_blocker(const enum landlock_request_type type, case LANDLOCK_REQUEST_SCOPE_ABSTRACT_UNIX_SOCKET: WARN_ON_ONCE(access_bit != -1); - return "scope.abstract_unix_socket"; + return scope_strings[BIT_INDEX( + LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET)]; case LANDLOCK_REQUEST_SCOPE_SIGNAL: WARN_ON_ONCE(access_bit != -1); - return "scope.signal"; + return scope_strings[BIT_INDEX(LANDLOCK_SCOPE_SIGNAL)]; } WARN_ON_ONCE(1); return "unknown"; } +/* + * Returns the audit category prefix prepended to the unprefixed blocker name + * returned by get_blocker() (filesystem and network access rights, + * change_topology, and scopes). The ptrace blocker is standalone and carries + * its full name in get_blocker(), so it uses no prefix. + */ +static __attribute_const__ const char * +blocker_prefix(const enum landlock_request_type type) +{ + switch (type) { + case LANDLOCK_REQUEST_PTRACE: + return ""; + + case LANDLOCK_REQUEST_FS_CHANGE_TOPOLOGY: + case LANDLOCK_REQUEST_FS_ACCESS: + return "fs."; + + case LANDLOCK_REQUEST_NET_ACCESS: + return "net."; + + case LANDLOCK_REQUEST_SCOPE_ABSTRACT_UNIX_SOCKET: + case LANDLOCK_REQUEST_SCOPE_SIGNAL: + return "scope."; + } + + WARN_ON_ONCE(1); + return ""; +} + static void log_blockers(struct audit_buffer *const ab, const enum landlock_request_type type, const access_mask_t access) { const unsigned long access_mask = access; + const char *const prefix = blocker_prefix(type); unsigned long access_bit; bool is_first = true; for_each_set_bit(access_bit, &access_mask, BITS_PER_TYPE(access)) { - audit_log_format(ab, "%s%s", is_first ? "" : ",", + audit_log_format(ab, "%s%s%s", is_first ? "" : ",", prefix, get_blocker(type, access_bit)); is_first = false; } if (is_first) - audit_log_format(ab, "%s", get_blocker(type, -1)); + audit_log_format(ab, "%s%s", prefix, get_blocker(type, -1)); } static void log_domain(struct landlock_hierarchy *const hierarchy) @@ -137,526 +158,32 @@ static void log_domain(struct landlock_hierarchy *const hierarchy) WRITE_ONCE(hierarchy->log_status, LANDLOCK_LOG_RECORDED); } -static struct landlock_hierarchy * -get_hierarchy(const struct landlock_ruleset *const domain, const size_t layer) -{ - struct landlock_hierarchy *hierarchy = domain->hierarchy; - ssize_t i; - - if (WARN_ON_ONCE(layer >= domain->num_layers)) - return hierarchy; - - for (i = domain->num_layers - 1; i > layer; i--) { - if (WARN_ON_ONCE(!hierarchy->parent)) - break; - - hierarchy = hierarchy->parent; - } - - return hierarchy; -} - -#ifdef CONFIG_SECURITY_LANDLOCK_KUNIT_TEST - -static void test_get_hierarchy(struct kunit *const test) -{ - struct landlock_hierarchy dom0_hierarchy = { - .id = 10, - }; - struct landlock_hierarchy dom1_hierarchy = { - .parent = &dom0_hierarchy, - .id = 20, - }; - struct landlock_hierarchy dom2_hierarchy = { - .parent = &dom1_hierarchy, - .id = 30, - }; - struct landlock_ruleset dom2 = { - .hierarchy = &dom2_hierarchy, - .num_layers = 3, - }; - - KUNIT_EXPECT_EQ(test, 10, get_hierarchy(&dom2, 0)->id); - KUNIT_EXPECT_EQ(test, 20, get_hierarchy(&dom2, 1)->id); - KUNIT_EXPECT_EQ(test, 30, get_hierarchy(&dom2, 2)->id); - /* KUNIT_EXPECT_EQ(test, 30, get_hierarchy(&dom2, -1)->id); */ -} - -#endif /* CONFIG_SECURITY_LANDLOCK_KUNIT_TEST */ - -/* Get the youngest layer that denied the access_request. */ -static size_t get_denied_layer(const struct landlock_ruleset *const domain, - access_mask_t *const access_request, - const struct layer_masks *masks) -{ - for (ssize_t i = ARRAY_SIZE(masks->layers) - 1; i >= 0; i--) { - if (masks->layers[i].access & *access_request) { - *access_request &= masks->layers[i].access; - return i; - } - } - - /* Not found - fall back to default values */ - *access_request = 0; - return domain->num_layers - 1; -} - -#ifdef CONFIG_SECURITY_LANDLOCK_KUNIT_TEST - -static void test_get_denied_layer(struct kunit *const test) -{ - const struct landlock_ruleset dom = { - .num_layers = 5, - }; - const struct layer_masks masks = { - .layers[0].access = LANDLOCK_ACCESS_FS_EXECUTE | - LANDLOCK_ACCESS_FS_READ_DIR, - .layers[1].access = LANDLOCK_ACCESS_FS_READ_FILE | - LANDLOCK_ACCESS_FS_READ_DIR, - .layers[2].access = LANDLOCK_ACCESS_FS_REMOVE_DIR, - }; - access_mask_t access; - - access = LANDLOCK_ACCESS_FS_EXECUTE; - KUNIT_EXPECT_EQ(test, 0, get_denied_layer(&dom, &access, &masks)); - KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_EXECUTE); - - access = LANDLOCK_ACCESS_FS_READ_FILE; - KUNIT_EXPECT_EQ(test, 1, get_denied_layer(&dom, &access, &masks)); - KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_READ_FILE); - - access = LANDLOCK_ACCESS_FS_READ_DIR; - KUNIT_EXPECT_EQ(test, 1, get_denied_layer(&dom, &access, &masks)); - KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_READ_DIR); - - access = LANDLOCK_ACCESS_FS_READ_FILE | LANDLOCK_ACCESS_FS_READ_DIR; - KUNIT_EXPECT_EQ(test, 1, get_denied_layer(&dom, &access, &masks)); - KUNIT_EXPECT_EQ(test, access, - LANDLOCK_ACCESS_FS_READ_FILE | - LANDLOCK_ACCESS_FS_READ_DIR); - - access = LANDLOCK_ACCESS_FS_EXECUTE | LANDLOCK_ACCESS_FS_READ_DIR; - KUNIT_EXPECT_EQ(test, 1, get_denied_layer(&dom, &access, &masks)); - KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_READ_DIR); - - access = LANDLOCK_ACCESS_FS_WRITE_FILE; - KUNIT_EXPECT_EQ(test, 4, get_denied_layer(&dom, &access, &masks)); - KUNIT_EXPECT_EQ(test, access, 0); -} - -#endif /* CONFIG_SECURITY_LANDLOCK_KUNIT_TEST */ - -static size_t -get_layer_from_deny_masks(access_mask_t *const access_request, - const access_mask_t all_existing_optional_access, - const deny_masks_t deny_masks, - optional_access_t quiet_optional_accesses, - bool *quiet) -{ - const unsigned long access_opt = all_existing_optional_access; - const unsigned long access_req = *access_request; - access_mask_t missing = 0; - size_t youngest_layer = 0; - size_t access_index = 0; - unsigned long access_bit; - bool should_quiet = false; - - /* This will require change with new object types. */ - WARN_ON_ONCE(access_opt != _LANDLOCK_ACCESS_FS_OPTIONAL); - - for_each_set_bit(access_bit, &access_opt, - BITS_PER_TYPE(access_mask_t)) { - if (access_req & BIT(access_bit)) { - const size_t layer = - (deny_masks >> - (access_index * - HWEIGHT(LANDLOCK_MAX_NUM_LAYERS - 1))) & - (LANDLOCK_MAX_NUM_LAYERS - 1); - const bool layer_has_quiet = - !!(quiet_optional_accesses & BIT(access_index)); - - if (layer > youngest_layer) { - youngest_layer = layer; - missing = BIT(access_bit); - should_quiet = layer_has_quiet; - } else if (layer == youngest_layer) { - missing |= BIT(access_bit); - /* - * Whether the layer has rules with quiet flag - * covering the file accessed does not depend on - * the access, and so the following - * WARN_ON_ONCE() should not fail. - */ - WARN_ON_ONCE(should_quiet && !layer_has_quiet); - should_quiet = layer_has_quiet; - } - } - access_index++; - } - - *access_request = missing; - *quiet = should_quiet; - return youngest_layer; -} - -#ifdef CONFIG_SECURITY_LANDLOCK_KUNIT_TEST - -static void test_get_layer_from_deny_masks(struct kunit *const test) -{ - deny_masks_t deny_mask; - access_mask_t access; - optional_access_t quiet_optional_accesses; - bool quiet; - - /* truncate:0 ioctl_dev:2 */ - deny_mask = 0x20; - quiet_optional_accesses = 0; - - access = LANDLOCK_ACCESS_FS_TRUNCATE; - KUNIT_EXPECT_EQ(test, 0, - get_layer_from_deny_masks( - &access, _LANDLOCK_ACCESS_FS_OPTIONAL, - deny_mask, quiet_optional_accesses, &quiet)); - KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_TRUNCATE); - KUNIT_EXPECT_EQ(test, quiet, false); - - access = LANDLOCK_ACCESS_FS_IOCTL_DEV; - KUNIT_EXPECT_EQ(test, 2, - get_layer_from_deny_masks( - &access, _LANDLOCK_ACCESS_FS_OPTIONAL, - deny_mask, quiet_optional_accesses, &quiet)); - KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_IOCTL_DEV); - KUNIT_EXPECT_EQ(test, quiet, false); - - access = LANDLOCK_ACCESS_FS_TRUNCATE | LANDLOCK_ACCESS_FS_IOCTL_DEV; - KUNIT_EXPECT_EQ(test, 2, - get_layer_from_deny_masks( - &access, _LANDLOCK_ACCESS_FS_OPTIONAL, - deny_mask, quiet_optional_accesses, &quiet)); - KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_IOCTL_DEV); - KUNIT_EXPECT_EQ(test, quiet, false); - - /* layer denying truncate: quiet, ioctl: not quiet */ - quiet_optional_accesses = 0b01; - - access = LANDLOCK_ACCESS_FS_TRUNCATE; - KUNIT_EXPECT_EQ(test, 0, - get_layer_from_deny_masks( - &access, _LANDLOCK_ACCESS_FS_OPTIONAL, - deny_mask, quiet_optional_accesses, &quiet)); - KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_TRUNCATE); - KUNIT_EXPECT_EQ(test, quiet, true); - - access = LANDLOCK_ACCESS_FS_IOCTL_DEV; - KUNIT_EXPECT_EQ(test, 2, - get_layer_from_deny_masks( - &access, _LANDLOCK_ACCESS_FS_OPTIONAL, - deny_mask, quiet_optional_accesses, &quiet)); - KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_IOCTL_DEV); - KUNIT_EXPECT_EQ(test, quiet, false); - - access = LANDLOCK_ACCESS_FS_TRUNCATE | LANDLOCK_ACCESS_FS_IOCTL_DEV; - KUNIT_EXPECT_EQ(test, 2, - get_layer_from_deny_masks( - &access, _LANDLOCK_ACCESS_FS_OPTIONAL, - deny_mask, quiet_optional_accesses, &quiet)); - KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_IOCTL_DEV); - KUNIT_EXPECT_EQ(test, quiet, false); - - /* Reverse order - truncate:2 ioctl_dev:0 */ - deny_mask = 0x02; - quiet_optional_accesses = 0; - - access = LANDLOCK_ACCESS_FS_TRUNCATE; - KUNIT_EXPECT_EQ(test, 2, - get_layer_from_deny_masks( - &access, _LANDLOCK_ACCESS_FS_OPTIONAL, - deny_mask, quiet_optional_accesses, &quiet)); - KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_TRUNCATE); - KUNIT_EXPECT_EQ(test, quiet, false); - - access = LANDLOCK_ACCESS_FS_IOCTL_DEV; - KUNIT_EXPECT_EQ(test, 0, - get_layer_from_deny_masks( - &access, _LANDLOCK_ACCESS_FS_OPTIONAL, - deny_mask, quiet_optional_accesses, &quiet)); - KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_IOCTL_DEV); - KUNIT_EXPECT_EQ(test, quiet, false); - - access = LANDLOCK_ACCESS_FS_TRUNCATE | LANDLOCK_ACCESS_FS_IOCTL_DEV; - KUNIT_EXPECT_EQ(test, 2, - get_layer_from_deny_masks( - &access, _LANDLOCK_ACCESS_FS_OPTIONAL, - deny_mask, quiet_optional_accesses, &quiet)); - KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_TRUNCATE); - KUNIT_EXPECT_EQ(test, quiet, false); - - /* layer denying truncate: quiet, ioctl: not quiet */ - quiet_optional_accesses = 0b01; - - access = LANDLOCK_ACCESS_FS_TRUNCATE; - KUNIT_EXPECT_EQ(test, 2, - get_layer_from_deny_masks( - &access, _LANDLOCK_ACCESS_FS_OPTIONAL, - deny_mask, quiet_optional_accesses, &quiet)); - KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_TRUNCATE); - KUNIT_EXPECT_EQ(test, quiet, true); - - access = LANDLOCK_ACCESS_FS_IOCTL_DEV; - KUNIT_EXPECT_EQ(test, 0, - get_layer_from_deny_masks( - &access, _LANDLOCK_ACCESS_FS_OPTIONAL, - deny_mask, quiet_optional_accesses, &quiet)); - KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_IOCTL_DEV); - KUNIT_EXPECT_EQ(test, quiet, false); - - access = LANDLOCK_ACCESS_FS_TRUNCATE | LANDLOCK_ACCESS_FS_IOCTL_DEV; - KUNIT_EXPECT_EQ(test, 2, - get_layer_from_deny_masks( - &access, _LANDLOCK_ACCESS_FS_OPTIONAL, - deny_mask, quiet_optional_accesses, &quiet)); - KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_TRUNCATE); - KUNIT_EXPECT_EQ(test, quiet, true); - - /* layer denying truncate: not quiet, ioctl: quiet */ - quiet_optional_accesses = 0b10; - - access = LANDLOCK_ACCESS_FS_TRUNCATE; - KUNIT_EXPECT_EQ(test, 2, - get_layer_from_deny_masks( - &access, _LANDLOCK_ACCESS_FS_OPTIONAL, - deny_mask, quiet_optional_accesses, &quiet)); - KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_TRUNCATE); - KUNIT_EXPECT_EQ(test, quiet, false); - - access = LANDLOCK_ACCESS_FS_IOCTL_DEV; - KUNIT_EXPECT_EQ(test, 0, - get_layer_from_deny_masks( - &access, _LANDLOCK_ACCESS_FS_OPTIONAL, - deny_mask, quiet_optional_accesses, &quiet)); - KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_IOCTL_DEV); - KUNIT_EXPECT_EQ(test, quiet, true); - - access = LANDLOCK_ACCESS_FS_TRUNCATE | LANDLOCK_ACCESS_FS_IOCTL_DEV; - KUNIT_EXPECT_EQ(test, 2, - get_layer_from_deny_masks( - &access, _LANDLOCK_ACCESS_FS_OPTIONAL, - deny_mask, quiet_optional_accesses, &quiet)); - KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_TRUNCATE); - KUNIT_EXPECT_EQ(test, quiet, false); - - /* truncate:15 ioctl_dev:15 */ - deny_mask = 0xff; - quiet_optional_accesses = 0; - - access = LANDLOCK_ACCESS_FS_TRUNCATE; - KUNIT_EXPECT_EQ(test, 15, - get_layer_from_deny_masks( - &access, _LANDLOCK_ACCESS_FS_OPTIONAL, - deny_mask, quiet_optional_accesses, &quiet)); - KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_TRUNCATE); - KUNIT_EXPECT_EQ(test, quiet, false); - - access = LANDLOCK_ACCESS_FS_TRUNCATE | LANDLOCK_ACCESS_FS_IOCTL_DEV; - KUNIT_EXPECT_EQ(test, 15, - get_layer_from_deny_masks( - &access, _LANDLOCK_ACCESS_FS_OPTIONAL, - deny_mask, quiet_optional_accesses, &quiet)); - KUNIT_EXPECT_EQ(test, access, - LANDLOCK_ACCESS_FS_TRUNCATE | - LANDLOCK_ACCESS_FS_IOCTL_DEV); - KUNIT_EXPECT_EQ(test, quiet, false); - - /* Both quiet (same layer so quietness must be the same) */ - quiet_optional_accesses = 0b11; - - access = LANDLOCK_ACCESS_FS_TRUNCATE; - KUNIT_EXPECT_EQ(test, 15, - get_layer_from_deny_masks( - &access, _LANDLOCK_ACCESS_FS_OPTIONAL, - deny_mask, quiet_optional_accesses, &quiet)); - KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_TRUNCATE); - KUNIT_EXPECT_EQ(test, quiet, true); - - access = LANDLOCK_ACCESS_FS_TRUNCATE | LANDLOCK_ACCESS_FS_IOCTL_DEV; - KUNIT_EXPECT_EQ(test, 15, - get_layer_from_deny_masks( - &access, _LANDLOCK_ACCESS_FS_OPTIONAL, - deny_mask, quiet_optional_accesses, &quiet)); - KUNIT_EXPECT_EQ(test, access, - LANDLOCK_ACCESS_FS_TRUNCATE | - LANDLOCK_ACCESS_FS_IOCTL_DEV); - KUNIT_EXPECT_EQ(test, quiet, true); -} - -#endif /* CONFIG_SECURITY_LANDLOCK_KUNIT_TEST */ - -static bool is_valid_request(const struct landlock_request *const request) -{ - if (WARN_ON_ONCE(request->layer_plus_one > LANDLOCK_MAX_NUM_LAYERS)) - return false; - - if (WARN_ON_ONCE(!(!!request->layer_plus_one ^ !!request->access))) - return false; - - if (request->access) { - if (WARN_ON_ONCE(!(!!request->layer_masks ^ - !!request->all_existing_optional_access))) - return false; - } else { - if (WARN_ON_ONCE(request->layer_masks || - request->all_existing_optional_access)) - return false; - } - - if (request->deny_masks) { - if (WARN_ON_ONCE(!request->all_existing_optional_access)) - return false; - static_assert(sizeof(request->all_existing_optional_access) == - sizeof(u32)); - if (WARN_ON_ONCE( - request->quiet_optional_accesses >= - BIT(hweight32( - request->all_existing_optional_access)))) - return false; - } - - return true; -} - -static access_mask_t -pick_access_mask_for_request_type(const enum landlock_request_type type, - const struct access_masks access_masks) -{ - switch (type) { - case LANDLOCK_REQUEST_FS_ACCESS: - return access_masks.fs; - case LANDLOCK_REQUEST_NET_ACCESS: - return access_masks.net; - default: - WARN_ONCE(1, "Invalid request type %d passed to %s", type, - __func__); - return 0; - } -} - /** - * landlock_log_denial - Create audit records related to a denial + * landlock_audit_denial - Create an audit record for a denied access request * - * @subject: The Landlock subject's credential denying an action. * @request: Detail of the user space request. + * @youngest_denied: The youngest hierarchy node that denied the access. + * @missing: The set of denied access rights. + * @logged: Whether the denial is selected for logging, as computed by + * landlock_log_denial() (domain policy and quiet rules). + * + * Emits the record when audit is enabled and the denial is selected for + * logging. */ -void landlock_log_denial(const struct landlock_cred_security *const subject, - const struct landlock_request *const request) +void landlock_audit_denial(const struct landlock_request *const request, + struct landlock_hierarchy *const youngest_denied, + const access_mask_t missing, const bool logged) { struct audit_buffer *ab; - struct landlock_hierarchy *youngest_denied; - size_t youngest_layer; - access_mask_t missing; - bool object_quiet_flag = false, quiet_applicable_to_access = false; - - if (WARN_ON_ONCE(!subject || !subject->domain || - !subject->domain->hierarchy || !request)) - return; - - if (!is_valid_request(request)) - return; - - missing = request->access; - if (missing) { - /* Gets the nearest domain that denies the request. */ - if (request->layer_masks) { - youngest_layer = get_denied_layer(subject->domain, - &missing, - request->layer_masks); - object_quiet_flag = - request->layer_masks->layers[youngest_layer] - .quiet; - } else { - youngest_layer = get_layer_from_deny_masks( - &missing, _LANDLOCK_ACCESS_FS_OPTIONAL, - request->deny_masks, - request->quiet_optional_accesses, - &object_quiet_flag); - } - youngest_denied = - get_hierarchy(subject->domain, youngest_layer); - } else { - youngest_layer = request->layer_plus_one - 1; - youngest_denied = - get_hierarchy(subject->domain, youngest_layer); - } - - if (READ_ONCE(youngest_denied->log_status) == LANDLOCK_LOG_DISABLED) - return; - - /* - * Consistently keeps track of the number of denied access requests - * even if audit is currently disabled, or if audit rules currently - * exclude this record type, or if landlock_restrict_self(2)'s flags - * quiet logs. - */ - atomic64_inc(&youngest_denied->num_denials); if (!audit_enabled) return; - /* Checks if the current exec was restricting itself. */ - if (subject->domain_exec & BIT(youngest_layer)) { - /* Ignores denials for the same execution. */ - if (!youngest_denied->log_same_exec) - return; - } else { - /* Ignores denials after a new execution. */ - if (!youngest_denied->log_new_exec) - return; - } - /* - * Checks if the object is marked quiet by the layer that denied the - * request. If it's a different layer that marked it as quiet, but that - * layer is not the one that denied the request, we should still audit - * log the denial. + * Skips denials the domain's policy or a quiet rule excludes from + * logging (folded into @logged by landlock_log_denial()). */ - if (object_quiet_flag) { - /* - * We now check if the denied requests are all covered by the - * layer's quiet access bits. - */ - const access_mask_t quiet_mask = - pick_access_mask_for_request_type( - request->type, youngest_denied->quiet_masks); - - quiet_applicable_to_access = (quiet_mask & missing) == missing; - } else { - /* - * Either the object is not quiet, or this is a scope request. - * We check request->type to distinguish between the two cases. - */ - const access_mask_t quiet_mask = - youngest_denied->quiet_masks.scope; - - switch (request->type) { - case LANDLOCK_REQUEST_SCOPE_SIGNAL: - quiet_applicable_to_access = - !!(quiet_mask & LANDLOCK_SCOPE_SIGNAL); - break; - case LANDLOCK_REQUEST_SCOPE_ABSTRACT_UNIX_SOCKET: - quiet_applicable_to_access = - !!(quiet_mask & - LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET); - break; - /* - * Leave LANDLOCK_REQUEST_PTRACE and - * LANDLOCK_REQUEST_FS_CHANGE_TOPOLOGY unhandled for now - they - * are never quiet. - */ - default: - break; - } - } - - if (quiet_applicable_to_access) + if (!logged) return; /* Uses consistent allocation flags wrt common_lsm_audit(). */ @@ -675,23 +202,19 @@ void landlock_log_denial(const struct landlock_cred_security *const subject, } /** - * landlock_log_drop_domain - Create an audit record on domain deallocation + * landlock_audit_free_domain - Create an audit record on domain deallocation * * @hierarchy: The domain's hierarchy being deallocated. * * Only domains which previously appeared in the audit logs are logged again. * This is useful to know when a domain will never show again in the audit log. * - * Called in a work queue scheduled by landlock_put_ruleset_deferred() called - * by hook_cred_free(). + * Called from landlock_log_free_domain(). */ -void landlock_log_drop_domain(const struct landlock_hierarchy *const hierarchy) +void landlock_audit_free_domain(const struct landlock_hierarchy *const hierarchy) { struct audit_buffer *ab; - if (WARN_ON_ONCE(!hierarchy)) - return; - if (!audit_enabled) return; @@ -712,23 +235,3 @@ void landlock_log_drop_domain(const struct landlock_hierarchy *const hierarchy) hierarchy->id, atomic64_read(&hierarchy->num_denials)); audit_log_end(ab); } - -#ifdef CONFIG_SECURITY_LANDLOCK_KUNIT_TEST - -static struct kunit_case test_cases[] = { - /* clang-format off */ - KUNIT_CASE(test_get_hierarchy), - KUNIT_CASE(test_get_denied_layer), - KUNIT_CASE(test_get_layer_from_deny_masks), - {} - /* clang-format on */ -}; - -static struct kunit_suite test_suite = { - .name = "landlock_audit", - .test_cases = test_cases, -}; - -kunit_test_suite(test_suite); - -#endif /* CONFIG_SECURITY_LANDLOCK_KUNIT_TEST */ diff --git a/security/landlock/audit.h b/security/landlock/audit.h index 620f8a24291d..14a514065e08 100644 --- a/security/landlock/audit.h +++ b/security/landlock/audit.h @@ -8,66 +8,33 @@ #ifndef _SECURITY_LANDLOCK_AUDIT_H #define _SECURITY_LANDLOCK_AUDIT_H -#include <linux/audit.h> -#include <linux/lsm_audit.h> +#include <linux/types.h> #include "access.h" -#include "cred.h" -enum landlock_request_type { - LANDLOCK_REQUEST_PTRACE = 1, - LANDLOCK_REQUEST_FS_CHANGE_TOPOLOGY, - LANDLOCK_REQUEST_FS_ACCESS, - LANDLOCK_REQUEST_NET_ACCESS, - LANDLOCK_REQUEST_SCOPE_ABSTRACT_UNIX_SOCKET, - LANDLOCK_REQUEST_SCOPE_SIGNAL, -}; - -/* - * We should be careful to only use a variable of this type for - * landlock_log_denial(). This way, the compiler can remove it entirely if - * CONFIG_AUDIT is not set. - */ -struct landlock_request { - /* Mandatory fields. */ - enum landlock_request_type type; - struct common_audit_data audit; - - /** - * layer_plus_one: First layer level that denies the request + 1. The - * extra one is useful to detect uninitialized field. - */ - size_t layer_plus_one; - - /* Required field for configurable access control. */ - access_mask_t access; - - /* Required fields for requests with layer masks. */ - const struct layer_masks *layer_masks; - - /* Required fields for requests with deny masks. */ - const access_mask_t all_existing_optional_access; - deny_masks_t deny_masks; - optional_access_t quiet_optional_accesses; -}; +struct landlock_hierarchy; +struct landlock_request; #ifdef CONFIG_AUDIT -void landlock_log_drop_domain(const struct landlock_hierarchy *const hierarchy); +void landlock_audit_denial(const struct landlock_request *const request, + struct landlock_hierarchy *const youngest_denied, + const access_mask_t missing, const bool logged); -void landlock_log_denial(const struct landlock_cred_security *const subject, - const struct landlock_request *const request); +void landlock_audit_free_domain( + const struct landlock_hierarchy *const hierarchy); #else /* CONFIG_AUDIT */ static inline void -landlock_log_drop_domain(const struct landlock_hierarchy *const hierarchy) +landlock_audit_denial(const struct landlock_request *const request, + struct landlock_hierarchy *const youngest_denied, + const access_mask_t missing, const bool logged) { } static inline void -landlock_log_denial(const struct landlock_cred_security *const subject, - const struct landlock_request *const request) +landlock_audit_free_domain(const struct landlock_hierarchy *const hierarchy) { } diff --git a/security/landlock/cred.c b/security/landlock/cred.c index cc419de75cd6..03449c26247e 100644 --- a/security/landlock/cred.c +++ b/security/landlock/cred.c @@ -22,7 +22,7 @@ static void hook_cred_transfer(struct cred *const new, const struct landlock_cred_security *const old_llcred = landlock_cred(old); - landlock_get_ruleset(old_llcred->domain); + landlock_get_domain(old_llcred->domain); *landlock_cred(new) = *old_llcred; } @@ -35,13 +35,13 @@ static int hook_cred_prepare(struct cred *const new, static void hook_cred_free(struct cred *const cred) { - struct landlock_ruleset *const dom = landlock_cred(cred)->domain; + struct landlock_domain *const dom = landlock_cred(cred)->domain; if (dom) - landlock_put_ruleset_deferred(dom); + landlock_put_domain_deferred(dom); } -#ifdef CONFIG_AUDIT +#ifdef CONFIG_SECURITY_LANDLOCK_LOG static int hook_bprm_creds_for_exec(struct linux_binprm *const bprm) { @@ -50,16 +50,16 @@ static int hook_bprm_creds_for_exec(struct linux_binprm *const bprm) return 0; } -#endif /* CONFIG_AUDIT */ +#endif /* CONFIG_SECURITY_LANDLOCK_LOG */ static struct security_hook_list landlock_hooks[] __ro_after_init = { LSM_HOOK_INIT(cred_prepare, hook_cred_prepare), LSM_HOOK_INIT(cred_transfer, hook_cred_transfer), LSM_HOOK_INIT(cred_free, hook_cred_free), -#ifdef CONFIG_AUDIT +#ifdef CONFIG_SECURITY_LANDLOCK_LOG LSM_HOOK_INIT(bprm_creds_for_exec, hook_bprm_creds_for_exec), -#endif /* CONFIG_AUDIT */ +#endif /* CONFIG_SECURITY_LANDLOCK_LOG */ }; __init void landlock_add_cred_hooks(void) diff --git a/security/landlock/cred.h b/security/landlock/cred.h index f287c56b5fd4..a5ff9957949a 100644 --- a/security/landlock/cred.h +++ b/security/landlock/cred.h @@ -16,6 +16,7 @@ #include <linux/rcupdate.h> #include "access.h" +#include "domain.h" #include "limits.h" #include "ruleset.h" #include "setup.h" @@ -31,11 +32,11 @@ */ struct landlock_cred_security { /** - * @domain: Immutable ruleset enforced on a task. + * @domain: Immutable domain enforced on a task. */ - struct landlock_ruleset *domain; + struct landlock_domain *domain; -#ifdef CONFIG_AUDIT +#ifdef CONFIG_SECURITY_LANDLOCK_LOG /** * @domain_exec: Bitmask identifying the domain layers that were enforced by * the current task's executed file (i.e. no new execve(2) since @@ -49,17 +50,17 @@ struct landlock_cred_security { * not require a current domain. */ u8 log_subdomains_off : 1; -#endif /* CONFIG_AUDIT */ +#endif /* CONFIG_SECURITY_LANDLOCK_LOG */ } __packed; -#ifdef CONFIG_AUDIT +#ifdef CONFIG_SECURITY_LANDLOCK_LOG /* Makes sure all layer executions can be stored. */ static_assert(BITS_PER_TYPE(typeof_member(struct landlock_cred_security, domain_exec)) >= LANDLOCK_MAX_NUM_LAYERS); -#endif /* CONFIG_AUDIT */ +#endif /* CONFIG_SECURITY_LANDLOCK_LOG */ static inline struct landlock_cred_security * landlock_cred(const struct cred *cred) @@ -70,22 +71,20 @@ landlock_cred(const struct cred *cred) static inline void landlock_cred_copy(struct landlock_cred_security *dst, const struct landlock_cred_security *src) { - landlock_put_ruleset(dst->domain); + landlock_put_domain(dst->domain); *dst = *src; - landlock_get_ruleset(src->domain); + landlock_get_domain(src->domain); } -static inline struct landlock_ruleset *landlock_get_current_domain(void) +static inline struct landlock_domain *landlock_get_current_domain(void) { return landlock_cred(current_cred())->domain; } -/* - * The call needs to come from an RCU read-side critical section. - */ -static inline const struct landlock_ruleset * +/* The call needs to come from an RCU read-side critical section. */ +static inline const struct landlock_domain * landlock_get_task_domain(const struct task_struct *const task) { return landlock_cred(__task_cred(task))->domain; @@ -126,7 +125,7 @@ landlock_get_applicable_subject(const struct cred *const cred, const union access_masks_all masks_all = { .masks = masks, }; - const struct landlock_ruleset *domain; + const struct landlock_domain *domain; ssize_t layer_level; if (!cred) @@ -139,7 +138,7 @@ landlock_get_applicable_subject(const struct cred *const cred, for (layer_level = domain->num_layers - 1; layer_level >= 0; layer_level--) { union access_masks_all layer = { - .masks = domain->access_masks[layer_level], + .masks = domain->handled_masks[layer_level], }; if (layer.all & masks_all.all) { diff --git a/security/landlock/domain.c b/security/landlock/domain.c index 9a8355fccd26..93c7104fd6b2 100644 --- a/security/landlock/domain.c +++ b/security/landlock/domain.c @@ -5,26 +5,486 @@ * Copyright © 2016-2020 Mickaël Salaün <mic@digikod.net> * Copyright © 2018-2020 ANSSI * Copyright © 2024-2025 Microsoft Corporation + * Copyright © 2026 Cloudflare, Inc. */ #include <kunit/test.h> #include <linux/bitops.h> #include <linux/bits.h> +#include <linux/cleanup.h> #include <linux/cred.h> +#include <linux/err.h> #include <linux/file.h> +#include <linux/lockdep.h> #include <linux/mm.h> +#include <linux/mutex.h> +#include <linux/overflow.h> #include <linux/path.h> #include <linux/pid.h> +#include <linux/rbtree.h> +#include <linux/refcount.h> #include <linux/sched.h> #include <linux/signal.h> +#include <linux/slab.h> #include <linux/uidgid.h> +#include <linux/workqueue.h> #include "access.h" #include "common.h" #include "domain.h" #include "id.h" +#include "limits.h" +#include "ruleset.h" -#ifdef CONFIG_AUDIT +static void build_check_domain(void) +{ + const struct landlock_domain domain = { + .num_layers = ~0, + }; + + BUILD_BUG_ON(domain.num_layers < LANDLOCK_MAX_NUM_LAYERS); +} + +static struct landlock_domain *create_domain(const u32 num_layers) +{ + struct landlock_domain *new_domain; + + build_check_domain(); + new_domain = kzalloc_flex(*new_domain, handled_masks, num_layers, + GFP_KERNEL_ACCOUNT); + if (!new_domain) + return ERR_PTR(-ENOMEM); + + refcount_set(&new_domain->usage, 1); + new_domain->rules.root_inode = RB_ROOT; + +#if IS_ENABLED(CONFIG_INET) + new_domain->rules.root_net_port = RB_ROOT; +#endif /* IS_ENABLED(CONFIG_INET) */ + + new_domain->num_layers = num_layers; + return new_domain; +} + +static void free_domain(struct landlock_domain *const domain) +{ + might_sleep(); + landlock_free_rules(&domain->rules); + landlock_put_hierarchy(domain->hierarchy); + kfree(domain); +} + +void landlock_put_domain(struct landlock_domain *const domain) +{ + might_sleep(); + if (domain && refcount_dec_and_test(&domain->usage)) + free_domain(domain); +} + +static void free_domain_work(struct work_struct *const work) +{ + struct landlock_domain *domain; + + domain = container_of(work, struct landlock_domain, work_free); + free_domain(domain); +} + +void landlock_put_domain_deferred(struct landlock_domain *const domain) +{ + if (domain && refcount_dec_and_test(&domain->usage)) { + INIT_WORK(&domain->work_free, free_domain_work); + schedule_work(&domain->work_free); + } +} + +/* The returned access has the same lifetime as the domain. */ +static const struct landlock_rule * +find_rule(const struct landlock_domain *const domain, + const struct landlock_id id) +{ + const struct rb_root *root; + const struct rb_node *node; + + root = landlock_get_rule_root((struct landlock_rules *)&domain->rules, + id.type); + if (IS_ERR(root)) + return NULL; + node = root->rb_node; + + while (node) { + struct landlock_rule *this = + rb_entry(node, struct landlock_rule, node); + + if (this->key.data == id.key.data) + return this; + if (this->key.data < id.key.data) + node = node->rb_right; + else + node = node->rb_left; + } + return NULL; +} + +/** + * landlock_unmask_layers - Remove the access rights in @masks which are + * granted by a matching rule + * + * Looks up the rule matching @id in @domain, then updates the set of + * (per-layer) unfulfilled access rights @masks so that all the access rights + * granted by that rule are removed (because they are now fulfilled). + * + * @domain: The Landlock domain to search for a matching rule. + * @id: Identifier for the rule target (e.g. inode, port). + * @masks: A matrix of unfulfilled access rights for each layer. + * @matched_rule: Optional output for the matched rule (for tracing); set to + * the matching rule when non-NULL, unchanged otherwise. + * + * Return: True if the request is allowed (i.e. the access rights granted all + * remaining unfulfilled access rights and masks has no leftover set bits). + */ +bool landlock_unmask_layers(const struct landlock_domain *const domain, + const struct landlock_id id, + struct layer_masks *masks, + const struct landlock_rule **matched_rule) +{ + const struct landlock_rule *rule; + + if (!masks) + return true; + + rule = find_rule(domain, id); + if (!rule) + return false; + + if (matched_rule) + *matched_rule = rule; + + /* + * An access is granted if, for each policy layer, at least one rule + * encountered on the pathwalk grants the requested access, regardless + * of its position in the layer stack. We must then check the remaining + * layers for each inode, from the first added layer to the last one. + * When there are multiple requested accesses, for each policy layer, + * the full set of requested accesses may not be granted by only one + * rule, but by the union (binary OR) of multiple rules. For example, + * /a/b <execute> + /a <read> grants /a/b <execute + read>. + * + * This function is called once per matching rule during the pathwalk, + * progressively clearing bits in @masks. The overall access decision + * is per-layer: access is granted iff masks->layers[l].access == 0 for + * all layers l. When two independent mechanisms can each grant access + * within a layer (e.g. a path rule OR a scope exception), the + * composition must evaluate per-layer: FOR-ALL l (A(l) OR B(l)), not + * (FOR-ALL l A(l)) OR (FOR-ALL l B(l)), to prevent bypass when + * different layers grant via different mechanisms. + */ + for (size_t i = 0; i < rule->num_layers; i++) { + const struct landlock_layer *const layer = &rule->layers[i]; + + /* Clear the bits where the layer in the rule grants access. */ + masks->layers[layer->level - 1].access &= ~layer->access; + +#ifdef CONFIG_SECURITY_LANDLOCK_LOG + /* Collect rule flags for each layer. */ + if (layer->flags.quiet) + masks->layers[layer->level - 1].quiet = true; +#endif /* CONFIG_SECURITY_LANDLOCK_LOG */ + } + + for (size_t i = 0; i < ARRAY_SIZE(masks->layers); i++) { + if (masks->layers[i].access) + return false; + } + return true; +} + +typedef access_mask_t +get_access_mask_t(const struct landlock_domain *const domain, + const u16 layer_level); + +/** + * landlock_init_layer_masks - Initialize layer masks from an access request + * + * Populates @masks such that for each access right in @access_request, the bits + * for all the layers are set where this access right is handled. Rule flags + * are also zeroed. + * + * @domain: The domain that defines the current restrictions. + * @access_request: The requested access rights to check. + * @masks: Layer access masks to populate. + * @key_type: The key type to switch between access masks of different types. + * + * Return: An access mask where each access right bit is set which is handled in + * any of the active layers in @domain. + */ +access_mask_t +landlock_init_layer_masks(const struct landlock_domain *const domain, + const access_mask_t access_request, + struct layer_masks *const masks, + const enum landlock_key_type key_type) +{ + access_mask_t handled_accesses = 0; + get_access_mask_t *get_access_mask; + + switch (key_type) { + case LANDLOCK_KEY_INODE: + get_access_mask = landlock_get_fs_access_mask; + break; + +#if IS_ENABLED(CONFIG_INET) + case LANDLOCK_KEY_NET_PORT: + get_access_mask = landlock_get_net_access_mask; + break; +#endif /* IS_ENABLED(CONFIG_INET) */ + + default: + WARN_ON_ONCE(1); + return 0; + } + + /* An empty access request can happen because of O_WRONLY | O_RDWR. */ + if (!access_request) + return 0; + + for (size_t i = 0; i < domain->num_layers; i++) { + const access_mask_t handled = get_access_mask(domain, i); + + masks->layers[i].access = access_request & handled; + handled_accesses |= masks->layers[i].access; +#ifdef CONFIG_SECURITY_LANDLOCK_LOG + masks->layers[i].quiet = false; +#endif /* CONFIG_SECURITY_LANDLOCK_LOG */ + } + for (size_t i = domain->num_layers; i < ARRAY_SIZE(masks->layers); + i++) { + masks->layers[i].access = 0; +#ifdef CONFIG_SECURITY_LANDLOCK_LOG + masks->layers[i].quiet = false; +#endif /* CONFIG_SECURITY_LANDLOCK_LOG */ + } + + return handled_accesses; +} + +static int merge_tree(struct landlock_domain *const dst, + struct landlock_ruleset *const src, + const enum landlock_key_type key_type) +{ + struct landlock_rule *walker_rule, *next_rule; + struct rb_root *src_root; + int err = 0; + + might_sleep(); + lockdep_assert_held(&src->lock); + + src_root = landlock_get_rule_root(&src->rules, key_type); + if (IS_ERR(src_root)) + return PTR_ERR(src_root); + + /* Merges the @src tree. */ + rbtree_postorder_for_each_entry_safe(walker_rule, next_rule, src_root, + node) { + struct landlock_layer layers[] = { { + .level = dst->num_layers, + } }; + const struct landlock_id id = { + .key = walker_rule->key, + .type = key_type, + }; + + if (WARN_ON_ONCE(walker_rule->num_layers != 1)) + return -EINVAL; + + if (WARN_ON_ONCE(walker_rule->layers[0].level != 0)) + return -EINVAL; + + layers[0].access = walker_rule->layers[0].access; + layers[0].flags = walker_rule->layers[0].flags; + + err = landlock_store_rule(&dst->rules, id, &layers, + ARRAY_SIZE(layers)); + if (err) + return err; + } + return err; +} + +static int merge_ruleset(struct landlock_domain *const dst, + struct landlock_ruleset *const src) +{ + int err = 0; + + might_sleep(); + /* Should already be checked by landlock_merge_ruleset() */ + if (WARN_ON_ONCE(!src)) + return 0; + /* Only merge into a domain. */ + if (WARN_ON_ONCE(!dst || !dst->hierarchy)) + return -EINVAL; + + lockdep_assert_held(&src->lock); + + /* Stacks the new layer. */ + if (WARN_ON_ONCE(dst->num_layers < 1)) + return -EINVAL; + + dst->handled_masks[dst->num_layers - 1] = + landlock_upgrade_handled_access_masks(src->handled_masks); + + /* Merges the @src inode tree. */ + err = merge_tree(dst, src, LANDLOCK_KEY_INODE); + if (err) + return err; + +#if IS_ENABLED(CONFIG_INET) + /* Merges the @src network port tree. */ + err = merge_tree(dst, src, LANDLOCK_KEY_NET_PORT); + if (err) + return err; +#endif /* IS_ENABLED(CONFIG_INET) */ + + return 0; +} + +static int inherit_tree(struct landlock_domain *const parent, + struct landlock_domain *const child, + const enum landlock_key_type key_type) +{ + struct landlock_rule *walker_rule, *next_rule; + struct rb_root *parent_root; + int err = 0; + + might_sleep(); + + parent_root = landlock_get_rule_root(&parent->rules, key_type); + if (IS_ERR(parent_root)) + return PTR_ERR(parent_root); + + /* Copies the @parent inode or network tree. */ + rbtree_postorder_for_each_entry_safe(walker_rule, next_rule, + parent_root, node) { + const struct landlock_id id = { + .key = walker_rule->key, + .type = key_type, + }; + + err = landlock_store_rule(&child->rules, id, + &walker_rule->layers, + walker_rule->num_layers); + if (err) + return err; + } + return err; +} + +static int inherit_ruleset(struct landlock_domain *const parent, + struct landlock_domain *const child) +{ + int err = 0; + + might_sleep(); + if (!parent) + return 0; + + /* Copies the @parent inode tree. */ + err = inherit_tree(parent, child, LANDLOCK_KEY_INODE); + if (err) + return err; + +#if IS_ENABLED(CONFIG_INET) + /* Copies the @parent network port tree. */ + err = inherit_tree(parent, child, LANDLOCK_KEY_NET_PORT); + if (err) + return err; +#endif /* IS_ENABLED(CONFIG_INET) */ + + if (WARN_ON_ONCE(child->num_layers <= parent->num_layers)) + return -EINVAL; + + /* + * Copies the parent layer stack and leaves a space for the new layer. + */ + memcpy(child->handled_masks, parent->handled_masks, + flex_array_size(parent, handled_masks, parent->num_layers)); + + if (WARN_ON_ONCE(!parent->hierarchy)) + return -EINVAL; + + landlock_get_hierarchy(parent->hierarchy); + child->hierarchy->parent = parent->hierarchy; + + return 0; +} + +/** + * landlock_merge_ruleset - Merge a ruleset with a domain + * + * @parent: Parent domain. + * @ruleset: New ruleset to be merged. + * + * The current task is requesting to be restricted. The subjective credentials + * must not be in an overridden state. cf. landlock_init_hierarchy_log(). + * + * The caller must hold @ruleset->lock. + * + * Return: A new domain merging @parent and @ruleset on success, or ERR_PTR() on + * failure. If @parent is NULL, the new domain duplicates @ruleset. + */ +struct landlock_domain * +landlock_merge_ruleset(struct landlock_domain *const parent, + struct landlock_ruleset *const ruleset) +{ + struct landlock_domain *new_dom __free(landlock_put_domain) = NULL; + u32 num_layers; + int err; + + might_sleep(); + lockdep_assert_held(&ruleset->lock); + if (WARN_ON_ONCE(!ruleset)) + return ERR_PTR(-EINVAL); + + if (parent) { + if (parent->num_layers >= LANDLOCK_MAX_NUM_LAYERS) + return ERR_PTR(-E2BIG); + num_layers = parent->num_layers + 1; + } else { + num_layers = 1; + } + + /* Creates a new domain... */ + new_dom = create_domain(num_layers); + if (IS_ERR(new_dom)) + return new_dom; + + new_dom->hierarchy = + kzalloc_obj(*new_dom->hierarchy, GFP_KERNEL_ACCOUNT); + if (!new_dom->hierarchy) + return ERR_PTR(-ENOMEM); + + refcount_set(&new_dom->hierarchy->usage, 1); + + /* ...as a child of @parent... */ + err = inherit_ruleset(parent, new_dom); + if (err) + return ERR_PTR(err); + + /* ...and including @ruleset. */ + err = merge_ruleset(new_dom, ruleset); + if (err) + return ERR_PTR(err); + + err = landlock_init_hierarchy_log(new_dom->hierarchy); + if (err) + return ERR_PTR(err); + +#ifdef CONFIG_SECURITY_LANDLOCK_LOG + new_dom->hierarchy->quiet_masks = ruleset->quiet_masks; +#endif /* CONFIG_SECURITY_LANDLOCK_LOG */ + + return no_free_ptr(new_dom); +} + +#ifdef CONFIG_SECURITY_LANDLOCK_LOG /** * get_current_exe - Get the current's executable path, if any @@ -128,7 +588,13 @@ int landlock_init_hierarchy_log(struct landlock_hierarchy *const hierarchy) hierarchy->details = details; hierarchy->id = landlock_get_id_range(1); - hierarchy->log_status = LANDLOCK_LOG_PENDING; + /* + * The hierarchy is born unobservable: landlock_restrict_self() moves it + * out of LANDLOCK_LOG_UNCOMMITTED once it has emitted the creation + * event, so the matching free_domain event fires for it and not for a + * hierarchy whose creation was never observed. + */ + hierarchy->log_status = LANDLOCK_LOG_UNCOMMITTED; hierarchy->log_same_exec = true; hierarchy->log_new_exec = false; atomic64_set(&hierarchy->num_denials, 0); @@ -306,4 +772,4 @@ kunit_test_suite(test_suite); #endif /* CONFIG_SECURITY_LANDLOCK_KUNIT_TEST */ -#endif /* CONFIG_AUDIT */ +#endif /* CONFIG_SECURITY_LANDLOCK_LOG */ diff --git a/security/landlock/domain.h b/security/landlock/domain.h index 2a1660e3dea7..5ce2f91488d5 100644 --- a/security/landlock/domain.h +++ b/security/landlock/domain.h @@ -5,11 +5,13 @@ * Copyright © 2016-2020 Mickaël Salaün <mic@digikod.net> * Copyright © 2018-2020 ANSSI * Copyright © 2024-2025 Microsoft Corporation + * Copyright © 2026 Cloudflare, Inc. */ #ifndef _SECURITY_LANDLOCK_DOMAIN_H #define _SECURITY_LANDLOCK_DOMAIN_H +#include <linux/cleanup.h> #include <linux/limits.h> #include <linux/mm.h> #include <linux/path.h> @@ -17,12 +19,28 @@ #include <linux/refcount.h> #include <linux/sched.h> #include <linux/slab.h> +#include <linux/workqueue.h> #include "access.h" -#include "audit.h" +#include "log.h" +#include "ruleset.h" enum landlock_log_status { - LANDLOCK_LOG_PENDING = 0, + /* + * Hierarchy whose creation event has not been emitted, so it is not yet + * observable from user space. A hierarchy is born in this state (the + * zero value, so a partially initialized hierarchy defaults to "not + * observable") and leaves it when landlock_restrict_self() emits its + * creation event, right after the merge and before the thread-sync + * wait. No trace free_domain event (and no audit deallocation record) + * fires while a hierarchy is in this state, so a hierarchy that never + * became observable (e.g. its initialization failed) is freed silently. + * A domain aborted by a thread-sync failure already emitted its + * creation event, so it is no longer UNCOMMITTED and does fire + * free_domain. + */ + LANDLOCK_LOG_UNCOMMITTED = 0, + LANDLOCK_LOG_PENDING, LANDLOCK_LOG_RECORDED, LANDLOCK_LOG_DISABLED, }; @@ -81,7 +99,7 @@ struct landlock_hierarchy { */ refcount_t usage; -#ifdef CONFIG_AUDIT +#ifdef CONFIG_SECURITY_LANDLOCK_LOG /** * @log_status: Whether this domain should be logged or not. Because * concurrent log entries may be created at the same time, it is still @@ -116,10 +134,10 @@ struct landlock_hierarchy { * logged) if the related object is marked as quiet. */ struct access_masks quiet_masks; -#endif /* CONFIG_AUDIT */ +#endif /* CONFIG_SECURITY_LANDLOCK_LOG */ }; -#ifdef CONFIG_AUDIT +#ifdef CONFIG_SECURITY_LANDLOCK_LOG deny_masks_t landlock_get_deny_masks(const access_mask_t all_existing_optional_access, @@ -142,7 +160,7 @@ landlock_free_hierarchy_details(struct landlock_hierarchy *const hierarchy) kfree(hierarchy->details); } -#else /* CONFIG_AUDIT */ +#else /* CONFIG_SECURITY_LANDLOCK_LOG */ static inline int landlock_init_hierarchy_log(struct landlock_hierarchy *const hierarchy) @@ -155,7 +173,7 @@ landlock_free_hierarchy_details(struct landlock_hierarchy *const hierarchy) { } -#endif /* CONFIG_AUDIT */ +#endif /* CONFIG_SECURITY_LANDLOCK_LOG */ static inline void landlock_get_hierarchy(struct landlock_hierarchy *const hierarchy) @@ -169,11 +187,140 @@ static inline void landlock_put_hierarchy(struct landlock_hierarchy *hierarchy) while (hierarchy && refcount_dec_and_test(&hierarchy->usage)) { const struct landlock_hierarchy *const freeme = hierarchy; - landlock_log_drop_domain(hierarchy); + landlock_log_free_domain(hierarchy); landlock_free_hierarchy_details(hierarchy); hierarchy = hierarchy->parent; kfree(freeme); } } +/** + * struct landlock_domain - Immutable Landlock domain + * + * A domain is created from a ruleset by landlock_merge_ruleset() and enforced + * on a task. Once created, its rules and access masks are immutable. Unlike + * &struct landlock_ruleset, a domain has no lock field. + */ +struct landlock_domain { + /** + * @rules: Red-black tree storage for rules. + */ + struct landlock_rules rules; + /** + * @hierarchy: Enables hierarchy identification even when a parent + * domain vanishes. This is needed for the ptrace and scope + * restrictions. + */ + struct landlock_hierarchy *hierarchy; + union { + /** + * @work_free: Enables to free a domain within a lockless + * section. This is only used by landlock_put_domain_deferred() + * when @usage reaches zero. The fields @usage, @num_layers and + * @handled_masks are then unused. + */ + struct work_struct work_free; + struct { + /** + * @usage: Number of credentials referencing this + * domain. + */ + refcount_t usage; + /** + * @num_layers: Number of layers that are used in this + * domain. This enables to check that all the layers + * allow an access request. + */ + u32 num_layers; + /** + * @handled_masks: Contains the subset of filesystem and + * network actions that are restricted by a domain. A + * domain saves all layers of merged rulesets in a stack + * (FAM), starting from the first layer to the last one. + * These layers are used when merging rulesets, for user + * space backward compatibility (i.e. future-proof), and + * to properly handle merged rulesets without + * overlapping access rights. These layers are set once + * and never changed for the lifetime of the domain. + */ + struct access_masks handled_masks[]; + }; + }; +}; + +static inline access_mask_t +landlock_get_fs_access_mask(const struct landlock_domain *const domain, + const u16 layer_level) +{ + /* Handles all initially denied by default access rights. */ + return domain->handled_masks[layer_level].fs | + _LANDLOCK_ACCESS_FS_INITIALLY_DENIED; +} + +static inline access_mask_t +landlock_get_net_access_mask(const struct landlock_domain *const domain, + const u16 layer_level) +{ + return domain->handled_masks[layer_level].net; +} + +static inline access_mask_t +landlock_get_scope_mask(const struct landlock_domain *const domain, + const u16 layer_level) +{ + return domain->handled_masks[layer_level].scope; +} + +/** + * landlock_union_access_masks - Return all access rights handled in the + * domain + * + * @domain: Landlock domain + * + * Return: An access_masks result of the OR of all the domain's access masks. + */ +static inline struct access_masks +landlock_union_access_masks(const struct landlock_domain *const domain) +{ + union access_masks_all matches = {}; + size_t layer_level; + + for (layer_level = 0; layer_level < domain->num_layers; layer_level++) { + union access_masks_all layer = { + .masks = domain->handled_masks[layer_level], + }; + + matches.all |= layer.all; + } + + return matches.masks; +} + +void landlock_put_domain(struct landlock_domain *const domain); +void landlock_put_domain_deferred(struct landlock_domain *const domain); + +DEFINE_FREE(landlock_put_domain, struct landlock_domain *, + if (!IS_ERR_OR_NULL(_T)) landlock_put_domain(_T)) + +struct landlock_domain * +landlock_merge_ruleset(struct landlock_domain *const parent, + struct landlock_ruleset *const ruleset); + +bool landlock_unmask_layers(const struct landlock_domain *const domain, + const struct landlock_id id, + struct layer_masks *masks, + const struct landlock_rule **matched_rule); + +access_mask_t +landlock_init_layer_masks(const struct landlock_domain *const domain, + const access_mask_t access_request, + struct layer_masks *masks, + const enum landlock_key_type key_type); + +static inline void landlock_get_domain(struct landlock_domain *const domain) +{ + if (domain) + refcount_inc(&domain->usage); +} + #endif /* _SECURITY_LANDLOCK_DOMAIN_H */ diff --git a/security/landlock/errata/abi-1.h b/security/landlock/errata/abi-1.h index 3f099555f059..315ea7e0fe50 100644 --- a/security/landlock/errata/abi-1.h +++ b/security/landlock/errata/abi-1.h @@ -22,3 +22,26 @@ * from their original mount points. */ LANDLOCK_ERRATUM(3) + +/** + * DOC: erratum_4 + * + * Erratum 4: Creation of whiteout objects + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * This fix changes the access rights required for the creation of whiteout + * objects through :manpage:`mknod(2)`, :manpage:`renameat2(2)`, or + * :manpage:`link(2)`. Creating whiteout objects is now guarded by + * ``LANDLOCK_ACCESS_FS_MAKE_REG`` instead of ``LANDLOCK_ACCESS_FS_MAKE_CHAR``. + * + * Whiteout objects are used in OverlayFS to mark the absence of a file in an + * upper file system. Despite being created with ``S_IFCHR``, whiteout objects + * do not count as character devices. + * + * Impact: + * + * Sandboxed programs that create OverlayFS whiteouts (such as fuse-overlayfs) + * now require ``LANDLOCK_ACCESS_FS_MAKE_REG`` instead of + * ``LANDLOCK_ACCESS_FS_MAKE_CHAR``. + */ +LANDLOCK_ERRATUM(4) diff --git a/security/landlock/fs.c b/security/landlock/fs.c index f7e5e4ef9eac..30aa6ce13590 100644 --- a/security/landlock/fs.c +++ b/security/landlock/fs.c @@ -20,6 +20,7 @@ #include <linux/falloc.h> #include <linux/fs.h> #include <linux/init.h> +#include <linux/kdev_t.h> #include <linux/kernel.h> #include <linux/limits.h> #include <linux/list.h> @@ -42,16 +43,18 @@ #include <uapi/linux/landlock.h> #include "access.h" -#include "audit.h" #include "common.h" #include "cred.h" #include "domain.h" #include "fs.h" #include "limits.h" +#include "log.h" #include "object.h" #include "ruleset.h" #include "setup.h" +#include <trace/events/landlock.h> + /* Underlying object management */ static void release_inode(struct landlock_object *const object) @@ -336,18 +339,34 @@ int landlock_append_fs_rule(struct landlock_ruleset *const ruleset, if (!d_is_dir(path->dentry) && !access_mask_subset(access_rights, ACCESS_FILE)) return -EINVAL; - if (WARN_ON_ONCE(ruleset->num_layers != 1)) - return -EINVAL; /* Transforms relative access rights to absolute ones. */ access_rights |= LANDLOCK_MASK_ACCESS_FS & - ~landlock_get_fs_access_mask(ruleset, 0); + ~(ruleset->handled_masks.fs | + _LANDLOCK_ACCESS_FS_INITIALLY_DENIED); id.key.object = get_inode_object(d_backing_inode(path->dentry)); if (IS_ERR(id.key.object)) return PTR_ERR(id.key.object); mutex_lock(&ruleset->lock); err = landlock_insert_rule(ruleset, id, access_rights, flags); + + /* + * Emit after the rule insertion succeeds, so every event corresponds to + * a rule that is actually in the ruleset. The ruleset lock is still + * held for BTF consistency (enforced by lockdep_assert_held in + * TP_fast_assign). + */ + if (!err && trace_landlock_add_rule_fs_enabled()) { + char *buffer __free(__putname) = __getname(); + const char *pathname = + buffer ? resolve_path_for_trace(path, buffer) : + "<no_mem>"; + + trace_landlock_add_rule_fs(ruleset, access_rights, path, + pathname); + } mutex_unlock(&ruleset->lock); + /* * No need to check for an error because landlock_insert_rule() * increments the refcount for the new object if needed. @@ -358,31 +377,55 @@ int landlock_append_fs_rule(struct landlock_ruleset *const ruleset, /* Access-control management */ -/* - * The lifetime of the returned rule is tied to @domain. +/** + * get_inode_id - Look up the Landlock object for a dentry + * @dentry: The dentry to look up. + * @id: Filled with the inode's Landlock object pointer on success. * - * Returns NULL if no rule is found or if @dentry is negative. + * Extracts the Landlock object pointer from @dentry's inode security blob and + * stores it in @id for use as a rule-tree lookup key. + * + * When this returns false (negative dentry or no Landlock object), no rule can + * match this inode, so landlock_unmask_layers() need not be called. Callers + * that gate landlock_unmask_layers() on this function must handle the NULL + * masks case independently, since the !masks-returns-true early-return in + * landlock_unmask_layers() will not be reached. See the allowed_parent2 + * initialization in is_access_to_paths_allowed(). + * + * Return: True if a Landlock object exists for @dentry, false otherwise. */ -static const struct landlock_rule * -find_rule(const struct landlock_ruleset *const domain, - const struct dentry *const dentry) +static bool get_inode_id(const struct dentry *const dentry, + struct landlock_id *id) { - const struct landlock_rule *rule; - const struct inode *inode; - struct landlock_id id = { - .type = LANDLOCK_KEY_INODE, - }; - /* Ignores nonexistent leafs. */ if (d_is_negative(dentry)) - return NULL; + return false; - inode = d_backing_inode(dentry); - rcu_read_lock(); - id.key.object = rcu_dereference(landlock_inode(inode)->object); - rule = landlock_find_rule(domain, id); - rcu_read_unlock(); - return rule; + /* + * rcu_access_pointer() is sufficient: the pointer is used only as a + * numeric comparison key for rule lookup, not dereferenced. The object + * cannot be freed while the domain exists because the domain's rule + * tree holds its own reference to it. + */ + id->key.object = rcu_access_pointer( + landlock_inode(d_backing_inode(dentry))->object); + return !!id->key.object; +} + +static bool unmask_layers_fs(const struct landlock_domain *const domain, + const struct landlock_id id, + const access_mask_t access_request, + struct layer_masks *masks, + const struct dentry *const dentry) +{ + const struct landlock_rule *rule = NULL; + bool ret; + + ret = landlock_unmask_layers(domain, id, masks, &rule); + if (rule) + trace_landlock_check_rule_fs(domain, rule, access_request, + dentry); + return ret; } /* @@ -749,7 +792,7 @@ static void test_is_eacces_with_write(struct kunit *const test) * Return: True if the access request is granted, false otherwise. */ static bool -is_access_to_paths_allowed(const struct landlock_ruleset *const domain, +is_access_to_paths_allowed(const struct landlock_domain *const domain, const struct path *const path, const access_mask_t access_request_parent1, struct layer_masks *layer_masks_parent1, @@ -763,6 +806,9 @@ is_access_to_paths_allowed(const struct landlock_ruleset *const domain, bool allowed_parent1 = false, allowed_parent2 = false, is_dom_check, child1_is_directory = true, child2_is_directory = true; struct path walker_path; + struct landlock_id id = { + .type = LANDLOCK_KEY_INODE, + }; access_mask_t access_masked_parent1, access_masked_parent2; struct layer_masks _layer_masks_child1, _layer_masks_child2; struct layer_masks *layer_masks_child1 = NULL, @@ -802,28 +848,46 @@ is_access_to_paths_allowed(const struct landlock_ruleset *const domain, /* For a simple request, only check for requested accesses. */ access_masked_parent1 = access_request_parent1; access_masked_parent2 = access_request_parent2; + /* + * Simple requests have no parent2 to check, so parent2 is + * trivially allowed. This must be set explicitly because the + * get_inode_id() gate in the pathwalk loop may prevent + * landlock_unmask_layers() from being called (which would + * otherwise return true for NULL masks as a side effect). + */ + allowed_parent2 = true; is_dom_check = false; } if (unlikely(dentry_child1)) { - /* - * Get the layer masks for the child dentries for use by domain - * check later. - */ - if (landlock_init_layer_masks(domain, LANDLOCK_MASK_ACCESS_FS, - &_layer_masks_child1, - LANDLOCK_KEY_INODE)) - landlock_unmask_layers(find_rule(domain, dentry_child1), - &_layer_masks_child1); + struct landlock_id id = { + .type = LANDLOCK_KEY_INODE, + }; + access_mask_t handled; + + handled = landlock_init_layer_masks(domain, + LANDLOCK_MASK_ACCESS_FS, + &_layer_masks_child1, + LANDLOCK_KEY_INODE); + if (handled && get_inode_id(dentry_child1, &id)) + unmask_layers_fs(domain, id, handled, + &_layer_masks_child1, dentry_child1); layer_masks_child1 = &_layer_masks_child1; child1_is_directory = d_is_dir(dentry_child1); } if (unlikely(dentry_child2)) { - if (landlock_init_layer_masks(domain, LANDLOCK_MASK_ACCESS_FS, - &_layer_masks_child2, - LANDLOCK_KEY_INODE)) - landlock_unmask_layers(find_rule(domain, dentry_child2), - &_layer_masks_child2); + struct landlock_id id = { + .type = LANDLOCK_KEY_INODE, + }; + access_mask_t handled; + + handled = landlock_init_layer_masks(domain, + LANDLOCK_MASK_ACCESS_FS, + &_layer_masks_child2, + LANDLOCK_KEY_INODE); + if (handled && get_inode_id(dentry_child2, &id)) + unmask_layers_fs(domain, id, handled, + &_layer_masks_child2, dentry_child2); layer_masks_child2 = &_layer_masks_child2; child2_is_directory = d_is_dir(dentry_child2); } @@ -835,8 +899,6 @@ is_access_to_paths_allowed(const struct landlock_ruleset *const domain, * restriction. */ while (true) { - const struct landlock_rule *rule; - /* * If at least all accesses allowed on the destination are * already allowed on the source, respectively if there is at @@ -877,13 +939,20 @@ is_access_to_paths_allowed(const struct landlock_ruleset *const domain, break; } - rule = find_rule(domain, walker_path.dentry); - allowed_parent1 = - allowed_parent1 || - landlock_unmask_layers(rule, layer_masks_parent1); - allowed_parent2 = - allowed_parent2 || - landlock_unmask_layers(rule, layer_masks_parent2); + if (get_inode_id(walker_path.dentry, &id)) { + allowed_parent1 = + allowed_parent1 || + unmask_layers_fs(domain, id, + access_masked_parent1, + layer_masks_parent1, + walker_path.dentry); + allowed_parent2 = + allowed_parent2 || + unmask_layers_fs(domain, id, + access_masked_parent2, + layer_masks_parent2, + walker_path.dentry); + } /* Stops when a rule from each layer grants access. */ if (allowed_parent1 && allowed_parent2) @@ -933,10 +1002,11 @@ jump_up: path_put(&walker_path); /* - * Check CONFIG_AUDIT to enable elision of log_request_parent* and - * associated caller's stack variables thanks to dead code elimination. + * Check CONFIG_SECURITY_LANDLOCK_LOG to enable elision of + * log_request_parent* and associated caller's stack variables thanks to + * dead code elimination. */ -#ifdef CONFIG_AUDIT +#ifdef CONFIG_SECURITY_LANDLOCK_LOG if (!allowed_parent1 && log_request_parent1) { log_request_parent1->type = LANDLOCK_REQUEST_FS_ACCESS; log_request_parent1->audit.type = LSM_AUDIT_DATA_PATH; @@ -952,7 +1022,7 @@ jump_up: log_request_parent2->access = access_masked_parent2; log_request_parent2->layer_masks = layer_masks_parent2; } -#endif /* CONFIG_AUDIT */ +#endif /* CONFIG_SECURITY_LANDLOCK_LOG */ return allowed_parent1 && allowed_parent2; } @@ -983,7 +1053,8 @@ static int current_check_access_path(const struct path *const path, return -EACCES; } -static __attribute_const__ access_mask_t get_mode_access(const umode_t mode) +static __attribute_const__ access_mask_t get_mode_access(const umode_t mode, + const dev_t dev) { switch (mode & S_IFMT) { case S_IFLNK: @@ -991,6 +1062,9 @@ static __attribute_const__ access_mask_t get_mode_access(const umode_t mode) case S_IFDIR: return LANDLOCK_ACCESS_FS_MAKE_DIR; case S_IFCHR: + /* Whiteout objects are guarded with MAKE_REG. */ + if (dev == WHITEOUT_DEV) + return LANDLOCK_ACCESS_FS_MAKE_REG; return LANDLOCK_ACCESS_FS_MAKE_CHAR; case S_IFBLK: return LANDLOCK_ACCESS_FS_MAKE_BLOCK; @@ -1007,6 +1081,13 @@ static __attribute_const__ access_mask_t get_mode_access(const umode_t mode) } } +static access_mask_t get_dentry_access(const struct dentry *const dentry) +{ + const struct inode *const inode = d_backing_inode(dentry); + + return get_mode_access(inode->i_mode, inode->i_rdev); +} + static access_mask_t maybe_remove(const struct dentry *const dentry) { if (d_is_negative(dentry)) @@ -1039,29 +1120,36 @@ static access_mask_t maybe_remove(const struct dentry *const dentry) * Return: True if all the domain access rights are allowed for @dir, false if * the walk reached @mnt_root. */ -static bool collect_domain_accesses(const struct landlock_ruleset *const domain, +static bool collect_domain_accesses(const struct landlock_domain *const domain, const struct dentry *const mnt_root, struct dentry *dir, struct layer_masks *layer_masks_dom) { bool ret = false; + access_mask_t access_masked_dom; if (WARN_ON_ONCE(!domain || !mnt_root || !dir || !layer_masks_dom)) return true; if (is_nouser_or_private(dir)) return true; - if (!landlock_init_layer_masks(domain, LANDLOCK_MASK_ACCESS_FS, - layer_masks_dom, LANDLOCK_KEY_INODE)) + access_masked_dom = + landlock_init_layer_masks(domain, LANDLOCK_MASK_ACCESS_FS, + layer_masks_dom, LANDLOCK_KEY_INODE); + if (!access_masked_dom) return true; dget(dir); while (true) { struct dentry *parent_dentry; + struct landlock_id id = { + .type = LANDLOCK_KEY_INODE, + }; /* Gets all layers allowing all domain accesses. */ - if (landlock_unmask_layers(find_rule(domain, dir), - layer_masks_dom)) { + if (get_inode_id(dir, &id) && + unmask_layers_fs(domain, id, access_masked_dom, + layer_masks_dom, dir)) { /* * Stops when all handled accesses are allowed by at * least one rule in each layer. @@ -1093,6 +1181,7 @@ static bool collect_domain_accesses(const struct landlock_ruleset *const domain, * @new_dentry: Destination file or directory. * @removable: Sets to true if it is a rename operation. * @exchange: Sets to true if it is a rename operation with RENAME_EXCHANGE. + * @whiteout: Sets to true if it is a rename operation with RENAME_WHITEOUT. * * Because of its unprivileged constraints, Landlock relies on file hierarchies * (and not only inodes) to tie access rights to files. Being able to link or @@ -1140,7 +1229,8 @@ static bool collect_domain_accesses(const struct landlock_ruleset *const domain, static int current_check_refer_path(struct dentry *const old_dentry, const struct path *const new_dir, struct dentry *const new_dentry, - const bool removable, const bool exchange) + const bool removable, const bool exchange, + const bool whiteout) { const struct landlock_cred_security *const subject = landlock_get_applicable_subject(current_cred(), any_fs, NULL); @@ -1159,18 +1249,25 @@ static int current_check_refer_path(struct dentry *const old_dentry, if (exchange) { if (unlikely(d_is_negative(new_dentry))) return -ENOENT; - access_request_parent1 = - get_mode_access(d_backing_inode(new_dentry)->i_mode); + access_request_parent1 = get_dentry_access(new_dentry); } else { access_request_parent1 = 0; } - access_request_parent2 = - get_mode_access(d_backing_inode(old_dentry)->i_mode); + access_request_parent2 = get_dentry_access(old_dentry); if (removable) { access_request_parent1 |= maybe_remove(old_dentry); access_request_parent2 |= maybe_remove(new_dentry); } + /* + * In case of renameat2(2) with RENAME_WHITEOUT, a whiteout object is + * created in the source location, so we require an additional access + * right there. + */ + if (whiteout) + access_request_parent1 |= + get_mode_access(S_IFCHR | WHITEOUT_MODE, WHITEOUT_DEV); + /* The mount points are the same for old and new paths, cf. EXDEV. */ if (old_dentry->d_parent == new_dir->dentry) { /* @@ -1520,7 +1617,7 @@ static int hook_path_link(struct dentry *const old_dentry, struct dentry *const new_dentry) { return current_check_refer_path(old_dentry, new_dir, new_dentry, false, - false); + false, false); } static int hook_path_rename(const struct path *const old_dir, @@ -1531,7 +1628,8 @@ static int hook_path_rename(const struct path *const old_dir, { /* old_dir refers to old_dentry->d_parent and new_dir->mnt */ return current_check_refer_path(old_dentry, new_dir, new_dentry, true, - !!(flags & RENAME_EXCHANGE)); + !!(flags & RENAME_EXCHANGE), + !!(flags & RENAME_WHITEOUT)); } static int hook_path_mkdir(const struct path *const dir, @@ -1544,7 +1642,8 @@ static int hook_path_mknod(const struct path *const dir, struct dentry *const dentry, const umode_t mode, const unsigned int dev) { - return current_check_access_path(dir, get_mode_access(mode)); + return current_check_access_path( + dir, get_mode_access(mode, new_decode_dev(dev))); } static int hook_path_symlink(const struct path *const dir, @@ -1589,8 +1688,8 @@ static int hook_path_truncate(const struct path *const path) * @masks: Layer access masks to unmask * @access: Access bits that control scoping */ -static void unmask_scoped_access(const struct landlock_ruleset *const client, - const struct landlock_ruleset *const server, +static void unmask_scoped_access(const struct landlock_domain *const client, + const struct landlock_domain *const server, struct layer_masks *const masks, const access_mask_t access) { @@ -1644,7 +1743,7 @@ static void unmask_scoped_access(const struct landlock_ruleset *const client, static int hook_unix_find(const struct path *const path, struct sock *other, int flags) { - const struct landlock_ruleset *dom_other; + const struct landlock_domain *dom_other; const struct landlock_cred_security *subject; struct layer_masks layer_masks; struct landlock_request request = {}; @@ -1802,14 +1901,14 @@ static int hook_file_open(struct file *const file) * file access rights in the opened struct file. */ landlock_file(file)->allowed_access = allowed_access; -#ifdef CONFIG_AUDIT +#ifdef CONFIG_SECURITY_LANDLOCK_LOG landlock_file(file)->deny_masks = landlock_get_deny_masks( _LANDLOCK_ACCESS_FS_OPTIONAL, optional_access, &layer_masks); landlock_file(file)->quiet_optional_accesses = landlock_get_quiet_optional_accesses( _LANDLOCK_ACCESS_FS_OPTIONAL, landlock_file(file)->deny_masks, &layer_masks); -#endif /* CONFIG_AUDIT */ +#endif /* CONFIG_SECURITY_LANDLOCK_LOG */ if (access_mask_subset(open_access_request, allowed_access)) return 0; @@ -1843,10 +1942,10 @@ static int hook_file_truncate(struct file *const file) }, .all_existing_optional_access = _LANDLOCK_ACCESS_FS_OPTIONAL, .access = LANDLOCK_ACCESS_FS_TRUNCATE, -#ifdef CONFIG_AUDIT +#ifdef CONFIG_SECURITY_LANDLOCK_LOG .deny_masks = landlock_file(file)->deny_masks, .quiet_optional_accesses = landlock_file(file)->quiet_optional_accesses, -#endif /* CONFIG_AUDIT */ +#endif /* CONFIG_SECURITY_LANDLOCK_LOG */ }); return -EACCES; } @@ -1883,10 +1982,10 @@ static int hook_file_ioctl_common(const struct file *const file, }, .all_existing_optional_access = _LANDLOCK_ACCESS_FS_OPTIONAL, .access = LANDLOCK_ACCESS_FS_IOCTL_DEV, -#ifdef CONFIG_AUDIT +#ifdef CONFIG_SECURITY_LANDLOCK_LOG .deny_masks = landlock_file(file)->deny_masks, .quiet_optional_accesses = landlock_file(file)->quiet_optional_accesses, -#endif /* CONFIG_AUDIT */ +#endif /* CONFIG_SECURITY_LANDLOCK_LOG */ }); return -EACCES; } @@ -1939,7 +2038,7 @@ static bool control_current_fowner(struct fown_struct *const fown) static void hook_file_set_fowner(struct file *file) { - struct landlock_ruleset *prev_dom; + struct landlock_domain *prev_dom; struct landlock_cred_security fown_subject = {}; struct pid *prev_tg, *fown_tg = NULL; size_t fown_layer = 0; @@ -1952,7 +2051,7 @@ static void hook_file_set_fowner(struct file *file) landlock_get_applicable_subject( current_cred(), signal_scope, &fown_layer); if (new_subject) { - landlock_get_ruleset(new_subject->domain); + landlock_get_domain(new_subject->domain); fown_subject = *new_subject; fown_tg = get_pid(task_tgid(current)); } @@ -1962,19 +2061,19 @@ static void hook_file_set_fowner(struct file *file) prev_tg = landlock_file(file)->fown_tg; landlock_file(file)->fown_subject = fown_subject; landlock_file(file)->fown_tg = fown_tg; -#ifdef CONFIG_AUDIT +#ifdef CONFIG_SECURITY_LANDLOCK_LOG landlock_file(file)->fown_layer = fown_layer; -#endif /* CONFIG_AUDIT*/ +#endif /* CONFIG_SECURITY_LANDLOCK_LOG */ /* May be called in an RCU read-side critical section. */ - landlock_put_ruleset_deferred(prev_dom); + landlock_put_domain_deferred(prev_dom); put_pid(prev_tg); } static void hook_file_free_security(struct file *file) { put_pid(landlock_file(file)->fown_tg); - landlock_put_ruleset_deferred(landlock_file(file)->fown_subject.domain); + landlock_put_domain_deferred(landlock_file(file)->fown_subject.domain); } static struct security_hook_list landlock_hooks[] __ro_after_init = { diff --git a/security/landlock/fs.h b/security/landlock/fs.h index b4421d9df68f..50b3ddb8ac1c 100644 --- a/security/landlock/fs.h +++ b/security/landlock/fs.h @@ -11,6 +11,7 @@ #define _SECURITY_LANDLOCK_FS_H #include <linux/build_bug.h> +#include <linux/cleanup.h> #include <linux/fs.h> #include <linux/init.h> #include <linux/rcupdate.h> @@ -20,6 +21,8 @@ #include "ruleset.h" #include "setup.h" +DEFINE_FREE(__putname, char *, if (_T) __putname(_T)) + /** * struct landlock_inode_security - Inode security blob * @@ -57,7 +60,7 @@ struct landlock_file_security { */ access_mask_t allowed_access; -#ifdef CONFIG_AUDIT +#ifdef CONFIG_SECURITY_LANDLOCK_LOG /** * @deny_masks: Domain layer levels that deny an optional access (see * _LANDLOCK_ACCESS_FS_OPTIONAL). @@ -75,7 +78,7 @@ struct landlock_file_security { * LANDLOCK_SCOPE_SIGNAL. */ u8 fown_layer; -#endif /* CONFIG_AUDIT */ +#endif /* CONFIG_SECURITY_LANDLOCK_LOG */ /** * @fown_subject: Landlock credential of the task that set the PID that @@ -97,7 +100,7 @@ struct landlock_file_security { struct pid *fown_tg; }; -#ifdef CONFIG_AUDIT +#ifdef CONFIG_SECURITY_LANDLOCK_LOG /* Makes sure all layers can be identified. */ /* clang-format off */ @@ -113,7 +116,7 @@ static_assert(BITS_PER_TYPE(typeof_member(struct landlock_file_security, quiet_optional_accesses)) >= HWEIGHT(_LANDLOCK_ACCESS_FS_OPTIONAL)); -#endif /* CONFIG_AUDIT */ +#endif /* CONFIG_SECURITY_LANDLOCK_LOG */ /** * struct landlock_superblock_security - Superblock security blob @@ -153,4 +156,33 @@ int landlock_append_fs_rule(struct landlock_ruleset *const ruleset, const struct path *const path, access_mask_t access_hierarchy, const u32 flags); +/** + * resolve_path_for_trace - Resolve a path for tracepoint display + * + * @path: The path to resolve. + * @buf: A buffer of at least PATH_MAX bytes for the resolved path. + * + * Uses d_absolute_path() to produce a namespace-independent absolute path, + * unlike d_path() which resolves relative to the process's chroot. This + * ensures trace output is deterministic regardless of the tracer's mount + * namespace. + * + * Return: A pointer into @buf with the resolved path, or an error string + * ("<too_long>", "<unreachable>"). + */ +static inline const char *resolve_path_for_trace(const struct path *path, + char *buf) +{ + const char *p; + + p = d_absolute_path(path, buf, PATH_MAX); + if (!IS_ERR_OR_NULL(p)) + return p; + + if (PTR_ERR(p) == -ENAMETOOLONG) + return "<too_long>"; + + return "<unreachable>"; +} + #endif /* _SECURITY_LANDLOCK_FS_H */ diff --git a/security/landlock/id.h b/security/landlock/id.h index 45dcfb9e9a8b..2a43c2b523a8 100644 --- a/security/landlock/id.h +++ b/security/landlock/id.h @@ -8,18 +8,18 @@ #ifndef _SECURITY_LANDLOCK_ID_H #define _SECURITY_LANDLOCK_ID_H -#ifdef CONFIG_AUDIT +#ifdef CONFIG_SECURITY_LANDLOCK_LOG void __init landlock_init_id(void); u64 landlock_get_id_range(size_t number_of_ids); -#else /* CONFIG_AUDIT */ +#else /* CONFIG_SECURITY_LANDLOCK_LOG */ static inline void __init landlock_init_id(void) { } -#endif /* CONFIG_AUDIT */ +#endif /* CONFIG_SECURITY_LANDLOCK_LOG */ #endif /* _SECURITY_LANDLOCK_ID_H */ diff --git a/security/landlock/limits.h b/security/landlock/limits.h index 08d5f2f6d321..1a7c5fb8f6fd 100644 --- a/security/landlock/limits.h +++ b/security/landlock/limits.h @@ -34,7 +34,7 @@ #define LANDLOCK_NUM_ACCESS_MAX \ MAX(MAX(LANDLOCK_NUM_ACCESS_FS, LANDLOCK_NUM_ACCESS_NET), LANDLOCK_NUM_SCOPE) -#define LANDLOCK_LAST_RESTRICT_SELF LANDLOCK_RESTRICT_SELF_TSYNC +#define LANDLOCK_LAST_RESTRICT_SELF LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS #define LANDLOCK_MASK_RESTRICT_SELF ((LANDLOCK_LAST_RESTRICT_SELF << 1) - 1) /* clang-format on */ diff --git a/security/landlock/log.c b/security/landlock/log.c new file mode 100644 index 000000000000..a8578a6f2ce9 --- /dev/null +++ b/security/landlock/log.c @@ -0,0 +1,587 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Landlock - Log helpers + * + * Copyright © 2023-2025 Microsoft Corporation + * Copyright © 2026 Cloudflare, Inc. + */ + +#include <kunit/test.h> +#include <linux/bitops.h> +#include <uapi/linux/landlock.h> + +#include "access.h" +#include "audit.h" +#include "common.h" +#include "cred.h" +#include "domain.h" +#include "limits.h" +#include "log.h" +#include "ruleset.h" +#include "trace.h" + +static struct landlock_hierarchy * +get_hierarchy(const struct landlock_domain *const domain, const size_t layer) +{ + struct landlock_hierarchy *hierarchy = domain->hierarchy; + ssize_t i; + + if (WARN_ON_ONCE(layer >= domain->num_layers)) + return hierarchy; + + for (i = domain->num_layers - 1; i > layer; i--) { + if (WARN_ON_ONCE(!hierarchy->parent)) + break; + + hierarchy = hierarchy->parent; + } + + return hierarchy; +} + +#ifdef CONFIG_SECURITY_LANDLOCK_KUNIT_TEST + +static void test_get_hierarchy(struct kunit *const test) +{ + struct landlock_hierarchy dom0_hierarchy = { + .id = 10, + }; + struct landlock_hierarchy dom1_hierarchy = { + .parent = &dom0_hierarchy, + .id = 20, + }; + struct landlock_hierarchy dom2_hierarchy = { + .parent = &dom1_hierarchy, + .id = 30, + }; + struct landlock_domain dom2 = { + .hierarchy = &dom2_hierarchy, + .num_layers = 3, + }; + + KUNIT_EXPECT_EQ(test, 10, get_hierarchy(&dom2, 0)->id); + KUNIT_EXPECT_EQ(test, 20, get_hierarchy(&dom2, 1)->id); + KUNIT_EXPECT_EQ(test, 30, get_hierarchy(&dom2, 2)->id); + /* KUNIT_EXPECT_EQ(test, 30, get_hierarchy(&dom2, -1)->id); */ +} + +#endif /* CONFIG_SECURITY_LANDLOCK_KUNIT_TEST */ + +/* Get the youngest layer that denied the access_request. */ +static size_t get_denied_layer(const struct landlock_domain *const domain, + access_mask_t *const access_request, + const struct layer_masks *masks) +{ + for (ssize_t i = ARRAY_SIZE(masks->layers) - 1; i >= 0; i--) { + if (masks->layers[i].access & *access_request) { + *access_request &= masks->layers[i].access; + return i; + } + } + + /* Not found - fall back to default values */ + *access_request = 0; + return domain->num_layers - 1; +} + +#ifdef CONFIG_SECURITY_LANDLOCK_KUNIT_TEST + +static void test_get_denied_layer(struct kunit *const test) +{ + const struct landlock_domain dom = { + .num_layers = 5, + }; + const struct layer_masks masks = { + .layers[0].access = LANDLOCK_ACCESS_FS_EXECUTE | + LANDLOCK_ACCESS_FS_READ_DIR, + .layers[1].access = LANDLOCK_ACCESS_FS_READ_FILE | + LANDLOCK_ACCESS_FS_READ_DIR, + .layers[2].access = LANDLOCK_ACCESS_FS_REMOVE_DIR, + }; + access_mask_t access; + + access = LANDLOCK_ACCESS_FS_EXECUTE; + KUNIT_EXPECT_EQ(test, 0, get_denied_layer(&dom, &access, &masks)); + KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_EXECUTE); + + access = LANDLOCK_ACCESS_FS_READ_FILE; + KUNIT_EXPECT_EQ(test, 1, get_denied_layer(&dom, &access, &masks)); + KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_READ_FILE); + + access = LANDLOCK_ACCESS_FS_READ_DIR; + KUNIT_EXPECT_EQ(test, 1, get_denied_layer(&dom, &access, &masks)); + KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_READ_DIR); + + access = LANDLOCK_ACCESS_FS_READ_FILE | LANDLOCK_ACCESS_FS_READ_DIR; + KUNIT_EXPECT_EQ(test, 1, get_denied_layer(&dom, &access, &masks)); + KUNIT_EXPECT_EQ(test, access, + LANDLOCK_ACCESS_FS_READ_FILE | + LANDLOCK_ACCESS_FS_READ_DIR); + + access = LANDLOCK_ACCESS_FS_EXECUTE | LANDLOCK_ACCESS_FS_READ_DIR; + KUNIT_EXPECT_EQ(test, 1, get_denied_layer(&dom, &access, &masks)); + KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_READ_DIR); + + access = LANDLOCK_ACCESS_FS_WRITE_FILE; + KUNIT_EXPECT_EQ(test, 4, get_denied_layer(&dom, &access, &masks)); + KUNIT_EXPECT_EQ(test, access, 0); +} + +#endif /* CONFIG_SECURITY_LANDLOCK_KUNIT_TEST */ + +static size_t +get_layer_from_deny_masks(access_mask_t *const access_request, + const access_mask_t all_existing_optional_access, + const deny_masks_t deny_masks, + optional_access_t quiet_optional_accesses, + bool *quiet) +{ + const unsigned long access_opt = all_existing_optional_access; + const unsigned long access_req = *access_request; + access_mask_t missing = 0; + size_t youngest_layer = 0; + size_t access_index = 0; + unsigned long access_bit; + bool should_quiet = false; + + /* This will require change with new object types. */ + WARN_ON_ONCE(access_opt != _LANDLOCK_ACCESS_FS_OPTIONAL); + + for_each_set_bit(access_bit, &access_opt, + BITS_PER_TYPE(access_mask_t)) { + if (access_req & BIT(access_bit)) { + const size_t layer = + (deny_masks >> + (access_index * + HWEIGHT(LANDLOCK_MAX_NUM_LAYERS - 1))) & + (LANDLOCK_MAX_NUM_LAYERS - 1); + const bool layer_has_quiet = + !!(quiet_optional_accesses & BIT(access_index)); + + if (layer > youngest_layer) { + youngest_layer = layer; + missing = BIT(access_bit); + should_quiet = layer_has_quiet; + } else if (layer == youngest_layer) { + missing |= BIT(access_bit); + /* + * Whether the layer has rules with quiet flag + * covering the file accessed does not depend on + * the access, and so the following + * WARN_ON_ONCE() should not fail. + */ + WARN_ON_ONCE(should_quiet && !layer_has_quiet); + should_quiet = layer_has_quiet; + } + } + access_index++; + } + + *access_request = missing; + *quiet = should_quiet; + return youngest_layer; +} + +#ifdef CONFIG_SECURITY_LANDLOCK_KUNIT_TEST + +static void test_get_layer_from_deny_masks(struct kunit *const test) +{ + deny_masks_t deny_mask; + access_mask_t access; + optional_access_t quiet_optional_accesses; + bool quiet; + + /* truncate:0 ioctl_dev:2 */ + deny_mask = 0x20; + quiet_optional_accesses = 0; + + access = LANDLOCK_ACCESS_FS_TRUNCATE; + KUNIT_EXPECT_EQ(test, 0, + get_layer_from_deny_masks( + &access, _LANDLOCK_ACCESS_FS_OPTIONAL, + deny_mask, quiet_optional_accesses, &quiet)); + KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_TRUNCATE); + KUNIT_EXPECT_EQ(test, quiet, false); + + access = LANDLOCK_ACCESS_FS_IOCTL_DEV; + KUNIT_EXPECT_EQ(test, 2, + get_layer_from_deny_masks( + &access, _LANDLOCK_ACCESS_FS_OPTIONAL, + deny_mask, quiet_optional_accesses, &quiet)); + KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_IOCTL_DEV); + KUNIT_EXPECT_EQ(test, quiet, false); + + access = LANDLOCK_ACCESS_FS_TRUNCATE | LANDLOCK_ACCESS_FS_IOCTL_DEV; + KUNIT_EXPECT_EQ(test, 2, + get_layer_from_deny_masks( + &access, _LANDLOCK_ACCESS_FS_OPTIONAL, + deny_mask, quiet_optional_accesses, &quiet)); + KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_IOCTL_DEV); + KUNIT_EXPECT_EQ(test, quiet, false); + + /* layer denying truncate: quiet, ioctl: not quiet */ + quiet_optional_accesses = 0b01; + + access = LANDLOCK_ACCESS_FS_TRUNCATE; + KUNIT_EXPECT_EQ(test, 0, + get_layer_from_deny_masks( + &access, _LANDLOCK_ACCESS_FS_OPTIONAL, + deny_mask, quiet_optional_accesses, &quiet)); + KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_TRUNCATE); + KUNIT_EXPECT_EQ(test, quiet, true); + + access = LANDLOCK_ACCESS_FS_IOCTL_DEV; + KUNIT_EXPECT_EQ(test, 2, + get_layer_from_deny_masks( + &access, _LANDLOCK_ACCESS_FS_OPTIONAL, + deny_mask, quiet_optional_accesses, &quiet)); + KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_IOCTL_DEV); + KUNIT_EXPECT_EQ(test, quiet, false); + + access = LANDLOCK_ACCESS_FS_TRUNCATE | LANDLOCK_ACCESS_FS_IOCTL_DEV; + KUNIT_EXPECT_EQ(test, 2, + get_layer_from_deny_masks( + &access, _LANDLOCK_ACCESS_FS_OPTIONAL, + deny_mask, quiet_optional_accesses, &quiet)); + KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_IOCTL_DEV); + KUNIT_EXPECT_EQ(test, quiet, false); + + /* Reverse order - truncate:2 ioctl_dev:0 */ + deny_mask = 0x02; + quiet_optional_accesses = 0; + + access = LANDLOCK_ACCESS_FS_TRUNCATE; + KUNIT_EXPECT_EQ(test, 2, + get_layer_from_deny_masks( + &access, _LANDLOCK_ACCESS_FS_OPTIONAL, + deny_mask, quiet_optional_accesses, &quiet)); + KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_TRUNCATE); + KUNIT_EXPECT_EQ(test, quiet, false); + + access = LANDLOCK_ACCESS_FS_IOCTL_DEV; + KUNIT_EXPECT_EQ(test, 0, + get_layer_from_deny_masks( + &access, _LANDLOCK_ACCESS_FS_OPTIONAL, + deny_mask, quiet_optional_accesses, &quiet)); + KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_IOCTL_DEV); + KUNIT_EXPECT_EQ(test, quiet, false); + + access = LANDLOCK_ACCESS_FS_TRUNCATE | LANDLOCK_ACCESS_FS_IOCTL_DEV; + KUNIT_EXPECT_EQ(test, 2, + get_layer_from_deny_masks( + &access, _LANDLOCK_ACCESS_FS_OPTIONAL, + deny_mask, quiet_optional_accesses, &quiet)); + KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_TRUNCATE); + KUNIT_EXPECT_EQ(test, quiet, false); + + /* layer denying truncate: quiet, ioctl: not quiet */ + quiet_optional_accesses = 0b01; + + access = LANDLOCK_ACCESS_FS_TRUNCATE; + KUNIT_EXPECT_EQ(test, 2, + get_layer_from_deny_masks( + &access, _LANDLOCK_ACCESS_FS_OPTIONAL, + deny_mask, quiet_optional_accesses, &quiet)); + KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_TRUNCATE); + KUNIT_EXPECT_EQ(test, quiet, true); + + access = LANDLOCK_ACCESS_FS_IOCTL_DEV; + KUNIT_EXPECT_EQ(test, 0, + get_layer_from_deny_masks( + &access, _LANDLOCK_ACCESS_FS_OPTIONAL, + deny_mask, quiet_optional_accesses, &quiet)); + KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_IOCTL_DEV); + KUNIT_EXPECT_EQ(test, quiet, false); + + access = LANDLOCK_ACCESS_FS_TRUNCATE | LANDLOCK_ACCESS_FS_IOCTL_DEV; + KUNIT_EXPECT_EQ(test, 2, + get_layer_from_deny_masks( + &access, _LANDLOCK_ACCESS_FS_OPTIONAL, + deny_mask, quiet_optional_accesses, &quiet)); + KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_TRUNCATE); + KUNIT_EXPECT_EQ(test, quiet, true); + + /* layer denying truncate: not quiet, ioctl: quiet */ + quiet_optional_accesses = 0b10; + + access = LANDLOCK_ACCESS_FS_TRUNCATE; + KUNIT_EXPECT_EQ(test, 2, + get_layer_from_deny_masks( + &access, _LANDLOCK_ACCESS_FS_OPTIONAL, + deny_mask, quiet_optional_accesses, &quiet)); + KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_TRUNCATE); + KUNIT_EXPECT_EQ(test, quiet, false); + + access = LANDLOCK_ACCESS_FS_IOCTL_DEV; + KUNIT_EXPECT_EQ(test, 0, + get_layer_from_deny_masks( + &access, _LANDLOCK_ACCESS_FS_OPTIONAL, + deny_mask, quiet_optional_accesses, &quiet)); + KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_IOCTL_DEV); + KUNIT_EXPECT_EQ(test, quiet, true); + + access = LANDLOCK_ACCESS_FS_TRUNCATE | LANDLOCK_ACCESS_FS_IOCTL_DEV; + KUNIT_EXPECT_EQ(test, 2, + get_layer_from_deny_masks( + &access, _LANDLOCK_ACCESS_FS_OPTIONAL, + deny_mask, quiet_optional_accesses, &quiet)); + KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_TRUNCATE); + KUNIT_EXPECT_EQ(test, quiet, false); + + /* truncate:15 ioctl_dev:15 */ + deny_mask = 0xff; + quiet_optional_accesses = 0; + + access = LANDLOCK_ACCESS_FS_TRUNCATE; + KUNIT_EXPECT_EQ(test, 15, + get_layer_from_deny_masks( + &access, _LANDLOCK_ACCESS_FS_OPTIONAL, + deny_mask, quiet_optional_accesses, &quiet)); + KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_TRUNCATE); + KUNIT_EXPECT_EQ(test, quiet, false); + + access = LANDLOCK_ACCESS_FS_TRUNCATE | LANDLOCK_ACCESS_FS_IOCTL_DEV; + KUNIT_EXPECT_EQ(test, 15, + get_layer_from_deny_masks( + &access, _LANDLOCK_ACCESS_FS_OPTIONAL, + deny_mask, quiet_optional_accesses, &quiet)); + KUNIT_EXPECT_EQ(test, access, + LANDLOCK_ACCESS_FS_TRUNCATE | + LANDLOCK_ACCESS_FS_IOCTL_DEV); + KUNIT_EXPECT_EQ(test, quiet, false); + + /* Both quiet (same layer so quietness must be the same) */ + quiet_optional_accesses = 0b11; + + access = LANDLOCK_ACCESS_FS_TRUNCATE; + KUNIT_EXPECT_EQ(test, 15, + get_layer_from_deny_masks( + &access, _LANDLOCK_ACCESS_FS_OPTIONAL, + deny_mask, quiet_optional_accesses, &quiet)); + KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_TRUNCATE); + KUNIT_EXPECT_EQ(test, quiet, true); + + access = LANDLOCK_ACCESS_FS_TRUNCATE | LANDLOCK_ACCESS_FS_IOCTL_DEV; + KUNIT_EXPECT_EQ(test, 15, + get_layer_from_deny_masks( + &access, _LANDLOCK_ACCESS_FS_OPTIONAL, + deny_mask, quiet_optional_accesses, &quiet)); + KUNIT_EXPECT_EQ(test, access, + LANDLOCK_ACCESS_FS_TRUNCATE | + LANDLOCK_ACCESS_FS_IOCTL_DEV); + KUNIT_EXPECT_EQ(test, quiet, true); +} + +#endif /* CONFIG_SECURITY_LANDLOCK_KUNIT_TEST */ + +static bool is_valid_request(const struct landlock_request *const request) +{ + if (WARN_ON_ONCE(request->layer_plus_one > LANDLOCK_MAX_NUM_LAYERS)) + return false; + + if (WARN_ON_ONCE(!(!!request->layer_plus_one ^ !!request->access))) + return false; + + if (request->access) { + if (WARN_ON_ONCE(!(!!request->layer_masks ^ + !!request->all_existing_optional_access))) + return false; + } else { + if (WARN_ON_ONCE(request->layer_masks || + request->all_existing_optional_access)) + return false; + } + + if (request->deny_masks) { + if (WARN_ON_ONCE(!request->all_existing_optional_access)) + return false; + static_assert(sizeof(request->all_existing_optional_access) == + sizeof(u32)); + if (WARN_ON_ONCE( + request->quiet_optional_accesses >= + BIT(hweight32( + request->all_existing_optional_access)))) + return false; + } + + return true; +} + +static access_mask_t +pick_access_mask_for_request_type(const enum landlock_request_type type, + const struct access_masks access_masks) +{ + switch (type) { + case LANDLOCK_REQUEST_FS_ACCESS: + return access_masks.fs; + case LANDLOCK_REQUEST_NET_ACCESS: + return access_masks.net; + default: + WARN_ONCE(1, "Invalid request type %d passed to %s", type, + __func__); + return 0; + } +} + +/* + * Whether a quiet rule silences the denial: the rule must cover the whole + * denied access in the layer that denied it (a quiet rule in a non-denying + * layer does not suppress the denial). + */ +static bool +is_denial_quieted(const struct landlock_request *const request, + const struct landlock_hierarchy *const youngest_denied, + const access_mask_t missing, const bool object_quiet_flag) +{ + if (object_quiet_flag) { + const access_mask_t quiet_mask = + pick_access_mask_for_request_type( + request->type, youngest_denied->quiet_masks); + + return (quiet_mask & missing) == missing; + } + + /* + * Either the object is not quiet, or this is a scope request. We check + * request->type to distinguish between the two cases. + */ + switch (request->type) { + case LANDLOCK_REQUEST_SCOPE_SIGNAL: + return !!(youngest_denied->quiet_masks.scope & + LANDLOCK_SCOPE_SIGNAL); + case LANDLOCK_REQUEST_SCOPE_ABSTRACT_UNIX_SOCKET: + return !!(youngest_denied->quiet_masks.scope & + LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET); + /* + * Leave LANDLOCK_REQUEST_PTRACE and LANDLOCK_REQUEST_FS_CHANGE_TOPOLOGY + * unhandled for now - they are never quiet. + */ + default: + return false; + } +} + +/* + * Computes whether a denial from youngest_denied is selected for logging by the + * domain's policy: its logging must not be disabled (by both per-execution + * flags being off, or by an ancestor's + * LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF), the per-execution flag matching + * same_exec must be set, and no quiet rule may cover the denied access. + * landlock_log_denial() computes this once and passes it to + * landlock_audit_denial(), which additionally requires audit_enabled. + */ +static bool +is_denial_logged(const struct landlock_request *const request, + const struct landlock_hierarchy *const youngest_denied, + const access_mask_t missing, const bool same_exec, + const bool object_quiet_flag) +{ + if (READ_ONCE(youngest_denied->log_status) == LANDLOCK_LOG_DISABLED) + return false; + + if (!(same_exec ? youngest_denied->log_same_exec : + youngest_denied->log_new_exec)) + return false; + + return !is_denial_quieted(request, youngest_denied, missing, + object_quiet_flag); +} + +/** + * landlock_log_denial - Log a denied access + * + * @subject: The Landlock subject's credential denying an action. + * @request: Detail of the user space request. + */ +void landlock_log_denial(const struct landlock_cred_security *const subject, + const struct landlock_request *const request) +{ + struct landlock_hierarchy *youngest_denied; + size_t youngest_layer; + access_mask_t missing; + bool object_quiet_flag = false; + + if (WARN_ON_ONCE(!subject || !subject->domain || + !subject->domain->hierarchy || !request)) + return; + + if (!is_valid_request(request)) + return; + + missing = request->access; + if (missing) { + /* Gets the nearest domain that denies the request. */ + if (request->layer_masks) { + youngest_layer = get_denied_layer(subject->domain, + &missing, + request->layer_masks); + object_quiet_flag = + request->layer_masks->layers[youngest_layer] + .quiet; + } else { + youngest_layer = get_layer_from_deny_masks( + &missing, _LANDLOCK_ACCESS_FS_OPTIONAL, + request->deny_masks, + request->quiet_optional_accesses, + &object_quiet_flag); + } + youngest_denied = + get_hierarchy(subject->domain, youngest_layer); + } else { + youngest_layer = request->layer_plus_one - 1; + youngest_denied = + get_hierarchy(subject->domain, youngest_layer); + } + + const bool same_exec = !!(subject->domain_exec & BIT(youngest_layer)); + const bool logged = is_denial_logged(request, youngest_denied, missing, + same_exec, object_quiet_flag); + + /* + * Consistently keeps track of the number of denied access requests even + * if audit is currently disabled, or if audit rules currently exclude + * this record type, or if landlock_restrict_self(2)'s flags quiet logs. + */ + atomic64_inc(&youngest_denied->num_denials); + + landlock_trace_denial(request, youngest_denied, missing, same_exec, + logged); + landlock_audit_denial(request, youngest_denied, missing, logged); +} + +/** + * landlock_log_free_domain - Log domain deallocation + * + * @hierarchy: The domain's hierarchy being deallocated. + * + * Called from landlock_put_domain_deferred() (via a work queue scheduled by + * hook_cred_free()) or directly from landlock_put_domain(). + */ +void landlock_log_free_domain(const struct landlock_hierarchy *const hierarchy) +{ + if (WARN_ON_ONCE(!hierarchy)) + return; + + landlock_trace_free_domain(hierarchy); + landlock_audit_free_domain(hierarchy); +} + +#ifdef CONFIG_SECURITY_LANDLOCK_KUNIT_TEST + +static struct kunit_case test_cases[] = { + /* clang-format off */ + KUNIT_CASE(test_get_hierarchy), + KUNIT_CASE(test_get_denied_layer), + KUNIT_CASE(test_get_layer_from_deny_masks), + {} + /* clang-format on */ +}; + +static struct kunit_suite test_suite = { + .name = "landlock_log", + .test_cases = test_cases, +}; + +kunit_test_suite(test_suite); + +#endif /* CONFIG_SECURITY_LANDLOCK_KUNIT_TEST */ diff --git a/security/landlock/log.h b/security/landlock/log.h new file mode 100644 index 000000000000..e0a6e44f3ddd --- /dev/null +++ b/security/landlock/log.h @@ -0,0 +1,86 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Landlock - Log helpers + * + * Copyright © 2023-2025 Microsoft Corporation + * Copyright © 2026 Cloudflare, Inc. + */ + +#ifndef _SECURITY_LANDLOCK_LOG_H +#define _SECURITY_LANDLOCK_LOG_H + +#include <linux/lsm_audit.h> + +#include "access.h" + +struct landlock_cred_security; +struct landlock_hierarchy; + +enum landlock_request_type { + LANDLOCK_REQUEST_PTRACE = 1, + LANDLOCK_REQUEST_FS_CHANGE_TOPOLOGY, + LANDLOCK_REQUEST_FS_ACCESS, + LANDLOCK_REQUEST_NET_ACCESS, + LANDLOCK_REQUEST_SCOPE_ABSTRACT_UNIX_SOCKET, + LANDLOCK_REQUEST_SCOPE_SIGNAL, +}; + +/* + * We should be careful to only use a variable of this type for + * landlock_log_denial(). This way, the compiler can remove it entirely if + * CONFIG_SECURITY_LANDLOCK_LOG is not set. + */ +struct landlock_request { + /* Mandatory fields. */ + enum landlock_request_type type; + struct common_audit_data audit; + + /** + * layer_plus_one: First layer level that denies the request + 1. The + * extra one is useful to detect uninitialized field. + */ + size_t layer_plus_one; + + /* Required field for configurable access control. */ + access_mask_t access; + + /* Required fields for requests with layer masks. */ + const struct layer_masks *layer_masks; + + /* Required fields for requests with deny masks. */ + const access_mask_t all_existing_optional_access; + deny_masks_t deny_masks; + optional_access_t quiet_optional_accesses; + + /* + * Other-party domain ID for a relational (scope/ptrace) denial, or 0 if + * that party is unsandboxed. An ID, not a pointer: the other task can + * replace its credential and free the domain it referenced. Trace path + * only; audit ignores it. + */ + u64 other_domain_id; +}; + +#ifdef CONFIG_SECURITY_LANDLOCK_LOG + +void landlock_log_free_domain(const struct landlock_hierarchy *const hierarchy); + +void landlock_log_denial(const struct landlock_cred_security *const subject, + const struct landlock_request *const request); + +#else /* CONFIG_SECURITY_LANDLOCK_LOG */ + +static inline void +landlock_log_free_domain(const struct landlock_hierarchy *const hierarchy) +{ +} + +static inline void +landlock_log_denial(const struct landlock_cred_security *const subject, + const struct landlock_request *const request) +{ +} + +#endif /* CONFIG_SECURITY_LANDLOCK_LOG */ + +#endif /* _SECURITY_LANDLOCK_LOG_H */ diff --git a/security/landlock/net.c b/security/landlock/net.c index 46c17116fcf4..8f2aaac54b33 100644 --- a/security/landlock/net.c +++ b/security/landlock/net.c @@ -12,13 +12,16 @@ #include <linux/socket.h> #include <net/ipv6.h> -#include "audit.h" #include "common.h" #include "cred.h" +#include "domain.h" #include "limits.h" +#include "log.h" #include "net.h" #include "ruleset.h" +#include <trace/events/landlock.h> + int landlock_append_net_rule(struct landlock_ruleset *const ruleset, const u16 port, access_mask_t access_rights, const u32 flags) @@ -32,16 +35,40 @@ int landlock_append_net_rule(struct landlock_ruleset *const ruleset, BUILD_BUG_ON(sizeof(port) > sizeof(id.key.data)); /* Transforms relative access rights to absolute ones. */ - access_rights |= LANDLOCK_MASK_ACCESS_NET & - ~landlock_get_net_access_mask(ruleset, 0); + access_rights |= LANDLOCK_MASK_ACCESS_NET & ~ruleset->handled_masks.net; mutex_lock(&ruleset->lock); err = landlock_insert_rule(ruleset, id, access_rights, flags); + + /* + * Emit after the rule insertion succeeds, so every event corresponds to + * a rule that is actually in the ruleset. The ruleset lock is still + * held for BTF consistency (enforced by lockdep_assert_held in + * TP_fast_assign). + */ + if (!err) + trace_landlock_add_rule_net(ruleset, access_rights, port); mutex_unlock(&ruleset->lock); return err; } +static bool unmask_layers_net(const struct landlock_domain *const domain, + const struct landlock_id id, + struct layer_masks *masks, + access_mask_t access_request) +{ + const struct landlock_rule *rule = NULL; + bool ret; + + ret = landlock_unmask_layers(domain, id, masks, &rule); + if (rule) + trace_landlock_check_rule_net( + domain, rule, access_request, + ntohs((__force __be16)id.key.data)); + return ret; +} + static int current_check_access_socket(struct socket *const sock, struct sockaddr *const address, const int addrlen, @@ -51,7 +78,6 @@ static int current_check_access_socket(struct socket *const sock, unsigned short sock_family; __be16 port; struct layer_masks layer_masks = {}; - const struct landlock_rule *rule; struct landlock_id id = { .type = LANDLOCK_KEY_NET_PORT, }; @@ -237,14 +263,14 @@ static int current_check_access_socket(struct socket *const sock, id.key.data = (__force uintptr_t)port; BUILD_BUG_ON(sizeof(port) > sizeof(id.key.data)); - rule = landlock_find_rule(subject->domain, id); access_request = landlock_init_layer_masks(subject->domain, access_request, &layer_masks, LANDLOCK_KEY_NET_PORT); if (!access_request) return 0; - if (landlock_unmask_layers(rule, &layer_masks)) + if (unmask_layers_net(subject->domain, id, &layer_masks, + access_request)) return 0; audit_net.family = address->sa_family; diff --git a/security/landlock/ruleset.c b/security/landlock/ruleset.c index 4dd09ea22c84..0d07707523cd 100644 --- a/security/landlock/ruleset.c +++ b/security/landlock/ruleset.c @@ -4,6 +4,7 @@ * * Copyright © 2016-2020 Mickaël Salaün <mic@digikod.net> * Copyright © 2018-2020 ANSSI + * Copyright © 2026 Cloudflare, Inc. */ #include <linux/bits.h> @@ -20,59 +21,64 @@ #include <linux/refcount.h> #include <linux/slab.h> #include <linux/spinlock.h> -#include <linux/workqueue.h> #include <uapi/linux/landlock.h> #include "access.h" -#include "domain.h" +#include "id.h" #include "limits.h" #include "object.h" #include "ruleset.h" -static struct landlock_ruleset *create_ruleset(const u32 num_layers) +#include <trace/events/landlock.h> + +struct landlock_ruleset * +landlock_create_ruleset(const access_mask_t fs_access_mask, + const access_mask_t net_access_mask, + const access_mask_t scope_mask) { struct landlock_ruleset *new_ruleset; - new_ruleset = kzalloc_flex(*new_ruleset, access_masks, num_layers, - GFP_KERNEL_ACCOUNT); + /* Informs about useless ruleset. */ + if (!fs_access_mask && !net_access_mask && !scope_mask) + return ERR_PTR(-ENOMSG); + + new_ruleset = kzalloc_obj(*new_ruleset, GFP_KERNEL_ACCOUNT); if (!new_ruleset) return ERR_PTR(-ENOMEM); + refcount_set(&new_ruleset->usage, 1); mutex_init(&new_ruleset->lock); - new_ruleset->root_inode = RB_ROOT; + new_ruleset->rules.root_inode = RB_ROOT; #if IS_ENABLED(CONFIG_INET) - new_ruleset->root_net_port = RB_ROOT; + new_ruleset->rules.root_net_port = RB_ROOT; #endif /* IS_ENABLED(CONFIG_INET) */ - new_ruleset->num_layers = num_layers; - /* - * hierarchy = NULL - * num_rules = 0 - * access_masks[] = 0 - */ - return new_ruleset; -} +#ifdef CONFIG_TRACEPOINTS + new_ruleset->id = landlock_get_id_range(1); +#endif /* CONFIG_TRACEPOINTS */ -struct landlock_ruleset * -landlock_create_ruleset(const access_mask_t fs_access_mask, - const access_mask_t net_access_mask, - const access_mask_t scope_mask) -{ - struct landlock_ruleset *new_ruleset; + /* Should already be checked in landlock_create_ruleset(). */ + if (fs_access_mask) { + const access_mask_t mask = fs_access_mask & + LANDLOCK_MASK_ACCESS_FS; - /* Informs about useless ruleset. */ - if (!fs_access_mask && !net_access_mask && !scope_mask) - return ERR_PTR(-ENOMSG); - new_ruleset = create_ruleset(1); - if (IS_ERR(new_ruleset)) - return new_ruleset; - if (fs_access_mask) - landlock_add_fs_access_mask(new_ruleset, fs_access_mask, 0); - if (net_access_mask) - landlock_add_net_access_mask(new_ruleset, net_access_mask, 0); - if (scope_mask) - landlock_add_scope_mask(new_ruleset, scope_mask, 0); + WARN_ON_ONCE(fs_access_mask != mask); + new_ruleset->handled_masks.fs |= mask; + } + if (net_access_mask) { + const access_mask_t mask = net_access_mask & + LANDLOCK_MASK_ACCESS_NET; + + WARN_ON_ONCE(net_access_mask != mask); + new_ruleset->handled_masks.net |= mask; + } + if (scope_mask) { + const access_mask_t mask = scope_mask & LANDLOCK_MASK_SCOPE; + + WARN_ON_ONCE(scope_mask != mask); + new_ruleset->handled_masks.scope |= mask; + } return new_ruleset; } @@ -129,7 +135,7 @@ create_rule(const struct landlock_id id, return ERR_PTR(-ENOMEM); RB_CLEAR_NODE(&new_rule->node); if (is_object_pointer(id.type)) { - /* This should have been caught by insert_rule(). */ + /* This should have been caught by landlock_store_rule(). */ WARN_ON_ONCE(!id.key.object); landlock_get_object(id.key.object); } @@ -145,24 +151,6 @@ create_rule(const struct landlock_id id, return new_rule; } -static struct rb_root *get_root(struct landlock_ruleset *const ruleset, - const enum landlock_key_type key_type) -{ - switch (key_type) { - case LANDLOCK_KEY_INODE: - return &ruleset->root_inode; - -#if IS_ENABLED(CONFIG_INET) - case LANDLOCK_KEY_NET_PORT: - return &ruleset->root_net_port; -#endif /* IS_ENABLED(CONFIG_INET) */ - - default: - WARN_ON_ONCE(1); - return ERR_PTR(-EINVAL); - } -} - static void free_rule(struct landlock_rule *const rule, const enum landlock_key_type key_type) { @@ -176,19 +164,20 @@ static void free_rule(struct landlock_rule *const rule, static void build_check_ruleset(void) { - const struct landlock_ruleset ruleset = { + const struct landlock_rules rules = { .num_rules = ~0, - .num_layers = ~0, }; - BUILD_BUG_ON(ruleset.num_rules < LANDLOCK_MAX_NUM_RULES); - BUILD_BUG_ON(ruleset.num_layers < LANDLOCK_MAX_NUM_LAYERS); + BUILD_BUG_ON(rules.num_rules < LANDLOCK_MAX_NUM_RULES); } /** - * insert_rule - Create and insert a rule in a ruleset + * landlock_store_rule - Create and insert a rule into the rule storage * - * @ruleset: The ruleset to be updated. + * @rules: The rule storage to be updated. The caller is responsible for + * any required locking. For rulesets, this means holding + * &landlock_ruleset.lock. For domains under construction, no lock is + * needed because the domain is not yet visible to other tasks. * @id: The ID to build the new rule with. The underlying kernel object, if * any, must be held by the caller. * @layers: One or multiple layers to be copied into the new rule. @@ -196,19 +185,19 @@ static void build_check_ruleset(void) * * When user space requests to add a new rule to a ruleset, @layers only * contains one entry and this entry is not assigned to any level. In this - * case, the new rule will extend @ruleset, similarly to a boolean OR between + * case, the new rule will extend @rules, similarly to a boolean OR between * access rights. * * When merging a ruleset in a domain, or copying a domain, @layers will be - * added to @ruleset as new constraints, similarly to a boolean AND between - * access rights. + * added to @rules as new constraints, similarly to a boolean AND between access + * rights. * * Return: 0 on success, -errno on failure. */ -static int insert_rule(struct landlock_ruleset *const ruleset, - const struct landlock_id id, - const struct landlock_layer (*layers)[], - const size_t num_layers) +int landlock_store_rule(struct landlock_rules *const rules, + const struct landlock_id id, + const struct landlock_layer (*layers)[], + const size_t num_layers) { struct rb_node **walker_node; struct rb_node *parent_node = NULL; @@ -216,14 +205,13 @@ static int insert_rule(struct landlock_ruleset *const ruleset, struct rb_root *root; might_sleep(); - lockdep_assert_held(&ruleset->lock); if (WARN_ON_ONCE(!layers)) return -ENOENT; if (is_object_pointer(id.type) && WARN_ON_ONCE(!id.key.object)) return -ENOENT; - root = get_root(ruleset, id.type); + root = landlock_get_rule_root(rules, id.type); if (IS_ERR(root)) return PTR_ERR(root); @@ -249,7 +237,7 @@ static int insert_rule(struct landlock_ruleset *const ruleset, if ((*layers)[0].level == 0) { /* * Extends access rights when the request comes from - * landlock_add_rule(2), i.e. @ruleset is not a domain. + * landlock_add_rule(2), i.e. @rules is not a domain. */ if (WARN_ON_ONCE(this->num_layers != 1)) return -EINVAL; @@ -278,14 +266,14 @@ static int insert_rule(struct landlock_ruleset *const ruleset, /* There is no match for @id. */ build_check_ruleset(); - if (ruleset->num_rules >= LANDLOCK_MAX_NUM_RULES) + if (rules->num_rules >= LANDLOCK_MAX_NUM_RULES) return -E2BIG; new_rule = create_rule(id, layers, num_layers, NULL); if (IS_ERR(new_rule)) return PTR_ERR(new_rule); rb_link_node(&new_rule->node, parent_node, walker_node); rb_insert_color(&new_rule->node, root); - ruleset->num_rules++; + rules->num_rules++; return 0; } @@ -311,197 +299,50 @@ int landlock_insert_rule(struct landlock_ruleset *const ruleset, { struct landlock_layer layers[] = { { .access = access, - /* When @level is zero, insert_rule() extends @ruleset. */ + /* + * When @level is zero, landlock_store_rule() extends @ruleset. + */ .level = 0, .flags = { .quiet = !!(flags & LANDLOCK_ADD_RULE_QUIET), }, } }; + int err; build_check_layer(); - return insert_rule(ruleset, id, &layers, ARRAY_SIZE(layers)); -} - -static int merge_tree(struct landlock_ruleset *const dst, - struct landlock_ruleset *const src, - const enum landlock_key_type key_type) -{ - struct landlock_rule *walker_rule, *next_rule; - struct rb_root *src_root; - int err = 0; - - might_sleep(); - lockdep_assert_held(&dst->lock); - lockdep_assert_held(&src->lock); - - src_root = get_root(src, key_type); - if (IS_ERR(src_root)) - return PTR_ERR(src_root); - - /* Merges the @src tree. */ - rbtree_postorder_for_each_entry_safe(walker_rule, next_rule, src_root, - node) { - struct landlock_layer layers[] = { { - .level = dst->num_layers, - } }; - const struct landlock_id id = { - .key = walker_rule->key, - .type = key_type, - }; - - if (WARN_ON_ONCE(walker_rule->num_layers != 1)) - return -EINVAL; - - if (WARN_ON_ONCE(walker_rule->layers[0].level != 0)) - return -EINVAL; - - layers[0].access = walker_rule->layers[0].access; - layers[0].flags = walker_rule->layers[0].flags; - - err = insert_rule(dst, id, &layers, ARRAY_SIZE(layers)); - if (err) - return err; - } - return err; -} - -static int merge_ruleset(struct landlock_ruleset *const dst, - struct landlock_ruleset *const src) -{ - int err = 0; - - might_sleep(); - /* Should already be checked by landlock_merge_ruleset() */ - if (WARN_ON_ONCE(!src)) - return 0; - /* Only merge into a domain. */ - if (WARN_ON_ONCE(!dst || !dst->hierarchy)) - return -EINVAL; - - /* Locks @dst first because we are its only owner. */ - mutex_lock(&dst->lock); - mutex_lock_nested(&src->lock, SINGLE_DEPTH_NESTING); - - /* Stacks the new layer. */ - if (WARN_ON_ONCE(src->num_layers != 1 || dst->num_layers < 1)) { - err = -EINVAL; - goto out_unlock; - } - dst->access_masks[dst->num_layers - 1] = - landlock_upgrade_handled_access_masks(src->access_masks[0]); - - /* Merges the @src inode tree. */ - err = merge_tree(dst, src, LANDLOCK_KEY_INODE); - if (err) - goto out_unlock; - -#if IS_ENABLED(CONFIG_INET) - /* Merges the @src network port tree. */ - err = merge_tree(dst, src, LANDLOCK_KEY_NET_PORT); - if (err) - goto out_unlock; -#endif /* IS_ENABLED(CONFIG_INET) */ - -out_unlock: - mutex_unlock(&src->lock); - mutex_unlock(&dst->lock); - return err; -} - -static int inherit_tree(struct landlock_ruleset *const parent, - struct landlock_ruleset *const child, - const enum landlock_key_type key_type) -{ - struct landlock_rule *walker_rule, *next_rule; - struct rb_root *parent_root; - int err = 0; - - might_sleep(); - lockdep_assert_held(&parent->lock); - lockdep_assert_held(&child->lock); - - parent_root = get_root(parent, key_type); - if (IS_ERR(parent_root)) - return PTR_ERR(parent_root); - - /* Copies the @parent inode or network tree. */ - rbtree_postorder_for_each_entry_safe(walker_rule, next_rule, - parent_root, node) { - const struct landlock_id id = { - .key = walker_rule->key, - .type = key_type, - }; - - err = insert_rule(child, id, &walker_rule->layers, - walker_rule->num_layers); - if (err) - return err; - } - return err; -} - -static int inherit_ruleset(struct landlock_ruleset *const parent, - struct landlock_ruleset *const child) -{ - int err = 0; - - might_sleep(); - if (!parent) - return 0; - - /* Locks @child first because we are its only owner. */ - mutex_lock(&child->lock); - mutex_lock_nested(&parent->lock, SINGLE_DEPTH_NESTING); - - /* Copies the @parent inode tree. */ - err = inherit_tree(parent, child, LANDLOCK_KEY_INODE); - if (err) - goto out_unlock; - -#if IS_ENABLED(CONFIG_INET) - /* Copies the @parent network port tree. */ - err = inherit_tree(parent, child, LANDLOCK_KEY_NET_PORT); - if (err) - goto out_unlock; -#endif /* IS_ENABLED(CONFIG_INET) */ - - if (WARN_ON_ONCE(child->num_layers <= parent->num_layers)) { - err = -EINVAL; - goto out_unlock; - } - /* Copies the parent layer stack and leaves a space for the new layer. */ - memcpy(child->access_masks, parent->access_masks, - flex_array_size(parent, access_masks, parent->num_layers)); + lockdep_assert_held(&ruleset->lock); + err = landlock_store_rule(&ruleset->rules, id, &layers, + ARRAY_SIZE(layers)); - if (WARN_ON_ONCE(!parent->hierarchy)) { - err = -EINVAL; - goto out_unlock; - } - landlock_get_hierarchy(parent->hierarchy); - child->hierarchy->parent = parent->hierarchy; +#ifdef CONFIG_TRACEPOINTS + if (!err) + ruleset->version++; +#endif /* CONFIG_TRACEPOINTS */ -out_unlock: - mutex_unlock(&parent->lock); - mutex_unlock(&child->lock); return err; } -static void free_ruleset(struct landlock_ruleset *const ruleset) +void landlock_free_rules(struct landlock_rules *const rules) { struct landlock_rule *freeme, *next; might_sleep(); - rbtree_postorder_for_each_entry_safe(freeme, next, &ruleset->root_inode, + rbtree_postorder_for_each_entry_safe(freeme, next, &rules->root_inode, node) free_rule(freeme, LANDLOCK_KEY_INODE); #if IS_ENABLED(CONFIG_INET) rbtree_postorder_for_each_entry_safe(freeme, next, - &ruleset->root_net_port, node) + &rules->root_net_port, node) free_rule(freeme, LANDLOCK_KEY_NET_PORT); #endif /* IS_ENABLED(CONFIG_INET) */ +} - landlock_put_hierarchy(ruleset->hierarchy); +static void free_ruleset(struct landlock_ruleset *const ruleset) +{ + might_sleep(); + trace_landlock_free_ruleset(ruleset); + landlock_free_rules(&ruleset->rules); kfree(ruleset); } @@ -511,234 +352,3 @@ void landlock_put_ruleset(struct landlock_ruleset *const ruleset) if (ruleset && refcount_dec_and_test(&ruleset->usage)) free_ruleset(ruleset); } - -static void free_ruleset_work(struct work_struct *const work) -{ - struct landlock_ruleset *ruleset; - - ruleset = container_of(work, struct landlock_ruleset, work_free); - free_ruleset(ruleset); -} - -/* Only called by hook_cred_free(). */ -void landlock_put_ruleset_deferred(struct landlock_ruleset *const ruleset) -{ - if (ruleset && refcount_dec_and_test(&ruleset->usage)) { - INIT_WORK(&ruleset->work_free, free_ruleset_work); - schedule_work(&ruleset->work_free); - } -} - -/** - * landlock_merge_ruleset - Merge a ruleset with a domain - * - * @parent: Parent domain. - * @ruleset: New ruleset to be merged. - * - * The current task is requesting to be restricted. The subjective credentials - * must not be in an overridden state. cf. landlock_init_hierarchy_log(). - * - * Return: A new domain merging @parent and @ruleset on success, or ERR_PTR() - * on failure. If @parent is NULL, the new domain duplicates @ruleset. - */ -struct landlock_ruleset * -landlock_merge_ruleset(struct landlock_ruleset *const parent, - struct landlock_ruleset *const ruleset) -{ - struct landlock_ruleset *new_dom __free(landlock_put_ruleset) = NULL; - u32 num_layers; - int err; - - might_sleep(); - if (WARN_ON_ONCE(!ruleset || parent == ruleset)) - return ERR_PTR(-EINVAL); - - if (parent) { - if (parent->num_layers >= LANDLOCK_MAX_NUM_LAYERS) - return ERR_PTR(-E2BIG); - num_layers = parent->num_layers + 1; - } else { - num_layers = 1; - } - - /* Creates a new domain... */ - new_dom = create_ruleset(num_layers); - if (IS_ERR(new_dom)) - return new_dom; - - new_dom->hierarchy = - kzalloc_obj(*new_dom->hierarchy, GFP_KERNEL_ACCOUNT); - if (!new_dom->hierarchy) - return ERR_PTR(-ENOMEM); - - refcount_set(&new_dom->hierarchy->usage, 1); - - /* ...as a child of @parent... */ - err = inherit_ruleset(parent, new_dom); - if (err) - return ERR_PTR(err); - - /* ...and including @ruleset. */ - err = merge_ruleset(new_dom, ruleset); - if (err) - return ERR_PTR(err); - - err = landlock_init_hierarchy_log(new_dom->hierarchy); - if (err) - return ERR_PTR(err); - -#ifdef CONFIG_AUDIT - new_dom->hierarchy->quiet_masks = ruleset->quiet_masks; -#endif /* CONFIG_AUDIT */ - - return no_free_ptr(new_dom); -} - -/* - * The returned access has the same lifetime as @ruleset. - */ -const struct landlock_rule * -landlock_find_rule(const struct landlock_ruleset *const ruleset, - const struct landlock_id id) -{ - const struct rb_root *root; - const struct rb_node *node; - - root = get_root((struct landlock_ruleset *)ruleset, id.type); - if (IS_ERR(root)) - return NULL; - node = root->rb_node; - - while (node) { - struct landlock_rule *this = - rb_entry(node, struct landlock_rule, node); - - if (this->key.data == id.key.data) - return this; - if (this->key.data < id.key.data) - node = node->rb_right; - else - node = node->rb_left; - } - return NULL; -} - -/** - * landlock_unmask_layers - Remove the access rights in @masks - * which are granted in @rule - * - * Updates the set of (per-layer) unfulfilled access rights @masks - * so that all the access rights granted in @rule are removed from it - * (because they are now fulfilled). - * - * @rule: A rule that grants a set of access rights for each layer - * @masks: A matrix of unfulfilled access rights for each layer - * - * Return: True if the request is allowed (i.e. the access rights granted all - * remaining unfulfilled access rights and masks has no leftover set bits). - */ -bool landlock_unmask_layers(const struct landlock_rule *const rule, - struct layer_masks *masks) -{ - if (!masks) - return true; - if (!rule) - return false; - - /* - * An access is granted if, for each policy layer, at least one rule - * encountered on the pathwalk grants the requested access, - * regardless of its position in the layer stack. We must then check - * the remaining layers for each inode, from the first added layer to - * the last one. When there is multiple requested accesses, for each - * policy layer, the full set of requested accesses may not be granted - * by only one rule, but by the union (binary OR) of multiple rules. - * E.g. /a/b <execute> + /a <read> => /a/b <execute + read> - */ - for (size_t i = 0; i < rule->num_layers; i++) { - const struct landlock_layer *const layer = &rule->layers[i]; - - /* Clear the bits where the layer in the rule grants access. */ - masks->layers[layer->level - 1].access &= ~layer->access; - -#ifdef CONFIG_AUDIT - /* Collect rule flags for each layer. */ - if (layer->flags.quiet) - masks->layers[layer->level - 1].quiet = true; -#endif /* CONFIG_AUDIT */ - } - - for (size_t i = 0; i < ARRAY_SIZE(masks->layers); i++) { - if (masks->layers[i].access) - return false; - } - return true; -} - -typedef access_mask_t -get_access_mask_t(const struct landlock_ruleset *const ruleset, - const u16 layer_level); - -/** - * landlock_init_layer_masks - Initialize layer masks from an access request - * - * Populates @masks such that for each access right in @access_request, the bits - * for all the layers are set where this access right is handled. Rule flags - * are also zeroed. - * - * @domain: The domain that defines the current restrictions. - * @access_request: The requested access rights to check. - * @masks: Layer access masks to populate. - * @key_type: The key type to switch between access masks of different types. - * - * Return: An access mask where each access right bit is set which is handled - * in any of the active layers in @domain. - */ -access_mask_t -landlock_init_layer_masks(const struct landlock_ruleset *const domain, - const access_mask_t access_request, - struct layer_masks *const masks, - const enum landlock_key_type key_type) -{ - access_mask_t handled_accesses = 0; - get_access_mask_t *get_access_mask; - - switch (key_type) { - case LANDLOCK_KEY_INODE: - get_access_mask = landlock_get_fs_access_mask; - break; - -#if IS_ENABLED(CONFIG_INET) - case LANDLOCK_KEY_NET_PORT: - get_access_mask = landlock_get_net_access_mask; - break; -#endif /* IS_ENABLED(CONFIG_INET) */ - - default: - WARN_ON_ONCE(1); - return 0; - } - - /* An empty access request can happen because of O_WRONLY | O_RDWR. */ - if (!access_request) - return 0; - - for (size_t i = 0; i < domain->num_layers; i++) { - const access_mask_t handled = get_access_mask(domain, i); - - masks->layers[i].access = access_request & handled; - handled_accesses |= masks->layers[i].access; -#ifdef CONFIG_AUDIT - masks->layers[i].quiet = false; -#endif /* CONFIG_AUDIT */ - } - for (size_t i = domain->num_layers; i < ARRAY_SIZE(masks->layers); - i++) { - masks->layers[i].access = 0; -#ifdef CONFIG_AUDIT - masks->layers[i].quiet = false; -#endif /* CONFIG_AUDIT */ - } - - return handled_accesses; -} diff --git a/security/landlock/ruleset.h b/security/landlock/ruleset.h index 0437adf17428..b536fa0425b7 100644 --- a/security/landlock/ruleset.h +++ b/security/landlock/ruleset.h @@ -4,6 +4,7 @@ * * Copyright © 2016-2020 Mickaël Salaün <mic@digikod.net> * Copyright © 2018-2020 ANSSI + * Copyright © 2026 Cloudflare, Inc. */ #ifndef _SECURITY_LANDLOCK_RULESET_H @@ -14,14 +15,11 @@ #include <linux/mutex.h> #include <linux/rbtree.h> #include <linux/refcount.h> -#include <linux/workqueue.h> #include "access.h" #include "limits.h" #include "object.h" -struct landlock_hierarchy; - /** * struct landlock_layer - Access rights for a given layer */ @@ -68,13 +66,12 @@ union landlock_key { */ enum landlock_key_type { /** - * @LANDLOCK_KEY_INODE: Type of &landlock_ruleset.root_inode's node - * keys. + * @LANDLOCK_KEY_INODE: Type of &landlock_rules.root_inode's node keys. */ LANDLOCK_KEY_INODE = 1, /** - * @LANDLOCK_KEY_NET_PORT: Type of &landlock_ruleset.root_net_port's - * node keys. + * @LANDLOCK_KEY_NET_PORT: Type of &landlock_rules.root_net_port's node + * keys. */ LANDLOCK_KEY_NET_PORT, }; @@ -122,88 +119,78 @@ struct landlock_rule { }; /** - * struct landlock_ruleset - Landlock ruleset + * struct landlock_rules - Red-black tree storage for Landlock rules * - * This data structure must contain unique entries, be updatable, and quick to - * match an object. + * This structure holds the rule trees shared by both rulesets and domains. */ -struct landlock_ruleset { +struct landlock_rules { /** * @root_inode: Root of a red-black tree containing &struct - * landlock_rule nodes with inode object. Once a ruleset is tied to a - * process (i.e. as a domain), this tree is immutable until @usage - * reaches zero. + * landlock_rule nodes with inode object. Immutable for domains. */ struct rb_root root_inode; #if IS_ENABLED(CONFIG_INET) /** * @root_net_port: Root of a red-black tree containing &struct - * landlock_rule nodes with network port. Once a ruleset is tied to a - * process (i.e. as a domain), this tree is immutable until @usage - * reaches zero. + * landlock_rule nodes with network port. Immutable for domains. */ struct rb_root root_net_port; #endif /* IS_ENABLED(CONFIG_INET) */ /** - * @hierarchy: Enables hierarchy identification even when a parent - * domain vanishes. This is needed for the ptrace protection. + * @num_rules: Number of non-overlapping (i.e. not for the same object) + * rules in this tree storage. */ - struct landlock_hierarchy *hierarchy; - union { - /** - * @work_free: Enables to free a ruleset within a lockless - * section. This is only used by - * landlock_put_ruleset_deferred() when @usage reaches zero. - * The fields @lock, @usage, @num_rules, @num_layers, - * @quiet_masks and @access_masks are then unused. - */ - struct work_struct work_free; - struct { - /** - * @lock: Protects against concurrent modifications of - * @root, if @usage is greater than zero. - */ - struct mutex lock; - /** - * @usage: Number of processes (i.e. domains) or file - * descriptors referencing this ruleset. - */ - refcount_t usage; - /** - * @num_rules: Number of non-overlapping (i.e. not for - * the same object) rules in this ruleset. - */ - u32 num_rules; - /** - * @num_layers: Number of layers that are used in this - * ruleset. This enables to check that all the layers - * allow an access request. A value of 0 identifies a - * non-merged ruleset (i.e. not a domain). - */ - u32 num_layers; - /** - * @quiet_masks: Stores the quiet flags for an unmerged - * ruleset. For a merged domain, this is stored in each - * layer's struct landlock_hierarchy instead. - */ - struct access_masks quiet_masks; - /** - * @access_masks: Contains the subset of filesystem and - * network actions that are restricted by a ruleset. - * A domain saves all layers of merged rulesets in a - * stack (FAM), starting from the first layer to the - * last one. These layers are used when merging - * rulesets, for user space backward compatibility - * (i.e. future-proof), and to properly handle merged - * rulesets without overlapping access rights. These - * layers are set once and never changed for the - * lifetime of the ruleset. - */ - struct access_masks access_masks[]; - }; - }; + u32 num_rules; +}; + +/** + * struct landlock_ruleset - Landlock ruleset + * + * This data structure must contain unique entries, be updatable, and quick to + * match an object. + */ +struct landlock_ruleset { + /** + * @rules: Red-black tree storage for rules. + */ + struct landlock_rules rules; + /** + * @lock: Protects against concurrent modifications of @rules, if @usage + * is greater than zero. + */ + struct mutex lock; + /** + * @usage: Number of file descriptors referencing this ruleset. + */ + refcount_t usage; + +#ifdef CONFIG_TRACEPOINTS + /** + * @version: Counter incremented on each successful + * landlock_add_rule(2), including when it only extends an existing + * rule's access rights. Used by tracepoints to correlate a domain with + * the exact ruleset state it was created from. Protected by @lock. + */ + u32 version; + /** + * @id: Unique identifier for this ruleset, used for tracing. + */ + u64 id; +#endif /* CONFIG_TRACEPOINTS */ + + /** + * @quiet_masks: Stores the quiet flags for an unmerged ruleset. For a + * merged domain, this is stored in each layer's struct + * landlock_hierarchy instead. + */ + struct access_masks quiet_masks; + /** + * @handled_masks: Contains the subset of filesystem and network actions + * that are handled by this ruleset. + */ + struct access_masks handled_masks; }; struct landlock_ruleset * @@ -212,7 +199,6 @@ landlock_create_ruleset(const access_mask_t access_mask_fs, const access_mask_t scope_mask); void landlock_put_ruleset(struct landlock_ruleset *const ruleset); -void landlock_put_ruleset_deferred(struct landlock_ruleset *const ruleset); DEFINE_FREE(landlock_put_ruleset, struct landlock_ruleset *, if (!IS_ERR_OR_NULL(_T)) landlock_put_ruleset(_T)) @@ -221,110 +207,44 @@ int landlock_insert_rule(struct landlock_ruleset *const ruleset, const struct landlock_id id, const access_mask_t access, const u32 flags); -struct landlock_ruleset * -landlock_merge_ruleset(struct landlock_ruleset *const parent, - struct landlock_ruleset *const ruleset); +int landlock_store_rule(struct landlock_rules *const rules, + const struct landlock_id id, + const struct landlock_layer (*layers)[], + const size_t num_layers); -const struct landlock_rule * -landlock_find_rule(const struct landlock_ruleset *const ruleset, - const struct landlock_id id); - -static inline void landlock_get_ruleset(struct landlock_ruleset *const ruleset) -{ - if (ruleset) - refcount_inc(&ruleset->usage); -} +void landlock_free_rules(struct landlock_rules *const rules); /** - * landlock_union_access_masks - Return all access rights handled in the - * domain + * landlock_get_rule_root - Get the root of a rule tree by key type * - * @domain: Landlock ruleset (used as a domain) + * @rules: The rules storage to look up. + * @key_type: The type of key to select the tree for. * - * Return: An access_masks result of the OR of all the domain's access masks. + * Return: A pointer to the rb_root, or ERR_PTR(-EINVAL) on unknown type. */ -static inline struct access_masks -landlock_union_access_masks(const struct landlock_ruleset *const domain) +static inline struct rb_root * +landlock_get_rule_root(struct landlock_rules *const rules, + const enum landlock_key_type key_type) { - union access_masks_all matches = {}; - size_t layer_level; + switch (key_type) { + case LANDLOCK_KEY_INODE: + return &rules->root_inode; - for (layer_level = 0; layer_level < domain->num_layers; layer_level++) { - union access_masks_all layer = { - .masks = domain->access_masks[layer_level], - }; +#if IS_ENABLED(CONFIG_INET) + case LANDLOCK_KEY_NET_PORT: + return &rules->root_net_port; +#endif /* IS_ENABLED(CONFIG_INET) */ - matches.all |= layer.all; + default: + WARN_ON_ONCE(1); + return ERR_PTR(-EINVAL); } - - return matches.masks; -} - -static inline void -landlock_add_fs_access_mask(struct landlock_ruleset *const ruleset, - const access_mask_t fs_access_mask, - const u16 layer_level) -{ - access_mask_t fs_mask = fs_access_mask & LANDLOCK_MASK_ACCESS_FS; - - /* Should already be checked in sys_landlock_create_ruleset(). */ - WARN_ON_ONCE(fs_access_mask != fs_mask); - ruleset->access_masks[layer_level].fs |= fs_mask; } -static inline void -landlock_add_net_access_mask(struct landlock_ruleset *const ruleset, - const access_mask_t net_access_mask, - const u16 layer_level) -{ - access_mask_t net_mask = net_access_mask & LANDLOCK_MASK_ACCESS_NET; - - /* Should already be checked in sys_landlock_create_ruleset(). */ - WARN_ON_ONCE(net_access_mask != net_mask); - ruleset->access_masks[layer_level].net |= net_mask; -} - -static inline void -landlock_add_scope_mask(struct landlock_ruleset *const ruleset, - const access_mask_t scope_mask, const u16 layer_level) -{ - access_mask_t mask = scope_mask & LANDLOCK_MASK_SCOPE; - - /* Should already be checked in sys_landlock_create_ruleset(). */ - WARN_ON_ONCE(scope_mask != mask); - ruleset->access_masks[layer_level].scope |= mask; -} - -static inline access_mask_t -landlock_get_fs_access_mask(const struct landlock_ruleset *const ruleset, - const u16 layer_level) -{ - /* Handles all initially denied by default access rights. */ - return ruleset->access_masks[layer_level].fs | - _LANDLOCK_ACCESS_FS_INITIALLY_DENIED; -} - -static inline access_mask_t -landlock_get_net_access_mask(const struct landlock_ruleset *const ruleset, - const u16 layer_level) -{ - return ruleset->access_masks[layer_level].net; -} - -static inline access_mask_t -landlock_get_scope_mask(const struct landlock_ruleset *const ruleset, - const u16 layer_level) +static inline void landlock_get_ruleset(struct landlock_ruleset *const ruleset) { - return ruleset->access_masks[layer_level].scope; + if (ruleset) + refcount_inc(&ruleset->usage); } -bool landlock_unmask_layers(const struct landlock_rule *const rule, - struct layer_masks *masks); - -access_mask_t -landlock_init_layer_masks(const struct landlock_ruleset *const domain, - const access_mask_t access_request, - struct layer_masks *masks, - const enum landlock_key_type key_type); - #endif /* _SECURITY_LANDLOCK_RULESET_H */ diff --git a/security/landlock/syscalls.c b/security/landlock/syscalls.c index 36b02892c62f..1d02d57f4c48 100644 --- a/security/landlock/syscalls.c +++ b/security/landlock/syscalls.c @@ -22,6 +22,7 @@ #include <linux/mount.h> #include <linux/path.h> #include <linux/sched.h> +#include <linux/sched/signal.h> #include <linux/security.h> #include <linux/stddef.h> #include <linux/syscalls.h> @@ -38,6 +39,8 @@ #include "setup.h" #include "tsync.h" +#include <trace/events/landlock.h> + static bool is_initialized(void) { if (likely(landlock_initialized)) @@ -169,7 +172,7 @@ static const struct file_operations ruleset_fops = { * If the change involves a fix that requires userspace awareness, also update * the errata documentation in Documentation/userspace-api/landlock.rst . */ -const int landlock_abi_version = 10; +const int landlock_abi_version = 11; /** * sys_landlock_create_ruleset - Create a new ruleset @@ -281,6 +284,15 @@ SYSCALL_DEFINE3(landlock_create_ruleset, ruleset->quiet_masks.net = ruleset_attr.quiet_access_net; ruleset->quiet_masks.scope = ruleset_attr.quiet_scoped; + /* + * Emits before anon_inode_getfd() installs the file descriptor, while + * the ruleset is still private to this thread: no lock is needed, and + * the event cannot race a concurrent close() freeing the ruleset under + * the tracepoint's BTF read. This is the last point at which the + * ruleset is guaranteed alive and unshared. + */ + trace_landlock_create_ruleset(ruleset); + /* Creates anonymous FD referring to the ruleset. */ ruleset_fd = anon_inode_getfd("[landlock-ruleset]", &ruleset_fops, ruleset, O_RDWR | O_CLOEXEC); @@ -308,8 +320,6 @@ static struct landlock_ruleset *get_ruleset_from_fd(const int fd, if (!(fd_file(ruleset_f)->f_mode & mode)) return ERR_PTR(-EPERM); ruleset = fd_file(ruleset_f)->private_data; - if (WARN_ON_ONCE(ruleset->num_layers != 1)) - return ERR_PTR(-EINVAL); landlock_get_ruleset(ruleset); return ruleset; } @@ -367,7 +377,7 @@ static int add_rule_path_beneath(struct landlock_ruleset *const ruleset, return -ENOMSG; /* Checks that allowed_access matches the @ruleset constraints. */ - mask = ruleset->access_masks[0].fs; + mask = ruleset->handled_masks.fs; if ((path_beneath_attr.allowed_access | mask) != mask) return -EINVAL; @@ -408,7 +418,7 @@ static int add_rule_net_port(struct landlock_ruleset *ruleset, return -ENOMSG; /* Checks that allowed_access matches the @ruleset constraints. */ - mask = landlock_get_net_access_mask(ruleset, 0); + mask = ruleset->handled_masks.net; if ((net_port_attr.allowed_access | mask) != mask) return -EINVAL; @@ -502,21 +512,28 @@ SYSCALL_DEFINE4(landlock_add_rule, const int, ruleset_fd, * - %LANDLOCK_RESTRICT_SELF_LOG_NEW_EXEC_ON * - %LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF * - %LANDLOCK_RESTRICT_SELF_TSYNC + * - %LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS * * This system call enforces a Landlock ruleset on the current thread. * Enforcing a ruleset requires that the task has %CAP_SYS_ADMIN in its * namespace or is running with no_new_privs. This avoids scenarios where * unprivileged tasks can affect the behavior of privileged children. * + * With %LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS, the no_new_privs attribute of the + * calling thread is set only once the enforcement of the ruleset succeeded, + * which fulfills the above requirement: no_new_privs is set if and only if the + * call succeeds. + * * Return: 0 on success, or -errno on failure. Possible returned errors are: * * - %EOPNOTSUPP: Landlock is supported by the kernel but disabled at boot time; * - %EINVAL: @flags contains an unknown bit. * - %EBADF: @ruleset_fd is not a file descriptor for the current thread; * - %EBADFD: @ruleset_fd is not a ruleset file descriptor; - * - %EPERM: @ruleset_fd has no read access to the underlying ruleset, or the - * current thread is not running with no_new_privs, or it doesn't have - * %CAP_SYS_ADMIN in its namespace. + * - %EPERM: @ruleset_fd has no read access to the underlying ruleset, or + * %LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS is not set while the current thread + * is not running with no_new_privs and doesn't have %CAP_SYS_ADMIN in its + * namespace. * - %E2BIG: The maximum number of stacked rulesets is reached for the current * thread. * @@ -527,26 +544,30 @@ SYSCALL_DEFINE2(landlock_restrict_self, const int, ruleset_fd, const __u32, flags) { struct landlock_ruleset *ruleset __free(landlock_put_ruleset) = NULL; + struct landlock_domain *new_dom = NULL; struct cred *new_cred; struct landlock_cred_security *new_llcred; + bool process_wide; bool __maybe_unused log_same_exec, log_new_exec, log_subdomains, prev_log_subdomains; if (!is_initialized()) return -EOPNOTSUPP; + if ((flags | LANDLOCK_MASK_RESTRICT_SELF) != + LANDLOCK_MASK_RESTRICT_SELF) + return -EINVAL; + /* * Similar checks as for seccomp(2), except that an -EPERM may be - * returned. + * returned. LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS fulfills this + * requirement. */ - if (!task_no_new_privs(current) && + if (!(flags & LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS) && + !task_no_new_privs(current) && !ns_capable_noaudit(current_user_ns(), CAP_SYS_ADMIN)) return -EPERM; - if ((flags | LANDLOCK_MASK_RESTRICT_SELF) != - LANDLOCK_MASK_RESTRICT_SELF) - return -EINVAL; - /* Translates "off" flag to boolean. */ log_same_exec = !(flags & LANDLOCK_RESTRICT_SELF_LOG_SAME_EXEC_OFF); /* Translates "on" flag to boolean. */ @@ -576,11 +597,11 @@ SYSCALL_DEFINE2(landlock_restrict_self, const int, ruleset_fd, const __u32, new_llcred = landlock_cred(new_cred); -#ifdef CONFIG_AUDIT +#ifdef CONFIG_SECURITY_LANDLOCK_LOG prev_log_subdomains = !new_llcred->log_subdomains_off; new_llcred->log_subdomains_off = !prev_log_subdomains || !log_subdomains; -#endif /* CONFIG_AUDIT */ +#endif /* CONFIG_SECURITY_LANDLOCK_LOG */ /* * The only case when a ruleset may not be set is if @@ -595,37 +616,91 @@ SYSCALL_DEFINE2(landlock_restrict_self, const int, ruleset_fd, const __u32, * manipulating the current credentials because they are * dedicated per thread. */ - struct landlock_ruleset *const new_dom = - landlock_merge_ruleset(new_llcred->domain, ruleset); + mutex_lock(&ruleset->lock); + new_dom = landlock_merge_ruleset(new_llcred->domain, ruleset); if (IS_ERR(new_dom)) { + mutex_unlock(&ruleset->lock); abort_creds(new_cred); return PTR_ERR(new_dom); } + /* + * Emits the domain-creation event while @ruleset->lock is still + * held, right after the merge, so an eBPF program attached to + * the tracepoint reads the exact ruleset that was merged into + * the domain: a consistent snapshot that a concurrent + * landlock_add_rule() (which holds the same lock) cannot + * modify. + * + * This must come before the thread-sync wait below. Holding + * @ruleset->lock across landlock_restrict_sibling_threads() + * would hang: a sibling thread blocked in landlock_add_rule() + * on the same @ruleset->lock cannot run the task_work that + * thread-sync waits for (the lock wait is uninterruptible). + * Emitting here keeps the lock off the thread-sync path. + * + * The trade-off is that the event fires for a domain that a + * later (rare) thread-sync failure aborts. That path emits the + * matching free_domain event so the create/free pair stays + * balanced (see the thread-sync error path below). + */ + trace_landlock_create_domain(new_dom, ruleset); + mutex_unlock(&ruleset->lock); -#ifdef CONFIG_AUDIT +#ifdef CONFIG_SECURITY_LANDLOCK_LOG new_dom->hierarchy->log_same_exec = log_same_exec; new_dom->hierarchy->log_new_exec = log_new_exec; + /* + * The creation event fired above, so move the domain out of + * LANDLOCK_LOG_UNCOMMITTED: its free_domain event must fire + * too, even if a thread-sync failure aborts it below. Audit + * logging may still be disabled (DISABLED); tracing observes it + * anyway. + */ if ((!log_same_exec && !log_new_exec) || !prev_log_subdomains) new_dom->hierarchy->log_status = LANDLOCK_LOG_DISABLED; -#endif /* CONFIG_AUDIT */ + else + new_dom->hierarchy->log_status = LANDLOCK_LOG_PENDING; +#endif /* CONFIG_SECURITY_LANDLOCK_LOG */ /* Replaces the old (prepared) domain. */ - landlock_put_ruleset(new_llcred->domain); + landlock_put_domain(new_llcred->domain); new_llcred->domain = new_dom; -#ifdef CONFIG_AUDIT +#ifdef CONFIG_SECURITY_LANDLOCK_LOG new_llcred->domain_exec |= BIT(new_dom->num_layers - 1); -#endif /* CONFIG_AUDIT */ +#endif /* CONFIG_SECURITY_LANDLOCK_LOG */ } if (flags & LANDLOCK_RESTRICT_SELF_TSYNC) { const int err = landlock_restrict_sibling_threads( - current_cred(), new_cred); + current_cred(), new_cred, flags); if (err) { + /* + * Thread-sync failed (rare), so the new domain is + * aborted instead of committed. Its creation event + * already fired above, so the imminent free must emit + * the matching free_domain event to keep the + * create/free pair balanced; no special log_status is + * set here. + */ abort_creds(new_cred); return err; } } - return commit_creds(new_cred); + /* Sets no_new_privs past the last point of failure. */ + if (flags & LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS) + task_set_no_new_privs(current); + + /* Whole process: thread-sync swept siblings, or single-threaded. */ + process_wide = (flags & LANDLOCK_RESTRICT_SELF_TSYNC) || + get_nr_threads(current) == 1; + commit_creds(new_cred); + + /* The caller commits last, so its event concludes the operation. */ + if (ruleset) + trace_landlock_enforce_domain(new_dom, true, process_wide, + task_no_new_privs(current)); + + return 0; } diff --git a/security/landlock/task.c b/security/landlock/task.c index 360d226d0f51..4491ce31ae04 100644 --- a/security/landlock/task.c +++ b/security/landlock/task.c @@ -20,11 +20,11 @@ #include <net/af_unix.h> #include <net/sock.h> -#include "audit.h" #include "common.h" #include "cred.h" #include "domain.h" #include "fs.h" +#include "log.h" #include "ruleset.h" #include "setup.h" #include "task.h" @@ -41,8 +41,8 @@ * Return: True if @parent is an ancestor of or equal to @child, false * otherwise. */ -static bool domain_scope_le(const struct landlock_ruleset *const parent, - const struct landlock_ruleset *const child) +static bool domain_scope_le(const struct landlock_domain *const parent, + const struct landlock_domain *const child) { const struct landlock_hierarchy *walker; @@ -63,8 +63,8 @@ static bool domain_scope_le(const struct landlock_ruleset *const parent, return false; } -static int domain_ptrace(const struct landlock_ruleset *const parent, - const struct landlock_ruleset *const child) +static int domain_ptrace(const struct landlock_domain *const parent, + const struct landlock_domain *const child) { if (domain_scope_le(parent, child)) return 0; @@ -88,6 +88,7 @@ static int hook_ptrace_access_check(struct task_struct *const child, const unsigned int mode) { const struct landlock_cred_security *parent_subject; + u64 tracee_domain_id = 0; int err; /* Quick return for non-landlocked tasks. */ @@ -96,9 +97,13 @@ static int hook_ptrace_access_check(struct task_struct *const child, return 0; scoped_guard(rcu) { - const struct landlock_ruleset *const child_dom = + const struct landlock_domain *const child_dom = landlock_get_task_domain(child); err = domain_ptrace(parent_subject->domain, child_dom); +#ifdef CONFIG_SECURITY_LANDLOCK_LOG + if (child_dom) + tracee_domain_id = child_dom->hierarchy->id; +#endif /* CONFIG_SECURITY_LANDLOCK_LOG */ } if (!err) @@ -116,6 +121,7 @@ static int hook_ptrace_access_check(struct task_struct *const child, .u.tsk = child, }, .layer_plus_one = parent_subject->domain->num_layers, + .other_domain_id = tracee_domain_id, }); return err; @@ -135,7 +141,8 @@ static int hook_ptrace_access_check(struct task_struct *const child, static int hook_ptrace_traceme(struct task_struct *const parent) { const struct landlock_cred_security *parent_subject; - const struct landlock_ruleset *child_dom; + const struct landlock_domain *child_dom; + u64 tracee_domain_id = 0; int err; child_dom = landlock_get_current_domain(); @@ -147,6 +154,12 @@ static int hook_ptrace_traceme(struct task_struct *const parent) if (!err) return 0; +#ifdef CONFIG_SECURITY_LANDLOCK_LOG + /* The tracee is the current task; its domain is stable here. */ + if (child_dom) + tracee_domain_id = child_dom->hierarchy->id; +#endif /* CONFIG_SECURITY_LANDLOCK_LOG */ + /* * For the ptrace_traceme case, we log the domain which is the cause of * the denial, which means the parent domain instead of the current @@ -161,6 +174,7 @@ static int hook_ptrace_traceme(struct task_struct *const parent) .u.tsk = current, }, .layer_plus_one = parent_subject->domain->num_layers, + .other_domain_id = tracee_domain_id, }); return err; } @@ -176,8 +190,8 @@ static int hook_ptrace_traceme(struct task_struct *const parent) * Return: True if @server is in a different domain from @client and @client * is scoped to access @server (i.e. access should be denied), false otherwise. */ -static bool domain_is_scoped(const struct landlock_ruleset *const client, - const struct landlock_ruleset *const server, +static bool domain_is_scoped(const struct landlock_domain *const client, + const struct landlock_domain *const server, access_mask_t scope) { int client_layer, server_layer; @@ -236,13 +250,28 @@ static bool domain_is_scoped(const struct landlock_ruleset *const client, } static bool sock_is_scoped(struct sock *const other, - const struct landlock_ruleset *const domain) + const struct landlock_domain *const domain, + u64 *const peer_domain_id) { - const struct landlock_ruleset *dom_other; + const struct landlock_domain *dom_other; /* The credentials will not change. */ lockdep_assert_held(&unix_sk(other)->lock); + + /* + * A live kernel socket (e.g. from sock_create_kern()) has no backing + * file, hence no Landlock domain, so treat it as unscoped. The + * sk_socket check only guards that dereference; sk_socket is NULL + * solely for a dead peer, which the caller already excludes under the + * held lock, so no separate SOCK_DEAD check is needed. + */ + if (unlikely(!other->sk_socket || !other->sk_socket->file)) + return false; + dom_other = landlock_cred(other->sk_socket->file->f_cred)->domain; +#ifdef CONFIG_SECURITY_LANDLOCK_LOG + *peer_domain_id = dom_other ? dom_other->hierarchy->id : 0; +#endif /* CONFIG_SECURITY_LANDLOCK_LOG */ return domain_is_scoped(domain, dom_other, LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET); } @@ -270,6 +299,7 @@ static int hook_unix_stream_connect(struct sock *const sock, struct sock *const newsk) { size_t handle_layer; + u64 peer_domain_id = 0; const struct landlock_cred_security *const subject = landlock_get_applicable_subject(current_cred(), unix_scope, &handle_layer); @@ -281,7 +311,7 @@ static int hook_unix_stream_connect(struct sock *const sock, if (!is_abstract_socket(other)) return 0; - if (!sock_is_scoped(other, subject->domain)) + if (!sock_is_scoped(other, subject->domain, &peer_domain_id)) return 0; landlock_log_denial(subject, &(struct landlock_request) { @@ -293,6 +323,7 @@ static int hook_unix_stream_connect(struct sock *const sock, }, }, .layer_plus_one = handle_layer + 1, + .other_domain_id = peer_domain_id, }); return -EPERM; } @@ -301,6 +332,7 @@ static int hook_unix_may_send(struct socket *const sock, struct socket *const other) { size_t handle_layer; + u64 peer_domain_id = 0; const struct landlock_cred_security *const subject = landlock_get_applicable_subject(current_cred(), unix_scope, &handle_layer); @@ -318,7 +350,7 @@ static int hook_unix_may_send(struct socket *const sock, if (!is_abstract_socket(other->sk)) return 0; - if (!sock_is_scoped(other->sk, subject->domain)) + if (!sock_is_scoped(other->sk, subject->domain, &peer_domain_id)) return 0; landlock_log_denial(subject, &(struct landlock_request) { @@ -330,6 +362,7 @@ static int hook_unix_may_send(struct socket *const sock, }, }, .layer_plus_one = handle_layer + 1, + .other_domain_id = peer_domain_id, }); return -EPERM; } @@ -344,6 +377,7 @@ static int hook_task_kill(struct task_struct *const p, { bool is_scoped; size_t handle_layer; + u64 target_domain_id = 0; const struct landlock_cred_security *subject; if (!cred) { @@ -370,9 +404,15 @@ static int hook_task_kill(struct task_struct *const p, return 0; scoped_guard(rcu) { - is_scoped = domain_is_scoped(subject->domain, - landlock_get_task_domain(p), + const struct landlock_domain *const other = + landlock_get_task_domain(p); + + is_scoped = domain_is_scoped(subject->domain, other, signal_scope.scope); +#ifdef CONFIG_SECURITY_LANDLOCK_LOG + if (other) + target_domain_id = other->hierarchy->id; +#endif /* CONFIG_SECURITY_LANDLOCK_LOG */ } if (!is_scoped) @@ -385,6 +425,7 @@ static int hook_task_kill(struct task_struct *const p, .u.tsk = p, }, .layer_plus_one = handle_layer + 1, + .other_domain_id = target_domain_id, }); return -EPERM; } @@ -394,6 +435,7 @@ static int hook_file_send_sigiotask(struct task_struct *tsk, { const struct landlock_cred_security *subject; bool is_scoped = false; + u64 target_domain_id = 0; /* Lock already held by send_sigio() and send_sigurg(). */ lockdep_assert_held(&fown->lock); @@ -421,9 +463,15 @@ static int hook_file_send_sigiotask(struct task_struct *tsk, return 0; scoped_guard(rcu) { - is_scoped = domain_is_scoped(subject->domain, - landlock_get_task_domain(tsk), + const struct landlock_domain *const other = + landlock_get_task_domain(tsk); + + is_scoped = domain_is_scoped(subject->domain, other, signal_scope.scope); +#ifdef CONFIG_SECURITY_LANDLOCK_LOG + if (other) + target_domain_id = other->hierarchy->id; +#endif /* CONFIG_SECURITY_LANDLOCK_LOG */ } if (!is_scoped) @@ -435,9 +483,10 @@ static int hook_file_send_sigiotask(struct task_struct *tsk, .type = LSM_AUDIT_DATA_TASK, .u.tsk = tsk, }, -#ifdef CONFIG_AUDIT +#ifdef CONFIG_SECURITY_LANDLOCK_LOG .layer_plus_one = landlock_file(fown->file)->fown_layer + 1, -#endif /* CONFIG_AUDIT */ +#endif /* CONFIG_SECURITY_LANDLOCK_LOG */ + .other_domain_id = target_domain_id, }); return -EPERM; } diff --git a/security/landlock/trace.c b/security/landlock/trace.c new file mode 100644 index 000000000000..2ea7aac8d75d --- /dev/null +++ b/security/landlock/trace.c @@ -0,0 +1,185 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Landlock - Tracepoint helpers + * + * Copyright © 2025 Microsoft Corporation + * Copyright © 2026 Cloudflare, Inc. + */ + +#include <linux/cleanup.h> +#include <linux/dcache.h> +#include <linux/err.h> +#include <linux/fs.h> +#include <linux/lsm_audit.h> +#include <net/sock.h> + +#include "access.h" +#include "domain.h" +#include "fs.h" +#include "log.h" +#include "ruleset.h" +#include "trace.h" + +/* + * Generates the tracepoint definitions in this translation unit. The trace + * event header dereferences the traced objects in TP_fast_assign, so the full + * struct definitions (e.g. ruleset.h, domain.h) must be included before it. + */ +#define CREATE_TRACE_POINTS +#include <trace/events/landlock.h> + +/** + * landlock_trace_free_domain - Emit a tracepoint on domain deallocation + * + * @hierarchy: The domain's hierarchy being deallocated. + * + * Fires only for a hierarchy whose creation event was emitted, i.e. one that + * left LANDLOCK_LOG_UNCOMMITTED in landlock_restrict_self(). This keeps the + * create/free pair balanced: a hierarchy that never became observable is freed + * silently, while a domain that landlock_restrict_self() created and a + * thread-sync failure then aborted still fires free_domain, because its + * creation event already fired. + * + * Called from landlock_log_free_domain(). + */ +void landlock_trace_free_domain(const struct landlock_hierarchy *const hierarchy) +{ + /* + * The log_status read is a correctness guard (keep the create/free pair + * balanced), not a cost guard, so this cold path needs no + * trace_..._enabled() check: the tracepoint is a static-branch no-op + * when disabled. The denial path guards trace_..._enabled() instead + * because it does expensive __getname()/path work before emitting. + */ + if (READ_ONCE(hierarchy->log_status) != LANDLOCK_LOG_UNCOMMITTED) + trace_landlock_free_domain(hierarchy); +} + +/** + * landlock_trace_denial - Emit a tracepoint for a denied access request + * + * @request: Detail of the user space request. + * @youngest_denied: The youngest hierarchy node that denied the access. + * @missing: The set of denied access rights. + * @same_exec: Whether the current task is the same executable that called + * landlock_restrict_self() for the denying domain, as computed + * by landlock_log_denial(). + * @logged: Whether the domain's policy selects this denial for logging, as + * computed by landlock_log_denial(). + * + * Emits the tracepoint matching @request->type when its event is enabled. + * Unlike audit, fires regardless of @logged; the value is recorded in the event + * so consumers can filter on it. + * + * Called from landlock_log_denial(). + */ +void landlock_trace_denial( + const struct landlock_request *const request, + const struct landlock_hierarchy *const youngest_denied, + const access_mask_t missing, const bool same_exec, const bool logged) +{ + switch (request->type) { + case LANDLOCK_REQUEST_FS_ACCESS: + case LANDLOCK_REQUEST_FS_CHANGE_TOPOLOGY: + if (trace_landlock_deny_access_fs_enabled()) { + char *buf __free(__putname) = __getname(); + struct path dentry_path; + const char *pathname; + const struct path *path = NULL; + + /* + * Selects the path from the audit data type, as + * dump_common_audit_data() does. A FS_ACCESS denial + * carries a file (hook_file_truncate) or an ioctl op + * (hook_file_ioctl) rather than a path; + * FS_CHANGE_TOPOLOGY carries a path or a bare dentry. + * Reading the wrong union member would dereference + * garbage, so every reachable type is handled here. + */ + switch (request->audit.type) { + case LSM_AUDIT_DATA_FILE: + path = &request->audit.u.file->f_path; + break; + case LSM_AUDIT_DATA_IOCTL_OP: + path = &request->audit.u.op->path; + break; + case LSM_AUDIT_DATA_DENTRY: + /* + * Build a path on the stack with the real + * dentry so TP_fast_assign can extract dev and + * ino; the mnt field is unused there. + */ + dentry_path = (struct path){ + .dentry = request->audit.u.dentry, + }; + path = &dentry_path; + break; + case LSM_AUDIT_DATA_PATH: + path = &request->audit.u.path; + break; + default: + WARN_ONCE(1, + "Unhandled Landlock FS audit type %d", + request->audit.type); + break; + } + + if (!path) + break; + + if (!buf) { + pathname = "<no_mem>"; + } else if (request->audit.type == + LSM_AUDIT_DATA_DENTRY) { + /* No vfsmount: render the dentry path alone. */ + pathname = dentry_path_raw( + request->audit.u.dentry, buf, PATH_MAX); + if (IS_ERR(pathname)) + pathname = + PTR_ERR(pathname) == + -ENAMETOOLONG ? + "<too_long>" : + "<unreachable>"; + } else { + pathname = resolve_path_for_trace(path, buf); + } + + trace_landlock_deny_access_fs(youngest_denied, + same_exec, logged, + missing, path, pathname); + } + break; + case LANDLOCK_REQUEST_NET_ACCESS: + if (trace_landlock_deny_access_net_enabled()) + trace_landlock_deny_access_net( + youngest_denied, same_exec, logged, missing, + request->audit.u.net->sk, + ntohs(request->audit.u.net->sport), + ntohs(request->audit.u.net->dport)); + break; + case LANDLOCK_REQUEST_PTRACE: + if (trace_landlock_deny_ptrace_enabled()) + trace_landlock_deny_ptrace(youngest_denied, same_exec, + logged, + request->other_domain_id, + request->audit.u.tsk); + break; + case LANDLOCK_REQUEST_SCOPE_SIGNAL: + if (trace_landlock_deny_scope_signal_enabled()) + trace_landlock_deny_scope_signal( + youngest_denied, same_exec, logged, + request->other_domain_id, request->audit.u.tsk); + break; + case LANDLOCK_REQUEST_SCOPE_ABSTRACT_UNIX_SOCKET: + if (trace_landlock_deny_scope_abstract_unix_socket_enabled()) + trace_landlock_deny_scope_abstract_unix_socket( + youngest_denied, same_exec, logged, + request->other_domain_id, + request->audit.u.net->sk); + break; + default: + WARN_ONCE(1, "Unhandled Landlock request type %d", + request->type); + break; + } +} diff --git a/security/landlock/trace.h b/security/landlock/trace.h new file mode 100644 index 000000000000..7be98e748855 --- /dev/null +++ b/security/landlock/trace.h @@ -0,0 +1,44 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Landlock - Tracepoint helpers + * + * Copyright © 2025 Microsoft Corporation + * Copyright © 2026 Cloudflare, Inc. + */ + +#ifndef _SECURITY_LANDLOCK_TRACE_H +#define _SECURITY_LANDLOCK_TRACE_H + +#include "access.h" + +struct landlock_hierarchy; +struct landlock_request; + +#ifdef CONFIG_TRACEPOINTS + +void landlock_trace_free_domain( + const struct landlock_hierarchy *const hierarchy); + +void landlock_trace_denial( + const struct landlock_request *const request, + const struct landlock_hierarchy *const youngest_denied, + const access_mask_t missing, const bool same_exec, const bool logged); + +#else /* CONFIG_TRACEPOINTS */ + +static inline void +landlock_trace_free_domain(const struct landlock_hierarchy *const hierarchy) +{ +} + +static inline void +landlock_trace_denial(const struct landlock_request *const request, + const struct landlock_hierarchy *const youngest_denied, + const access_mask_t missing, const bool same_exec, + const bool logged) +{ +} + +#endif /* CONFIG_TRACEPOINTS */ + +#endif /* _SECURITY_LANDLOCK_TRACE_H */ diff --git a/security/landlock/tsync.c b/security/landlock/tsync.c index c5730bbd9ed3..cfa73a2a67ca 100644 --- a/security/landlock/tsync.c +++ b/security/landlock/tsync.c @@ -17,10 +17,13 @@ #include <linux/sched/task.h> #include <linux/slab.h> #include <linux/task_work.h> +#include <uapi/linux/landlock.h> #include "cred.h" #include "tsync.h" +#include <trace/events/landlock.h> + /* * Shared state between multiple threads which are enforcing Landlock rulesets * in lockstep with each other. @@ -78,6 +81,8 @@ struct tsync_work { */ static void restrict_one_thread(struct tsync_shared_context *ctx) { + const struct landlock_domain *new_dom = + landlock_cred(ctx->new_cred)->domain; int err; struct cred *cred = NULL; @@ -146,6 +151,18 @@ static void restrict_one_thread(struct tsync_shared_context *ctx) commit_creds(cred); + /* + * Emitted strictly after commit_creds() and before the out: label, so + * it fires only for a thread now enforcing new_dom, and every + * non-concluding (complete == false) event happens-before the + * operation's single concluding one. Skipped on the flags-only path, + * where old_cred and new_cred carry the same domain. A sibling never + * concludes the operation and its enforcement is always process-wide. + */ + if (new_dom != landlock_cred(ctx->old_cred)->domain) + trace_landlock_enforce_domain(new_dom, false, true, + task_no_new_privs(current)); + out: /* Notify the calling thread once all threads are done */ if (atomic_dec_return(&ctx->num_unfinished) == 0) @@ -466,7 +483,8 @@ static void cancel_tsync_works(const struct tsync_works *works, * restrict_sibling_threads - enables a Landlock policy for all sibling threads */ int landlock_restrict_sibling_threads(const struct cred *old_cred, - const struct cred *new_cred) + const struct cred *new_cred, + const u32 restrict_flags) { int err; struct tsync_shared_context shared_ctx; @@ -481,7 +499,9 @@ int landlock_restrict_sibling_threads(const struct cred *old_cred, init_completion(&shared_ctx.all_finished); shared_ctx.old_cred = old_cred; shared_ctx.new_cred = new_cred; - shared_ctx.set_no_new_privs = task_no_new_privs(current); + shared_ctx.set_no_new_privs = + (restrict_flags & LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS) || + task_no_new_privs(current); /* * Serialize concurrent TSYNC operations to prevent deadlocks when diff --git a/security/landlock/tsync.h b/security/landlock/tsync.h index ef86bb61c2f6..2ae4f938ca00 100644 --- a/security/landlock/tsync.h +++ b/security/landlock/tsync.h @@ -9,8 +9,10 @@ #define _SECURITY_LANDLOCK_TSYNC_H #include <linux/cred.h> +#include <linux/types.h> int landlock_restrict_sibling_threads(const struct cred *old_cred, - const struct cred *new_cred); + const struct cred *new_cred, + u32 restrict_flags); #endif /* _SECURITY_LANDLOCK_TSYNC_H */ diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index 035aaf113d1d..e5e17f100aae 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -7267,24 +7267,6 @@ static int selinux_bpf_prog(struct bpf_prog *prog) BPF__PROG_RUN, NULL); } -static u32 selinux_bpffs_creator_sid(u32 fd) -{ - struct path path; - struct super_block *sb; - struct superblock_security_struct *sbsec; - - CLASS(fd, f)(fd); - - if (fd_empty(f)) - return SECSID_NULL; - - path = fd_file(f)->f_path; - sb = path.dentry->d_sb; - sbsec = selinux_superblock(sb); - - return sbsec->creator_sid; -} - static int selinux_bpf_map_create(struct bpf_map *map, union bpf_attr *attr, struct bpf_token *token, bool kernel) { @@ -7297,7 +7279,7 @@ static int selinux_bpf_map_create(struct bpf_map *map, union bpf_attr *attr, if (!token) ssid = bpfsec->sid; else - ssid = selinux_bpffs_creator_sid(attr->map_token_fd); + ssid = selinux_bpf_token_security(token)->grantor_sid; return avc_has_perm(ssid, bpfsec->sid, SECCLASS_BPF, BPF__MAP_CREATE, NULL); @@ -7315,7 +7297,7 @@ static int selinux_bpf_prog_load(struct bpf_prog *prog, union bpf_attr *attr, if (!token) ssid = bpfsec->sid; else - ssid = selinux_bpffs_creator_sid(attr->prog_token_fd); + ssid = selinux_bpf_token_security(token)->grantor_sid; return avc_has_perm(ssid, bpfsec->sid, SECCLASS_BPF, BPF__PROG_LOAD, NULL); @@ -7329,12 +7311,14 @@ static int selinux_bpf_token_create(struct bpf_token *token, const struct path *path) { struct bpf_security_struct *bpfsec; - u32 sid = selinux_bpffs_creator_sid(attr->token_create.bpffs_fd); + struct superblock_security_struct *sbsec; int err; + sbsec = selinux_superblock(path->dentry->d_sb); + bpfsec = selinux_bpf_token_security(token); bpfsec->sid = current_sid(); - bpfsec->grantor_sid = sid; + bpfsec->grantor_sid = sbsec->creator_sid; bpfsec->perms = 0; /** @@ -7343,15 +7327,15 @@ static int selinux_bpf_token_create(struct bpf_token *token, * in the allowed_cmds bitmap. */ if (bpf_token_cmd(token, BPF_MAP_CREATE)) { - err = avc_has_perm(bpfsec->sid, sid, SECCLASS_BPF, - BPF__MAP_CREATE_AS, NULL); + err = avc_has_perm(bpfsec->sid, bpfsec->grantor_sid, + SECCLASS_BPF, BPF__MAP_CREATE_AS, NULL); if (err) return err; bpfsec->perms |= BPF__MAP_CREATE; } if (bpf_token_cmd(token, BPF_PROG_LOAD)) { - err = avc_has_perm(bpfsec->sid, sid, SECCLASS_BPF, - BPF__PROG_LOAD_AS, NULL); + err = avc_has_perm(bpfsec->sid, bpfsec->grantor_sid, + SECCLASS_BPF, BPF__PROG_LOAD_AS, NULL); if (err) return err; bpfsec->perms |= BPF__PROG_LOAD; |
