Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
+4
View File
@@ -0,0 +1,4 @@
# SPDX-License-Identifier: GPL-2.0-only
net_names.h
capability_names.h
rlim_names.h
@@ -0,0 +1,5 @@
CONFIG_KUNIT=y
CONFIG_NET=y
CONFIG_SECURITY=y
CONFIG_SECURITY_APPARMOR=y
CONFIG_SECURITY_APPARMOR_KUNIT_TEST=y
+122
View File
@@ -0,0 +1,122 @@
# SPDX-License-Identifier: GPL-2.0-only
config SECURITY_APPARMOR
bool "AppArmor support"
depends on SECURITY && NET
select AUDIT
select SECURITY_PATH
select SECURITYFS
select SECURITY_NETWORK
default n
help
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
If you are unsure how to answer this question, answer N.
config SECURITY_APPARMOR_DEBUG
bool "Build AppArmor with debug code"
depends on SECURITY_APPARMOR
default n
help
Build apparmor with debugging logic in apparmor. Not all
debugging logic will necessarily be enabled. A submenu will
provide fine grained control of the debug options that are
available.
config SECURITY_APPARMOR_DEBUG_ASSERTS
bool "Build AppArmor with debugging asserts"
depends on SECURITY_APPARMOR_DEBUG
default y
help
Enable code assertions made with AA_BUG. These are primarily
function entry preconditions but also exist at other key
points. If the assert is triggered it will trigger a WARN
message.
config SECURITY_APPARMOR_DEBUG_MESSAGES
bool "Debug messages enabled by default"
depends on SECURITY_APPARMOR_DEBUG
default n
help
Set the default value of the apparmor.debug kernel parameter.
When enabled, various debug messages will be logged to
the kernel message buffer.
config SECURITY_APPARMOR_INTROSPECT_POLICY
bool "Allow loaded policy to be introspected"
depends on SECURITY_APPARMOR
default y
help
This option selects whether introspection of loaded policy
is available to userspace via the apparmor filesystem. This
adds to kernel memory usage. It is required for introspection
of loaded policy, and check point and restore support. It
can be disabled for embedded systems where reducing memory and
cpu is paramount.
config SECURITY_APPARMOR_HASH
bool "Enable introspection of sha256 hashes for loaded profiles"
depends on SECURITY_APPARMOR_INTROSPECT_POLICY
select CRYPTO_LIB_SHA256
default y
help
This option selects whether introspection of loaded policy
hashes is available to userspace via the apparmor
filesystem. This option provides a light weight means of
checking loaded policy. This option adds to policy load
time and can be disabled for small embedded systems.
config SECURITY_APPARMOR_HASH_DEFAULT
bool "Enable policy hash introspection by default"
depends on SECURITY_APPARMOR_HASH
default y
help
This option selects whether sha256 hashing of loaded policy
is enabled by default. The generation of sha256 hashes for
loaded policy provide system administrators a quick way to
verify that policy in the kernel matches what is expected,
however it can slow down policy load on some devices. In
these cases policy hashing can be disabled by default and
enabled only if needed.
config SECURITY_APPARMOR_EXPORT_BINARY
bool "Allow exporting the raw binary policy"
depends on SECURITY_APPARMOR_INTROSPECT_POLICY
select ZSTD_COMPRESS
select ZSTD_DECOMPRESS
default y
help
This option allows reading back binary policy as it was loaded.
It increases the amount of kernel memory needed by policy and
also increases policy load time. This option is required for
checkpoint and restore support, and debugging of loaded policy.
config SECURITY_APPARMOR_PARANOID_LOAD
bool "Perform full verification of loaded policy"
depends on SECURITY_APPARMOR
default y
help
This options allows controlling whether apparmor does a full
verification of loaded policy. This should not be disabled
except for embedded systems where the image is read only,
includes policy, and has some form of integrity check.
Disabling the check will speed up policy loads.
config SECURITY_APPARMOR_KUNIT_TEST
tristate "Build KUnit tests for policy_unpack.c" if !KUNIT_ALL_TESTS
depends on KUNIT && SECURITY_APPARMOR
default KUNIT_ALL_TESTS
help
This builds the AppArmor KUnit tests.
KUnit tests run during boot and output the results to the debug log
in TAP format (https://testanything.org/). Only useful for kernel devs
running KUnit test harness and are not for inclusion into a
production build.
For more information on KUnit and unit tests in general please refer
to the KUnit documentation in Documentation/dev-tools/kunit/.
If unsure, say N.
+114
View File
@@ -0,0 +1,114 @@
# SPDX-License-Identifier: GPL-2.0
# Makefile for AppArmor Linux Security Module
#
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
apparmor-$(CONFIG_SECURITY_APPARMOR_HASH) += crypto.o
obj-$(CONFIG_SECURITY_APPARMOR_KUNIT_TEST) += apparmor_policy_unpack_test.o
apparmor_policy_unpack_test-objs += policy_unpack_test.o
clean-files := capability_names.h rlim_names.h net_names.h
# Build a lower case string table of address family names
# Transform lines from
# #define AF_LOCAL 1 /* POSIX name for AF_UNIX */
# #define AF_INET 2 /* Internet IP Protocol */
# to
# [1] = "local",
# [2] = "inet",
#
# and build the securityfs entries for the mapping.
# Transforms lines from
# #define AF_INET 2 /* Internet IP Protocol */
# to
# #define AA_SFS_AF_MASK "local inet"
quiet_cmd_make-af = GEN $@
cmd_make-af = echo "static const char *const address_family_names[] = {" > $@ ;\
sed $< >>$@ -r -n -e "/AF_MAX/d" -e "/AF_LOCAL/d" -e "/AF_ROUTE/d" -e \
's/^\#define[ \t]+AF_([A-Z0-9_]+)[ \t]+([0-9]+)(.*)/[\2] = "\L\1",/p';\
echo "};" >> $@ ;\
printf '%s' '\#define AA_SFS_AF_MASK "' >> $@ ;\
sed -r -n -e "/AF_MAX/d" -e "/AF_LOCAL/d" -e "/AF_ROUTE/d" -e \
's/^\#define[ \t]+AF_([A-Z0-9_]+)[ \t]+([0-9]+)(.*)/\L\1/p'\
$< | tr '\n' ' ' | sed -e 's/ $$/"\n/' >> $@
# Build a lower case string table of sock type names
# Transform lines from
# SOCK_STREAM = 1,
# to
# [1] = "stream",
quiet_cmd_make-sock = GEN $@
cmd_make-sock = echo "static const char *const sock_type_names[] = {" >> $@ ;\
sed $^ >>$@ -r -n \
-e 's/^\tSOCK_([A-Z0-9_]+)[\t]+=[ \t]+([0-9]+)(.*)/[\2] = "\L\1",/p';\
echo "};" >> $@
# Build a lower case string table of capability names
# Transforms lines from
# #define CAP_DAC_OVERRIDE 1
# to
# [1] = "dac_override",
quiet_cmd_make-caps = GEN $@
cmd_make-caps = echo "static const char *const capability_names[] = {" > $@ ;\
sed $< >>$@ -r -n -e '/CAP_FS_MASK/d' \
-e 's/^\#define[ \t]+CAP_([A-Z0-9_]+)[ \t]+([0-9]+)/[\2] = "\L\1",/p';\
echo "};" >> $@ ;\
printf '%s' '\#define AA_SFS_CAPS_MASK "' >> $@ ;\
sed $< -r -n -e '/CAP_FS_MASK/d' \
-e 's/^\#define[ \t]+CAP_([A-Z0-9_]+)[ \t]+([0-9]+)/\L\1/p' | \
tr '\n' ' ' | sed -e 's/ $$/"\n/' >> $@
# Build a lower case string table of rlimit names.
# Transforms lines from
# #define RLIMIT_STACK 3 /* max stack size */
# to
# [RLIMIT_STACK] = "stack",
#
# and build a second integer table (with the second sed cmd), that maps
# RLIMIT defines to the order defined in asm-generic/resource.h This is
# required by policy load to map policy ordering of RLIMITs to internal
# ordering for architectures that redefine an RLIMIT.
# Transforms lines from
# #define RLIMIT_STACK 3 /* max stack size */
# to
# RLIMIT_STACK,
#
# and build the securityfs entries for the mapping.
# Transforms lines from
# #define RLIMIT_FSIZE 1 /* Maximum filesize */
# #define RLIMIT_STACK 3 /* max stack size */
# to
# #define AA_SFS_RLIMIT_MASK "fsize stack"
quiet_cmd_make-rlim = GEN $@
cmd_make-rlim = echo "static const char *const rlim_names[RLIM_NLIMITS] = {" \
> $@ ;\
sed $< >> $@ -r -n \
-e 's/^\# ?define[ \t]+(RLIMIT_([A-Z0-9_]+)).*/[\1] = "\L\2",/p';\
echo "};" >> $@ ;\
echo "static const int rlim_map[RLIM_NLIMITS] = {" >> $@ ;\
sed -r -n "s/^\# ?define[ \t]+(RLIMIT_[A-Z0-9_]+).*/\1,/p" $< >> $@ ;\
echo "};" >> $@ ; \
printf '%s' '\#define AA_SFS_RLIMIT_MASK "' >> $@ ;\
sed -r -n 's/^\# ?define[ \t]+RLIMIT_([A-Z0-9_]+).*/\L\1/p' $< | \
tr '\n' ' ' | sed -e 's/ $$/"\n/' >> $@
$(obj)/capability.o : $(obj)/capability_names.h
$(obj)/net.o : $(obj)/net_names.h
$(obj)/resource.o : $(obj)/rlim_names.h
$(obj)/capability_names.h : $(srctree)/include/uapi/linux/capability.h \
$(src)/Makefile
$(call cmd,make-caps)
$(obj)/rlim_names.h : $(srctree)/include/uapi/asm-generic/resource.h \
$(src)/Makefile
$(call cmd,make-rlim)
$(obj)/net_names.h : $(srctree)/include/linux/socket.h \
$(srctree)/include/linux/net.h \
$(src)/Makefile
$(call cmd,make-af)
$(call cmd,make-sock)
+806
View File
@@ -0,0 +1,806 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* AppArmor security module
*
* This file contains AppArmor af_unix fine grained mediation
*
* Copyright 2023 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.
*/
#include <linux/fs.h>
#include <net/tcp_states.h>
#include "include/audit.h"
#include "include/af_unix.h"
#include "include/apparmor.h"
#include "include/file.h"
#include "include/label.h"
#include "include/path.h"
#include "include/policy.h"
#include "include/cred.h"
static inline struct sock *aa_unix_sk(struct unix_sock *u)
{
return &u->sk;
}
static int unix_fs_perm(const char *op, u32 mask, const struct cred *subj_cred,
struct aa_label *label, const struct path *path)
{
AA_BUG(!label);
AA_BUG(!path);
if (unconfined(label) || !label_mediates(label, AA_CLASS_FILE))
return 0;
mask &= NET_FS_PERMS;
/* if !u->path.dentry socket is being shutdown - implicit delegation
* until obj delegation is supported
*/
if (path->dentry) {
/* the sunpath may not be valid for this ns so use the path */
struct inode *inode = path->dentry->d_inode;
vfsuid_t vfsuid = i_uid_into_vfsuid(mnt_idmap(path->mnt), inode);
struct path_cond cond = {
.uid = vfsuid_into_kuid(vfsuid),
.mode = inode->i_mode,
};
return aa_path_perm(op, subj_cred, label, path,
PATH_SOCK_COND, mask, &cond);
} /* else implicitly delegated */
return 0;
}
/* match_addr special constants */
#define ABSTRACT_ADDR "\x00" /* abstract socket addr */
#define ANONYMOUS_ADDR "\x01" /* anonymous endpoint, no addr */
#define DISCONNECTED_ADDR "\x02" /* addr is another namespace */
#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,
struct sockaddr_un *addr, int addrlen)
{
if (addr)
/* include leading \0 */
state = aa_dfa_match_len(dfa, state, addr->sun_path,
unix_addr_len(addrlen));
else
state = aa_dfa_match_len(dfa, state, ANONYMOUS_ADDR, 1);
/* todo: could change to out of band for cleaner separation */
state = aa_dfa_null_transition(dfa, state);
return state;
}
static aa_state_t match_to_local(struct aa_policydb *policy,
aa_state_t state, u32 request,
int type, int protocol,
struct sockaddr_un *addr, int addrlen,
struct aa_perms **p,
const char **info)
{
state = aa_match_to_prot(policy, state, request, PF_UNIX, type,
protocol, NULL, info);
if (state) {
state = match_addr(policy->dfa, state, addr, addrlen);
if (state) {
/* todo: local label matching */
state = aa_dfa_null_transition(policy->dfa, state);
if (!state)
*info = "failed local label match";
} else {
*info = "failed local address match";
}
}
return state;
}
struct sockaddr_un *aa_sunaddr(const struct unix_sock *u, int *addrlen)
{
struct unix_address *addr;
/* memory barrier is sufficient see note in net/unix/af_unix.c */
addr = smp_load_acquire(&u->addr);
if (addr) {
*addrlen = addr->len;
return addr->name;
}
*addrlen = 0;
return NULL;
}
static aa_state_t match_to_sk(struct aa_policydb *policy,
aa_state_t state, u32 request,
struct unix_sock *u, struct aa_perms **p,
const char **info)
{
int addrlen;
struct sockaddr_un *addr = aa_sunaddr(u, &addrlen);
return match_to_local(policy, state, request, u->sk.sk_type,
u->sk.sk_protocol, addr, addrlen, p, info);
}
#define CMD_ADDR 1
#define CMD_LISTEN 2
#define CMD_OPT 4
static aa_state_t match_to_cmd(struct aa_policydb *policy, aa_state_t state,
u32 request, struct unix_sock *u,
char cmd, struct aa_perms **p,
const char **info)
{
AA_BUG(!p);
state = match_to_sk(policy, state, request, u, p, info);
if (state && !*p) {
state = aa_dfa_match_len(policy->dfa, state, &cmd, 1);
if (!state)
*info = "failed cmd selection match";
}
return state;
}
static aa_state_t match_to_peer(struct aa_policydb *policy, aa_state_t state,
u32 request, struct unix_sock *u,
struct sockaddr_un *peer_addr, int peer_addrlen,
struct aa_perms **p, const char **info)
{
AA_BUG(!p);
state = match_to_cmd(policy, state, request, u, CMD_ADDR, p, info);
if (state && !*p) {
state = match_addr(policy->dfa, state, peer_addr, peer_addrlen);
if (!state)
*info = "failed peer address match";
}
return 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,
struct apparmor_audit_data *ad)
{
AA_BUG(!profile);
AA_BUG(!peer);
ad->peer = &peer->label;
if (state && !p) {
state = aa_dfa_match(rule->policy->dfa, state,
peer->base.hname);
if (!state)
ad->info = "failed peer label match";
}
return aa_do_perms(profile, rule->policy, state, request, p, ad);
}
/* unix sock creation comes before we know if the socket will be an fs
* socket
* v6 - semantics are handled by mapping in profile load
* v7 - semantics require sock create for tasks creating an fs socket.
* v8 - same as v7
*/
static int profile_create_perm(struct aa_profile *profile, int family,
int type, int protocol,
struct apparmor_audit_data *ad)
{
struct aa_ruleset *rules = profile->label.rules[0];
aa_state_t state;
AA_BUG(!profile);
AA_BUG(profile_unconfined(profile));
state = RULE_MEDIATES_v9NET(rules);
if (state) {
state = aa_match_to_prot(rules->policy, state, AA_MAY_CREATE,
PF_UNIX, type, protocol, NULL,
&ad->info);
return aa_do_perms(profile, rules->policy, state, AA_MAY_CREATE,
NULL, ad);
}
return aa_profile_af_perm(profile, ad, AA_MAY_CREATE, family, type,
protocol);
}
static int profile_sk_perm(struct aa_profile *profile,
struct apparmor_audit_data *ad,
u32 request, struct sock *sk, const struct path *path)
{
struct aa_ruleset *rules = profile->label.rules[0];
struct aa_perms *p = NULL;
aa_state_t state;
AA_BUG(!profile);
AA_BUG(!sk);
AA_BUG(profile_unconfined(profile));
state = RULE_MEDIATES_v9NET(rules);
if (state) {
if (is_unix_fs(sk))
return unix_fs_perm(ad->op, request, ad->subj_cred,
&profile->label,
&unix_sk(sk)->path);
state = match_to_sk(rules->policy, state, request, unix_sk(sk),
&p, &ad->info);
return aa_do_perms(profile, rules->policy, state, request, p,
ad);
}
return aa_profile_af_sk_perm(profile, ad, request, sk);
}
static int profile_bind_perm(struct aa_profile *profile, struct sock *sk,
struct apparmor_audit_data *ad)
{
struct aa_ruleset *rules = profile->label.rules[0];
struct aa_perms *p = NULL;
aa_state_t state;
AA_BUG(!profile);
AA_BUG(!sk);
AA_BUG(!ad);
AA_BUG(profile_unconfined(profile));
state = RULE_MEDIATES_v9NET(rules);
if (state) {
if (is_unix_addr_fs(ad->net.addr, ad->net.addrlen))
/* under v7-9 fs hook handles bind */
return 0;
/* bind for abstract socket */
state = match_to_local(rules->policy, state, AA_MAY_BIND,
sk->sk_type, sk->sk_protocol,
unix_addr(ad->net.addr),
ad->net.addrlen,
&p, &ad->info);
return aa_do_perms(profile, rules->policy, state, AA_MAY_BIND,
p, ad);
}
return aa_profile_af_sk_perm(profile, ad, AA_MAY_BIND, sk);
}
static int profile_listen_perm(struct aa_profile *profile, struct sock *sk,
int backlog, struct apparmor_audit_data *ad)
{
struct aa_ruleset *rules = profile->label.rules[0];
struct aa_perms *p = NULL;
aa_state_t state;
AA_BUG(!profile);
AA_BUG(!sk);
AA_BUG(!ad);
AA_BUG(profile_unconfined(profile));
state = RULE_MEDIATES_v9NET(rules);
if (state) {
__be16 b = cpu_to_be16(backlog);
if (is_unix_fs(sk))
return unix_fs_perm(ad->op, AA_MAY_LISTEN,
ad->subj_cred, &profile->label,
&unix_sk(sk)->path);
state = match_to_cmd(rules->policy, state, AA_MAY_LISTEN,
unix_sk(sk), CMD_LISTEN, &p, &ad->info);
if (state && !p) {
state = aa_dfa_match_len(rules->policy->dfa, state,
(char *) &b, 2);
if (!state)
ad->info = "failed listen backlog match";
}
return aa_do_perms(profile, rules->policy, state, AA_MAY_LISTEN,
p, ad);
}
return aa_profile_af_sk_perm(profile, ad, AA_MAY_LISTEN, sk);
}
static int profile_accept_perm(struct aa_profile *profile,
struct sock *sk,
struct apparmor_audit_data *ad)
{
struct aa_ruleset *rules = profile->label.rules[0];
struct aa_perms *p = NULL;
aa_state_t state;
AA_BUG(!profile);
AA_BUG(!sk);
AA_BUG(!ad);
AA_BUG(profile_unconfined(profile));
state = RULE_MEDIATES_v9NET(rules);
if (state) {
if (is_unix_fs(sk))
return unix_fs_perm(ad->op, AA_MAY_ACCEPT,
ad->subj_cred, &profile->label,
&unix_sk(sk)->path);
state = match_to_sk(rules->policy, state, AA_MAY_ACCEPT,
unix_sk(sk), &p, &ad->info);
return aa_do_perms(profile, rules->policy, state, AA_MAY_ACCEPT,
p, ad);
}
return aa_profile_af_sk_perm(profile, ad, AA_MAY_ACCEPT, sk);
}
static int profile_opt_perm(struct aa_profile *profile, u32 request,
struct sock *sk, int optname,
struct apparmor_audit_data *ad)
{
struct aa_ruleset *rules = profile->label.rules[0];
struct aa_perms *p = NULL;
aa_state_t state;
AA_BUG(!profile);
AA_BUG(!sk);
AA_BUG(!ad);
AA_BUG(profile_unconfined(profile));
state = RULE_MEDIATES_v9NET(rules);
if (state) {
__be16 b = cpu_to_be16(optname);
if (is_unix_fs(sk))
return unix_fs_perm(ad->op, request,
ad->subj_cred, &profile->label,
&unix_sk(sk)->path);
state = match_to_cmd(rules->policy, state, request, unix_sk(sk),
CMD_OPT, &p, &ad->info);
if (state && !p) {
state = aa_dfa_match_len(rules->policy->dfa, state,
(char *) &b, 2);
if (!state)
ad->info = "failed sockopt match";
}
return aa_do_perms(profile, rules->policy, state, request, p,
ad);
}
return aa_profile_af_sk_perm(profile, ad, request, sk);
}
/* null peer_label is allowed, in which case the peer_sk label is used */
static int profile_peer_perm(struct aa_profile *profile, u32 request,
struct sock *sk, const struct path *path,
struct sockaddr_un *peer_addr,
int peer_addrlen, const struct path *peer_path,
struct aa_label *peer_label,
struct apparmor_audit_data *ad)
{
struct aa_ruleset *rules = profile->label.rules[0];
struct aa_perms *p = NULL;
aa_state_t state;
AA_BUG(!profile);
AA_BUG(profile_unconfined(profile));
AA_BUG(!sk);
AA_BUG(!peer_label);
AA_BUG(!ad);
state = RULE_MEDIATES_v9NET(rules);
if (state) {
struct aa_profile *peerp;
if (peer_path)
return unix_fs_perm(ad->op, request, ad->subj_cred,
&profile->label, peer_path);
else if (path)
return unix_fs_perm(ad->op, request, ad->subj_cred,
&profile->label, path);
state = match_to_peer(rules->policy, state, request,
unix_sk(sk),
peer_addr, peer_addrlen, &p, &ad->info);
return fn_for_each_in_scope(peer_label, peerp,
match_label(profile, rules, state, request,
peerp, p, ad));
}
return aa_profile_af_sk_perm(profile, ad, request, sk);
}
/* -------------------------------- */
int aa_unix_create_perm(struct aa_label *label, int family, int type,
int protocol)
{
if (!unconfined(label)) {
struct aa_profile *profile;
DEFINE_AUDIT_NET(ad, OP_CREATE, current_cred(), NULL, family,
type, protocol);
return fn_for_each_confined(label, profile,
profile_create_perm(profile, family, type,
protocol, &ad));
}
return 0;
}
static int aa_unix_label_sk_perm(const struct cred *subj_cred,
struct aa_label *label,
const char *op, u32 request, struct sock *sk,
const struct path *path)
{
if (!unconfined(label)) {
struct aa_profile *profile;
DEFINE_AUDIT_SK(ad, op, subj_cred, sk);
return fn_for_each_confined(label, profile,
profile_sk_perm(profile, &ad, request, sk,
path));
}
return 0;
}
/* revalidation, get/set attr, shutdown */
int aa_unix_sock_perm(const char *op, u32 request, struct socket *sock)
{
struct aa_label *label;
int error;
label = begin_current_label_crit_section();
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);
return error;
}
static int valid_addr(struct sockaddr *addr, int addr_len)
{
struct sockaddr_un *sunaddr = unix_addr(addr);
/* addr_len == offsetof(struct sockaddr_un, sun_path) is autobind */
if (addr_len < offsetof(struct sockaddr_un, sun_path) ||
addr_len > sizeof(*sunaddr))
return -EINVAL;
return 0;
}
int aa_unix_bind_perm(struct socket *sock, struct sockaddr *addr,
int addrlen)
{
struct aa_profile *profile;
struct aa_label *label;
int error = 0;
error = valid_addr(addr, addrlen);
if (error)
return error;
label = begin_current_label_crit_section();
/* fs bind is handled by mknod */
if (!unconfined(label)) {
DEFINE_AUDIT_SK(ad, OP_BIND, current_cred(), sock->sk);
ad.net.addr = unix_addr(addr);
ad.net.addrlen = addrlen;
error = fn_for_each_confined(label, profile,
profile_bind_perm(profile, sock->sk, &ad));
}
end_current_label_crit_section(label);
return error;
}
/*
* unix connections are covered by the
* - unix_stream_connect (stream) and unix_may_send hooks (dgram)
* - fs connect is handled by open
* This is just here to document this is not needed for af_unix
*
int aa_unix_connect_perm(struct socket *sock, struct sockaddr *address,
int addrlen)
{
return 0;
}
*/
int aa_unix_listen_perm(struct socket *sock, int backlog)
{
struct aa_profile *profile;
struct aa_label *label;
int error = 0;
label = begin_current_label_crit_section();
if (!unconfined(label)) {
DEFINE_AUDIT_SK(ad, OP_LISTEN, current_cred(), sock->sk);
error = fn_for_each_confined(label, profile,
profile_listen_perm(profile, sock->sk,
backlog, &ad));
}
end_current_label_crit_section(label);
return error;
}
/* ability of sock to connect, not peer address binding */
int aa_unix_accept_perm(struct socket *sock, struct socket *newsock)
{
struct aa_profile *profile;
struct aa_label *label;
int error = 0;
label = begin_current_label_crit_section();
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);
return error;
}
/*
* dgram handled by unix_may_sendmsg, right to send on stream done at connect
* could do per msg unix_stream here, but connect + socket transfer is
* sufficient. This is just here to document this is not needed for af_unix
*
* sendmsg, recvmsg
int aa_unix_msg_perm(const char *op, u32 request, struct socket *sock,
struct msghdr *msg, int size)
{
return 0;
}
*/
int aa_unix_opt_perm(const char *op, u32 request, struct socket *sock,
int level, int optname)
{
struct aa_profile *profile;
struct aa_label *label;
int error = 0;
label = begin_current_label_crit_section();
if (!unconfined(label)) {
DEFINE_AUDIT_SK(ad, op, current_cred(), sock->sk);
error = fn_for_each_confined(label, profile,
profile_opt_perm(profile, request, sock->sk,
optname, &ad));
}
end_current_label_crit_section(label);
return error;
}
static int unix_peer_perm(const struct cred *subj_cred,
struct aa_label *label, const char *op, u32 request,
struct sock *sk, const struct path *path,
struct sockaddr_un *peer_addr, int peer_addrlen,
const struct path *peer_path, struct aa_label *peer_label)
{
struct aa_profile *profile;
DEFINE_AUDIT_SK(ad, op, subj_cred, sk);
ad.net.peer.addr = peer_addr;
ad.net.peer.addrlen = peer_addrlen;
return fn_for_each_confined(label, profile,
profile_peer_perm(profile, request, sk, path,
peer_addr, peer_addrlen, peer_path,
peer_label, &ad));
}
/*
*
* Requires: lock held on both @sk and @peer_sk
* called by unix_stream_connect, unix_may_send
*/
int aa_unix_peer_perm(const struct cred *subj_cred,
struct aa_label *label, const char *op, u32 request,
struct sock *sk, struct sock *peer_sk,
struct aa_label *peer_label)
{
struct unix_sock *peeru = unix_sk(peer_sk);
struct unix_sock *u = unix_sk(sk);
int plen;
struct sockaddr_un *paddr = aa_sunaddr(unix_sk(peer_sk), &plen);
AA_BUG(!label);
AA_BUG(!sk);
AA_BUG(!peer_sk);
AA_BUG(!peer_label);
return unix_peer_perm(subj_cred, label, op, request, sk,
is_unix_fs(sk) ? &u->path : NULL,
paddr, plen,
is_unix_fs(peer_sk) ? &peeru->path : NULL,
peer_label);
}
/* sk_plabel for comparison only */
static void update_sk_ctx(struct sock *sk, struct aa_label *label,
struct aa_label *plabel)
{
struct aa_label *l, *old;
struct aa_sk_ctx *ctx = aa_sock(sk);
bool update_sk;
rcu_read_lock();
update_sk = (plabel &&
(plabel != rcu_access_pointer(ctx->peer_lastupdate) ||
!aa_label_is_subset(plabel, rcu_dereference(ctx->peer)))) ||
!__aa_subj_label_is_cached(label, rcu_dereference(ctx->label));
rcu_read_unlock();
if (!update_sk)
return;
spin_lock(&unix_sk(sk)->lock);
old = rcu_dereference_protected(ctx->label,
lockdep_is_held(&unix_sk(sk)->lock));
l = aa_label_merge(old, label, GFP_ATOMIC);
if (l) {
if (l != old) {
rcu_assign_pointer(ctx->label, l);
aa_put_label(old);
} else
aa_put_label(l);
}
if (plabel && rcu_access_pointer(ctx->peer_lastupdate) != plabel) {
old = rcu_dereference_protected(ctx->peer, lockdep_is_held(&unix_sk(sk)->lock));
if (old == plabel) {
rcu_assign_pointer(ctx->peer_lastupdate,
aa_get_label(plabel));
} else if (aa_label_is_subset(plabel, old)) {
rcu_assign_pointer(ctx->peer_lastupdate,
aa_get_label(plabel));
rcu_assign_pointer(ctx->peer, aa_get_label(plabel));
aa_put_label(old);
} /* else race or a subset - don't update */
}
spin_unlock(&unix_sk(sk)->lock);
}
static void update_peer_ctx(struct sock *sk, struct aa_sk_ctx *ctx,
struct aa_label *label)
{
struct aa_label *l, *old;
spin_lock(&unix_sk(sk)->lock);
old = rcu_dereference_protected(ctx->peer,
lockdep_is_held(&unix_sk(sk)->lock));
l = aa_label_merge(old, label, GFP_ATOMIC);
if (l) {
if (l != old) {
rcu_assign_pointer(ctx->peer, l);
aa_put_label(old);
} else
aa_put_label(l);
}
spin_unlock(&unix_sk(sk)->lock);
}
/* This fn is only checked if something has changed in the security
* boundaries. Otherwise cached info off file is sufficient
*/
int aa_unix_file_perm(const struct cred *subj_cred, struct aa_label *label,
const char *op, u32 request, struct file *file)
{
struct socket *sock = (struct socket *) file->private_data;
struct sockaddr_un *addr, *peer_addr;
int addrlen, peer_addrlen;
struct aa_label *plabel = NULL;
struct sock *peer_sk = NULL;
u32 sk_req = request & ~NET_PEER_MASK;
struct path path;
bool is_sk_fs;
int error = 0;
AA_BUG(!label);
AA_BUG(!sock);
AA_BUG(!sock->sk);
AA_BUG(sock->sk->sk_family != PF_UNIX);
/* investigate only using lock via unix_peer_get()
* addr only needs the memory barrier, but need to investigate
* path
*/
unix_state_lock(sock->sk);
peer_sk = unix_peer(sock->sk);
if (peer_sk)
sock_hold(peer_sk);
is_sk_fs = is_unix_fs(sock->sk);
addr = aa_sunaddr(unix_sk(sock->sk), &addrlen);
path = unix_sk(sock->sk)->path;
unix_state_unlock(sock->sk);
if (is_sk_fs && peer_sk)
sk_req = request;
if (sk_req) {
error = aa_unix_label_sk_perm(subj_cred, label, op,
sk_req, sock->sk,
is_sk_fs ? &path : NULL);
}
if (!peer_sk)
goto out;
if (!is_sk_fs) {
bool is_peer_fs = is_unix_fs(peer_sk);
peer_addr = aa_sunaddr(unix_sk(peer_sk), &peer_addrlen);
if (is_peer_fs) {
struct path peer_path;
unix_state_lock(peer_sk);
peer_path = unix_sk(peer_sk)->path;
if (peer_path.dentry)
path_get(&peer_path);
unix_state_unlock(peer_sk);
last_error(error,
unix_fs_perm(op, request, subj_cred, label,
&peer_path));
if (peer_path.dentry)
path_put(&peer_path);
} else {
struct aa_sk_ctx *pctx = aa_sock(peer_sk);
rcu_read_lock();
plabel = aa_get_newest_label(pctx->label);
rcu_read_unlock();
/* no fs check of aa_unix_peer_perm because conditions
* above ensure they will never be done
*/
last_error(error,
xcheck(unix_peer_perm(subj_cred, label, op,
MAY_READ | MAY_WRITE, sock->sk,
is_sk_fs ? &path : NULL,
peer_addr, peer_addrlen,
NULL, plabel),
unix_peer_perm(file->f_cred, plabel, op,
MAY_READ | MAY_WRITE, peer_sk,
NULL, addr, addrlen,
is_sk_fs ? &path : NULL,
label)));
if (!error && !__aa_subj_label_is_cached(plabel, label))
update_peer_ctx(peer_sk, pctx, label);
}
}
sock_put(peer_sk);
out:
/* update peer cache to latest successful perm check */
if (error == 0)
update_sk_ctx(sock->sk, label, plabel);
aa_put_label(plabel);
return error;
}
File diff suppressed because it is too large Load Diff
+291
View File
@@ -0,0 +1,291 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* AppArmor security module
*
* This file contains AppArmor auditing functions
*
* Copyright (C) 1998-2008 Novell/SUSE
* Copyright 2009-2010 Canonical Ltd.
*/
#include <linux/audit.h>
#include <linux/socket.h>
#include "include/apparmor.h"
#include "include/audit.h"
#include "include/policy.h"
#include "include/policy_ns.h"
#include "include/secid.h"
const char *const audit_mode_names[] = {
"normal",
"quiet_denied",
"quiet",
"noquiet",
"all"
};
static const char *const aa_audit_type[] = {
"AUDIT",
"ALLOWED",
"DENIED",
"HINT",
"STATUS",
"ERROR",
"KILLED",
"AUTO"
};
static const char *const aa_class_names[] = {
"none",
"unknown",
"file",
"cap",
"net",
"rlimits",
"domain",
"mount",
"unknown",
"ptrace",
"signal",
"xmatch",
"unknown",
"unknown",
"net",
"unknown",
"label",
"posix_mqueue",
"io_uring",
"module",
"lsm",
"namespace",
"io_uring",
"unknown",
"unknown",
"unknown",
"unknown",
"unknown",
"unknown",
"unknown",
"unknown",
"X",
"dbus",
};
/*
* Currently AppArmor auditing is fed straight into the audit framework.
*
* TODO:
* netlink interface for complain mode
* user auditing, - send user auditing to netlink interface
* system control of whether user audit messages go to system log
*/
/**
* audit_pre() - core AppArmor function.
* @ab: audit buffer to fill (NOT NULL)
* @va: audit structure containing data to audit (NOT NULL)
*
* Record common AppArmor audit data from @va
*/
static void audit_pre(struct audit_buffer *ab, void *va)
{
struct apparmor_audit_data *ad = aad_of_va(va);
if (aa_g_audit_header) {
audit_log_format(ab, "apparmor=\"%s\"",
aa_audit_type[ad->type]);
}
if (ad->op)
audit_log_format(ab, " operation=\"%s\"", ad->op);
if (ad->class)
audit_log_format(ab, " class=\"%s\"",
ad->class <= AA_CLASS_LAST ?
aa_class_names[ad->class] :
"unknown");
if (ad->info) {
audit_log_format(ab, " info=\"%s\"", ad->info);
if (ad->error)
audit_log_format(ab, " error=%d", ad->error);
}
if (ad->subj_label) {
struct aa_label *label = ad->subj_label;
if (label_isprofile(label)) {
struct aa_profile *profile = labels_profile(label);
if (profile->ns != root_ns) {
audit_log_format(ab, " namespace=");
audit_log_untrustedstring(ab,
profile->ns->base.hname);
}
audit_log_format(ab, " profile=");
audit_log_untrustedstring(ab, profile->base.hname);
} else {
audit_log_format(ab, " label=");
aa_label_xaudit(ab, root_ns, label, FLAG_VIEW_SUBNS,
GFP_ATOMIC);
}
}
if (ad->name) {
audit_log_format(ab, " name=");
audit_log_untrustedstring(ab, ad->name);
}
}
/**
* aa_audit_msg - Log a message to the audit subsystem
* @type: audit type for the message
* @ad: audit event structure (NOT NULL)
* @cb: optional callback fn for type specific fields (MAYBE NULL)
*/
void aa_audit_msg(int type, struct apparmor_audit_data *ad,
void (*cb) (struct audit_buffer *, void *))
{
ad->type = type;
common_lsm_audit(&ad->common, audit_pre, cb);
}
/**
* aa_audit - Log a profile based audit event to the audit subsystem
* @type: audit type for the message
* @profile: profile to check against (NOT NULL)
* @ad: audit event (NOT NULL)
* @cb: optional callback fn for type specific fields (MAYBE NULL)
*
* Handle default message switching based off of audit mode flags
*
* Returns: error on failure
*/
int aa_audit(int type, struct aa_profile *profile,
struct apparmor_audit_data *ad,
void (*cb) (struct audit_buffer *, void *))
{
AA_BUG(!profile);
if (type == AUDIT_APPARMOR_AUTO) {
if (likely(!ad->error)) {
if (AUDIT_MODE(profile) != AUDIT_ALL)
return 0;
type = AUDIT_APPARMOR_AUDIT;
} else if (COMPLAIN_MODE(profile))
type = AUDIT_APPARMOR_ALLOWED;
else
type = AUDIT_APPARMOR_DENIED;
}
if (AUDIT_MODE(profile) == AUDIT_QUIET ||
(type == AUDIT_APPARMOR_DENIED &&
AUDIT_MODE(profile) == AUDIT_QUIET_DENIED))
return ad->error;
if (KILL_MODE(profile) && type == AUDIT_APPARMOR_DENIED)
type = AUDIT_APPARMOR_KILL;
ad->subj_label = &profile->label;
aa_audit_msg(type, ad, cb);
if (ad->type == AUDIT_APPARMOR_KILL)
(void)send_sig_info(profile->signal, NULL,
ad->common.type == LSM_AUDIT_DATA_TASK &&
ad->common.u.tsk ? ad->common.u.tsk : current);
if (ad->type == AUDIT_APPARMOR_ALLOWED)
return complain_error(ad->error);
return ad->error;
}
struct aa_audit_rule {
struct aa_label *label;
};
void aa_audit_rule_free(void *vrule)
{
struct aa_audit_rule *rule = vrule;
if (rule) {
if (!IS_ERR(rule->label))
aa_put_label(rule->label);
kfree(rule);
}
}
int aa_audit_rule_init(u32 field, u32 op, char *rulestr, void **vrule, gfp_t gfp)
{
struct aa_audit_rule *rule;
switch (field) {
case AUDIT_SUBJ_ROLE:
if (op != Audit_equal && op != Audit_not_equal)
return -EINVAL;
break;
default:
return -EINVAL;
}
rule = kzalloc_obj(struct aa_audit_rule, gfp);
if (!rule)
return -ENOMEM;
/* Currently rules are treated as coming from the root ns */
rule->label = aa_label_parse(&root_ns->unconfined->label, rulestr,
gfp, true, false);
if (IS_ERR(rule->label)) {
int err = PTR_ERR(rule->label);
aa_audit_rule_free(rule);
return err;
}
*vrule = rule;
return 0;
}
int aa_audit_rule_known(struct audit_krule *rule)
{
int i;
for (i = 0; i < rule->field_count; i++) {
struct audit_field *f = &rule->fields[i];
switch (f->type) {
case AUDIT_SUBJ_ROLE:
return 1;
}
}
return 0;
}
int aa_audit_rule_match(struct lsm_prop *prop, u32 field, u32 op, void *vrule)
{
struct aa_audit_rule *rule = vrule;
struct aa_label *label;
int found = 0;
label = prop->apparmor.label;
if (!label)
return -ENOENT;
if (aa_label_is_subset(label, rule->label))
found = 1;
switch (field) {
case AUDIT_SUBJ_ROLE:
switch (op) {
case Audit_equal:
return found;
case Audit_not_equal:
return !found;
}
}
return 0;
}
+223
View File
@@ -0,0 +1,223 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* AppArmor security module
*
* This file contains AppArmor capability mediation functions
*
* Copyright (C) 1998-2008 Novell/SUSE
* Copyright 2009-2010 Canonical Ltd.
*/
#include <linux/capability.h>
#include <linux/errno.h>
#include <linux/gfp.h>
#include <linux/security.h>
#include <linux/timekeeping.h>
#include "include/apparmor.h"
#include "include/capability.h"
#include "include/cred.h"
#include "include/policy.h"
#include "include/audit.h"
/*
* Table of capability names: we generate it from capabilities.h.
*/
#include "capability_names.h"
struct aa_sfs_entry aa_sfs_entry_caps[] = {
AA_SFS_FILE_STRING("mask", AA_SFS_CAPS_MASK),
AA_SFS_FILE_BOOLEAN("extended", 1),
{ }
};
struct audit_cache {
const struct cred *ad_subj_cred;
/* Capabilities go from 0 to CAP_LAST_CAP */
u64 ktime_ns_expiration[CAP_LAST_CAP+1];
};
static DEFINE_PER_CPU(struct audit_cache, audit_cache);
/**
* audit_cb - call back for capability components of audit struct
* @ab: audit buffer (NOT NULL)
* @va: audit struct to audit data from (NOT NULL)
*/
static void audit_cb(struct audit_buffer *ab, void *va)
{
struct common_audit_data *sa = va;
audit_log_format(ab, " capname=");
audit_log_untrustedstring(ab, capability_names[sa->u.cap]);
}
/**
* audit_caps - audit a capability
* @ad: audit data
* @profile: profile being tested for confinement (NOT NULL)
* @cap: capability tested
* @error: error code returned by test
*
* Do auditing of capability and handle, audit/complain/kill modes switching
* and duplicate message elimination.
*
* Returns: 0 or ad->error on success, error code on failure
*/
static int audit_caps(struct apparmor_audit_data *ad, struct aa_profile *profile,
int cap, int error)
{
const u64 AUDIT_CACHE_TIMEOUT_NS = 1000*1000*1000; /* 1 second */
struct aa_ruleset *rules = profile->label.rules[0];
struct audit_cache *ent;
int type = AUDIT_APPARMOR_AUTO;
ad->error = error;
if (likely(!error)) {
/* test if auditing is being forced */
if (likely((AUDIT_MODE(profile) != AUDIT_ALL) &&
!cap_raised(rules->caps.audit, cap)))
return 0;
type = AUDIT_APPARMOR_AUDIT;
} else if (KILL_MODE(profile) ||
cap_raised(rules->caps.kill, cap)) {
type = AUDIT_APPARMOR_KILL;
} else if (cap_raised(rules->caps.quiet, cap) &&
AUDIT_MODE(profile) != AUDIT_NOQUIET &&
AUDIT_MODE(profile) != AUDIT_ALL) {
/* quiet auditing */
return error;
}
/* Do simple duplicate message elimination */
ent = &get_cpu_var(audit_cache);
/* If the capability was never raised the timestamp check would also catch that */
if (ad->subj_cred == ent->ad_subj_cred && ktime_get_ns() <= ent->ktime_ns_expiration[cap]) {
put_cpu_var(audit_cache);
if (COMPLAIN_MODE(profile))
return complain_error(error);
return error;
} else {
put_cred(ent->ad_subj_cred);
ent->ad_subj_cred = get_cred(ad->subj_cred);
ent->ktime_ns_expiration[cap] = ktime_get_ns() + AUDIT_CACHE_TIMEOUT_NS;
}
put_cpu_var(audit_cache);
return aa_audit(type, profile, ad, audit_cb);
}
/**
* profile_capable - test if profile allows use of capability @cap
* @profile: profile being enforced (NOT NULL, NOT unconfined)
* @cap: capability to test if allowed
* @opts: CAP_OPT_NOAUDIT bit determines whether audit record is generated
* @ad: audit data (NOT NULL)
*
* Returns: 0 if allowed else -EPERM
*/
static int profile_capable(struct aa_profile *profile, int cap,
unsigned int opts, struct apparmor_audit_data *ad)
{
struct aa_ruleset *rules = profile->label.rules[0];
aa_state_t state;
int error;
state = RULE_MEDIATES(rules, ad->class);
if (state) {
struct aa_perms perms = { };
u32 request;
/* caps broken into 256 x 32 bit permission chunks */
state = aa_dfa_next(rules->policy->dfa, state, cap >> 5);
request = 1 << (cap & 0x1f);
perms = *aa_lookup_perms(rules->policy, state);
aa_apply_modes_to_perms(profile, &perms);
if (opts & CAP_OPT_NOAUDIT) {
if (perms.complain & request)
ad->info = "optional: no audit";
else
ad = NULL;
}
return aa_check_perms(profile, &perms, request, ad,
audit_cb);
}
/* fallback to old caps mediation that doesn't support conditionals */
if (cap_raised(rules->caps.allow, cap) &&
!cap_raised(rules->caps.denied, cap))
error = 0;
else
error = -EPERM;
if (opts & CAP_OPT_NOAUDIT) {
if (!COMPLAIN_MODE(profile))
return error;
/* audit the cap request in complain mode but note that it
* should be optional.
*/
ad->info = "optional: no audit";
}
return audit_caps(ad, profile, cap, error);
}
/**
* aa_capable - test permission to use capability
* @subj_cred: cred we are testing capability against
* @label: label being tested for capability (NOT NULL)
* @cap: capability to be tested
* @opts: CAP_OPT_NOAUDIT bit determines whether audit record is generated
*
* Look up capability in profile capability set.
*
* Returns: 0 on success, or else an error code.
*/
int aa_capable(const struct cred *subj_cred, struct aa_label *label,
int cap, unsigned int opts)
{
struct aa_profile *profile;
int error = 0;
DEFINE_AUDIT_DATA(ad, LSM_AUDIT_DATA_CAP, AA_CLASS_CAP, OP_CAPABLE);
ad.subj_cred = subj_cred;
ad.common.u.cap = cap;
error = fn_for_each_confined(label, profile,
profile_capable(profile, cap, opts, &ad));
return error;
}
kernel_cap_t aa_profile_capget(struct aa_profile *profile)
{
struct aa_ruleset *rules = profile->label.rules[0];
aa_state_t state;
state = RULE_MEDIATES(rules, AA_CLASS_CAP);
if (state) {
kernel_cap_t caps = CAP_EMPTY_SET;
int i;
/* caps broken into up to 256, 32 bit permission chunks */
for (i = 0; i < (CAP_LAST_CAP >> 5); i++) {
struct aa_perms perms = { };
aa_state_t tmp;
tmp = aa_dfa_next(rules->policy->dfa, state, i);
perms = *aa_lookup_perms(rules->policy, tmp);
aa_apply_modes_to_perms(profile, &perms);
caps.val |= ((u64)(perms.allow)) << (i * 5);
caps.val |= ((u64)(perms.complain)) << (i * 5);
}
return caps;
}
/* fallback to old caps */
if (COMPLAIN_MODE(profile))
return CAP_FULL_SET;
return rules->caps.allow;
}
+61
View File
@@ -0,0 +1,61 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* AppArmor security module
*
* This file contains AppArmor policy loading interface function definitions.
*
* Copyright 2013 Canonical Ltd.
*
* Fns to provide a checksum of policy that has been loaded this can be
* compared to userspace policy compiles to check loaded policy is what
* it should be.
*/
#include <crypto/sha2.h>
#include "include/apparmor.h"
#include "include/crypto.h"
unsigned int aa_hash_size(void)
{
return SHA256_DIGEST_SIZE;
}
char *aa_calc_hash(void *data, size_t len)
{
char *hash;
hash = kzalloc(SHA256_DIGEST_SIZE, GFP_KERNEL);
if (!hash)
return ERR_PTR(-ENOMEM);
sha256(data, len, hash);
return hash;
}
int aa_calc_profile_hash(struct aa_profile *profile, u32 version, void *start,
size_t len)
{
struct sha256_ctx sctx;
__le32 le32_version = cpu_to_le32(version);
if (!aa_g_hash_policy)
return 0;
profile->hash = kzalloc(SHA256_DIGEST_SIZE, GFP_KERNEL);
if (!profile->hash)
return -ENOMEM;
sha256_init(&sctx);
sha256_update(&sctx, (u8 *)&le32_version, 4);
sha256_update(&sctx, (u8 *)start, len);
sha256_final(&sctx, profile->hash);
return 0;
}
int __init init_profile_hash(void)
{
if (apparmor_initialized)
aa_info_message("AppArmor sha256 policy hashing enabled");
return 0;
}
File diff suppressed because it is too large Load Diff
+747
View File
@@ -0,0 +1,747 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* AppArmor security module
*
* This file contains AppArmor mediation of files
*
* Copyright (C) 1998-2008 Novell/SUSE
* Copyright 2009-2010 Canonical Ltd.
*/
#include <linux/tty.h>
#include <linux/fdtable.h>
#include <linux/file.h>
#include <linux/fs.h>
#include <linux/mount.h>
#include "include/af_unix.h"
#include "include/apparmor.h"
#include "include/audit.h"
#include "include/cred.h"
#include "include/file.h"
#include "include/match.h"
#include "include/net.h"
#include "include/path.h"
#include "include/policy.h"
#include "include/label.h"
static u32 map_mask_to_chr_mask(u32 mask)
{
u32 m = mask & PERMS_CHRS_MASK;
if (mask & AA_MAY_GETATTR)
m |= MAY_READ;
if (mask & (AA_MAY_SETATTR | AA_MAY_CHMOD | AA_MAY_CHOWN))
m |= MAY_WRITE;
return m;
}
/**
* file_audit_cb - call back for file specific audit fields
* @ab: audit_buffer (NOT NULL)
* @va: audit struct to audit values of (NOT NULL)
*/
static void file_audit_cb(struct audit_buffer *ab, void *va)
{
struct common_audit_data *sa = va;
struct apparmor_audit_data *ad = aad(sa);
kuid_t fsuid = ad->subj_cred ? ad->subj_cred->fsuid : current_fsuid();
char str[10];
if (ad->request & AA_AUDIT_FILE_MASK) {
aa_perm_mask_to_str(str, sizeof(str), aa_file_perm_chrs,
map_mask_to_chr_mask(ad->request));
audit_log_format(ab, " requested_mask=\"%s\"", str);
}
if (ad->denied & AA_AUDIT_FILE_MASK) {
aa_perm_mask_to_str(str, sizeof(str), aa_file_perm_chrs,
map_mask_to_chr_mask(ad->denied));
audit_log_format(ab, " denied_mask=\"%s\"", str);
}
if (ad->request & AA_AUDIT_FILE_MASK) {
audit_log_format(ab, " fsuid=%d",
from_kuid(&init_user_ns, fsuid));
audit_log_format(ab, " ouid=%d",
from_kuid(&init_user_ns, ad->fs.ouid));
}
if (ad->peer) {
audit_log_format(ab, " target=");
aa_label_xaudit(ab, labels_ns(ad->subj_label), ad->peer,
FLAG_VIEW_SUBNS, GFP_KERNEL);
} else if (ad->fs.target) {
audit_log_format(ab, " target=");
audit_log_untrustedstring(ab, ad->fs.target);
}
}
/**
* aa_audit_file - handle the auditing of file operations
* @subj_cred: cred of the subject
* @profile: the profile being enforced (NOT NULL)
* @perms: the permissions computed for the request (NOT NULL)
* @op: operation being mediated
* @request: permissions requested
* @name: name of object being mediated (MAYBE NULL)
* @target: name of target (MAYBE NULL)
* @tlabel: target label (MAY BE NULL)
* @ouid: object uid
* @info: extra information message (MAYBE NULL)
* @error: 0 if operation allowed else failure error code
*
* Returns: %0 or error on failure
*/
int aa_audit_file(const struct cred *subj_cred,
struct aa_profile *profile, 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)
{
int type = AUDIT_APPARMOR_AUTO;
DEFINE_AUDIT_DATA(ad, LSM_AUDIT_DATA_TASK, AA_CLASS_FILE, op);
ad.subj_cred = subj_cred;
ad.request = request;
ad.tags = perms->tag;
ad.name = name;
ad.fs.target = target;
ad.peer = tlabel;
ad.fs.ouid = ouid;
ad.info = info;
ad.error = error;
ad.common.u.tsk = NULL;
if (likely(!ad.error)) {
u32 mask = perms->audit;
if (unlikely(AUDIT_MODE(profile) == AUDIT_ALL))
mask = 0xffff;
/* mask off perms that are not being force audited */
ad.request &= mask;
if (likely(!ad.request))
return 0;
type = AUDIT_APPARMOR_AUDIT;
} else {
/* only report permissions that were denied */
ad.request = ad.request & ~perms->allow;
AA_BUG(!ad.request);
if (ad.request & perms->kill)
type = AUDIT_APPARMOR_KILL;
/* quiet known rejects, assumes quiet and kill do not overlap */
if ((ad.request & perms->quiet) &&
AUDIT_MODE(profile) != AUDIT_NOQUIET &&
AUDIT_MODE(profile) != AUDIT_ALL)
ad.request &= ~perms->quiet;
if (!ad.request)
return ad.error;
}
ad.denied = ad.request & ~perms->allow;
return aa_audit(type, profile, &ad, file_audit_cb);
}
static int path_name(const char *op, const struct cred *subj_cred,
struct aa_label *label,
const struct path *path, int flags, char *buffer,
const char **name, struct path_cond *cond, u32 request)
{
struct aa_profile *profile;
const char *info = NULL;
int error;
/* don't reaudit files closed during inheritance */
if (unlikely(path->dentry == aa_null.dentry))
return -EACCES;
error = aa_path_name(path, flags, buffer, name, &info,
labels_profile(label)->disconnected);
if (error) {
fn_for_each_confined(label, profile,
aa_audit_file(subj_cred,
profile, &nullperms, op, request, *name,
NULL, NULL, cond->uid, info, error));
return error;
}
return 0;
}
struct aa_perms default_perms = {};
/**
* aa_lookup_condperms - convert dfa compressed perms to internal perms
* @subj_uid: uid to use for subject owner test
* @rules: the aa_policydb to lookup perms for (NOT NULL)
* @state: state in dfa
* @cond: conditions to consider (NOT NULL)
*
* TODO: convert from dfa + state to permission entry
*
* Returns: a pointer to a file permission set
*/
struct aa_perms *aa_lookup_condperms(kuid_t subj_uid, struct aa_policydb *rules,
aa_state_t state, struct path_cond *cond)
{
unsigned int index = ACCEPT_TABLE(rules->dfa)[state];
if (!(rules->perms))
return &default_perms;
if ((ACCEPT_TABLE2(rules->dfa)[state] & ACCEPT_FLAG_OWNER)) {
if (uid_eq(subj_uid, cond->uid))
return &(rules->perms[index]);
return &(rules->perms[index + 1]);
}
return &(rules->perms[index]);
}
/**
* aa_str_perms - find permission that match @name
* @file_rules: the aa_policydb to match against (NOT NULL)
* @start: state to start matching in
* @name: string to match against dfa (NOT NULL)
* @cond: conditions to consider for permission set computation (NOT NULL)
* @perms: Returns - the permissions found when matching @name
*
* Returns: the final state in @dfa when beginning @start and walking @name
*/
aa_state_t aa_str_perms(struct aa_policydb *file_rules, aa_state_t start,
const char *name, struct path_cond *cond,
struct aa_perms *perms)
{
aa_state_t state;
state = aa_dfa_match(file_rules->dfa, start, name);
*perms = *(aa_lookup_condperms(current_fsuid(), file_rules, state,
cond));
return state;
}
int __aa_path_perm(const char *op, const struct cred *subj_cred,
struct aa_profile *profile, const char *name,
u32 request, struct path_cond *cond, int flags,
struct aa_perms *perms)
{
struct aa_ruleset *rules = profile->label.rules[0];
int e = 0;
if (profile_unconfined(profile) ||
((flags & PATH_SOCK_COND) && !RULE_MEDIATES_v9NET(rules)))
return 0;
aa_str_perms(rules->file, rules->file->start[AA_CLASS_FILE],
name, cond, perms);
if (request & ~perms->allow)
e = -EACCES;
return aa_audit_file(subj_cred,
profile, perms, op, request, name, NULL, NULL,
cond->uid, NULL, e);
}
static int profile_path_perm(const char *op, const struct cred *subj_cred,
struct aa_profile *profile,
const struct path *path, char *buffer, u32 request,
struct path_cond *cond, int flags,
struct aa_perms *perms)
{
const char *name = NULL;
int error;
if (profile_unconfined(profile))
return 0;
error = path_name(op, subj_cred, &profile->label, path,
flags | profile->path_flags, buffer, &name, cond,
request);
if (error)
return error;
return __aa_path_perm(op, subj_cred, profile, name, request, cond,
flags, perms);
}
/**
* aa_path_perm - do permissions check & audit for @path
* @op: operation being checked
* @subj_cred: subject cred
* @label: profile being enforced (NOT NULL)
* @path: path to check permissions of (NOT NULL)
* @flags: any additional path flags beyond what the profile specifies
* @request: requested permissions
* @cond: conditional info for this request (NOT NULL)
*
* Returns: %0 else error if access denied or other error
*/
int aa_path_perm(const char *op, const struct cred *subj_cred,
struct aa_label *label,
const struct path *path, int flags, u32 request,
struct path_cond *cond)
{
struct aa_perms perms = {};
struct aa_profile *profile;
char *buffer = NULL;
int error;
flags |= PATH_DELEGATE_DELETED | (S_ISDIR(cond->mode) ? PATH_IS_DIR :
0);
buffer = aa_get_buffer(false);
if (!buffer)
return -ENOMEM;
error = fn_for_each_confined(label, profile,
profile_path_perm(op, subj_cred, profile, path, buffer,
request, cond, flags, &perms));
aa_put_buffer(buffer);
return error;
}
/**
* xindex_is_subset - helper for aa_path_link
* @link: link permission set
* @target: target permission set
*
* test target x permissions are equal OR a subset of link x permissions
* this is done as part of the subset test, where a hardlink must have
* a subset of permissions that the target has.
*
* Returns: true if subset else false
*/
static inline bool xindex_is_subset(u32 link, u32 target)
{
if (((link & ~AA_X_UNSAFE) != (target & ~AA_X_UNSAFE)) ||
((link & AA_X_UNSAFE) && !(target & AA_X_UNSAFE)))
return false;
return true;
}
static int profile_path_link(const struct cred *subj_cred,
struct aa_profile *profile,
const struct path *link, char *buffer,
const struct path *target, char *buffer2,
struct path_cond *cond)
{
struct aa_ruleset *rules = profile->label.rules[0];
const char *lname = NULL, *tname = NULL;
struct aa_perms lperms = {}, perms;
const char *info = NULL;
u32 request = AA_MAY_LINK;
aa_state_t state;
int error;
error = path_name(OP_LINK, subj_cred, &profile->label, link,
profile->path_flags,
buffer, &lname, cond, AA_MAY_LINK);
if (error)
goto audit;
/* buffer2 freed below, tname is pointer in buffer2 */
error = path_name(OP_LINK, subj_cred, &profile->label, target,
profile->path_flags,
buffer2, &tname, cond, AA_MAY_LINK);
if (error)
goto audit;
error = -EACCES;
/* aa_str_perms - handles the case of the dfa being NULL */
state = aa_str_perms(rules->file,
rules->file->start[AA_CLASS_FILE], lname,
cond, &lperms);
if (!(lperms.allow & AA_MAY_LINK))
goto audit;
/* test to see if target can be paired with link */
state = aa_dfa_null_transition(rules->file->dfa, state);
aa_str_perms(rules->file, state, tname, cond, &perms);
/* force audit/quiet masks for link are stored in the second entry
* in the link pair.
*/
lperms.audit = perms.audit;
lperms.quiet = perms.quiet;
lperms.kill = perms.kill;
if (!(perms.allow & AA_MAY_LINK)) {
info = "target restricted";
lperms = perms;
goto audit;
}
/* done if link subset test is not required */
if (!(perms.allow & AA_LINK_SUBSET))
goto done_tests;
/* Do link perm subset test requiring allowed permission on link are
* a subset of the allowed permissions on target.
*/
aa_str_perms(rules->file, rules->file->start[AA_CLASS_FILE],
tname, cond, &perms);
/* AA_MAY_LINK is not considered in the subset test */
request = lperms.allow & ~AA_MAY_LINK;
lperms.allow &= perms.allow | AA_MAY_LINK;
request |= AA_AUDIT_FILE_MASK & (lperms.allow & ~perms.allow);
if (request & ~lperms.allow) {
goto audit;
} else if ((lperms.allow & MAY_EXEC) &&
!xindex_is_subset(lperms.xindex, perms.xindex)) {
lperms.allow &= ~MAY_EXEC;
request |= MAY_EXEC;
info = "link not subset of target";
goto audit;
}
done_tests:
error = 0;
audit:
return aa_audit_file(subj_cred,
profile, &lperms, OP_LINK, request, lname, tname,
NULL, cond->uid, info, error);
}
/**
* aa_path_link - Handle hard link permission check
* @subj_cred: subject cred
* @label: the label being enforced (NOT NULL)
* @old_dentry: the target dentry (NOT NULL)
* @new_dir: directory the new link will be created in (NOT NULL)
* @new_dentry: the link being created (NOT NULL)
*
* Handle the permission test for a link & target pair. Permission
* is encoded as a pair where the link permission is determined
* first, and if allowed, the target is tested. The target test
* is done from the point of the link match (not start of DFA)
* making the target permission dependent on the link permission match.
*
* The subset test if required forces that permissions granted
* on link are a subset of the permission granted to target.
*
* Returns: %0 if allowed else error
*/
int aa_path_link(const struct cred *subj_cred,
struct aa_label *label, struct dentry *old_dentry,
const struct path *new_dir, struct dentry *new_dentry)
{
struct path link = { .mnt = new_dir->mnt, .dentry = new_dentry };
struct path target = { .mnt = new_dir->mnt, .dentry = old_dentry };
struct inode *inode = d_backing_inode(old_dentry);
vfsuid_t vfsuid = i_uid_into_vfsuid(mnt_idmap(target.mnt), inode);
struct path_cond cond = {
.uid = vfsuid_into_kuid(vfsuid),
.mode = inode->i_mode,
};
char *buffer = NULL, *buffer2 = NULL;
struct aa_profile *profile;
int error;
/* buffer freed below, lname is pointer in buffer */
buffer = aa_get_buffer(false);
buffer2 = aa_get_buffer(false);
error = -ENOMEM;
if (!buffer || !buffer2)
goto out;
error = fn_for_each_confined(label, profile,
profile_path_link(subj_cred, profile, &link, buffer,
&target, buffer2, &cond));
out:
aa_put_buffer(buffer);
aa_put_buffer(buffer2);
return error;
}
static void update_file_ctx(struct aa_file_ctx *fctx, struct aa_label *label,
u32 request)
{
struct aa_label *l, *old;
/* update caching of label on file_ctx */
spin_lock(&fctx->lock);
old = rcu_dereference_protected(fctx->label,
lockdep_is_held(&fctx->lock));
l = aa_label_merge(old, label, GFP_ATOMIC);
if (l) {
if (l != old) {
rcu_assign_pointer(fctx->label, l);
aa_put_label(old);
} else
aa_put_label(l);
fctx->allow |= request;
}
spin_unlock(&fctx->lock);
}
static int __file_path_perm(const char *op, const struct cred *subj_cred,
struct aa_label *label,
struct aa_label *flabel, struct file *file,
u32 request, u32 denied, bool in_atomic)
{
struct aa_profile *profile;
struct aa_perms perms = {};
vfsuid_t vfsuid = i_uid_into_vfsuid(file_mnt_idmap(file),
file_inode(file));
struct path_cond cond = {
.uid = vfsuid_into_kuid(vfsuid),
.mode = file_inode(file)->i_mode
};
char *buffer;
int flags, error;
/* revalidation due to label out of date. No revocation at this time */
if (!denied && aa_label_is_subset(flabel, label))
/* TODO: check for revocation on stale profiles */
return 0;
flags = PATH_DELEGATE_DELETED | (S_ISDIR(cond.mode) ? PATH_IS_DIR : 0);
buffer = aa_get_buffer(in_atomic);
if (!buffer)
return -ENOMEM;
/* check every profile in task label not in current cache */
error = fn_for_each_not_in_set(flabel, label, profile,
profile_path_perm(op, subj_cred, profile,
&file->f_path, buffer,
request, &cond, flags, &perms));
if (denied && !error) {
/*
* check every profile in file label that was not tested
* in the initial check above.
*
* TODO: cache full perms so this only happens because of
* conditionals
* TODO: don't audit here
*/
if (label == flabel)
error = fn_for_each(label, profile,
profile_path_perm(op, subj_cred,
profile, &file->f_path,
buffer, request, &cond, flags,
&perms));
else
error = fn_for_each_not_in_set(label, flabel, profile,
profile_path_perm(op, subj_cred,
profile, &file->f_path,
buffer, request, &cond, flags,
&perms));
}
if (!error)
update_file_ctx(file_ctx(file), label, request);
aa_put_buffer(buffer);
return error;
}
static int __file_sock_perm(const char *op, const struct cred *subj_cred,
struct aa_label *label,
struct aa_label *flabel, struct file *file,
u32 request, u32 denied)
{
int error;
/* revalidation due to label out of date. No revocation at this time */
if (!denied && aa_label_is_subset(flabel, label))
return 0;
/* TODO: improve to skip profiles cached in flabel */
error = aa_sock_file_perm(subj_cred, label, op, request, file);
if (denied) {
/* TODO: improve to skip profiles checked above */
/* check every profile in file label to is cached */
last_error(error, aa_sock_file_perm(subj_cred, flabel, op,
request, file));
}
if (!error)
update_file_ctx(file_ctx(file), label, request);
return error;
}
/* for now separate fn to indicate semantics of the check */
static bool __file_is_delegated(struct aa_label *obj_label)
{
return unconfined(obj_label);
}
static bool __is_unix_file(struct file *file)
{
struct socket *sock = (struct socket *) file->private_data;
lockdep_assert_in_rcu_read_lock();
if (!S_ISSOCK(file_inode(file)->i_mode))
return false;
/* sock and sock->sk can be NULL for sockets being set up or torn down */
if (!sock || !sock->sk)
return false;
if (sock->sk->sk_family == PF_UNIX)
return true;
return false;
}
static bool __unix_needs_revalidation(struct file *file, struct aa_label *label,
u32 request)
{
struct socket *sock = (struct socket *) file->private_data;
AA_BUG(!__is_unix_file(file));
lockdep_assert_in_rcu_read_lock();
struct aa_sk_ctx *skctx = aa_sock(sock->sk);
if (rcu_access_pointer(skctx->peer) !=
rcu_access_pointer(skctx->peer_lastupdate))
return true;
return !__aa_subj_label_is_cached(rcu_dereference(skctx->label), label);
}
/**
* aa_file_perm - do permission revalidation check & audit for @file
* @op: operation being checked
* @subj_cred: subject cred
* @label: label being enforced (NOT NULL)
* @file: file to revalidate access permissions on (NOT NULL)
* @request: requested permissions
* @in_atomic: whether allocations need to be done in atomic context
*
* Returns: %0 if access allowed else error
*/
int aa_file_perm(const char *op, const struct cred *subj_cred,
struct aa_label *label, struct file *file,
u32 request, bool in_atomic)
{
struct aa_file_ctx *fctx;
struct aa_label *flabel;
u32 denied;
int error = 0;
AA_BUG(!label);
AA_BUG(!file);
/* don't reaudit files closed during inheritance */
if (unlikely(file->f_path.dentry == aa_null.dentry))
return -EACCES;
fctx = file_ctx(file);
rcu_read_lock();
flabel = rcu_dereference(fctx->label);
AA_BUG(!flabel);
/* revalidate access, if task is unconfined, or the cached cred
* doesn't match or if the request is for more permissions than
* was granted.
*
* Note: the test for !unconfined(flabel) is to handle file
* delegation from unconfined tasks
*/
denied = request & ~fctx->allow;
if (unconfined(label) || __file_is_delegated(flabel) ||
(!denied && __is_unix_file(file) && !__unix_needs_revalidation(file, label, request)) ||
(!denied && __aa_subj_label_is_cached(label, flabel))) {
rcu_read_unlock();
goto done;
}
/* slow path - revalidate access */
flabel = aa_get_newest_label(flabel);
rcu_read_unlock();
if (path_mediated_fs(file->f_path.dentry))
error = __file_path_perm(op, subj_cred, label, flabel, file,
request, denied, in_atomic);
else if (S_ISSOCK(file_inode(file)->i_mode))
error = __file_sock_perm(op, subj_cred, label, flabel, file,
request, denied);
aa_put_label(flabel);
done:
return error;
}
static void revalidate_tty(const struct cred *subj_cred, struct aa_label *label)
{
struct tty_struct *tty;
int drop_tty = 0;
tty = get_current_tty();
if (!tty)
return;
spin_lock(&tty->files_lock);
if (!list_empty(&tty->tty_files)) {
struct tty_file_private *file_priv;
struct file *file;
/* TODO: Revalidate access to controlling tty. */
file_priv = list_first_entry(&tty->tty_files,
struct tty_file_private, list);
file = file_priv->file;
if (aa_file_perm(OP_INHERIT, subj_cred, label, file,
MAY_READ | MAY_WRITE, IN_ATOMIC))
drop_tty = 1;
}
spin_unlock(&tty->files_lock);
tty_kref_put(tty);
if (drop_tty)
no_tty();
}
struct cred_label {
const struct cred *cred;
struct aa_label *label;
};
static int match_file(const void *p, struct file *file, unsigned int fd)
{
struct cred_label *cl = (struct cred_label *)p;
if (aa_file_perm(OP_INHERIT, cl->cred, cl->label, file,
aa_map_file_to_perms(file), IN_ATOMIC))
return fd + 1;
return 0;
}
/* based on selinux's flush_unauthorized_files */
void aa_inherit_files(const struct cred *cred, struct files_struct *files)
{
struct aa_label *label = aa_get_newest_cred_label(cred);
struct cred_label cl = {
.cred = cred,
.label = label,
};
struct file *devnull = NULL;
unsigned int n;
revalidate_tty(cred, label);
/* Revalidate access to inherited open files. */
n = iterate_fd(files, 0, match_file, &cl);
if (!n) /* none found? */
goto out;
devnull = dentry_open(&aa_null, O_RDWR, cred);
if (IS_ERR(devnull))
devnull = NULL;
/* replace all the matching ones with this */
do {
replace_fd(n - 1, devnull, 0);
} while ((n = iterate_fd(files, n, match_file, &cl)) != 0);
if (devnull)
fput(devnull);
out:
aa_put_label(label);
}
@@ -0,0 +1,55 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* AppArmor security module
*
* This file contains AppArmor af_unix fine grained mediation
*
* Copyright 2023 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_UNIX_H
#include <net/af_unix.h>
#include "label.h"
#define unix_addr(A) ((struct sockaddr_un *)(A))
#define unix_addr_len(L) ((L) - sizeof(sa_family_t))
#define unix_peer(sk) (unix_sk(sk)->peer)
#define is_unix_addr_abstract_name(B) ((B)[0] == 0)
#define is_unix_addr_anon(A, L) ((A) && unix_addr_len(L) <= 0)
#define is_unix_addr_fs(A, L) (!is_unix_addr_anon(A, L) && \
!is_unix_addr_abstract_name(unix_addr(A)->sun_path))
#define is_unix_anonymous(U) (!unix_sk(U)->addr)
#define is_unix_fs(U) (!is_unix_anonymous(U) && \
unix_sk(U)->addr->name->sun_path[0])
#define is_unix_connected(S) ((S)->state == SS_CONNECTED)
struct sockaddr_un *aa_sunaddr(const struct unix_sock *u, int *addrlen);
int aa_unix_peer_perm(const struct cred *subj_cred,
struct aa_label *label, const char *op, u32 request,
struct sock *sk, struct sock *peer_sk,
struct aa_label *peer_label);
int aa_unix_sock_perm(const char *op, u32 request, struct socket *sock);
int aa_unix_create_perm(struct aa_label *label, int family, int type,
int protocol);
int aa_unix_bind_perm(struct socket *sock, struct sockaddr *address,
int addrlen);
int aa_unix_connect_perm(struct socket *sock, struct sockaddr *address,
int addrlen);
int aa_unix_listen_perm(struct socket *sock, int backlog);
int aa_unix_accept_perm(struct socket *sock, struct socket *newsock);
int aa_unix_msg_perm(const char *op, u32 request, struct socket *sock,
struct msghdr *msg, int size);
int aa_unix_opt_perm(const char *op, u32 request, struct socket *sock, int level,
int optname);
int aa_unix_file_perm(const struct cred *subj_cred, struct aa_label *label,
const char *op, u32 request, struct file *file);
#endif /* __AA_AF_UNIX_H */
@@ -0,0 +1,68 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* AppArmor security module
*
* This file contains AppArmor basic global
*
* Copyright (C) 1998-2008 Novell/SUSE
* Copyright 2009-2017 Canonical Ltd.
*/
#ifndef __APPARMOR_H
#define __APPARMOR_H
#include <linux/types.h>
/*
* Class of mediation types in the AppArmor policy db
*/
#define AA_CLASS_NONE 0
#define AA_CLASS_UNKNOWN 1
#define AA_CLASS_FILE 2
#define AA_CLASS_CAP 3
#define AA_CLASS_DEPRECATED 4
#define AA_CLASS_RLIMITS 5
#define AA_CLASS_DOMAIN 6
#define AA_CLASS_MOUNT 7
#define AA_CLASS_PTRACE 9
#define AA_CLASS_SIGNAL 10
#define AA_CLASS_XMATCH 11
#define AA_CLASS_NET 14
#define AA_CLASS_NETV9 15
#define AA_CLASS_LABEL 16
#define AA_CLASS_POSIX_MQUEUE 17
#define AA_CLASS_MODULE 19
#define AA_CLASS_DISPLAY_LSM 20
#define AA_CLASS_NS 21
#define AA_CLASS_IO_URING 22
#define AA_CLASS_X 31
#define AA_CLASS_DBUS 32
/* NOTE: if AA_CLASS_LAST > 63 need to update label->mediates */
#define AA_CLASS_LAST AA_CLASS_DBUS
/* Control parameters settable through module/boot flags */
extern enum audit_mode aa_g_audit;
extern bool aa_g_audit_header;
extern int aa_g_debug;
extern bool aa_g_hash_policy;
extern bool aa_g_export_binary;
extern int aa_g_rawdata_compression_level;
extern bool aa_g_lock_policy;
extern bool aa_g_logsyscall;
extern bool aa_g_paranoid_load;
extern unsigned int aa_g_path_max;
#ifdef CONFIG_SECURITY_APPARMOR_EXPORT_BINARY
#define AA_MIN_CLEVEL zstd_min_clevel()
#define AA_MAX_CLEVEL zstd_max_clevel()
#define AA_DEFAULT_CLEVEL ZSTD_CLEVEL_DEFAULT
#else
#define AA_MIN_CLEVEL 0
#define AA_MAX_CLEVEL 0
#define AA_DEFAULT_CLEVEL 0
#endif /* CONFIG_SECURITY_APPARMOR_EXPORT_BINARY */
#endif /* __APPARMOR_H */
@@ -0,0 +1,148 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* AppArmor security module
*
* This file contains AppArmor filesystem definitions.
*
* Copyright (C) 1998-2008 Novell/SUSE
* Copyright 2009-2010 Canonical Ltd.
*/
#ifndef __AA_APPARMORFS_H
#define __AA_APPARMORFS_H
extern struct path aa_null;
enum aa_sfs_type {
AA_SFS_TYPE_BOOLEAN,
AA_SFS_TYPE_STRING,
AA_SFS_TYPE_U64,
AA_SFS_TYPE_FOPS,
AA_SFS_TYPE_DIR,
};
struct aa_sfs_entry;
struct aa_sfs_entry {
const char *name;
struct dentry *dentry;
umode_t mode;
enum aa_sfs_type v_type;
union {
bool boolean;
char *string;
unsigned long u64;
struct aa_sfs_entry *files;
} v;
const struct file_operations *file_ops;
};
extern const struct file_operations aa_sfs_seq_file_ops;
#define AA_SFS_FILE_BOOLEAN(_name, _value) \
{ .name = (_name), .mode = 0444, \
.v_type = AA_SFS_TYPE_BOOLEAN, .v.boolean = (_value), \
.file_ops = &aa_sfs_seq_file_ops }
#define AA_SFS_FILE_STRING(_name, _value) \
{ .name = (_name), .mode = 0444, \
.v_type = AA_SFS_TYPE_STRING, .v.string = (_value), \
.file_ops = &aa_sfs_seq_file_ops }
#define AA_SFS_FILE_U64(_name, _value) \
{ .name = (_name), .mode = 0444, \
.v_type = AA_SFS_TYPE_U64, .v.u64 = (_value), \
.file_ops = &aa_sfs_seq_file_ops }
#define AA_SFS_FILE_FOPS(_name, _mode, _fops) \
{ .name = (_name), .v_type = AA_SFS_TYPE_FOPS, \
.mode = (_mode), .file_ops = (_fops) }
#define AA_SFS_DIR(_name, _value) \
{ .name = (_name), .v_type = AA_SFS_TYPE_DIR, .v.files = (_value) }
extern void __init aa_destroy_aafs(void);
struct aa_profile;
struct aa_ns;
enum aafs_ns_type {
AAFS_NS_DIR,
AAFS_NS_PROFS,
AAFS_NS_NS,
AAFS_NS_RAW_DATA,
AAFS_NS_LOAD,
AAFS_NS_REPLACE,
AAFS_NS_REMOVE,
AAFS_NS_REVISION,
AAFS_NS_COUNT,
AAFS_NS_MAX_COUNT,
AAFS_NS_SIZE,
AAFS_NS_MAX_SIZE,
AAFS_NS_OWNER,
AAFS_NS_SIZEOF,
};
enum aafs_prof_type {
AAFS_PROF_DIR,
AAFS_PROF_PROFS,
AAFS_PROF_NAME,
AAFS_PROF_MODE,
AAFS_PROF_ATTACH,
AAFS_PROF_HASH,
AAFS_PROF_RAW_DATA,
AAFS_PROF_RAW_HASH,
AAFS_PROF_RAW_ABI,
AAFS_PROF_SIZEOF,
};
#define ns_dir(X) ((X)->dents[AAFS_NS_DIR])
#define ns_subns_dir(X) ((X)->dents[AAFS_NS_NS])
#define ns_subprofs_dir(X) ((X)->dents[AAFS_NS_PROFS])
#define ns_subdata_dir(X) ((X)->dents[AAFS_NS_RAW_DATA])
#define ns_subload(X) ((X)->dents[AAFS_NS_LOAD])
#define ns_subreplace(X) ((X)->dents[AAFS_NS_REPLACE])
#define ns_subremove(X) ((X)->dents[AAFS_NS_REMOVE])
#define ns_subrevision(X) ((X)->dents[AAFS_NS_REVISION])
#define prof_dir(X) ((X)->dents[AAFS_PROF_DIR])
#define prof_child_dir(X) ((X)->dents[AAFS_PROF_PROFS])
int aa_create_aafs(void);
void __aa_bump_ns_revision(struct aa_ns *ns);
void __aafs_profile_rmdir(struct aa_profile *profile);
void __aafs_profile_migrate_dents(struct aa_profile *old,
struct aa_profile *new);
int __aafs_profile_mkdir(struct aa_profile *profile, struct dentry *parent);
void __aafs_ns_rmdir(struct aa_ns *ns);
int __aafs_ns_mkdir(struct aa_ns *ns, struct dentry *parent, const char *name,
struct dentry *dent);
struct aa_loaddata;
#ifdef CONFIG_SECURITY_APPARMOR_EXPORT_BINARY
void __aa_fs_remove_rawdata(struct aa_loaddata *rawdata);
int __aa_fs_create_rawdata(struct aa_ns *ns, struct aa_loaddata *rawdata);
void __aa_remove_rawdata_symlink_dents(struct aa_profile *profile);
int __aa_create_rawdata_symlink_dents(struct aa_profile *profile);
#else
static inline void __aa_fs_remove_rawdata(struct aa_loaddata *rawdata)
{
/* empty stub */
}
static inline int __aa_fs_create_rawdata(struct aa_ns *ns,
struct aa_loaddata *rawdata)
{
return 0;
}
static inline void __aa_remove_rawdata_symlink_dents(struct aa_profile *profile)
{
/* empty stub */
}
static inline int __aa_create_rawdata_symlink_dents(struct aa_profile *profile)
{
return 0;
}
#endif /* CONFIG_SECURITY_APPARMOR_EXPORT_BINARY */
#endif /* __AA_APPARMORFS_H */
@@ -0,0 +1,212 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* AppArmor security module
*
* This file contains AppArmor auditing function definitions.
*
* Copyright (C) 1998-2008 Novell/SUSE
* Copyright 2009-2010 Canonical Ltd.
*/
#ifndef __AA_AUDIT_H
#define __AA_AUDIT_H
#include <linux/audit.h>
#include <linux/fs.h>
#include <linux/lsm_audit.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include "file.h"
#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, /* quiet all messages */
AUDIT_NOQUIET, /* do not quiet audit messages */
AUDIT_ALL /* audit all accesses */
};
enum audit_type {
AUDIT_APPARMOR_AUDIT,
AUDIT_APPARMOR_ALLOWED,
AUDIT_APPARMOR_DENIED,
AUDIT_APPARMOR_HINT,
AUDIT_APPARMOR_STATUS,
AUDIT_APPARMOR_ERROR,
AUDIT_APPARMOR_KILL,
AUDIT_APPARMOR_AUTO
};
#define OP_NULL NULL
#define OP_SYSCTL "sysctl"
#define OP_CAPABLE "capable"
#define OP_UNLINK "unlink"
#define OP_MKDIR "mkdir"
#define OP_RMDIR "rmdir"
#define OP_MKNOD "mknod"
#define OP_TRUNC "truncate"
#define OP_LINK "link"
#define OP_SYMLINK "symlink"
#define OP_RENAME_SRC "rename_src"
#define OP_RENAME_DEST "rename_dest"
#define OP_CHMOD "chmod"
#define OP_CHOWN "chown"
#define OP_GETATTR "getattr"
#define OP_OPEN "open"
#define OP_FRECEIVE "file_receive"
#define OP_FPERM "file_perm"
#define OP_FLOCK "file_lock"
#define OP_FMMAP "file_mmap"
#define OP_FMPROT "file_mprotect"
#define OP_INHERIT "file_inherit"
#define OP_PIVOTROOT "pivotroot"
#define OP_MOUNT "mount"
#define OP_UMOUNT "umount"
#define OP_CREATE "create"
#define OP_POST_CREATE "post_create"
#define OP_BIND "bind"
#define OP_CONNECT "connect"
#define OP_LISTEN "listen"
#define OP_ACCEPT "accept"
#define OP_SENDMSG "sendmsg"
#define OP_RECVMSG "recvmsg"
#define OP_GETSOCKNAME "getsockname"
#define OP_GETPEERNAME "getpeername"
#define OP_GETSOCKOPT "getsockopt"
#define OP_SETSOCKOPT "setsockopt"
#define OP_SHUTDOWN "socket_shutdown"
#define OP_PTRACE "ptrace"
#define OP_SIGNAL "signal"
#define OP_EXEC "exec"
#define OP_CHANGE_HAT "change_hat"
#define OP_CHANGE_PROFILE "change_profile"
#define OP_CHANGE_ONEXEC "change_onexec"
#define OP_STACK "stack"
#define OP_STACK_ONEXEC "stack_onexec"
#define OP_SETPROCATTR "setprocattr"
#define OP_SETRLIMIT "setrlimit"
#define OP_PROF_REPL "profile_replace"
#define OP_PROF_LOAD "profile_load"
#define OP_PROF_RM "profile_remove"
#define OP_USERNS_CREATE "userns_create"
#define OP_URING_OVERRIDE "uring_override"
#define OP_URING_SQPOLL "uring_sqpoll"
struct apparmor_audit_data {
int error;
int type;
u16 class;
const char *op;
const struct cred *subj_cred;
struct aa_label *subj_label;
const char *name;
const char *info;
u32 request;
u32 denied;
u32 tags;
union {
/* these entries require a custom callback fn */
struct {
struct aa_label *peer;
union {
struct {
const char *target;
kuid_t ouid;
} fs;
struct {
int rlim;
unsigned long max;
} rlim;
struct {
int signal;
int unmappedsig;
};
struct {
int type, protocol;
void *addr;
int addrlen;
struct {
void *addr;
int addrlen;
} peer;
} net;
};
};
struct {
struct aa_profile *profile;
const char *ns;
long pos;
} iface;
struct {
const char *src_name;
const char *type;
const char *trans;
const char *data;
unsigned long flags;
} mnt;
struct {
struct aa_label *target;
} uring;
};
struct common_audit_data common;
};
/* macros for dealing with apparmor_audit_data structure */
#define aad(SA) (container_of(SA, struct apparmor_audit_data, common))
#define aad_of_va(VA) aad((struct common_audit_data *)(VA))
#define DEFINE_AUDIT_DATA(NAME, T, C, X) \
/* TODO: cleanup audit init so we don't need _aad = {0,} */ \
struct apparmor_audit_data NAME = { \
.class = (C), \
.op = (X), \
.common.type = (T), \
.common.u.tsk = NULL, \
.common.apparmor_audit_data = &NAME, \
};
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,
struct apparmor_audit_data *ad,
void (*cb) (struct audit_buffer *, void *));
#define aa_audit_error(ERROR, AD, CB) \
({ \
(AD)->error = (ERROR); \
aa_audit_msg(AUDIT_APPARMOR_ERROR, (AD), (CB)); \
(AD)->error; \
})
static inline int complain_error(int error)
{
if (error == -EPERM || error == -EACCES)
return 0;
return error;
}
void aa_audit_rule_free(void *vrule);
int aa_audit_rule_init(u32 field, u32 op, char *rulestr, void **vrule, gfp_t gfp);
int aa_audit_rule_known(struct audit_krule *rule);
int aa_audit_rule_match(struct lsm_prop *prop, u32 field, u32 op, void *vrule);
#endif /* __AA_AUDIT_H */
@@ -0,0 +1,48 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* AppArmor security module
*
* This file contains AppArmor capability mediation definitions.
*
* Copyright (C) 1998-2008 Novell/SUSE
* Copyright 2009-2013 Canonical Ltd.
*/
#ifndef __AA_CAPABILITY_H
#define __AA_CAPABILITY_H
#include <linux/sched.h>
#include "apparmorfs.h"
struct aa_label;
/* aa_caps - confinement data for capabilities
* @allowed: capabilities mask
* @audit: caps that are to be audited
* @denied: caps that are explicitly denied
* @quiet: caps that should not be audited
* @kill: caps that when requested will result in the task being killed
* @extended: caps that are subject finer grained mediation
*/
struct aa_caps {
kernel_cap_t allow;
kernel_cap_t audit;
kernel_cap_t denied;
kernel_cap_t quiet;
kernel_cap_t kill;
kernel_cap_t extended;
};
extern struct aa_sfs_entry aa_sfs_entry_caps[];
kernel_cap_t aa_profile_capget(struct aa_profile *profile);
int aa_capable(const struct cred *subj_cred, struct aa_label *label,
int cap, unsigned int opts);
static inline void aa_free_cap_rules(struct aa_caps *caps)
{
/* NOP */
}
#endif /* __AA_CAPBILITY_H */
@@ -0,0 +1,250 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* AppArmor security module
*
* This file contains AppArmor contexts used to associate "labels" to objects.
*
* Copyright (C) 1998-2008 Novell/SUSE
* Copyright 2009-2010 Canonical Ltd.
*/
#ifndef __AA_CONTEXT_H
#define __AA_CONTEXT_H
#include <linux/cred.h>
#include <linux/slab.h>
#include <linux/sched.h>
#include "label.h"
#include "policy_ns.h"
#include "task.h"
static inline struct aa_label *cred_label(const struct cred *cred)
{
struct aa_label **blob = cred->security + apparmor_blob_sizes.lbs_cred;
AA_BUG(!blob);
return *blob;
}
static inline void set_cred_label(const struct cred *cred,
struct aa_label *label)
{
struct aa_label **blob = cred->security + apparmor_blob_sizes.lbs_cred;
AA_BUG(!blob);
*blob = label;
}
/**
* aa_get_newest_cred_label - obtain the newest label on a cred
* @cred: cred to obtain label from (NOT NULL)
*
* Returns: newest version of confining label
*/
static inline struct aa_label *aa_get_newest_cred_label(const struct cred *cred)
{
return aa_get_newest_label(cred_label(cred));
}
static inline struct aa_label *aa_get_newest_cred_label_condref(const struct cred *cred,
bool *needput)
{
struct aa_label *l = cred_label(cred);
if (unlikely(label_is_stale(l))) {
*needput = true;
return aa_get_newest_label(l);
}
*needput = false;
return l;
}
static inline void aa_put_label_condref(struct aa_label *l, bool needput)
{
if (unlikely(needput))
aa_put_label(l);
}
/**
* aa_current_raw_label - find the current tasks confining label
*
* Returns: up to date confining label or the ns unconfined label (NOT NULL)
*
* This fn will not update the tasks cred to the most up to date version
* of the label so it is safe to call when inside of locks.
*/
static inline struct aa_label *aa_current_raw_label(void)
{
return cred_label(current_cred());
}
/**
* aa_get_current_label - get the newest version of the current tasks label
*
* Returns: newest version of confining label (NOT NULL)
*
* This fn will not update the tasks cred, so it is safe inside of locks
*
* The returned reference must be put with aa_put_label()
*/
static inline struct aa_label *aa_get_current_label(void)
{
struct aa_label *l = aa_current_raw_label();
if (label_is_stale(l))
return aa_get_newest_label(l);
return aa_get_label(l);
}
/**
* __end_cred_crit_section - end crit section begun with __begin_...
* @label: label obtained from __begin_cred_crit_section
* @needput: output: bool set by __begin_cred_crit_section
*
* While the cred passed to __begin is guaranteed to not change
* and the cred and label could be passed here instead of needput
* using needput with a local var makes it easier for the compiler
* and processor to optimize and speculatively execute the comparison
* than chasing a pointer in the cred struct.
*/
static inline void __end_cred_crit_section(struct aa_label *label,
bool needput)
{
if (unlikely(needput))
aa_put_label(label);
}
/**
* __begin_cred_crit_section - @cred's confining label
* @cred: current's cred to start a crit section on its label
* @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)
*
* safe to call inside locks
*
* The returned reference must be put with __end_cred_crit_section()
* This must NOT be used if the task cred could be updated within the
* critical section between
* __begin_cred_crit_section() .. __end_cred_crit_section()
*
* The crit section is an optimization to avoid having to get and put
* the newest version of the label. While the cred won't change and
* hence the label it contains won't change, the newest version of the
* label can. During the crit section the newest versions of the label
* will be used until the end of the crit section.
*
* If the label has not been updated at the start of the crit section
* no refcount is taken, the cred's refcount is enough to hold the
* label for the duration of the crit section.
*
* If the label has been updated then a refcount will be taken and the
* newest version of the label will be returned. While the cred label
* and the returned label could be compared at the end of the crit
* section, needput is used because it allows better optimization by
* the compiler and the processor's speculative execution.
*/
static inline struct aa_label *__begin_cred_crit_section(const struct cred *cred,
bool *needput)
{
struct aa_label *label = cred_label(cred);
if (label_is_stale(label)) {
*needput = true;
return aa_get_newest_label(label);
}
*needput = false;
return label;
}
/**
* __end_current_label_crit_section - end crit section begun with __begin_...
* @label: label obtained from __begin_current_label_crit_section
* @needput: output: bool set by __begin_current_label_crit_section
*
* wrapper around __end_cred_crit_section() to pair nicely with
* __begin_current_label_crit_section()
*/
static inline void __end_current_label_crit_section(struct aa_label *label,
bool needput)
{
__end_cred_crit_section(label, needput);
}
/**
* end_current_label_crit_section - put a reference found with begin_current_label..
* @label: label reference to put
*
* 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)
{
if (label != aa_current_raw_label())
aa_put_label(label);
}
/**
* __begin_current_label_crit_section - current's confining label
* @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)
*
* 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
* critical section between __begin_current_label_crit_section() ..
* __end_current_label_crit_section()
*/
static inline struct aa_label *__begin_current_label_crit_section(bool *needput)
{
return __begin_cred_crit_section(current_cred(), needput);
}
/**
* begin_current_label_crit_section - current's confining label and update it
*
* 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
* critical section between begin_current_label_crit_section() ..
* end_current_label_crit_section()
*/
static inline struct aa_label *begin_current_label_crit_section(void)
{
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);
}
return label;
}
static inline struct aa_ns *aa_get_current_ns(void)
{
struct aa_label *label;
struct aa_ns *ns;
bool needput;
label = __begin_current_label_crit_section(&needput);
ns = aa_get_ns(labels_ns(label));
__end_current_label_crit_section(label, needput);
return ns;
}
#endif /* __AA_CONTEXT_H */
@@ -0,0 +1,38 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* AppArmor security module
*
* This file contains AppArmor policy loading interface function definitions.
*
* Copyright 2013 Canonical Ltd.
*/
#ifndef __APPARMOR_CRYPTO_H
#define __APPARMOR_CRYPTO_H
#include "policy.h"
#ifdef CONFIG_SECURITY_APPARMOR_HASH
int init_profile_hash(void);
unsigned int aa_hash_size(void);
char *aa_calc_hash(void *data, size_t len);
int aa_calc_profile_hash(struct aa_profile *profile, u32 version, void *start,
size_t len);
#else
static inline char *aa_calc_hash(void *data, size_t len)
{
return NULL;
}
static inline int aa_calc_profile_hash(struct aa_profile *profile, u32 version,
void *start, size_t len)
{
return 0;
}
static inline unsigned int aa_hash_size(void)
{
return 0;
}
#endif
#endif /* __APPARMOR_CRYPTO_H */
@@ -0,0 +1,33 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* AppArmor security module
*
* This file contains AppArmor security domain transition function definitions.
*
* Copyright (C) 1998-2008 Novell/SUSE
* Copyright 2009-2010 Canonical Ltd.
*/
#include <linux/binfmts.h>
#include <linux/types.h>
#include "label.h"
#ifndef __AA_DOMAIN_H
#define __AA_DOMAIN_H
#define AA_CHANGE_NOFLAGS 0
#define AA_CHANGE_TEST 1
#define AA_CHANGE_CHILD 2
#define AA_CHANGE_ONEXEC 4
#define AA_CHANGE_STACK 8
struct aa_label *x_table_lookup(struct aa_profile *profile, u32 xindex,
const char **name);
int apparmor_bprm_creds_for_exec(struct linux_binprm *bprm);
int aa_change_hat(const char *hats[], int count, u64 token, int flags);
int aa_change_profile(const char *fqname, int flags);
#endif /* __AA_DOMAIN_H */
@@ -0,0 +1,133 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* AppArmor security module
*
* This file contains AppArmor file mediation function definitions.
*
* Copyright (C) 1998-2008 Novell/SUSE
* Copyright 2009-2010 Canonical Ltd.
*/
#ifndef __AA_FILE_H
#define __AA_FILE_H
#include <linux/spinlock.h>
#include "domain.h"
#include "match.h"
#include "perms.h"
struct aa_policydb;
struct aa_profile;
struct path;
#define mask_mode_t(X) (X & (MAY_EXEC | MAY_WRITE | MAY_READ | MAY_APPEND))
#define AA_AUDIT_FILE_MASK (MAY_READ | MAY_WRITE | MAY_EXEC | MAY_APPEND |\
AA_MAY_CREATE | AA_MAY_DELETE | \
AA_MAY_GETATTR | AA_MAY_SETATTR | \
AA_MAY_CHMOD | AA_MAY_CHOWN | AA_MAY_LOCK | \
AA_EXEC_MMAP | AA_MAY_LINK)
static inline struct aa_file_ctx *file_ctx(struct file *file)
{
return file->f_security + apparmor_blob_sizes.lbs_file;
}
/* struct aa_file_ctx - the AppArmor context the file was opened in
* @lock: lock to update the ctx
* @label: label currently cached on the ctx
* @perms: the permission the file was opened with
*/
struct aa_file_ctx {
spinlock_t lock;
struct aa_label __rcu *label;
u32 allow;
};
/*
* The xindex is broken into 3 parts
* - index - an index into either the exec name table or the variable table
* - exec type - which determines how the executable name and index are used
* - flags - which modify how the destination name is applied
*/
#define AA_X_INDEX_MASK AA_INDEX_MASK
#define AA_X_TYPE_MASK 0x0c000000
#define AA_X_NONE AA_INDEX_NONE
#define AA_X_NAME 0x04000000 /* use executable name px */
#define AA_X_TABLE 0x08000000 /* use a specified name ->n# */
#define AA_X_UNSAFE 0x10000000
#define AA_X_CHILD 0x20000000
#define AA_X_INHERIT 0x40000000
#define AA_X_UNCONFINED 0x80000000
/* need to make conditional which ones are being set */
struct path_cond {
kuid_t uid;
umode_t mode;
};
#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,
const char *op, u32 request, const char *name,
const char *target, struct aa_label *tlabel, kuid_t ouid,
const char *info, int error);
struct aa_perms *aa_lookup_condperms(kuid_t subj_uid,
struct aa_policydb *file_rules,
aa_state_t state, struct path_cond *cond);
aa_state_t aa_str_perms(struct aa_policydb *file_rules, aa_state_t start,
const char *name, struct path_cond *cond,
struct aa_perms *perms);
int __aa_path_perm(const char *op, const struct cred *subj_cred,
struct aa_profile *profile, const char *name,
u32 request, struct path_cond *cond, int flags,
struct aa_perms *perms);
int aa_path_perm(const char *op, const struct cred *subj_cred,
struct aa_label *label, const struct path *path,
int flags, u32 request, struct path_cond *cond);
int aa_path_link(const struct cred *subj_cred, struct aa_label *label,
struct dentry *old_dentry, const struct path *new_dir,
struct dentry *new_dentry);
int aa_file_perm(const char *op, const struct cred *subj_cred,
struct aa_label *label, struct file *file,
u32 request, bool in_atomic);
void aa_inherit_files(const struct cred *cred, struct files_struct *files);
/**
* aa_map_file_to_perms - map file flags to AppArmor permissions
* @file: open file to map flags to AppArmor permissions
*
* Returns: apparmor permission set for the file
*/
static inline u32 aa_map_file_to_perms(struct file *file)
{
int flags = file->f_flags;
u32 perms = 0;
if (file->f_mode & FMODE_WRITE)
perms |= MAY_WRITE;
if (file->f_mode & FMODE_READ)
perms |= MAY_READ;
if ((flags & O_APPEND) && (perms & MAY_WRITE))
perms = (perms & ~MAY_WRITE) | MAY_APPEND;
/* trunc implies write permission */
if (flags & O_TRUNC)
perms |= MAY_WRITE;
if (flags & O_CREAT)
perms |= AA_MAY_CREATE;
return perms;
}
#endif /* __AA_FILE_H */
@@ -0,0 +1,23 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* AppArmor security module
*
* This file contains AppArmor ipc mediation function definitions.
*
* Copyright (C) 1998-2008 Novell/SUSE
* Copyright 2009-2017 Canonical Ltd.
*/
#ifndef __AA_IPC_H
#define __AA_IPC_H
#include <linux/sched.h>
#define SIGUNKNOWN 0
#define MAXMAPPED_SIG 35
int aa_may_signal(const struct cred *subj_cred, struct aa_label *sender,
const struct cred *target_cred, struct aa_label *target,
int sig);
#endif /* __AA_IPC_H */
@@ -0,0 +1,491 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* AppArmor security module
*
* This file contains AppArmor label definitions
*
* Copyright 2017 Canonical Ltd.
*/
#ifndef __AA_LABEL_H
#define __AA_LABEL_H
#include <linux/atomic.h>
#include <linux/audit.h>
#include <linux/rbtree.h>
#include <linux/rcupdate.h>
#include "apparmor.h"
#include "lib.h"
struct aa_ns;
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)
#define vec_setup(T, V, N, GFP) \
({ \
if ((N) <= LOCAL_VEC_ENTRIES) { \
typeof(N) i; \
(V) = (_ ## V ## _localtmp); \
for (i = 0; i < (N); i++) \
(V)[i] = NULL; \
} else \
(V) = kzalloc(sizeof(struct aa_ ## T *) * (N), (GFP)); \
(V) ? 0 : -ENOMEM; \
})
#define vec_cleanup(T, V, N) \
do { \
int i; \
for (i = 0; i < (N); i++) { \
if (!IS_ERR_OR_NULL((V)[i])) \
aa_put_ ## T((V)[i]); \
} \
if ((V) != _ ## V ## _localtmp) \
kfree(V); \
} while (0)
#define vec_last(VEC, SIZE) ((VEC)[(SIZE) - 1])
#define vec_ns(VEC, SIZE) (vec_last((VEC), (SIZE))->ns)
#define vec_labelset(VEC, SIZE) (&vec_ns((VEC), (SIZE))->labels)
#define cleanup_domain_vec(V, L) cleanup_label_vec((V), (L)->size)
struct aa_profile;
#define VEC_FLAG_TERMINATE 1
int aa_vec_unique(struct aa_profile **vec, int n, int flags);
struct aa_label *aa_vec_find_or_create_label(struct aa_profile **vec, int len,
gfp_t gfp);
#define aa_sort_and_merge_vec(N, V) \
aa_sort_and_merge_profiles((N), (struct aa_profile **)(V))
/* struct aa_labelset - set of labels for a namespace
*
* Labels are reference counted; aa_labelset does not contribute to label
* reference counts. Once a label's last refcount is put it is removed from
* the set.
*/
struct aa_labelset {
rwlock_t lock;
struct rb_root root;
};
#define __labelset_for_each(LS, N) \
for ((N) = rb_first(&(LS)->root); (N); (N) = rb_next(N))
enum label_flags {
FLAG_HAT = 1, /* profile is a hat */
FLAG_UNCONFINED = 2, /* label unconfined only if all */
FLAG_NULL = 4, /* profile is null learning profile */
FLAG_IX_ON_NAME_ERROR = 8, /* fallback to ix on name lookup fail */
FLAG_IMMUTIBLE = 0x10, /* don't allow changes/replacement */
FLAG_USER_DEFINED = 0x20, /* user based profile - lower privs */
FLAG_NO_LIST_REF = 0x40, /* list doesn't keep profile ref */
FLAG_NS_COUNT = 0x80, /* carries NS ref count */
FLAG_IN_TREE = 0x100, /* label is in tree */
FLAG_PROFILE = 0x200, /* label is a profile */
FLAG_EXPLICIT = 0x400, /* explicit static label */
FLAG_STALE = 0x800, /* replaced/removed */
FLAG_RENAMED = 0x1000, /* label has renaming in it */
FLAG_REVOKED = 0x2000, /* label has revocation in it */
FLAG_DEBUG1 = 0x4000,
FLAG_DEBUG2 = 0x8000,
/* These flags must correspond with PATH_flags */
/* TODO: add new path flags */
};
struct aa_label;
struct aa_proxy {
struct aa_common_ref count;
struct aa_label __rcu *label;
};
struct label_it {
int i, j;
};
/* struct aa_label_base - base info of label
* @count: ref count of active users
* @node: rbtree position
* @rcu: rcu callback struct
* @proxy: is set to the label that replaced this label
* @hname: text representation of the label (MAYBE_NULL)
* @flags: stale and other flags - values may change under label set lock
* @secid: secid that references this label
* @size: number of entries in @ent[]
* @mediates: bitmask for label_mediates
* profile: label vec when embedded in a profile FLAG_PROFILE is set
* rules: variable length rules in a profile FLAG_PROFILE is set
* vec: vector of profiles comprising the compound label
*/
struct aa_label {
struct aa_common_ref count;
struct rb_node node;
struct rcu_head rcu;
struct aa_proxy *proxy;
__counted char *hname;
long flags;
u32 secid;
int size;
u64 mediates;
union {
struct {
/* only used is the label is a profile, size of
* rules[] is determined by the profile
* profile[1] is poison or null as guard
*/
struct aa_profile *profile[2];
DECLARE_FLEX_ARRAY(struct aa_ruleset *, rules);
};
DECLARE_FLEX_ARRAY(struct aa_profile *, vec);
};
};
#define last_error(E, FN) \
do { \
int __subE = (FN); \
if (__subE) \
(E) = __subE; \
} while (0)
#define label_isprofile(X) ((X)->flags & FLAG_PROFILE)
#define label_unconfined(X) ((X)->flags & FLAG_UNCONFINED)
#define unconfined(X) label_unconfined(X)
#define label_is_stale(X) ((X)->flags & FLAG_STALE)
#define __label_make_stale(X) ((X)->flags |= FLAG_STALE)
#define labels_ns(X) (vec_ns(&((X)->vec[0]), (X)->size))
#define labels_set(X) (&labels_ns(X)->labels)
#define labels_view(X) labels_ns(X)
#define labels_profile(X) ((X)->vec[(X)->size - 1])
int aa_label_next_confined(struct aa_label *l, int i);
/* for each profile in a label */
#define label_for_each(I, L, P) \
for ((I).i = 0; ((P) = (L)->vec[(I).i]); ++((I).i))
/* assumes break/goto ended label_for_each */
#define label_for_each_cont(I, L, P) \
for (++((I).i); ((P) = (L)->vec[(I).i]); ++((I).i))
/* for each profile that is enforcing confinement in a label */
#define label_for_each_confined(I, L, P) \
for ((I).i = aa_label_next_confined((L), 0); \
((P) = (L)->vec[(I).i]); \
(I).i = aa_label_next_confined((L), (I).i + 1))
#define label_for_each_in_merge(I, A, B, P) \
for ((I).i = (I).j = 0; \
((P) = aa_label_next_in_merge(&(I), (A), (B))); \
)
#define label_for_each_not_in_set(I, SET, SUB, P) \
for ((I).i = (I).j = 0; \
((P) = __aa_label_next_not_in_set(&(I), (SET), (SUB))); \
)
#define next_in_ns(i, NS, L) \
({ \
typeof(i) ___i = (i); \
while ((L)->vec[___i] && (L)->vec[___i]->ns != (NS)) \
(___i)++; \
(___i); \
})
#define label_for_each_in_ns(I, NS, L, P) \
for ((I).i = next_in_ns(0, (NS), (L)); \
((P) = (L)->vec[(I).i]); \
(I).i = next_in_ns((I).i + 1, (NS), (L)))
#define fn_for_each_in_ns(L, P, FN) \
({ \
struct label_it __i; \
struct aa_ns *__ns = labels_ns(L); \
int __E = 0; \
label_for_each_in_ns(__i, __ns, (L), (P)) { \
last_error(__E, (FN)); \
} \
__E; \
})
#define fn_for_each_XXX(L, P, FN, ...) \
({ \
struct label_it i; \
int __E = 0; \
label_for_each ## __VA_ARGS__(i, (L), (P)) { \
last_error(__E, (FN)); \
} \
__E; \
})
#define fn_for_each(L, P, FN) fn_for_each_XXX(L, P, FN)
#define fn_for_each_confined(L, P, FN) fn_for_each_XXX(L, P, FN, _confined)
#define fn_for_each2_XXX(L1, L2, P, FN, ...) \
({ \
struct label_it i; \
int __E = 0; \
label_for_each ## __VA_ARGS__(i, (L1), (L2), (P)) { \
last_error(__E, (FN)); \
} \
__E; \
})
#define fn_for_each_in_merge(L1, L2, P, FN) \
fn_for_each2_XXX((L1), (L2), P, FN, _in_merge)
#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)
{
return (L)->mediates & (((u64) 1) << (C));
}
static inline bool label_mediates_safe(struct aa_label *L, unsigned char C)
{
if (C > AA_CLASS_LAST)
return false;
return label_mediates(L, C);
}
void aa_labelset_destroy(struct aa_labelset *ls);
void aa_labelset_init(struct aa_labelset *ls);
void __aa_labelset_update_subtree(struct aa_ns *ns);
void aa_label_destroy(struct aa_label *label);
void aa_label_free(struct aa_label *label);
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);
struct aa_profile *__aa_label_next_not_in_set(struct label_it *I,
struct aa_label *set,
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);
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);
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);
bool aa_update_label_name(struct aa_ns *ns, struct aa_label *label, gfp_t gfp);
#define FLAGS_NONE 0
#define FLAG_SHOW_MODE 1
#define FLAG_VIEW_SUBNS 2
#define FLAG_HIDDEN_UNCONFINED 4
#define FLAG_ABS_ROOT 8
int aa_label_snxprint(char *str, size_t size, struct aa_ns *view,
struct aa_label *label, int flags);
int aa_label_asxprint(char **strp, struct aa_ns *ns, struct aa_label *label,
int flags, gfp_t gfp);
int aa_label_acntsxprint(char __counted **strp, struct aa_ns *ns,
struct aa_label *label, int flags, gfp_t gfp);
void aa_label_xaudit(struct audit_buffer *ab, struct aa_ns *ns,
struct aa_label *label, int flags, gfp_t gfp);
void aa_label_seq_xprint(struct seq_file *f, struct aa_ns *ns,
struct aa_label *label, int flags, gfp_t gfp);
void aa_label_xprintk(struct aa_ns *ns, struct aa_label *label, int flags,
gfp_t gfp);
void aa_label_printk(struct aa_label *label, gfp_t gfp);
struct aa_label *aa_label_strn_parse(struct aa_label *base, const char *str,
size_t n, gfp_t gfp, bool create,
bool force_stack);
struct aa_label *aa_label_parse(struct aa_label *base, const char *str,
gfp_t gfp, bool create, bool force_stack);
static inline const char *aa_label_strn_split(const char *str, int n)
{
const char *pos;
aa_state_t state;
state = aa_dfa_matchn_until(stacksplitdfa, DFA_START, str, n, &pos);
if (!ACCEPT_TABLE(stacksplitdfa)[state])
return NULL;
return pos - 3;
}
static inline const char *aa_label_str_split(const char *str)
{
const char *pos;
aa_state_t state;
state = aa_dfa_match_until(stacksplitdfa, DFA_START, str, &pos);
if (!ACCEPT_TABLE(stacksplitdfa)[state])
return NULL;
return pos - 3;
}
struct aa_perms;
struct aa_ruleset;
int aa_label_match(struct aa_profile *profile, struct aa_ruleset *rules,
struct aa_label *label, aa_state_t state, bool subns,
u32 request, struct aa_perms *perms);
/**
* __aa_get_label - get a reference count to uncounted label reference
* @l: reference to get a count on
*
* Returns: pointer to reference OR NULL if race is lost and reference is
* being repeated.
* Requires: lock held, and the return code MUST be checked
*/
static inline struct aa_label *__aa_get_label(struct aa_label *l)
{
if (l && kref_get_unless_zero(&l->count.count))
return l;
return NULL;
}
static inline struct aa_label *aa_get_label(struct aa_label *l)
{
if (l)
kref_get(&(l->count.count));
return l;
}
/**
* aa_get_label_rcu - increment refcount on a label that can be replaced
* @l: pointer to label that can be replaced (NOT NULL)
*
* Returns: pointer to a refcounted label.
* else NULL if no label
*/
static inline struct aa_label *aa_get_label_rcu(struct aa_label __rcu **l)
{
struct aa_label *c;
rcu_read_lock();
do {
c = rcu_dereference(*l);
} while (c && !kref_get_unless_zero(&c->count.count));
rcu_read_unlock();
return c;
}
/**
* aa_get_newest_label - find the newest version of @l
* @l: the label to check for newer versions of
*
* Returns: refcounted newest version of @l taking into account
* replacement, renames and removals
* return @l.
*/
static inline struct aa_label *aa_get_newest_label(struct aa_label *l)
{
if (!l)
return NULL;
if (label_is_stale(l)) {
struct aa_label *tmp;
AA_BUG(!l->proxy);
AA_BUG(!l->proxy->label);
/* BUG: only way this can happen is @l ref count and its
* replacement count have gone to 0 and are on their way
* to destruction. ie. we have a refcounting error
*/
tmp = aa_get_label_rcu(&l->proxy->label);
AA_BUG(!tmp);
return tmp;
}
return aa_get_label(l);
}
/**
* aa_get_newest_label_condref - find the newest version of @l
* @l: the label to check for newer versions of
* @needput: returns whether the reference needs put
*
* Returns: refcounted newest version of @l taking into account
* replacement, renames and removals
* return @l.
*/
static inline struct aa_label *aa_get_newest_label_condref(struct aa_label *l,
bool *needput)
{
if (l && unlikely(label_is_stale(l))) {
struct aa_label *tmp;
AA_BUG(!l->proxy);
AA_BUG(!l->proxy->label);
/* BUG: only way this can happen is @l ref count and its
* replacement count have gone to 0 and are on their way
* to destruction. ie. we have a refcounting error
*/
tmp = aa_get_label_rcu(&l->proxy->label);
AA_BUG(!tmp);
*needput = true;
return tmp;
}
*needput = false;
return l;
}
static inline void aa_put_label(struct aa_label *l)
{
if (l)
kref_put(&l->count.count, aa_label_kref);
}
/* 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)
{
return aa_label_is_subset(obj_label, subj_label);
}
struct aa_proxy *aa_alloc_proxy(struct aa_label *l, gfp_t gfp);
void aa_proxy_kref(struct kref *kref);
static inline struct aa_proxy *aa_get_proxy(struct aa_proxy *proxy)
{
if (proxy)
kref_get(&(proxy->count.count));
return proxy;
}
static inline void aa_put_proxy(struct aa_proxy *proxy)
{
if (proxy)
kref_put(&proxy->count.count, aa_proxy_kref);
}
void __aa_proxy_redirect(struct aa_label *orig, struct aa_label *new);
#endif /* __AA_LABEL_H */
+363
View File
@@ -0,0 +1,363 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* AppArmor security module
*
* This file contains AppArmor lib definitions
*
* 2017 Canonical Ltd.
*/
#ifndef __AA_LIB_H
#define __AA_LIB_H
#include <linux/slab.h>
#include <linux/fs.h>
#include <linux/lsm_hooks.h>
#include "match.h"
extern struct aa_dfa *stacksplitdfa;
/*
* split individual debug cases out in preparation for finer grained
* debug controls in the future.
*/
#define dbg_printk(__fmt, __args...) pr_debug(__fmt, ##__args)
#define DEBUG_NONE 0
#define DEBUG_LABEL_ABS_ROOT 1
#define DEBUG_LABEL 2
#define DEBUG_DOMAIN 4
#define DEBUG_POLICY 8
#define DEBUG_INTERFACE 0x10
#define DEBUG_UNPACK 0x20
#define DEBUG_TAGS 0x40
#define DEBUG_ALL 0x7f /* update if new DEBUG_X added */
#define DEBUG_PARSE_ERROR (-1)
#define DEBUG_ON (aa_g_debug != DEBUG_NONE)
#define DEBUG_ABS_ROOT (aa_g_debug & DEBUG_LABEL_ABS_ROOT)
#define AA_DEBUG(opt, fmt, args...) \
do { \
if (aa_g_debug & opt) \
pr_warn_ratelimited("%s: " fmt, __func__, ##args); \
} while (0)
#define AA_DEBUG_LABEL(LAB, X, fmt, args...) \
do { \
if ((LAB)->flags & FLAG_DEBUG1) \
AA_DEBUG(X, fmt, ##args); \
} while (0)
#define AA_DEBUG_PROFILE(PROF, X, fmt...) AA_DEBUG_LABEL(&(PROF)->label, X, ##fmt)
#define AA_WARN(X) WARN((X), "APPARMOR WARN %s: %s\n", __func__, #X)
#define AA_BUG(X, args...) \
do { \
_Pragma("GCC diagnostic ignored \"-Wformat-zero-length\""); \
AA_BUG_FMT((X), "" args); \
_Pragma("GCC diagnostic warning \"-Wformat-zero-length\""); \
} while (0)
#ifdef CONFIG_SECURITY_APPARMOR_DEBUG_ASSERTS
#define AA_BUG_FMT(X, fmt, args...) \
WARN((X), "AppArmor WARN %s: (" #X "): " fmt, __func__, ##args)
#else
#define AA_BUG_FMT(X, fmt, args...) \
do { \
BUILD_BUG_ON_INVALID(X); \
no_printk(fmt, ##args); \
} while (0)
#endif
int aa_parse_debug_params(const char *str);
int aa_print_debug_params(char *buffer);
#define AA_ERROR(fmt, args...) \
pr_err_ratelimited("AppArmor: " fmt, ##args)
/* Flag indicating whether initialization completed */
extern int apparmor_initialized;
/* semantic split of scope and view */
#define aa_in_scope(SUBJ, OBJ) \
aa_ns_visible(SUBJ, OBJ, false)
#define aa_in_view(SUBJ, OBJ) \
aa_ns_visible(SUBJ, OBJ, true)
#define label_for_each_in_scope(I, NS, L, P) \
label_for_each_in_ns(I, NS, L, P)
#define fn_for_each_in_scope(L, P, FN) \
fn_for_each_in_ns(L, P, FN)
/* fn's in lib */
const char *skipn_spaces(const char *str, size_t n);
const char *aa_splitn_fqname(const char *fqname, size_t n, const char **ns_name,
size_t *ns_len);
void aa_info_message(const char *str);
/* Security blob offsets */
extern struct lsm_blob_sizes apparmor_blob_sizes;
enum reftype {
REF_NS,
REF_PROXY,
REF_RAWDATA,
};
/* common reference count used by data the shows up in aafs */
struct aa_common_ref {
struct kref count;
enum reftype reftype;
};
/**
* aa_strneq - compare null terminated @str to a non null terminated substring
* @str: a null terminated string
* @sub: a substring, not necessarily null terminated
* @len: length of @sub to compare
*
* The @str string must be full consumed for this to be considered a match
*/
static inline bool aa_strneq(const char *str, const char *sub, int len)
{
return !strncmp(str, sub, len) && !str[len];
}
/**
* aa_dfa_null_transition - step to next state after null character
* @dfa: the dfa to match against
* @start: the state of the dfa to start matching in
*
* aa_dfa_null_transition transitions to the next state after a null
* 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,
aa_state_t start)
{
/* the null transition only needs the string's null terminator byte */
return aa_dfa_next(dfa, start, 0);
}
static inline bool path_mediated_fs(struct dentry *dentry)
{
return !(dentry->d_sb->s_flags & SB_NOUSER);
}
struct aa_str_table_ent {
int count;
int size;
char *strs;
};
struct aa_str_table {
int size;
struct aa_str_table_ent *table;
};
bool aa_resize_str_table(struct aa_str_table *t, int newsize, gfp_t gfp);
void aa_destroy_str_table(struct aa_str_table *table);
struct counted_str {
struct kref count;
char name[];
};
#define str_to_counted(str) \
((struct counted_str *)(str - offsetof(struct counted_str, name)))
#define __counted /* atm just a notation */
void aa_str_kref(struct kref *kref);
char *aa_str_alloc(int size, gfp_t gfp);
static inline __counted char *aa_get_str(__counted char *str)
{
if (str)
kref_get(&(str_to_counted(str)->count));
return str;
}
static inline void aa_put_str(__counted char *str)
{
if (str)
kref_put(&str_to_counted(str)->count, aa_str_kref);
}
/* struct aa_policy - common part of both namespaces and profiles
* @name: name of the object
* @hname - The hierarchical name
* @list: list policy object is on
* @profiles: head of the profiles list contained in the object
*/
struct aa_policy {
const char *name;
__counted char *hname;
struct list_head list;
struct list_head profiles;
};
/**
* basename - find the last component of an hname
* @hname: hname to find the base profile name component of (NOT NULL)
*
* Returns: the tail (base profile name) name component of an hname
*/
static inline const char *basename(const char *hname)
{
char *split;
hname = strim((char *)hname);
for (split = strstr(hname, "//"); split; split = strstr(hname, "//"))
hname = split + 2;
return hname;
}
/**
* __policy_find - find a policy by @name on a policy list
* @head: list to search (NOT NULL)
* @name: name to search for (NOT NULL)
*
* Requires: rcu_read_lock be held
*
* Returns: unrefcounted policy that match @name or NULL if not found
*/
static inline struct aa_policy *__policy_find(struct list_head *head,
const char *name)
{
struct aa_policy *policy;
list_for_each_entry_rcu(policy, head, list) {
if (!strcmp(policy->name, name))
return policy;
}
return NULL;
}
/**
* __policy_strn_find - find a policy that's name matches @len chars of @str
* @head: list to search (NOT NULL)
* @str: string to search for (NOT NULL)
* @len: length of match required
*
* Requires: rcu_read_lock be held
*
* Returns: unrefcounted policy that match @str or NULL if not found
*
* if @len == strlen(@strlen) then this is equiv to __policy_find
* other wise it allows searching for policy by a partial match of name
*/
static inline struct aa_policy *__policy_strn_find(struct list_head *head,
const char *str, int len)
{
struct aa_policy *policy;
list_for_each_entry_rcu(policy, head, list) {
if (aa_strneq(policy->name, str, len))
return policy;
}
return NULL;
}
bool aa_policy_init(struct aa_policy *policy, const char *prefix,
const char *name, gfp_t gfp);
void aa_policy_destroy(struct aa_policy *policy);
/*
* fn_label_build - abstract out the build of a label transition
* @L: label the transition is being computed for
* @P: profile parameter derived from L by this macro, can be passed to FN
* @GFP: memory allocation type to use
* @FN: fn to call for each profile transition. @P is set to the profile
*
* Returns: new label on success
* NULL if all callbacks decline to specify a transition
* ERR_PTR if build @FN fails
*
* @FN must return a label or ERR_PTR on failure.
*/
#define fn_label_build(L, P, GFP, FN) \
({ \
__label__ __do_cleanup, __done; \
struct aa_label *__new_ = NULL; \
\
if ((L)->size > 1) { \
/* TODO: add cache of transitions already done */ \
struct label_it __i; \
int __j, __k, __count; \
DEFINE_VEC(label, __lvec); \
DEFINE_VEC(profile, __pvec); \
if (vec_setup(label, __lvec, (L)->size, (GFP))) { \
__new_ = ERR_PTR(-ENOMEM); \
goto __done; \
} \
__j = 0; \
label_for_each(__i, (L), (P)) { \
__new_ = (FN); \
if (!__new_) \
continue; \
if (IS_ERR(__new_)) \
goto __do_cleanup; \
__lvec[__j++] = __new_; \
} \
if (__j == 0) \
/* no components adding to build */ \
goto __do_cleanup; \
for (__j = __count = 0; __j < (L)->size; __j++) \
__count += __lvec[__j]->size; \
if (!vec_setup(profile, __pvec, __count, (GFP))) { \
for (__j = __k = 0; __j < (L)->size; __j++) { \
label_for_each(__i, __lvec[__j], (P)) \
__pvec[__k++] = aa_get_profile(P); \
} \
__count -= aa_vec_unique(__pvec, __count, 0); \
if (__count > 1) { \
__new_ = aa_vec_find_or_create_label(__pvec,\
__count, (GFP)); \
if (!__new_) \
__new_ = ERR_PTR(-ENOMEM); \
} else \
__new_ = aa_get_label(&__pvec[0]->label); \
vec_cleanup(profile, __pvec, __count); \
} else \
__new_ = ERR_PTR(-ENOMEM); \
__do_cleanup: \
vec_cleanup(label, __lvec, (L)->size); \
} else { \
(P) = labels_profile(L); \
__new_ = (FN); \
} \
__done: \
if (PTR_ERR(__new_)) \
AA_DEBUG(DEBUG_LABEL, "label build failed\n"); \
(__new_); \
})
#define __fn_build_in_scope(NS, P, NS_FN, OTHER_FN) \
({ \
struct aa_label *__new; \
if ((P)->ns != (NS)) \
__new = (OTHER_FN); \
else \
__new = (NS_FN); \
(__new); \
})
#define fn_label_build_in_scope(L, P, GFP, NS_FN, OTHER_FN) \
({ \
fn_label_build((L), (P), (GFP), \
__fn_build_in_scope(labels_ns(L), (P), (NS_FN), (OTHER_FN))); \
})
#endif /* __AA_LIB_H */
@@ -0,0 +1,193 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* AppArmor security module
*
* This file contains AppArmor policy dfa matching engine definitions.
*
* Copyright (C) 1998-2008 Novell/SUSE
* Copyright 2009-2012 Canonical Ltd.
*/
#ifndef __AA_MATCH_H
#define __AA_MATCH_H
#include <linux/kref.h>
#define DFA_NOMATCH 0
#define DFA_START 1
/*
* The format used for transition tables is based on the GNU flex table
* file format (--tables-file option; see Table File Format in the flex
* info pages and the flex sources for documentation). The magic number
* used in the header is 0x1B5E783D instead of 0xF13C57B1 though, because
* new tables have been defined and others YY_ID_CHK (check) and YY_ID_DEF
* (default) tables are used slightly differently (see the apparmor-parser
* package).
*
*
* The data in the packed dfa is stored in network byte order, and the tables
* are arranged for flexibility. We convert the table data to host native
* byte order.
*
* The dfa begins with a table set header, and is followed by the actual
* tables.
*/
#define YYTH_MAGIC 0x1B5E783D
#define YYTH_FLAG_DIFF_ENCODE 1
#define YYTH_FLAG_OOB_TRANS 2
#define YYTH_FLAGS (YYTH_FLAG_DIFF_ENCODE | YYTH_FLAG_OOB_TRANS)
#define MAX_OOB_SUPPORTED 1
struct table_set_header {
u32 th_magic; /* YYTH_MAGIC */
u32 th_hsize;
u32 th_ssize;
u16 th_flags;
char th_version[];
};
/* The YYTD_ID are one less than flex table mappings. The flex id
* has 1 subtracted at table load time, this allows us to directly use the
* ID's as indexes.
*/
#define YYTD_ID_ACCEPT 0
#define YYTD_ID_BASE 1
#define YYTD_ID_CHK 2
#define YYTD_ID_DEF 3
#define YYTD_ID_EC 4
#define YYTD_ID_META 5
#define YYTD_ID_ACCEPT2 6
#define YYTD_ID_NXT 7
#define YYTD_ID_TSIZE 8
#define YYTD_ID_MAX 8
#define YYTD_DATA8 1
#define YYTD_DATA16 2
#define YYTD_DATA32 4
#define YYTD_DATA64 8
/* ACCEPT & ACCEPT2 tables gets 6 dedicated flags, YYTD_DATAX define the
* first flags
*/
#define ACCEPT1_FLAGS(X) ((X) & 0x3f)
#define ACCEPT2_FLAGS(X) ACCEPT1_FLAGS((X) >> YYTD_ID_ACCEPT2)
#define TO_ACCEPT1_FLAG(X) ACCEPT1_FLAGS(X)
#define TO_ACCEPT2_FLAG(X) (ACCEPT1_FLAGS(X) << YYTD_ID_ACCEPT2)
#define DFA_FLAG_VERIFY_STATES 0x1000
struct table_header {
u16 td_id;
u16 td_flags;
u32 td_hilen;
u32 td_lolen;
char td_data[];
};
#define TABLE_DATAU16(TABLE) ((u16 *)((TABLE)->td_data))
#define TABLE_DATAU32(TABLE) ((u32 *)((TABLE)->td_data))
#define DEFAULT_TABLE(DFA) ((u32 *)((DFA)->tables[YYTD_ID_DEF]->td_data))
#define BASE_TABLE(DFA) ((u32 *)((DFA)->tables[YYTD_ID_BASE]->td_data))
#define NEXT_TABLE(DFA) ((u32 *)((DFA)->tables[YYTD_ID_NXT]->td_data))
#define CHECK_TABLE(DFA) ((u32 *)((DFA)->tables[YYTD_ID_CHK]->td_data))
#define EQUIV_TABLE(DFA) ((u8 *)((DFA)->tables[YYTD_ID_EC]->td_data))
#define ACCEPT_TABLE(DFA) ((u32 *)((DFA)->tables[YYTD_ID_ACCEPT]->td_data))
#define ACCEPT_TABLE2(DFA) ((u32 *)((DFA)->tables[YYTD_ID_ACCEPT2]->td_data))
struct aa_dfa {
struct kref count;
u16 flags;
u32 max_oob;
struct table_header *tables[YYTD_ID_TSIZE];
};
#define UNPACK_ARRAY(TABLE, BLOB, LEN, TTYPE, BTYPE, NTOHX) \
do { \
typeof(LEN) __i; \
TTYPE *__t = (TTYPE *) TABLE; \
BTYPE *__b = (BTYPE *) BLOB; \
BUILD_BUG_ON(sizeof(TTYPE) != sizeof(BTYPE)); \
if (IS_ENABLED(CONFIG_CPU_BIG_ENDIAN)) \
memcpy(__t, __b, (LEN) * sizeof(BTYPE)); \
else /* copy & convert from big-endian */ \
for (__i = 0; __i < LEN; __i++) { \
__t[__i] = NTOHX(&__b[__i]); \
} \
} while (0)
static inline size_t table_size(size_t len, size_t el_size)
{
return ALIGN(sizeof(struct table_header) + len * el_size, 8);
}
#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,
const char *str, int len);
aa_state_t aa_dfa_match(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,
const char *str, const char **retpos);
aa_state_t aa_dfa_matchn_until(struct aa_dfa *dfa, aa_state_t start,
const char *str, int n, const char **retpos);
void aa_dfa_free_kref(struct kref *kref);
/* This needs to be a power of 2 */
#define WB_HISTORY_SIZE 32
struct match_workbuf {
unsigned int pos;
unsigned int len;
aa_state_t history[WB_HISTORY_SIZE];
};
#define DEFINE_MATCH_WB(N) \
struct match_workbuf N = { \
.pos = 0, \
.len = 0, \
}
aa_state_t aa_dfa_leftmatch(struct aa_dfa *dfa, aa_state_t start,
const char *str, unsigned int *count);
/**
* aa_get_dfa - increment refcount on dfa @p
* @dfa: dfa (MAYBE NULL)
*
* Returns: pointer to @dfa if @dfa is NULL will return NULL
* Requires: @dfa must be held with valid refcount when called
*/
static inline struct aa_dfa *aa_get_dfa(struct aa_dfa *dfa)
{
if (dfa)
kref_get(&(dfa->count));
return dfa;
}
/**
* aa_put_dfa - put a dfa refcount
* @dfa: dfa to put refcount (MAYBE NULL)
*
* Requires: if @dfa != NULL that a valid refcount be held
*/
static inline void aa_put_dfa(struct aa_dfa *dfa)
{
if (dfa)
kref_put(&dfa->count, aa_dfa_free_kref);
}
#define MATCH_FLAG_DIFF_ENCODE 0x80000000
#define MARK_DIFF_ENCODE 0x40000000
#define MATCH_FLAG_OOB_TRANSITION 0x20000000
#define MARK_DIFF_ENCODE_VERIFIED 0x10000000
#define MATCH_FLAGS_MASK 0xff000000
#define MATCH_FLAGS_VALID (MATCH_FLAG_DIFF_ENCODE | MATCH_FLAG_OOB_TRANSITION)
#define MATCH_FLAGS_INVALID (MATCH_FLAGS_MASK & ~MATCH_FLAGS_VALID)
#endif /* __AA_MATCH_H */
@@ -0,0 +1,60 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* AppArmor security module
*
* This file contains AppArmor file mediation function definitions.
*
* Copyright 2017 Canonical Ltd.
*/
#ifndef __AA_MOUNT_H
#define __AA_MOUNT_H
#include <linux/fs.h>
#include <linux/path.h>
#include "domain.h"
#include "policy.h"
/* mount perms */
#define AA_MAY_PIVOTROOT 0x01
#define AA_MAY_MOUNT 0x02
#define AA_MAY_UMOUNT 0x04
#define AA_AUDIT_DATA 0x40
#define AA_MNT_CONT_MATCH 0x40
#define AA_MS_IGNORE_MASK (MS_KERNMOUNT | MS_NOSEC | MS_ACTIVE | MS_BORN)
int aa_remount(const struct cred *subj_cred,
struct aa_label *label, const struct path *path,
unsigned long flags, void *data);
int aa_bind_mount(const struct cred *subj_cred,
struct aa_label *label, const struct path *path,
const char *old_name, unsigned long flags);
int aa_mount_change_type(const struct cred *subj_cred,
struct aa_label *label, const struct path *path,
unsigned long flags);
int aa_move_mount_old(const struct cred *subj_cred,
struct aa_label *label, const struct path *path,
const char *old_name);
int aa_move_mount(const struct cred *subj_cred,
struct aa_label *label, const struct path *from_path,
const struct path *to_path);
int aa_new_mount(const struct cred *subj_cred,
struct aa_label *label, const char *dev_name,
const struct path *path, const char *type, unsigned long flags,
void *data);
int aa_umount(const struct cred *subj_cred,
struct aa_label *label, struct vfsmount *mnt, int flags);
int aa_pivotroot(const struct cred *subj_cred,
struct aa_label *label, const struct path *old_path,
const struct path *new_path);
#endif /* __AA_MOUNT_H */
+119
View File
@@ -0,0 +1,119 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* AppArmor security module
*
* This file contains AppArmor network mediation definitions.
*
* Copyright (C) 1998-2008 Novell/SUSE
* Copyright 2009-2017 Canonical Ltd.
*/
#ifndef __AA_NET_H
#define __AA_NET_H
#include <net/sock.h>
#include <linux/path.h>
#include "apparmorfs.h"
#include "label.h"
#include "perms.h"
#include "policy.h"
#define AA_MAY_SEND AA_MAY_WRITE
#define AA_MAY_RECEIVE AA_MAY_READ
#define AA_MAY_SHUTDOWN AA_MAY_DELETE
#define AA_MAY_CONNECT AA_MAY_OPEN
#define AA_MAY_ACCEPT 0x00100000
#define AA_MAY_BIND 0x00200000
#define AA_MAY_LISTEN 0x00400000
#define AA_MAY_SETOPT 0x01000000
#define AA_MAY_GETOPT 0x02000000
#define NET_PERMS_MASK (AA_MAY_SEND | AA_MAY_RECEIVE | AA_MAY_CREATE | \
AA_MAY_SHUTDOWN | AA_MAY_BIND | AA_MAY_LISTEN | \
AA_MAY_CONNECT | AA_MAY_ACCEPT | AA_MAY_SETATTR | \
AA_MAY_GETATTR | AA_MAY_SETOPT | AA_MAY_GETOPT)
#define NET_FS_PERMS (AA_MAY_SEND | AA_MAY_RECEIVE | AA_MAY_CREATE | \
AA_MAY_SHUTDOWN | AA_MAY_CONNECT | AA_MAY_RENAME |\
AA_MAY_SETATTR | AA_MAY_GETATTR | AA_MAY_CHMOD | \
AA_MAY_CHOWN | AA_MAY_CHGRP | AA_MAY_LOCK | \
AA_MAY_MPROT)
#define NET_PEER_MASK (AA_MAY_SEND | AA_MAY_RECEIVE | AA_MAY_CONNECT | \
AA_MAY_ACCEPT)
struct aa_sk_ctx {
struct aa_label __rcu *label;
struct aa_label __rcu *peer;
struct aa_label __rcu *peer_lastupdate; /* ptr cmp only, no deref */
};
static inline struct aa_sk_ctx *aa_sock(const struct sock *sk)
{
return sk->sk_security + apparmor_blob_sizes.lbs_sock;
}
#define DEFINE_AUDIT_NET(NAME, OP, CRED, SK, F, T, P) \
struct lsm_network_audit NAME ## _net = { .sk = (SK), \
.family = (F)}; \
DEFINE_AUDIT_DATA(NAME, \
((SK) && (F) != AF_UNIX) ? LSM_AUDIT_DATA_NET : \
LSM_AUDIT_DATA_NONE, \
AA_CLASS_NET, \
OP); \
NAME.common.u.net = &(NAME ## _net); \
NAME.subj_cred = (CRED); \
NAME.net.type = (T); \
NAME.net.protocol = (P)
#define DEFINE_AUDIT_SK(NAME, OP, CRED, SK) \
DEFINE_AUDIT_NET(NAME, OP, CRED, SK, (SK)->sk_family, (SK)->sk_type, \
(SK)->sk_protocol)
struct aa_secmark {
u8 audit;
u8 deny;
u32 secid;
char *label;
};
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,
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,
u32 request, u16 af, int type, int protocol,
struct aa_perms **p, const char **info);
void audit_net_cb(struct audit_buffer *ab, void *va);
int aa_profile_af_perm(struct aa_profile *profile,
struct apparmor_audit_data *ad,
u32 request, u16 family, int type, int protocol);
int aa_af_perm(const struct cred *subj_cred, struct aa_label *label,
const char *op, u32 request, u16 family,
int type, int protocol);
static inline int aa_profile_af_sk_perm(struct aa_profile *profile,
struct apparmor_audit_data *ad,
u32 request,
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_sock_file_perm(const struct cred *subj_cred, struct aa_label *label,
const char *op, u32 request,
struct file *file);
int apparmor_secmark_check(struct aa_label *label, char *op, u32 request,
u32 secid, const struct sock *sk);
#endif /* __AA_NET_H */
@@ -0,0 +1,33 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* AppArmor security module
*
* This file contains AppArmor basic path manipulation function definitions.
*
* Copyright (C) 1998-2008 Novell/SUSE
* Copyright 2009-2010 Canonical Ltd.
*/
#ifndef __AA_PATH_H
#define __AA_PATH_H
enum path_flags {
PATH_IS_DIR = 0x1, /* path is a directory */
PATH_SOCK_COND = 0x2,
PATH_CONNECT_PATH = 0x4, /* connect disconnected paths to / */
PATH_CHROOT_REL = 0x8, /* do path lookup relative to chroot */
PATH_CHROOT_NSCONNECT = 0x10, /* connect paths that are at ns root */
PATH_DELEGATE_DELETED = 0x10000, /* delegate deleted files */
PATH_MEDIATE_DELETED = 0x20000, /* mediate deleted paths */
};
int aa_path_name(const struct path *path, int flags, char *buffer,
const char **name, const char **info,
const char *disconnected);
#define IN_ATOMIC true
char *aa_get_buffer(bool in_atomic);
void aa_put_buffer(char *buf);
#endif /* __AA_PATH_H */
@@ -0,0 +1,219 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* AppArmor security module
*
* This file contains AppArmor basic permission sets definitions.
*
* Copyright 2017 Canonical Ltd.
*/
#ifndef __AA_PERM_H
#define __AA_PERM_H
#include <linux/fs.h>
#include "label.h"
#define AA_MAY_EXEC MAY_EXEC
#define AA_MAY_WRITE MAY_WRITE
#define AA_MAY_READ MAY_READ
#define AA_MAY_APPEND MAY_APPEND
#define AA_MAY_CREATE 0x0010
#define AA_MAY_DELETE 0x0020
#define AA_MAY_OPEN 0x0040
#define AA_MAY_RENAME 0x0080 /* pair */
#define AA_MAY_SETATTR 0x0100 /* meta write */
#define AA_MAY_GETATTR 0x0200 /* meta read */
#define AA_MAY_SETCRED 0x0400 /* security cred/attr */
#define AA_MAY_GETCRED 0x0800
#define AA_MAY_CHMOD 0x1000 /* pair */
#define AA_MAY_CHOWN 0x2000 /* pair */
#define AA_MAY_CHGRP 0x4000 /* pair */
#define AA_MAY_LOCK 0x8000 /* LINK_SUBSET overlaid */
#define AA_EXEC_MMAP 0x00010000
#define AA_MAY_MPROT 0x00020000 /* extend conditions */
#define AA_MAY_LINK 0x00040000 /* pair */
#define AA_MAY_SNAPSHOT 0x00080000 /* pair */
#define AA_MAY_DELEGATE
#define AA_CONT_MATCH 0x08000000
#define AA_MAY_STACK 0x10000000
#define AA_MAY_ONEXEC 0x20000000 /* either stack or change_profile */
#define AA_MAY_CHANGE_PROFILE 0x40000000
#define AA_MAY_CHANGEHAT 0x80000000
#define AA_LINK_SUBSET AA_MAY_LOCK /* overlaid */
#define AA_MAY_CREATE_SQPOLL AA_MAY_CREATE
#define AA_MAY_OVERRIDE_CRED AA_MAY_APPEND
#define AA_URING_PERM_MASK (AA_MAY_OVERRIDE_CRED | AA_MAY_CREATE_SQPOLL)
#define PERMS_CHRS_MASK (MAY_READ | MAY_WRITE | AA_MAY_CREATE | \
AA_MAY_DELETE | AA_MAY_LINK | AA_MAY_LOCK | \
AA_MAY_EXEC | AA_EXEC_MMAP | AA_MAY_APPEND)
#define PERMS_NAMES_MASK (PERMS_CHRS_MASK | AA_MAY_OPEN | AA_MAY_RENAME | \
AA_MAY_SETATTR | AA_MAY_GETATTR | AA_MAY_SETCRED | \
AA_MAY_GETCRED | AA_MAY_CHMOD | AA_MAY_CHOWN | \
AA_MAY_CHGRP | AA_MAY_MPROT | AA_MAY_SNAPSHOT | \
AA_MAY_STACK | AA_MAY_ONEXEC | \
AA_MAY_CHANGE_PROFILE | AA_MAY_CHANGEHAT)
extern const char aa_file_perm_chrs[];
extern const char *aa_file_perm_names[];
struct aa_perms {
u32 allow;
u32 deny; /* explicit deny, or conflict if allow also set */
u32 subtree; /* allow perm on full subtree only when allow is set */
u32 cond; /* set only when ~allow and ~deny */
u32 kill; /* set only when ~allow | deny */
u32 complain; /* accumulates only used when ~allow & ~deny */
u32 prompt; /* accumulates only used when ~allow & ~deny */
u32 audit; /* set only when allow is set */
u32 quiet; /* set only when ~allow | deny */
u32 hide; /* set only when ~allow | deny */
u32 xindex;
u32 tag; /* tag string index, if present */
u32 label; /* label string index, if present */
};
/*
* Indexes are broken into a 24 bit index and 8 bit flag.
* For the index to be valid there must be a value in the flag
*/
#define AA_INDEX_MASK 0x00ffffff
#define AA_INDEX_FLAG_MASK 0xff000000
#define AA_INDEX_NONE 0
#define ALL_PERMS_MASK 0xffffffff
extern struct aa_perms nullperms;
extern struct aa_perms allperms;
/**
* aa_perms_accum_raw - accumulate perms with out masking off overlapping perms
* @accum: perms struct to accumulate into
* @addend: perms struct to add to @accum
*/
static inline void aa_perms_accum_raw(struct aa_perms *accum,
struct aa_perms *addend)
{
accum->deny |= addend->deny;
accum->allow &= addend->allow & ~addend->deny;
accum->audit |= addend->audit & addend->allow;
accum->quiet &= addend->quiet & ~addend->allow;
accum->kill |= addend->kill & ~addend->allow;
accum->complain |= addend->complain & ~addend->allow & ~addend->deny;
accum->cond |= addend->cond & ~addend->allow & ~addend->deny;
accum->hide &= addend->hide & ~addend->allow;
accum->prompt |= addend->prompt & ~addend->allow & ~addend->deny;
accum->subtree |= addend->subtree & ~addend->deny;
if (!accum->xindex)
accum->xindex = addend->xindex;
if (!accum->tag)
accum->tag = addend->tag;
if (!accum->label)
accum->label = addend->label;
}
/**
* aa_perms_accum - accumulate perms, masking off overlapping perms
* @accum: perms struct to accumulate into
* @addend: perms struct to add to @accum
*/
static inline void aa_perms_accum(struct aa_perms *accum,
struct aa_perms *addend)
{
accum->deny |= addend->deny;
accum->allow &= addend->allow & ~accum->deny;
accum->audit |= addend->audit & accum->allow;
accum->quiet &= addend->quiet & ~accum->allow;
accum->kill |= addend->kill & ~accum->allow;
accum->complain |= addend->complain & ~accum->allow & ~accum->deny;
accum->cond |= addend->cond & ~accum->allow & ~accum->deny;
accum->hide &= addend->hide & ~accum->allow;
accum->prompt |= addend->prompt & ~accum->allow & ~accum->deny;
accum->subtree &= addend->subtree & ~accum->deny;
if (!accum->xindex)
accum->xindex = addend->xindex;
if (!accum->tag)
accum->tag = addend->tag;
if (!accum->label)
accum->label = addend->label;
}
#define xcheck(FN1, FN2) \
({ \
int e, error = FN1; \
e = FN2; \
if (e) \
error = e; \
error; \
})
/*
* TODO: update for labels pointing to labels instead of profiles
* TODO: optimize the walk, currently does subwalk of L2 for each P in L1
* gah this doesn't allow for label compound check!!!!
*/
#define xcheck_ns_profile_profile(P1, P2, FN, args...) \
({ \
int ____e = 0; \
if (P1->ns == P2->ns) \
____e = FN((P1), (P2), args); \
(____e); \
})
#define xcheck_ns_profile_label(P, L, FN, args...) \
({ \
struct aa_profile *__p2; \
fn_for_each((L), __p2, \
xcheck_ns_profile_profile((P), __p2, (FN), args)); \
})
#define xcheck_ns_labels(L1, L2, FN, args...) \
({ \
struct aa_profile *__p1; \
fn_for_each((L1), __p1, FN(__p1, (L2), args)); \
})
/* Do the cross check but applying FN at the profiles level */
#define xcheck_labels_profiles(L1, L2, FN, args...) \
xcheck_ns_labels((L1), (L2), xcheck_ns_profile_label, (FN), args)
#define xcheck_labels(L1, L2, P, FN1, FN2) \
xcheck(fn_for_each((L1), (P), (FN1)), fn_for_each((L2), (P), (FN2)))
extern struct aa_perms default_perms;
void aa_perm_mask_to_str(char *str, size_t str_size, const char *chrs,
u32 mask);
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,
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,
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,
u32 request, struct apparmor_audit_data *ad,
void (*cb)(struct audit_buffer *, void *));
#endif /* __AA_PERM_H */
@@ -0,0 +1,450 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* AppArmor security module
*
* This file contains AppArmor policy definitions.
*
* Copyright (C) 1998-2008 Novell/SUSE
* Copyright 2009-2010 Canonical Ltd.
*/
#ifndef __AA_POLICY_H
#define __AA_POLICY_H
#include <linux/capability.h>
#include <linux/cred.h>
#include <linux/kref.h>
#include <linux/rhashtable.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <linux/socket.h>
#include "apparmor.h"
#include "audit.h"
#include "capability.h"
#include "domain.h"
#include "file.h"
#include "lib.h"
#include "label.h"
#include "net.h"
#include "perms.h"
#include "resource.h"
struct aa_ns;
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)) || \
((_profile)->mode == (_mode)))
#define COMPLAIN_MODE(_profile) PROFILE_MODE((_profile), APPARMOR_COMPLAIN)
#define USER_MODE(_profile) PROFILE_MODE((_profile), APPARMOR_USER)
#define KILL_MODE(_profile) PROFILE_MODE((_profile), APPARMOR_KILL)
#define PROFILE_IS_HAT(_profile) ((_profile)->label.flags & FLAG_HAT)
#define CHECK_DEBUG1(_profile) ((_profile)->label.flags & FLAG_DEBUG1)
#define CHECK_DEBUG2(_profile) ((_profile)->label.flags & FLAG_DEBUG2)
#define profile_is_stale(_profile) (label_is_stale(&(_profile)->label))
#define on_list_rcu(X) (!list_empty(X) && (X)->prev != LIST_POISON2)
/* flags in the dfa accept2 table */
enum dfa_accept_flags {
ACCEPT_FLAG_OWNER = 1,
};
/*
* FIXME: currently need a clean way to replace and remove profiles as a
* set. It should be done at the namespace level.
* Either, with a set of profiles loaded at the namespace level or via
* a mark and remove marked interface.
*/
enum profile_mode {
APPARMOR_ENFORCE, /* enforce access rules */
APPARMOR_COMPLAIN, /* allow and log access violations */
APPARMOR_KILL, /* kill task on access violation */
APPARMOR_UNCONFINED, /* profile set to unconfined */
APPARMOR_USER, /* modified complain mode to userspace */
};
struct aa_tags_header {
u32 mask; /* bit mask matching permissions */
u32 count; /* number of strings per entry */
u32 size; /* size of all strings covered by count */
u32 tags; /* index into string table */
};
struct aa_tags_struct {
struct {
u32 size; /* number of entries in tagsets */
u32 *table; /* indexes into headers & strs */
} sets;
struct {
u32 size; /* number of headers == num of strs */
struct aa_tags_header *table;
} hdrs;
struct aa_str_table strs;
};
/* struct aa_policydb - match engine for a policy
* @count: refcount for the pdb
* @dfa: dfa pattern match
* @perms: table of permissions
* @size: number of entries in @perms
* @trans: table of strings, index by x
* @tags: table of tags that perms->tag indexes
* @start:_states to start in for each class
* start: set of start states for the different classes of data
*/
struct aa_policydb {
struct kref count;
struct aa_dfa *dfa;
struct {
struct aa_perms *perms;
u32 size;
};
struct aa_str_table trans;
struct aa_tags_struct tags;
aa_state_t start[AA_CLASS_LAST + 1];
};
extern struct aa_policydb *nullpdb;
void aa_destroy_tags(struct aa_tags_struct *tags);
struct aa_policydb *aa_alloc_pdb(gfp_t gfp);
void aa_pdb_free_kref(struct kref *kref);
/**
* aa_get_pdb - increment refcount on @pdb
* @pdb: policydb (MAYBE NULL)
*
* Returns: pointer to @pdb if @pdb is NULL will return NULL
* Requires: @pdb must be held with valid refcount when called
*/
static inline struct aa_policydb *aa_get_pdb(struct aa_policydb *pdb)
{
if (pdb)
kref_get(&(pdb->count));
return pdb;
}
/**
* aa_put_pdb - put a pdb refcount
* @pdb: pdb to put refcount (MAYBE NULL)
*
* Requires: if @pdb != NULL that a valid refcount be held
*/
static inline void aa_put_pdb(struct aa_policydb *pdb)
{
if (pdb)
kref_put(&pdb->count, aa_pdb_free_kref);
}
/* lookup perm that doesn't have and object conditional */
static inline struct aa_perms *aa_lookup_perms(struct aa_policydb *policy,
aa_state_t state)
{
unsigned int index = ACCEPT_TABLE(policy->dfa)[state];
if (!(policy->perms))
return &default_perms;
return &(policy->perms[index]);
}
/* struct aa_data - generic data structure
* key: name for retrieving this data
* size: size of data in bytes
* data: binary data
* head: reserved for rhashtable
*/
struct aa_data {
char *key;
u32 size;
char *data;
struct rhash_head head;
};
/* struct aa_ruleset - data covering mediation rules
* @list: list the rule is on
* @size: the memory consumed by this ruleset
* @policy: general match rules governing policy
* @file: The set of rules governing basic file access and domain transitions
* @caps: capabilities for the profile
* @rlimits: rlimits for the profile
* @secmark_count: number of secmark entries
* @secmark: secmark label match info
*/
struct aa_ruleset {
int size;
/* TODO: merge policy and file */
struct aa_policydb *policy;
struct aa_policydb *file;
struct aa_caps caps;
struct aa_rlimit rlimits;
int secmark_count;
struct aa_secmark *secmark;
};
/* struct aa_attachment - data and rules for a profiles attachment
* @list:
* @xmatch_str: human readable attachment string
* @xmatch: optional extended matching for unconfined executables names
* @xmatch_len: xmatch prefix len, used to determine xmatch priority
* @xattr_count: number of xattrs in table
* @xattrs: table of xattrs
*/
struct aa_attachment {
const char *xmatch_str;
struct aa_policydb *xmatch;
unsigned int xmatch_len;
int xattr_count;
char **xattrs;
};
/* struct aa_profile - basic confinement data
* @base - base components of the profile (name, refcount, lists, lock ...)
* @parent: parent of profile
* @ns: namespace the profile is in
* @rename: optional profile name that this profile renamed
*
* @audit: the auditing mode of the profile
* @mode: the enforcement mode of the profile
* @path_flags: flags controlling path generation behavior
* @signal: the signal that should be used when kill is used
* @disconnected: what to prepend if attach_disconnected is specified
* @attach: attachment rules for the profile
* @rules: rules to be enforced
*
* learning_cache: the accesses learned in complain mode
* raw_data: rawdata of the loaded profile policy
* hash: cryptographic hash of the profile
* @dents: dentries for the profiles file entries in apparmorfs
* @dirname: name of the profile dir in apparmorfs
* @dents: set of dentries associated with the profile
* @data: hashtable for free-form policy aa_data
* @label - label this profile is an extension of
* @rules - label with the rule vec on its end
*
* The AppArmor profile contains the basic confinement data. Each profile
* has a name, and exists in a namespace. The @name and @exec_match are
* used to determine profile attachment against unconfined tasks. All other
* attachments are determined by profile X transition rules.
*
* Profiles have a hierarchy where hats and children profiles keep
* a reference to their parent.
*
* Profile names can not begin with a : and can not contain the \0
* character. If a profile name begins with / it will be considered when
* determining profile attachment on "unconfined" tasks.
*/
struct aa_profile {
struct aa_policy base;
struct aa_profile __rcu *parent;
struct aa_ns *ns;
const char *rename;
enum audit_mode audit;
long mode;
u32 path_flags;
int signal;
const char *disconnected;
struct aa_attachment attach;
struct aa_loaddata *rawdata;
unsigned char *hash;
char *dirname;
struct dentry *dents[AAFS_PROF_SIZEOF];
struct rhashtable *data;
int n_rules;
/* special - variable length must be last entry in profile */
struct aa_label label;
};
extern enum profile_mode aa_g_profile_mode;
#define AA_MAY_LOAD_POLICY AA_MAY_APPEND
#define AA_MAY_REPLACE_POLICY AA_MAY_WRITE
#define AA_MAY_REMOVE_POLICY AA_MAY_DELETE
#define profiles_ns(P) ((P)->ns)
#define name_is_shared(A, B) ((A)->hname && (A)->hname == (B)->hname)
struct aa_ruleset *aa_alloc_ruleset(gfp_t gfp);
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);
void aa_free_profile(struct aa_profile *profile);
struct aa_profile *aa_find_child(struct aa_profile *parent, const char *name);
struct aa_profile *aa_lookupn_profile(struct aa_ns *ns, const char *hname,
size_t n);
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);
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);
#define profile_unconfined(X) ((X)->mode == APPARMOR_UNCONFINED)
/**
* aa_get_newest_profile - simple wrapper fn to wrap the label version
* @p: profile (NOT NULL)
*
* Returns refcount to newest version of the profile (maybe @p)
*
* Requires: @p must be held with a valid refcount
*/
static inline struct aa_profile *aa_get_newest_profile(struct aa_profile *p)
{
return labels_profile(aa_get_newest_label(&p->label));
}
static inline aa_state_t RULE_MEDIATES(struct aa_ruleset *rules,
unsigned char class)
{
if (class <= AA_CLASS_LAST)
return rules->policy->start[class];
else
return aa_dfa_match_len(rules->policy->dfa,
rules->policy->start[0], &class, 1);
}
static inline aa_state_t RULE_MEDIATES_v9NET(struct aa_ruleset *rules)
{
return RULE_MEDIATES(rules, AA_CLASS_NETV9);
}
static inline aa_state_t RULE_MEDIATES_NET(struct aa_ruleset *rules)
{
/* can not use RULE_MEDIATE_v9AF here, because AF match fail
* can not be distiguished from class match fail, and we only
* fallback to checking older class on class match failure
*/
aa_state_t state = RULE_MEDIATES(rules, AA_CLASS_NETV9);
/* fallback and check v7/8 if v9 is NOT mediated */
if (!state)
state = RULE_MEDIATES(rules, AA_CLASS_NET);
return state;
}
void aa_compute_profile_mediates(struct aa_profile *profile);
static inline bool profile_mediates(struct aa_profile *profile,
unsigned char class)
{
return label_mediates(&profile->label, class);
}
static inline bool profile_mediates_safe(struct aa_profile *profile,
unsigned char class)
{
return label_mediates_safe(&profile->label, class);
}
/**
* aa_get_profile - increment refcount on profile @p
* @p: profile (MAYBE NULL)
*
* Returns: pointer to @p if @p is NULL will return NULL
* Requires: @p must be held with valid refcount when called
*/
static inline struct aa_profile *aa_get_profile(struct aa_profile *p)
{
if (p)
kref_get(&(p->label.count.count));
return p;
}
/**
* aa_get_profile_not0 - increment refcount on profile @p found via lookup
* @p: profile (MAYBE NULL)
*
* Returns: pointer to @p if @p is NULL will return NULL
* Requires: @p must be held with valid refcount when called
*/
static inline struct aa_profile *aa_get_profile_not0(struct aa_profile *p)
{
if (p && kref_get_unless_zero(&p->label.count.count))
return p;
return NULL;
}
/**
* aa_get_profile_rcu - increment a refcount profile that can be replaced
* @p: pointer to profile that can be replaced (NOT NULL)
*
* Returns: pointer to a refcounted profile.
* else NULL if no profile
*/
static inline struct aa_profile *aa_get_profile_rcu(struct aa_profile __rcu **p)
{
struct aa_profile *c;
rcu_read_lock();
do {
c = rcu_dereference(*p);
} while (c && !kref_get_unless_zero(&c->label.count.count));
rcu_read_unlock();
return c;
}
/**
* aa_put_profile - decrement refcount on profile @p
* @p: profile (MAYBE NULL)
*/
static inline void aa_put_profile(struct aa_profile *p)
{
if (p)
kref_put(&p->label.count.count, aa_label_kref);
}
static inline int AUDIT_MODE(struct aa_profile *profile)
{
if (aa_g_audit != AUDIT_NORMAL)
return aa_g_audit;
return profile->audit;
}
bool aa_policy_view_capable(const struct cred *subj_cred,
struct aa_label *label, struct aa_ns *ns);
bool aa_policy_admin_capable(const struct cred *subj_cred,
struct aa_label *label, struct aa_ns *ns);
int aa_may_manage_policy(const struct cred *subj_cred,
struct aa_label *label, struct aa_ns *ns,
const struct cred *ocred, u32 mask);
bool aa_current_policy_view_capable(struct aa_ns *ns);
bool aa_current_policy_admin_capable(struct aa_ns *ns);
#endif /* __AA_POLICY_H */
@@ -0,0 +1,33 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* AppArmor security module
*
* Code to provide backwards compatibility with older policy versions,
* by converting/mapping older policy formats into the newer internal
* formats.
*
* Copyright 2022 Canonical Ltd.
*/
#ifndef __POLICY_COMPAT_H
#define __POLICY_COMPAT_H
#include "policy.h"
#define K_ABI_MASK 0x3ff
#define FORCE_COMPLAIN_FLAG 0x800
#define VERSION_LT(X, Y) (((X) & K_ABI_MASK) < ((Y) & K_ABI_MASK))
#define VERSION_LE(X, Y) (((X) & K_ABI_MASK) <= ((Y) & K_ABI_MASK))
#define VERSION_GT(X, Y) (((X) & K_ABI_MASK) > ((Y) & K_ABI_MASK))
#define v5 5 /* base version */
#define v6 6 /* per entry policydb mediation check */
#define v7 7
#define v8 8 /* full network masking */
#define v9 9 /* xbits are used as permission bits in policydb */
int aa_compat_map_xmatch(struct aa_policydb *policy);
int aa_compat_map_policy(struct aa_policydb *policy, u32 version);
int aa_compat_map_file(struct aa_policydb *policy);
#endif /* __POLICY_COMPAT_H */
@@ -0,0 +1,153 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* AppArmor security module
*
* This file contains AppArmor policy definitions.
*
* Copyright (C) 1998-2008 Novell/SUSE
* Copyright 2009-2017 Canonical Ltd.
*/
#ifndef __AA_NAMESPACE_H
#define __AA_NAMESPACE_H
#include <linux/kref.h>
#include "apparmor.h"
#include "apparmorfs.h"
#include "label.h"
#include "policy.h"
/* Match max depth of user namespaces */
#define MAX_NS_DEPTH 32
/* struct aa_ns_acct - accounting of profiles in namespace
* @max_size: maximum space allowed for all profiles in namespace
* @max_count: maximum number of profiles that can be in this namespace
* @size: current size of profiles
* @count: current count of profiles (includes null profiles)
*/
struct aa_ns_acct {
int max_size;
int max_count;
int size;
int count;
};
/* struct aa_ns - namespace for a set of profiles
* @base: common policy
* @parent: parent of namespace
* @lock: lock for modifying the object
* @acct: accounting for the namespace
* @unconfined: special unconfined profile for the namespace
* @sub_ns: list of namespaces under the current namespace.
* @uniq_null: uniq value used for null learning profiles
* @uniq_id: a unique id count for the profiles in the namespace
* @level: level of ns within the tree hierarchy
* @dents: dentries for the namespaces file entries in apparmorfs
*
* An aa_ns defines the set profiles that are searched to determine which
* profile to attach to a task. Profiles can not be shared between aa_ns
* and profile names within a namespace are guaranteed to be unique. When
* profiles in separate namespaces have the same name they are NOT considered
* to be equivalent.
*
* Namespaces are hierarchical and only namespaces and profiles below the
* current namespace are visible.
*
* Namespace names must be unique and can not contain the characters :/\0
*/
struct aa_ns {
struct aa_policy base;
struct aa_ns *parent;
struct mutex lock;
struct aa_ns_acct acct;
struct aa_profile *unconfined;
struct list_head sub_ns;
atomic_t uniq_null;
long uniq_id;
int level;
long revision;
wait_queue_head_t wait;
struct aa_labelset labels;
struct list_head rawdata_list;
struct dentry *dents[AAFS_NS_SIZEOF];
};
extern struct aa_label *kernel_t;
extern struct aa_ns *root_ns;
extern const char *aa_hidden_ns_name;
#define ns_unconfined(NS) (&(NS)->unconfined->label)
bool aa_ns_visible(struct aa_ns *curr, struct aa_ns *view, bool subns);
const char *aa_ns_name(struct aa_ns *parent, struct aa_ns *child, bool subns);
void aa_free_ns(struct aa_ns *ns);
int aa_alloc_root_ns(void);
void aa_free_root_ns(void);
struct aa_ns *__aa_lookupn_ns(struct aa_ns *view, const char *hname, size_t n);
struct aa_ns *aa_lookupn_ns(struct aa_ns *view, const char *name, size_t n);
struct aa_ns *__aa_find_or_create_ns(struct aa_ns *parent, const char *name,
struct dentry *dir);
struct aa_ns *aa_prepare_ns(struct aa_ns *root, const char *name);
void __aa_remove_ns(struct aa_ns *ns);
static inline struct aa_profile *aa_deref_parent(struct aa_profile *p)
{
return rcu_dereference_protected(p->parent,
mutex_is_locked(&p->ns->lock));
}
/**
* aa_get_ns - increment references count on @ns
* @ns: namespace to increment reference count of (MAYBE NULL)
*
* Returns: pointer to @ns, if @ns is NULL returns NULL
* Requires: @ns must be held with valid refcount when called
*/
static inline struct aa_ns *aa_get_ns(struct aa_ns *ns)
{
if (ns)
aa_get_profile(ns->unconfined);
return ns;
}
/**
* aa_put_ns - decrement refcount on @ns
* @ns: namespace to put reference of
*
* Decrement reference count of @ns and if no longer in use free it
*/
static inline void aa_put_ns(struct aa_ns *ns)
{
if (ns)
aa_put_profile(ns->unconfined);
}
/**
* __aa_findn_ns - find a namespace on a list by @name
* @head: list to search for namespace on (NOT NULL)
* @name: name of namespace to look for (NOT NULL)
* @n: length of @name
* Returns: unrefcounted namespace
*
* Requires: rcu_read_lock be held
*/
static inline struct aa_ns *__aa_findn_ns(struct list_head *head,
const char *name, size_t n)
{
return (struct aa_ns *)__policy_strn_find(head, name, n);
}
static inline struct aa_ns *__aa_find_ns(struct list_head *head,
const char *name)
{
return __aa_findn_ns(head, name, strlen(name));
}
#endif /* AA_NAMESPACE_H */
@@ -0,0 +1,215 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* AppArmor security module
*
* This file contains AppArmor policy loading interface function definitions.
*
* Copyright (C) 1998-2008 Novell/SUSE
* Copyright 2009-2010 Canonical Ltd.
*/
#ifndef __POLICY_INTERFACE_H
#define __POLICY_INTERFACE_H
#include <linux/list.h>
#include <linux/kref.h>
#include <linux/dcache.h>
#include <linux/workqueue.h>
struct aa_load_ent {
struct list_head list;
struct aa_profile *new;
struct aa_profile *old;
struct aa_profile *rename;
const char *ns_name;
};
void aa_load_ent_free(struct aa_load_ent *ent);
struct aa_load_ent *aa_load_ent_alloc(void);
#define PACKED_FLAG_HAT 1
#define PACKED_FLAG_DEBUG1 2
#define PACKED_FLAG_DEBUG2 4
#define PACKED_MODE_ENFORCE 0
#define PACKED_MODE_COMPLAIN 1
#define PACKED_MODE_KILL 2
#define PACKED_MODE_UNCONFINED 3
#define PACKED_MODE_USER 4
struct aa_ns;
enum {
AAFS_LOADDATA_ABI = 0,
AAFS_LOADDATA_REVISION,
AAFS_LOADDATA_HASH,
AAFS_LOADDATA_DATA,
AAFS_LOADDATA_COMPRESSED_SIZE,
AAFS_LOADDATA_DIR, /* must be last actual entry */
AAFS_LOADDATA_NDENTS /* count of entries */
};
/*
* The AppArmor interface treats data as a type byte followed by the
* actual data. The interface has the notion of a named entry
* which has a name (AA_NAME typecode followed by name string) followed by
* the entries typecode and data. Named types allow for optional
* elements and extensions to be added and tested for without breaking
* backwards compatibility.
*/
enum aa_code {
AA_U8,
AA_U16,
AA_U32,
AA_U64,
AA_NAME, /* same as string except it is items name */
AA_STRING,
AA_BLOB,
AA_STRUCT,
AA_STRUCTEND,
AA_LIST,
AA_LISTEND,
AA_ARRAY,
AA_ARRAYEND,
};
/*
* aa_ext is the read of the buffer containing the serialized profile. The
* data is copied into a kernel buffer in apparmorfs and then handed off to
* the unpack routines.
*/
struct aa_ext {
void *start;
void *end;
void *pos; /* pointer to current position in the buffer */
u32 version;
};
/* struct aa_loaddata - buffer of policy raw_data set
* @count: inode/filesystem refcount - use aa_get_i_loaddata()
* @pcount: profile refcount - use aa_get_profile_loaddata()
* @list: list the loaddata is on
* @work: used to do a delayed cleanup
* @dents: refs to dents created in aafs
* @ns: the namespace this loaddata was loaded into
* @name:
* @size: the size of the data that was loaded
* @compressed_size: the size of the data when it is compressed
* @revision: unique revision count that this data was loaded as
* @abi: the abi number the loaddata uses
* @hash: a hash of the loaddata, used to help dedup data
*
* There is no loaddata ref for being on ns->rawdata_list, so
* @ns->lock must be held when walking the list. Dentries and
* inode opens hold refs on @count; profiles hold refs on @pcount.
* When the last @pcount drops, do_ploaddata_rmfs() removes the
* fs entries and drops the associated @count ref.
*/
struct aa_loaddata {
struct aa_common_ref count;
struct kref pcount;
struct list_head list;
struct work_struct work;
struct dentry *dents[AAFS_LOADDATA_NDENTS];
struct aa_ns *ns;
char *name;
size_t size; /* the original size of the payload */
size_t compressed_size; /* the compressed size of the payload */
long revision; /* the ns policy revision this caused */
int abi;
unsigned char *hash;
/* Pointer to payload. If @compressed_size > 0, then this is the
* compressed version of the payload, else it is the uncompressed
* version (with the size indicated by @size).
*/
char *data;
};
int aa_unpack(struct aa_loaddata *udata, struct list_head *lh, const char **ns);
/**
* aa_get_i_loaddata - get a reference count from a counted data reference
* @data: reference to get a count on
*
* Returns: pointer to reference
* Requires: @data to have a valid reference count on it. It is a bug
* if the race to reap can be encountered when it is used.
*/
static inline struct aa_loaddata *
aa_get_i_loaddata(struct aa_loaddata *data)
{
if (data)
kref_get(&(data->count.count));
return data;
}
/**
* aa_get_profile_loaddata - get a profile reference count on loaddata
* @data: reference to get a count on
*
* Returns: pointer to reference
* Requires: @data to have a valid reference count on it.
*/
static inline struct aa_loaddata *
aa_get_profile_loaddata(struct aa_loaddata *data)
{
if (data)
kref_get(&(data->pcount));
return data;
}
/**
* aa_get_profile_loaddata_not0 - get a profile reference count if not zero
* @data: reference to get a count on
*
* Like aa_get_profile_loaddata(), but safe to call on an entry that may
* be on a list (e.g. ns->rawdata_list) where the last pcount has already
* dropped and the deferred cleanup has not yet run.
*
* Returns: pointer to reference, or %NULL if @data is NULL or its
* profile refcount has already reached zero.
*/
static inline struct aa_loaddata *
aa_get_profile_loaddata_not0(struct aa_loaddata *data)
{
if (data && kref_get_unless_zero(&data->pcount))
return data;
return NULL;
}
void __aa_loaddata_update(struct aa_loaddata *data, long revision);
bool aa_rawdata_eq(struct aa_loaddata *l, struct aa_loaddata *r);
void aa_loaddata_kref(struct kref *kref);
void aa_ploaddata_kref(struct kref *kref);
struct aa_loaddata *aa_loaddata_alloc(size_t size);
static inline void aa_put_i_loaddata(struct aa_loaddata *data)
{
if (data)
kref_put(&data->count.count, aa_loaddata_kref);
}
static inline void aa_put_profile_loaddata(struct aa_loaddata *data)
{
if (data)
kref_put(&data->pcount, aa_ploaddata_kref);
}
#if IS_ENABLED(CONFIG_KUNIT)
bool aa_inbounds(struct aa_ext *e, size_t size);
size_t aa_unpack_u16_chunk(struct aa_ext *e, char **chunk);
bool aa_unpack_X(struct aa_ext *e, enum aa_code code);
bool aa_unpack_nameX(struct aa_ext *e, enum aa_code code, const char *name);
bool aa_unpack_u32(struct aa_ext *e, u32 *data, const char *name);
bool aa_unpack_u64(struct aa_ext *e, u64 *data, const char *name);
bool aa_unpack_array(struct aa_ext *e, const char *name, u16 *size);
size_t aa_unpack_blob(struct aa_ext *e, char **blob, const char *name);
int aa_unpack_str(struct aa_ext *e, const char **string, const char *name);
int aa_unpack_strdup(struct aa_ext *e, char **string, const char *name);
#endif
#endif /* __POLICY_INTERFACE_H */
@@ -0,0 +1,17 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* AppArmor security module
*
* This file contains AppArmor /proc/<pid>/attr/ interface function definitions.
*
* Copyright (C) 1998-2008 Novell/SUSE
* Copyright 2009-2010 Canonical Ltd.
*/
#ifndef __AA_PROCATTR_H
#define __AA_PROCATTR_H
int aa_getprocattr(struct aa_label *label, char **string, bool newline);
int aa_setprocattr_changehat(char *args, size_t size, int flags);
#endif /* __AA_PROCATTR_H */
@@ -0,0 +1,47 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* AppArmor security module
*
* This file contains AppArmor resource limits function definitions.
*
* Copyright (C) 1998-2008 Novell/SUSE
* Copyright 2009-2010 Canonical Ltd.
*/
#ifndef __AA_RESOURCE_H
#define __AA_RESOURCE_H
#include <linux/resource.h>
#include <linux/sched.h>
#include "apparmorfs.h"
struct aa_profile;
/* struct aa_rlimit - rlimit settings for the profile
* @mask: which hard limits to set
* @limits: rlimit values that override task limits
*
* AppArmor rlimits are used to set confined task rlimits. Only the
* limits specified in @mask will be controlled by apparmor.
*/
struct aa_rlimit {
unsigned int mask;
struct rlimit limits[RLIM_NLIMITS];
};
extern struct aa_sfs_entry aa_sfs_entry_rlimit[];
int aa_map_resource(int resource);
int aa_task_setrlimit(const struct cred *subj_cred, struct aa_label *label,
struct task_struct *task,
unsigned int resource, struct rlimit *new_rlim);
void __aa_transition_rlimits(struct aa_label *old, struct aa_label *new);
static inline void aa_free_rlimit_rules(struct aa_rlimit *rlims)
{
/* NOP */
}
#endif /* __AA_RESOURCE_H */
@@ -0,0 +1,37 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* AppArmor security module
*
* This file contains AppArmor security identifier (secid) definitions
*
* Copyright 2009-2018 Canonical Ltd.
*/
#ifndef __AA_SECID_H
#define __AA_SECID_H
#include <linux/slab.h>
#include <linux/types.h>
struct aa_label;
/* secid value that will not be allocated */
#define AA_SECID_INVALID 0
/* secid value that matches any other secid */
#define AA_SECID_WILDCARD 1
/* sysctl to enable displaying mode when converting secid to secctx */
extern int apparmor_display_secid_mode;
struct aa_label *aa_secid_to_label(u32 secid);
int apparmor_secid_to_secctx(u32 secid, struct lsm_context *cp);
int apparmor_lsmprop_to_secctx(struct lsm_prop *prop, struct lsm_context *cp);
int apparmor_secctx_to_secid(const char *secdata, u32 seclen, u32 *secid);
void apparmor_release_secctx(struct lsm_context *cp);
int aa_alloc_secid(struct aa_label *label, gfp_t gfp);
void aa_free_secid(u32 secid);
#endif /* __AA_SECID_H */
@@ -0,0 +1,97 @@
#include <linux/signal.h>
#include "signal.h"
/* provide a mapping of arch signal to internal signal # for mediation
* those that are always an alias SIGCLD for SIGCLHD and SIGPOLL for SIGIO
* map to the same entry those that may/or may not get a separate entry
*/
static const int sig_map[MAXMAPPED_SIG] = {
[0] = MAXMAPPED_SIG, /* existence test */
[SIGHUP] = 1,
[SIGINT] = 2,
[SIGQUIT] = 3,
[SIGILL] = 4,
[SIGTRAP] = 5, /* -, 5, - */
[SIGABRT] = 6, /* SIGIOT: -, 6, - */
[SIGBUS] = 7, /* 10, 7, 10 */
[SIGFPE] = 8,
[SIGKILL] = 9,
[SIGUSR1] = 10, /* 30, 10, 16 */
[SIGSEGV] = 11,
[SIGUSR2] = 12, /* 31, 12, 17 */
[SIGPIPE] = 13,
[SIGALRM] = 14,
[SIGTERM] = 15,
#ifdef SIGSTKFLT
[SIGSTKFLT] = 16, /* -, 16, - */
#endif
[SIGCHLD] = 17, /* 20, 17, 18. SIGCHLD -, -, 18 */
[SIGCONT] = 18, /* 19, 18, 25 */
[SIGSTOP] = 19, /* 17, 19, 23 */
[SIGTSTP] = 20, /* 18, 20, 24 */
[SIGTTIN] = 21, /* 21, 21, 26 */
[SIGTTOU] = 22, /* 22, 22, 27 */
[SIGURG] = 23, /* 16, 23, 21 */
[SIGXCPU] = 24, /* 24, 24, 30 */
[SIGXFSZ] = 25, /* 25, 25, 31 */
[SIGVTALRM] = 26, /* 26, 26, 28 */
[SIGPROF] = 27, /* 27, 27, 29 */
[SIGWINCH] = 28, /* 28, 28, 20 */
[SIGIO] = 29, /* SIGPOLL: 23, 29, 22 */
[SIGPWR] = 30, /* 29, 30, 19. SIGINFO 29, -, - */
#ifdef SIGSYS
[SIGSYS] = 31, /* 12, 31, 12. often SIG LOST/UNUSED */
#endif
#ifdef SIGEMT
[SIGEMT] = 32, /* 7, - , 7 */
#endif
#if defined(SIGLOST) && SIGPWR != SIGLOST /* sparc */
[SIGLOST] = 33, /* unused on Linux */
#endif
#if defined(SIGUNUSED) && \
defined(SIGLOST) && defined(SIGSYS) && SIGLOST != SIGSYS
[SIGUNUSED] = 34, /* -, 31, - */
#endif
};
/* this table is ordered post sig_map[sig] mapping */
static const char *const sig_names[MAXMAPPED_SIGNAME] = {
"unknown",
"hup",
"int",
"quit",
"ill",
"trap",
"abrt",
"bus",
"fpe",
"kill",
"usr1",
"segv",
"usr2",
"pipe",
"alrm",
"term",
"stkflt",
"chld",
"cont",
"stop",
"stp",
"ttin",
"ttou",
"urg",
"xcpu",
"xfsz",
"vtalrm",
"prof",
"winch",
"io",
"pwr",
"sys",
"emt",
"lost",
"unused",
"exists", /* always last existence test mapped to MAXMAPPED_SIG */
};
@@ -0,0 +1,19 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* AppArmor security module
*
* This file contains AppArmor ipc mediation function definitions.
*
* Copyright 2023 Canonical Ltd.
*/
#ifndef __AA_SIGNAL_H
#define __AA_SIGNAL_H
#define SIGUNKNOWN 0
#define MAXMAPPED_SIG 35
#define MAXMAPPED_SIGNAME (MAXMAPPED_SIG + 1)
#define SIGRT_BASE 128
#endif /* __AA_SIGNAL_H */
@@ -0,0 +1,105 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* AppArmor security module
*
* This file contains AppArmor task related definitions and mediation
*
* Copyright 2017 Canonical Ltd.
*/
#ifndef __AA_TASK_H
#define __AA_TASK_H
static inline struct aa_task_ctx *task_ctx(struct task_struct *task)
{
return task->security + apparmor_blob_sizes.lbs_task;
}
/*
* struct aa_task_ctx - information for current task label change
* @nnp: snapshot of label at time of no_new_privs
* @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
*/
struct aa_task_ctx {
struct aa_label *nnp;
struct aa_label *onexec;
struct aa_label *previous;
u64 token;
};
int aa_replace_current_label(struct aa_label *label);
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);
struct aa_label *aa_get_task_label(struct task_struct *task);
/**
* aa_free_task_ctx - free a task_ctx
* @ctx: task_ctx to free (MAYBE NULL)
*/
static inline void aa_free_task_ctx(struct aa_task_ctx *ctx)
{
if (ctx) {
aa_put_label(ctx->nnp);
aa_put_label(ctx->previous);
aa_put_label(ctx->onexec);
}
}
/**
* aa_dup_task_ctx - duplicate a task context, incrementing reference counts
* @new: a blank task context (NOT NULL)
* @old: the task context to copy (NOT NULL)
*/
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);
}
/**
* aa_clear_task_ctx_trans - clear transition tracking info from the ctx
* @ctx: task context to clear (NOT NULL)
*/
static inline void aa_clear_task_ctx_trans(struct aa_task_ctx *ctx)
{
AA_BUG(!ctx);
aa_put_label(ctx->previous);
aa_put_label(ctx->onexec);
ctx->previous = NULL;
ctx->onexec = NULL;
ctx->token = 0;
}
#define AA_PTRACE_TRACE MAY_WRITE
#define AA_PTRACE_READ MAY_READ
#define AA_MAY_BE_TRACED AA_MAY_APPEND
#define AA_MAY_BE_READ AA_MAY_CREATE
#define PTRACE_PERM_SHIFT 2
#define AA_PTRACE_PERM_MASK (AA_PTRACE_READ | AA_PTRACE_TRACE | \
AA_MAY_BE_READ | AA_MAY_BE_TRACED)
#define AA_SIGNAL_PERM_MASK (MAY_READ | MAY_WRITE)
#define AA_SFS_SIG_MASK "hup int quit ill trap abrt bus fpe kill usr1 " \
"segv usr2 pipe alrm term stkflt chld cont stop stp ttin ttou urg " \
"xcpu xfsz vtalrm prof winch io pwr sys emt lost"
int aa_may_ptrace(const struct cred *tracer_cred, struct aa_label *tracer,
const struct cred *tracee_cred, struct aa_label *tracee,
u32 request);
#define AA_USERNS_CREATE 8
int aa_profile_ns_perm(struct aa_profile *profile,
struct apparmor_audit_data *ad, u32 request);
#endif /* __AA_TASK_H */
+116
View File
@@ -0,0 +1,116 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* AppArmor security module
*
* This file contains AppArmor ipc mediation
*
* Copyright (C) 1998-2008 Novell/SUSE
* Copyright 2009-2017 Canonical Ltd.
*/
#include <linux/gfp.h>
#include "include/audit.h"
#include "include/capability.h"
#include "include/cred.h"
#include "include/policy.h"
#include "include/ipc.h"
#include "include/sig_names.h"
static inline int map_signal_num(int sig)
{
if (sig > SIGRTMAX)
return SIGUNKNOWN;
else if (sig >= SIGRTMIN)
return sig - SIGRTMIN + SIGRT_BASE;
else if (sig < MAXMAPPED_SIG)
return sig_map[sig];
return SIGUNKNOWN;
}
/**
* audit_signal_mask - convert mask to permission string
* @mask: permission mask to convert
*
* Returns: pointer to static string
*/
static const char *audit_signal_mask(u32 mask)
{
if (mask & MAY_READ)
return "receive";
if (mask & MAY_WRITE)
return "send";
return "";
}
/**
* audit_signal_cb() - call back for signal specific audit fields
* @ab: audit_buffer (NOT NULL)
* @va: audit struct to audit values of (NOT NULL)
*/
static void audit_signal_cb(struct audit_buffer *ab, void *va)
{
struct common_audit_data *sa = va;
struct apparmor_audit_data *ad = aad(sa);
if (ad->request & AA_SIGNAL_PERM_MASK) {
audit_log_format(ab, " requested_mask=\"%s\"",
audit_signal_mask(ad->request));
if (ad->denied & AA_SIGNAL_PERM_MASK) {
audit_log_format(ab, " denied_mask=\"%s\"",
audit_signal_mask(ad->denied));
}
}
if (ad->signal == SIGUNKNOWN)
audit_log_format(ab, "signal=unknown(%d)",
ad->unmappedsig);
else if (ad->signal < MAXMAPPED_SIGNAME)
audit_log_format(ab, " signal=%s", sig_names[ad->signal]);
else
audit_log_format(ab, " signal=rtmin+%d",
ad->signal - SIGRT_BASE);
audit_log_format(ab, " peer=");
aa_label_xaudit(ab, labels_ns(ad->subj_label), ad->peer,
FLAGS_NONE, GFP_ATOMIC);
}
static int profile_signal_perm(const struct cred *cred,
struct aa_profile *profile,
struct aa_label *peer, u32 request,
struct apparmor_audit_data *ad)
{
struct aa_ruleset *rules = profile->label.rules[0];
struct aa_perms perms;
aa_state_t state;
if (profile_unconfined(profile))
return 0;
ad->subj_cred = cred;
ad->peer = peer;
/* TODO: secondary cache check <profile, profile, perm> */
state = RULE_MEDIATES(rules, AA_CLASS_SIGNAL);
if (!state)
return 0;
state = aa_dfa_next(rules->policy->dfa, state, ad->signal);
aa_label_match(profile, rules, peer, state, false, request, &perms);
aa_apply_modes_to_perms(profile, &perms);
return aa_check_perms(profile, &perms, request, ad, audit_signal_cb);
}
int aa_may_signal(const struct cred *subj_cred, struct aa_label *sender,
const struct cred *target_cred, struct aa_label *target,
int sig)
{
struct aa_profile *profile;
DEFINE_AUDIT_DATA(ad, LSM_AUDIT_DATA_NONE, AA_CLASS_SIGNAL, OP_SIGNAL);
ad.signal = map_signal_num(sig);
ad.unmappedsig = sig;
return xcheck_labels(sender, target, profile,
profile_signal_perm(subj_cred, profile, target,
MAY_WRITE, &ad),
profile_signal_perm(target_cred, profile, sender,
MAY_READ, &ad));
}
File diff suppressed because it is too large Load Diff
+515
View File
@@ -0,0 +1,515 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* AppArmor security module
*
* This file contains basic common functions used in AppArmor
*
* Copyright (C) 1998-2008 Novell/SUSE
* Copyright 2009-2010 Canonical Ltd.
*/
#include <linux/ctype.h>
#include <linux/mm.h>
#include <linux/slab.h>
#include <linux/string.h>
#include <linux/vmalloc.h>
#include "include/audit.h"
#include "include/apparmor.h"
#include "include/lib.h"
#include "include/perms.h"
#include "include/policy.h"
struct aa_perms nullperms;
struct aa_perms allperms = { .allow = ALL_PERMS_MASK,
.quiet = ALL_PERMS_MASK,
.hide = ALL_PERMS_MASK };
struct val_table_ent {
const char *str;
int value;
};
static struct val_table_ent debug_values_table[] = {
{ "N", DEBUG_NONE },
{ "none", DEBUG_NONE },
{ "n", DEBUG_NONE },
{ "0", DEBUG_NONE },
{ "all", DEBUG_ALL },
{ "Y", DEBUG_ALL },
{ "y", DEBUG_ALL },
{ "1", DEBUG_ALL },
{ "abs_root", DEBUG_LABEL_ABS_ROOT },
{ "label", DEBUG_LABEL },
{ "domain", DEBUG_DOMAIN },
{ "policy", DEBUG_POLICY },
{ "interface", DEBUG_INTERFACE },
{ "unpack", DEBUG_UNPACK },
{ "tags", DEBUG_TAGS },
{ NULL, 0 }
};
static struct val_table_ent *val_table_find_ent(struct val_table_ent *table,
const char *name, size_t len)
{
struct val_table_ent *entry;
for (entry = table; entry->str != NULL; entry++) {
if (strncmp(entry->str, name, len) == 0 &&
strlen(entry->str) == len)
return entry;
}
return NULL;
}
int aa_parse_debug_params(const char *str)
{
struct val_table_ent *ent;
const char *next;
int val = 0;
do {
size_t n = strcspn(str, "\r\n,");
next = str + n;
ent = val_table_find_ent(debug_values_table, str, next - str);
if (ent)
val |= ent->value;
else
AA_DEBUG(DEBUG_INTERFACE, "unknown debug type '%.*s'",
(int)(next - str), str);
str = next + 1;
} while (*next != 0);
return val;
}
/**
* val_mask_to_str - convert a perm mask to its short string
* @str: character buffer to store string in (at least 10 characters)
* @size: size of the @str buffer
* @table: NUL-terminated character buffer of permission characters (NOT NULL)
* @mask: permission mask to convert
*/
static int val_mask_to_str(char *str, size_t size,
const struct val_table_ent *table, u32 mask)
{
const struct val_table_ent *ent;
int total = 0;
for (ent = table; ent->str; ent++) {
if (ent->value && (ent->value & mask) == ent->value) {
int len = scnprintf(str, size, "%s%s", total ? "," : "",
ent->str);
size -= len;
str += len;
total += len;
mask &= ~ent->value;
}
}
return total;
}
int aa_print_debug_params(char *buffer)
{
if (!aa_g_debug)
return sprintf(buffer, "N");
return val_mask_to_str(buffer, PAGE_SIZE, debug_values_table,
aa_g_debug);
}
bool aa_resize_str_table(struct aa_str_table *t, int newsize, gfp_t gfp)
{
struct aa_str_table_ent *n;
int i;
if (t->size == newsize)
return true;
n = kzalloc_objs(*n, newsize, gfp);
if (!n)
return false;
for (i = 0; i < min(t->size, newsize); i++)
n[i] = t->table[i];
for (; i < t->size; i++)
kfree_sensitive(t->table[i].strs);
if (newsize > t->size)
memset(&n[t->size], 0, (newsize-t->size)*sizeof(*n));
kfree_sensitive(t->table);
t->table = n;
t->size = newsize;
return true;
}
/**
* aa_destroy_str_table - free entries str table
* @t: the string table to free (MAYBE NULL)
*/
void aa_destroy_str_table(struct aa_str_table *t)
{
int i;
if (t) {
if (!t->table)
return;
for (i = 0; i < t->size; i++)
kfree_sensitive(t->table[i].strs);
kfree_sensitive(t->table);
t->table = NULL;
t->size = 0;
}
}
/**
* skipn_spaces - Removes leading whitespace from @str.
* @str: The string to be stripped.
* @n: length of str to parse, will stop at \0 if encountered before n
*
* Returns a pointer to the first non-whitespace character in @str.
* if all whitespace will return NULL
*/
const char *skipn_spaces(const char *str, size_t n)
{
for (; n && isspace(*str); --n)
++str;
if (n)
return (char *)str;
return NULL;
}
const char *aa_splitn_fqname(const char *fqname, size_t n, const char **ns_name,
size_t *ns_len)
{
const char *end = fqname + n;
const char *name = skipn_spaces(fqname, n);
*ns_name = NULL;
*ns_len = 0;
if (!name)
return NULL;
if (name[0] == ':') {
char *split = strnchr(&name[1], end - &name[1], ':');
*ns_name = skipn_spaces(&name[1], end - &name[1]);
if (!*ns_name)
return NULL;
if (split) {
*ns_len = split - *ns_name;
if (*ns_len == 0)
*ns_name = NULL;
split++;
if (end - split > 1 && strncmp(split, "//", 2) == 0)
split += 2;
name = skipn_spaces(split, end - split);
} else {
/* a ns name without a following profile is allowed */
name = NULL;
*ns_len = end - *ns_name;
}
}
if (name && *name == 0)
name = NULL;
return name;
}
/**
* aa_info_message - log a none profile related status message
* @str: message to log
*/
void aa_info_message(const char *str)
{
if (audit_enabled) {
DEFINE_AUDIT_DATA(ad, LSM_AUDIT_DATA_NONE, AA_CLASS_NONE, NULL);
ad.info = str;
aa_audit_msg(AUDIT_APPARMOR_STATUS, &ad, NULL);
}
printk(KERN_INFO "AppArmor: %s\n", str);
}
__counted char *aa_str_alloc(int size, gfp_t gfp)
{
struct counted_str *str;
str = kmalloc_flex(*str, name, size, gfp);
if (!str)
return NULL;
kref_init(&str->count);
return str->name;
}
void aa_str_kref(struct kref *kref)
{
kfree(container_of(kref, struct counted_str, count));
}
const char aa_file_perm_chrs[] = "xwracd km l ";
const char *aa_file_perm_names[] = {
"exec",
"write",
"read",
"append",
"create",
"delete",
"open",
"rename",
"setattr",
"getattr",
"setcred",
"getcred",
"chmod",
"chown",
"chgrp",
"lock",
"mmap",
"mprot",
"link",
"snapshot",
"unknown",
"unknown",
"unknown",
"unknown",
"unknown",
"unknown",
"unknown",
"unknown",
"stack",
"change_onexec",
"change_profile",
"change_hat",
};
/**
* aa_perm_mask_to_str - convert a perm mask to its short string
* @str: character buffer to store string in (at least 10 characters)
* @str_size: size of the @str buffer
* @chrs: NUL-terminated character buffer of permission characters
* @mask: permission mask to convert
*/
void aa_perm_mask_to_str(char *str, size_t str_size, const char *chrs, u32 mask)
{
unsigned int i, perm = 1;
size_t num_chrs = strlen(chrs);
for (i = 0; i < num_chrs; perm <<= 1, i++) {
if (mask & perm) {
/* Ensure that one byte is left for NUL-termination */
if (WARN_ON_ONCE(str_size <= 1))
break;
*str++ = chrs[i];
str_size--;
}
}
*str = '\0';
}
void aa_audit_perm_names(struct audit_buffer *ab, const char * const *names,
u32 mask)
{
const char *fmt = "%s";
unsigned int i, perm = 1;
bool prev = false;
for (i = 0; i < 32; perm <<= 1, i++) {
if (mask & perm) {
audit_log_format(ab, fmt, names[i]);
if (!prev) {
prev = true;
fmt = " %s";
}
}
}
}
void aa_audit_perm_mask(struct audit_buffer *ab, u32 mask, const char *chrs,
u32 chrsmask, const char * const *names, u32 namesmask)
{
char str[33];
audit_log_format(ab, "\"");
if ((mask & chrsmask) && chrs) {
aa_perm_mask_to_str(str, sizeof(str), chrs, mask & chrsmask);
mask &= ~chrsmask;
audit_log_format(ab, "%s", str);
if (mask & namesmask)
audit_log_format(ab, " ");
}
if ((mask & namesmask) && names)
aa_audit_perm_names(ab, names, mask & namesmask);
audit_log_format(ab, "\"");
}
/**
* aa_apply_modes_to_perms - apply namespace and profile flags to perms
* @profile: that perms where computed from
* @perms: perms to apply mode modifiers to
*
* 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)
{
switch (AUDIT_MODE(profile)) {
case AUDIT_ALL:
perms->audit = ALL_PERMS_MASK;
fallthrough;
case AUDIT_NOQUIET:
perms->quiet = 0;
break;
case AUDIT_QUIET:
perms->audit = 0;
fallthrough;
case AUDIT_QUIET_DENIED:
perms->quiet = ALL_PERMS_MASK;
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,
struct aa_ruleset *rules,
struct aa_label *label,
int type, u32 request, struct aa_perms *perms)
{
/* TODO: doesn't yet handle extended types */
aa_state_t state;
state = aa_dfa_next(rules->policy->dfa,
rules->policy->start[AA_CLASS_LABEL],
type);
aa_label_match(profile, rules, label, state, false, request, perms);
}
/**
* aa_check_perms - do audit mode selection based on perms set
* @profile: profile being checked
* @perms: perms computed for the request
* @request: requested perms
* @ad: initialized audit structure (MAY BE NULL if not auditing)
* @cb: callback fn for type specific fields (MAY BE NULL)
*
* Returns: 0 if permission else error code
*
* Note: profile audit modes need to be set before calling by setting the
* perm masks appropriately.
*
* If not auditing then complain mode is not enabled and the
* 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,
u32 request, struct apparmor_audit_data *ad,
void (*cb)(struct audit_buffer *, void *))
{
int type, error;
u32 denied = request & (~perms->allow | perms->deny);
if (likely(!denied)) {
/* mask off perms that are not being force audited */
request &= perms->audit;
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;
denied &= ~perms->quiet;
if (!ad || !denied)
return error;
}
if (ad) {
ad->subj_label = &profile->label;
ad->request = request;
ad->denied = denied;
ad->error = error;
aa_audit_msg(type, ad, cb);
}
if (type == AUDIT_APPARMOR_ALLOWED)
error = 0;
return error;
}
/**
* aa_policy_init - initialize a policy structure
* @policy: policy to initialize (NOT NULL)
* @prefix: prefix name if any is required. (MAYBE NULL)
* @name: name of the policy, init will make a copy of it (NOT NULL)
* @gfp: allocation mode
*
* Note: this fn creates a copy of strings passed in
*
* Returns: true if policy init successful
*/
bool aa_policy_init(struct aa_policy *policy, const char *prefix,
const char *name, gfp_t gfp)
{
char *hname;
size_t hname_sz;
hname_sz = (prefix ? strlen(prefix) + 2 : 0) + strlen(name) + 1;
/* freed by policy_free */
hname = aa_str_alloc(hname_sz, gfp);
if (!hname)
return false;
if (prefix)
scnprintf(hname, hname_sz, "%s//%s", prefix, name);
else
strscpy(hname, name, hname_sz);
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;
}
/**
* aa_policy_destroy - free the elements referenced by @policy
* @policy: policy that is to have its elements freed (NOT NULL)
*/
void aa_policy_destroy(struct aa_policy *policy)
{
AA_BUG(on_list_rcu(&policy->profiles));
AA_BUG(on_list_rcu(&policy->list));
/* don't free name as its a subset of hname */
aa_put_str(policy->hname);
}
File diff suppressed because it is too large Load Diff
+832
View File
@@ -0,0 +1,832 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* AppArmor security module
*
* This file contains AppArmor dfa based regular expression matching engine
*
* Copyright (C) 1998-2008 Novell/SUSE
* Copyright 2009-2012 Canonical Ltd.
*/
#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/mm.h>
#include <linux/slab.h>
#include <linux/vmalloc.h>
#include <linux/err.h>
#include <linux/kref.h>
#include <linux/unaligned.h>
#include "include/lib.h"
#include "include/match.h"
#define base_idx(X) ((X) & 0xffffff)
/**
* unpack_table - unpack a dfa table (one of accept, default, base, next check)
* @blob: data to unpack (NOT NULL)
* @bsize: size of blob
*
* Returns: pointer to table else ERR_PTR on failure
*
* NOTE: must be freed by kvfree (not kfree)
*/
static struct table_header *unpack_table(char *blob, size_t bsize)
{
struct table_header *table = ERR_PTR(-EPROTO);
struct table_header th;
size_t tsize;
if (bsize < sizeof(struct table_header))
goto out;
/* loaded td_id's start at 1, subtract 1 now to avoid doing
* it every time we use td_id as an index
*/
th.td_id = get_unaligned_be16(blob) - 1;
if (th.td_id > YYTD_ID_MAX)
goto out;
th.td_flags = get_unaligned_be16(blob + 2);
th.td_lolen = get_unaligned_be32(blob + 8);
blob += sizeof(struct table_header);
if (!(th.td_flags == YYTD_DATA16 || th.td_flags == YYTD_DATA32 ||
th.td_flags == YYTD_DATA8))
goto out;
/* if we have a table it must have some entries */
if (th.td_lolen == 0)
goto out;
tsize = table_size(th.td_lolen, th.td_flags);
if (bsize < tsize)
goto out;
table = kvzalloc(tsize, GFP_KERNEL);
if (table) {
table->td_id = th.td_id;
table->td_flags = th.td_flags;
table->td_lolen = th.td_lolen;
if (th.td_flags == YYTD_DATA8)
memcpy(table->td_data, blob, th.td_lolen);
else if (th.td_flags == YYTD_DATA16)
UNPACK_ARRAY(table->td_data, blob, th.td_lolen,
u16, __be16, get_unaligned_be16);
else if (th.td_flags == YYTD_DATA32)
UNPACK_ARRAY(table->td_data, blob, th.td_lolen,
u32, __be32, get_unaligned_be32);
else {
kvfree(table);
table = ERR_PTR(-EPROTO);
goto out;
}
/* if table was vmalloced make sure the page tables are synced
* before it is used, as it goes live to all cpus.
*/
if (is_vmalloc_addr(table))
vm_unmap_aliases();
} else
table = ERR_PTR(-ENOMEM);
out:
return table;
}
/**
* verify_table_headers - verify that the tables headers are as expected
* @tables: array of dfa tables to check (NOT NULL)
* @flags: flags controlling what type of accept table are acceptable
*
* Assumes dfa has gone through the first pass verification done by unpacking
* NOTE: this does not valid accept table values
*
* Returns: %0 else error code on failure to verify
*/
static int verify_table_headers(struct table_header **tables, int flags)
{
size_t state_count, trans_count;
int error = -EPROTO;
/* check that required tables exist */
if (!(tables[YYTD_ID_DEF] && tables[YYTD_ID_BASE] &&
tables[YYTD_ID_NXT] && tables[YYTD_ID_CHK]))
goto out;
/* accept.size == default.size == base.size */
state_count = tables[YYTD_ID_BASE]->td_lolen;
if (ACCEPT1_FLAGS(flags)) {
if (!tables[YYTD_ID_ACCEPT])
goto out;
if (state_count != tables[YYTD_ID_ACCEPT]->td_lolen)
goto out;
}
if (ACCEPT2_FLAGS(flags)) {
if (!tables[YYTD_ID_ACCEPT2])
goto out;
if (state_count != tables[YYTD_ID_ACCEPT2]->td_lolen)
goto out;
}
if (state_count != tables[YYTD_ID_DEF]->td_lolen)
goto out;
/* next.size == chk.size */
trans_count = tables[YYTD_ID_NXT]->td_lolen;
if (trans_count != tables[YYTD_ID_CHK]->td_lolen)
goto out;
/* if equivalence classes then its table size must be 256 */
if (tables[YYTD_ID_EC] && tables[YYTD_ID_EC]->td_lolen != 256)
goto out;
error = 0;
out:
return error;
}
/**
* verify_dfa - verify that transitions and states in the tables are in bounds.
* @dfa: dfa to test (NOT NULL)
*
* Assumes dfa has gone through the first pass verification done by unpacking
* NOTE: this does not valid accept table values
*
* Returns: %0 else error code on failure to verify
*/
static int verify_dfa(struct aa_dfa *dfa)
{
size_t i, state_count, trans_count;
int error = -EPROTO;
state_count = dfa->tables[YYTD_ID_BASE]->td_lolen;
trans_count = dfa->tables[YYTD_ID_NXT]->td_lolen;
if (state_count < 2)
goto out;
for (i = 0; i < state_count; i++) {
if (DEFAULT_TABLE(dfa)[i] >= state_count) {
pr_err("AppArmor DFA default state out of bounds");
goto out;
}
if (BASE_TABLE(dfa)[i] & MATCH_FLAGS_INVALID) {
pr_err("AppArmor DFA state with invalid match flags");
goto out;
}
if ((BASE_TABLE(dfa)[i] & MATCH_FLAG_DIFF_ENCODE)) {
if (!(dfa->flags & YYTH_FLAG_DIFF_ENCODE)) {
pr_err("AppArmor DFA diff encoded transition state without header flag");
goto out;
}
}
if ((BASE_TABLE(dfa)[i] & MATCH_FLAG_OOB_TRANSITION)) {
if (base_idx(BASE_TABLE(dfa)[i]) < dfa->max_oob) {
pr_err("AppArmor DFA out of bad transition out of range");
goto out;
}
if (!(dfa->flags & YYTH_FLAG_OOB_TRANS)) {
pr_err("AppArmor DFA out of bad transition state without header flag");
goto out;
}
}
if (base_idx(BASE_TABLE(dfa)[i]) + 255 >= trans_count) {
pr_err("AppArmor DFA next/check upper bounds error\n");
goto out;
}
}
for (i = 0; i < trans_count; i++) {
if (NEXT_TABLE(dfa)[i] >= state_count)
goto out;
if (CHECK_TABLE(dfa)[i] >= state_count)
goto out;
}
/* Now that all the other tables are verified, verify diffencoding */
for (i = 0; i < state_count; i++) {
size_t j, k;
for (j = i;
((BASE_TABLE(dfa)[j] & MATCH_FLAG_DIFF_ENCODE) &&
!(BASE_TABLE(dfa)[j] & MARK_DIFF_ENCODE_VERIFIED));
j = k) {
if (BASE_TABLE(dfa)[j] & MARK_DIFF_ENCODE)
/* loop in current chain */
goto out;
k = DEFAULT_TABLE(dfa)[j];
if (j == k)
/* self loop */
goto out;
BASE_TABLE(dfa)[j] |= MARK_DIFF_ENCODE;
}
/* move mark to verified */
for (j = i;
(BASE_TABLE(dfa)[j] & MATCH_FLAG_DIFF_ENCODE);
j = k) {
k = DEFAULT_TABLE(dfa)[j];
if (j < i)
/* jumps to state/chain that has been
* verified
*/
break;
BASE_TABLE(dfa)[j] &= ~MARK_DIFF_ENCODE;
BASE_TABLE(dfa)[j] |= MARK_DIFF_ENCODE_VERIFIED;
}
}
error = 0;
out:
return error;
}
/**
* dfa_free - free a dfa allocated by aa_dfa_unpack
* @dfa: the dfa to free (MAYBE NULL)
*
* Requires: reference count to dfa == 0
*/
static void dfa_free(struct aa_dfa *dfa)
{
if (dfa) {
int i;
for (i = 0; i < ARRAY_SIZE(dfa->tables); i++) {
kvfree(dfa->tables[i]);
dfa->tables[i] = NULL;
}
kfree(dfa);
}
}
/**
* aa_dfa_free_kref - free aa_dfa by kref (called by aa_put_dfa)
* @kref: kref callback for freeing of a dfa (NOT NULL)
*/
void aa_dfa_free_kref(struct kref *kref)
{
struct aa_dfa *dfa = container_of(kref, struct aa_dfa, count);
dfa_free(dfa);
}
/**
* remap_data16_to_data32 - remap u16 @old table to a u32 based table
* @old: table to remap
*
* Returns: new table with u32 entries instead of u16.
*
* Note: will free @old so caller does not have to
*/
static struct table_header *remap_data16_to_data32(struct table_header *old)
{
struct table_header *new;
size_t tsize;
u32 i;
tsize = table_size(old->td_lolen, YYTD_DATA32);
new = kvzalloc(tsize, GFP_KERNEL);
if (!new) {
kvfree(old);
return NULL;
}
new->td_id = old->td_id;
new->td_flags = YYTD_DATA32;
new->td_lolen = old->td_lolen;
for (i = 0; i < old->td_lolen; i++)
TABLE_DATAU32(new)[i] = (u32) TABLE_DATAU16(old)[i];
kvfree(old);
if (is_vmalloc_addr(new))
vm_unmap_aliases();
return new;
}
/**
* aa_dfa_unpack - unpack the binary tables of a serialized dfa
* @blob: aligned serialized stream of data to unpack (NOT NULL)
* @size: size of data to unpack
* @flags: flags controlling what type of accept tables are acceptable
*
* Unpack a dfa that has been serialized. To find information on the dfa
* format look in Documentation/admin-guide/LSM/apparmor.rst
* Assumes the dfa @blob stream has been aligned on a 8 byte boundary
*
* 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)
{
int hsize;
int error = -ENOMEM;
char *data = blob;
struct table_header *table = NULL;
struct aa_dfa *dfa = kzalloc_obj(struct aa_dfa);
if (!dfa)
goto fail;
kref_init(&dfa->count);
error = -EPROTO;
/* get dfa table set header */
if (size < sizeof(struct table_set_header))
goto fail;
if (get_unaligned_be32(data) != YYTH_MAGIC)
goto fail;
hsize = get_unaligned_be32(data + 4);
if (size < hsize)
goto fail;
dfa->flags = get_unaligned_be16(data + 12);
if (dfa->flags & ~(YYTH_FLAGS))
goto fail;
/*
* TODO: needed for dfa to support more than 1 oob
* if (dfa->flags & YYTH_FLAGS_OOB_TRANS) {
* if (hsize < 16 + 4)
* goto fail;
* dfa->max_oob = get_unaligned_be32(data + 16);
* if (dfa->max <= MAX_OOB_SUPPORTED) {
* pr_err("AppArmor DFA OOB greater than supported\n");
* goto fail;
* }
* }
*/
dfa->max_oob = 1;
data += hsize;
size -= hsize;
while (size > 0) {
table = unpack_table(data, size);
if (IS_ERR(table)) {
error = PTR_ERR(table);
table = NULL;
goto fail;
}
switch (table->td_id) {
case YYTD_ID_ACCEPT:
if (!(table->td_flags & ACCEPT1_FLAGS(flags)))
goto fail;
break;
case YYTD_ID_ACCEPT2:
if (!(table->td_flags & ACCEPT2_FLAGS(flags)))
goto fail;
break;
case YYTD_ID_BASE:
if (table->td_flags != YYTD_DATA32)
goto fail;
break;
case YYTD_ID_DEF:
case YYTD_ID_NXT:
case YYTD_ID_CHK:
if (!(table->td_flags == YYTD_DATA16 ||
table->td_flags == YYTD_DATA32)) {
goto fail;
}
break;
case YYTD_ID_EC:
if (table->td_flags != YYTD_DATA8)
goto fail;
break;
default:
goto fail;
}
/* check for duplicate table entry */
if (dfa->tables[table->td_id])
goto fail;
dfa->tables[table->td_id] = table;
data += table_size(table->td_lolen, table->td_flags);
size -= table_size(table->td_lolen, table->td_flags);
/*
* this remapping has to be done after incrementing data above
* for now straight remap, later have dfa support both
*/
switch (table->td_id) {
case YYTD_ID_DEF:
case YYTD_ID_NXT:
case YYTD_ID_CHK:
if (table->td_flags == YYTD_DATA16) {
table = remap_data16_to_data32(table);
if (!table)
goto fail;
}
dfa->tables[table->td_id] = table;
break;
}
table = NULL;
}
error = verify_table_headers(dfa->tables, flags);
if (error)
goto fail;
if (flags & DFA_FLAG_VERIFY_STATES) {
error = verify_dfa(dfa);
if (error)
goto fail;
}
return dfa;
fail:
kvfree(table);
dfa_free(dfa);
return ERR_PTR(error);
}
#define match_char(state, def, base, next, check, C) \
do { \
u32 b = (base)[(state)]; \
unsigned int pos = base_idx(b) + (C); \
if ((check)[pos] != (state)) { \
(state) = (def)[(state)]; \
if (b & MATCH_FLAG_DIFF_ENCODE) \
continue; \
break; \
} \
(state) = (next)[pos]; \
break; \
} while (1)
/**
* aa_dfa_match_len - traverse @dfa to find state @str stops at
* @dfa: the dfa to match @str against (NOT NULL)
* @start: the state of the dfa to start matching in
* @str: the string of bytes to match against the dfa (NOT NULL)
* @len: length of the string of bytes to match
*
* aa_dfa_match_len will match @str against the dfa and return the state it
* finished matching in. The final state can be used to look up the accepting
* label, or as the start state of a continuing match.
*
* This function will happily match again the 0 byte and only finishes
* when @len input is consumed.
*
* Returns: final state reached after input is consumed
*/
aa_state_t aa_dfa_match_len(struct aa_dfa *dfa, aa_state_t start,
const char *str, int len)
{
u32 *def = DEFAULT_TABLE(dfa);
u32 *base = BASE_TABLE(dfa);
u32 *next = NEXT_TABLE(dfa);
u32 *check = CHECK_TABLE(dfa);
aa_state_t state = start;
if (state == DFA_NOMATCH)
return DFA_NOMATCH;
/* current state is <state>, matching character *str */
if (dfa->tables[YYTD_ID_EC]) {
/* Equivalence class table defined */
u8 *equiv = EQUIV_TABLE(dfa);
for (; len; len--) {
u8 c = equiv[(u8) *str];
match_char(state, def, base, next, check, c);
str++;
}
} else {
/* default is direct to next state */
for (; len; len--) {
match_char(state, def, base, next, check, (u8) *str);
str++;
}
}
return state;
}
/**
* aa_dfa_match - traverse @dfa to find state @str stops at
* @dfa: the dfa to match @str against (NOT NULL)
* @start: the state of the dfa to start matching in
* @str: the null terminated string of bytes to match against the dfa (NOT NULL)
*
* aa_dfa_match will match @str against the dfa and return the state it
* finished matching in. The final state can be used to look up the accepting
* label, or as the start state of a continuing match.
*
* 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)
{
u32 *def = DEFAULT_TABLE(dfa);
u32 *base = BASE_TABLE(dfa);
u32 *next = NEXT_TABLE(dfa);
u32 *check = CHECK_TABLE(dfa);
aa_state_t state = start;
if (state == DFA_NOMATCH)
return DFA_NOMATCH;
/* current state is <state>, matching character *str */
if (dfa->tables[YYTD_ID_EC]) {
/* Equivalence class table defined */
u8 *equiv = EQUIV_TABLE(dfa);
/* default is direct to next state */
while (*str) {
u8 c = equiv[(u8) *str];
match_char(state, def, base, next, check, c);
str++;
}
} else {
/* default is direct to next state */
while (*str) {
match_char(state, def, base, next, check, (u8) *str);
str++;
}
}
return state;
}
/**
* aa_dfa_next - step one character to the next state in the dfa
* @dfa: the dfa to traverse (NOT NULL)
* @state: the state to start in
* @c: the input character to transition on
*
* aa_dfa_match will step through the dfa by one input character @c
*
* Returns: state reach after input @c
*/
aa_state_t aa_dfa_next(struct aa_dfa *dfa, aa_state_t state, const char c)
{
u32 *def = DEFAULT_TABLE(dfa);
u32 *base = BASE_TABLE(dfa);
u32 *next = NEXT_TABLE(dfa);
u32 *check = CHECK_TABLE(dfa);
/* current state is <state>, matching character *str */
if (dfa->tables[YYTD_ID_EC]) {
/* Equivalence class table defined */
u8 *equiv = EQUIV_TABLE(dfa);
match_char(state, def, base, next, check, equiv[(u8) c]);
} else
match_char(state, def, base, next, check, (u8) c);
return state;
}
aa_state_t aa_dfa_outofband_transition(struct aa_dfa *dfa, aa_state_t state)
{
u32 *def = DEFAULT_TABLE(dfa);
u32 *base = BASE_TABLE(dfa);
u32 *next = NEXT_TABLE(dfa);
u32 *check = CHECK_TABLE(dfa);
u32 b = (base)[(state)];
if (!(b & MATCH_FLAG_OOB_TRANSITION))
return DFA_NOMATCH;
/* No Equivalence class remapping for outofband transitions */
match_char(state, def, base, next, check, -1);
return state;
}
/**
* aa_dfa_match_until - traverse @dfa until accept state or end of input
* @dfa: the dfa to match @str against (NOT NULL)
* @start: the state of the dfa to start matching in
* @str: the null terminated string of bytes to match against the dfa (NOT NULL)
* @retpos: first character in str after match OR end of string
*
* aa_dfa_match will match @str against the dfa and return the state it
* finished matching in. The final state can be used to look up the accepting
* label, or as the start state of a continuing match.
*
* 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)
{
u32 *def = DEFAULT_TABLE(dfa);
u32 *base = BASE_TABLE(dfa);
u32 *next = NEXT_TABLE(dfa);
u32 *check = CHECK_TABLE(dfa);
u32 *accept = ACCEPT_TABLE(dfa);
aa_state_t state = start, pos;
if (state == DFA_NOMATCH)
return DFA_NOMATCH;
/* current state is <state>, matching character *str */
if (dfa->tables[YYTD_ID_EC]) {
/* Equivalence class table defined */
u8 *equiv = EQUIV_TABLE(dfa);
/* default is direct to next state */
while (*str) {
pos = base_idx(base[state]) + equiv[(u8) *str++];
if (check[pos] == state)
state = next[pos];
else
state = def[state];
if (accept[state])
break;
}
} else {
/* default is direct to next state */
while (*str) {
pos = base_idx(base[state]) + (u8) *str++;
if (check[pos] == state)
state = next[pos];
else
state = def[state];
if (accept[state])
break;
}
}
*retpos = str;
return state;
}
/**
* aa_dfa_matchn_until - traverse @dfa until accept or @n bytes consumed
* @dfa: the dfa to match @str against (NOT NULL)
* @start: the state of the dfa to start matching in
* @str: the string of bytes to match against the dfa (NOT NULL)
* @n: length of the string of bytes to match
* @retpos: first character in str after match OR str + n
*
* aa_dfa_match_len will match @str against the dfa and return the state it
* finished matching in. The final state can be used to look up the accepting
* label, or as the start state of a continuing match.
*
* This function will happily match again the 0 byte and only finishes
* when @n input is consumed.
*
* 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)
{
u32 *def = DEFAULT_TABLE(dfa);
u32 *base = BASE_TABLE(dfa);
u32 *next = NEXT_TABLE(dfa);
u32 *check = CHECK_TABLE(dfa);
u32 *accept = ACCEPT_TABLE(dfa);
aa_state_t state = start, pos;
*retpos = NULL;
if (state == DFA_NOMATCH)
return DFA_NOMATCH;
/* current state is <state>, matching character *str */
if (dfa->tables[YYTD_ID_EC]) {
/* Equivalence class table defined */
u8 *equiv = EQUIV_TABLE(dfa);
/* default is direct to next state */
for (; n; n--) {
pos = base_idx(base[state]) + equiv[(u8) *str++];
if (check[pos] == state)
state = next[pos];
else
state = def[state];
if (accept[state])
break;
}
} else {
/* default is direct to next state */
for (; n; n--) {
pos = base_idx(base[state]) + (u8) *str++;
if (check[pos] == state)
state = next[pos];
else
state = def[state];
if (accept[state])
break;
}
}
*retpos = str;
return state;
}
#define inc_wb_pos(wb) \
do { \
BUILD_BUG_ON_NOT_POWER_OF_2(WB_HISTORY_SIZE); \
wb->pos = (wb->pos + 1) & (WB_HISTORY_SIZE - 1); \
wb->len = (wb->len + 1) > WB_HISTORY_SIZE ? WB_HISTORY_SIZE : \
wb->len + 1; \
} while (0)
/* For DFAs that don't support extended tagging of states */
/* adjust is only set if is_loop returns true */
static bool is_loop(struct match_workbuf *wb, aa_state_t state,
unsigned int *adjust)
{
int pos = wb->pos;
int i;
if (wb->history[pos] < state)
return false;
for (i = 0; i < wb->len; i++) {
if (wb->history[pos] == state) {
*adjust = i;
return true;
}
/* -1 wraps to WB_HISTORY_SIZE - 1 */
pos = (pos - 1) & (WB_HISTORY_SIZE - 1);
}
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)
{
u32 *def = DEFAULT_TABLE(dfa);
u32 *base = BASE_TABLE(dfa);
u32 *next = NEXT_TABLE(dfa);
u32 *check = CHECK_TABLE(dfa);
aa_state_t state = start, pos;
AA_BUG(!dfa);
AA_BUG(!str);
AA_BUG(!wb);
AA_BUG(!count);
*count = 0;
if (state == DFA_NOMATCH)
return DFA_NOMATCH;
/* current state is <state>, matching character *str */
if (dfa->tables[YYTD_ID_EC]) {
/* Equivalence class table defined */
u8 *equiv = EQUIV_TABLE(dfa);
/* default is direct to next state */
while (*str) {
unsigned int adjust;
wb->history[wb->pos] = state;
pos = base_idx(base[state]) + equiv[(u8) *str++];
if (check[pos] == state)
state = next[pos];
else
state = def[state];
if (is_loop(wb, state, &adjust)) {
state = aa_dfa_match(dfa, state, str);
*count -= adjust;
goto out;
}
inc_wb_pos(wb);
(*count)++;
}
} else {
/* default is direct to next state */
while (*str) {
unsigned int adjust;
wb->history[wb->pos] = state;
pos = base_idx(base[state]) + (u8) *str++;
if (check[pos] == state)
state = next[pos];
else
state = def[state];
if (is_loop(wb, state, &adjust)) {
state = aa_dfa_match(dfa, state, str);
*count -= adjust;
goto out;
}
inc_wb_pos(wb);
(*count)++;
}
}
out:
if (!state)
*count = 0;
return state;
}
/**
* aa_dfa_leftmatch - traverse @dfa to find state @str stops at
* @dfa: the dfa to match @str against (NOT NULL)
* @start: the state of the dfa to start matching in
* @str: the null terminated string of bytes to match against the dfa (NOT NULL)
* @count: current count of longest left.
*
* aa_dfa_match will match @str against the dfa and return the state it
* finished matching in. The final state can be used to look up the accepting
* label, or as the start state of a continuing match.
*
* Returns: final state reached after input is consumed
*/
aa_state_t aa_dfa_leftmatch(struct aa_dfa *dfa, aa_state_t start,
const char *str, unsigned int *count)
{
DEFINE_MATCH_WB(wb);
/* TODO: match for extended state dfas */
return leftmatch_fb(dfa, start, str, &wb, count);
}
+764
View File
@@ -0,0 +1,764 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* AppArmor security module
*
* This file contains AppArmor mediation of files
*
* Copyright (C) 1998-2008 Novell/SUSE
* Copyright 2009-2017 Canonical Ltd.
*/
#include <linux/fs.h>
#include <linux/mount.h>
#include <linux/namei.h>
#include <uapi/linux/mount.h>
#include "include/apparmor.h"
#include "include/audit.h"
#include "include/cred.h"
#include "include/domain.h"
#include "include/file.h"
#include "include/match.h"
#include "include/mount.h"
#include "include/path.h"
#include "include/policy.h"
static void audit_mnt_flags(struct audit_buffer *ab, unsigned long flags)
{
if (flags & MS_RDONLY)
audit_log_format(ab, "ro");
else
audit_log_format(ab, "rw");
if (flags & MS_NOSUID)
audit_log_format(ab, ", nosuid");
if (flags & MS_NODEV)
audit_log_format(ab, ", nodev");
if (flags & MS_NOEXEC)
audit_log_format(ab, ", noexec");
if (flags & MS_SYNCHRONOUS)
audit_log_format(ab, ", sync");
if (flags & MS_REMOUNT)
audit_log_format(ab, ", remount");
if (flags & MS_MANDLOCK)
audit_log_format(ab, ", mand");
if (flags & MS_DIRSYNC)
audit_log_format(ab, ", dirsync");
if (flags & MS_NOSYMFOLLOW)
audit_log_format(ab, ", nosymfollow");
if (flags & MS_NOATIME)
audit_log_format(ab, ", noatime");
if (flags & MS_NODIRATIME)
audit_log_format(ab, ", nodiratime");
if (flags & MS_BIND)
audit_log_format(ab, flags & MS_REC ? ", rbind" : ", bind");
if (flags & MS_MOVE)
audit_log_format(ab, ", move");
if (flags & MS_SILENT)
audit_log_format(ab, ", silent");
if (flags & MS_POSIXACL)
audit_log_format(ab, ", acl");
if (flags & MS_UNBINDABLE)
audit_log_format(ab, flags & MS_REC ? ", runbindable" :
", unbindable");
if (flags & MS_PRIVATE)
audit_log_format(ab, flags & MS_REC ? ", rprivate" :
", private");
if (flags & MS_SLAVE)
audit_log_format(ab, flags & MS_REC ? ", rslave" :
", slave");
if (flags & MS_SHARED)
audit_log_format(ab, flags & MS_REC ? ", rshared" :
", shared");
if (flags & MS_RELATIME)
audit_log_format(ab, ", relatime");
if (flags & MS_I_VERSION)
audit_log_format(ab, ", iversion");
if (flags & MS_STRICTATIME)
audit_log_format(ab, ", strictatime");
if (flags & MS_NOUSER)
audit_log_format(ab, ", nouser");
}
/**
* audit_cb - call back for mount specific audit fields
* @ab: audit_buffer (NOT NULL)
* @va: audit struct to audit values of (NOT NULL)
*/
static void audit_cb(struct audit_buffer *ab, void *va)
{
struct common_audit_data *sa = va;
struct apparmor_audit_data *ad = aad(sa);
if (ad->mnt.type) {
audit_log_format(ab, " fstype=");
audit_log_untrustedstring(ab, ad->mnt.type);
}
if (ad->mnt.src_name) {
audit_log_format(ab, " srcname=");
audit_log_untrustedstring(ab, ad->mnt.src_name);
}
if (ad->mnt.trans) {
audit_log_format(ab, " trans=");
audit_log_untrustedstring(ab, ad->mnt.trans);
}
if (ad->mnt.flags) {
audit_log_format(ab, " flags=\"");
audit_mnt_flags(ab, ad->mnt.flags);
audit_log_format(ab, "\"");
}
if (ad->mnt.data) {
audit_log_format(ab, " options=");
audit_log_untrustedstring(ab, ad->mnt.data);
}
}
/**
* 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
* @flags: mount flags to match against
*
* Mount flags are encoded as an ordered match. This is done instead of
* checking against a simple bitmask, to allow for logical operations
* on the flags.
*
* Returns: next state after flags match
*/
static aa_state_t match_mnt_flags(struct aa_dfa *dfa, aa_state_t state,
unsigned long flags)
{
unsigned int i;
for (i = 0; i <= 31 ; ++i) {
if ((1 << i) & flags)
state = aa_dfa_next(dfa, state, i + 1);
}
return state;
}
static const char * const mnt_info_table[] = {
"match succeeded",
"failed mntpnt match",
"failed srcname match",
"failed type match",
"failed flags match",
"failed data match",
"failed perms check"
};
/*
* Returns 0 on success else element that match failed in, this is the
* index into the mnt_info_table above
*/
static int do_match_mnt(struct aa_policydb *policy, aa_state_t start,
const char *mntpnt, const char *devname,
const char *type, unsigned long flags,
void *data, bool binary, struct aa_perms *perms)
{
aa_state_t state;
AA_BUG(!policy);
AA_BUG(!policy->dfa);
AA_BUG(!policy->perms);
AA_BUG(!perms);
state = aa_dfa_match(policy->dfa, start, mntpnt);
state = aa_dfa_null_transition(policy->dfa, state);
if (!state)
return 1;
if (devname)
state = aa_dfa_match(policy->dfa, state, devname);
state = aa_dfa_null_transition(policy->dfa, state);
if (!state)
return 2;
if (type)
state = aa_dfa_match(policy->dfa, state, type);
state = aa_dfa_null_transition(policy->dfa, state);
if (!state)
return 3;
state = match_mnt_flags(policy->dfa, state, flags);
if (!state)
return 4;
*perms = *aa_lookup_perms(policy, state);
if (perms->allow & AA_MAY_MOUNT)
return 0;
/* only match data if not binary and the DFA flags data is expected */
if (data && !binary && (perms->allow & AA_MNT_CONT_MATCH)) {
state = aa_dfa_null_transition(policy->dfa, state);
if (!state)
return 4;
state = aa_dfa_match(policy->dfa, state, data);
if (!state)
return 5;
*perms = *aa_lookup_perms(policy, state);
if (perms->allow & AA_MAY_MOUNT)
return 0;
}
/* failed at perms check, don't confuse with flags match */
return 6;
}
static int path_flags(struct aa_profile *profile, const struct path *path)
{
AA_BUG(!profile);
AA_BUG(!path);
return profile->path_flags |
(S_ISDIR(path->dentry->d_inode->i_mode) ? PATH_IS_DIR : 0);
}
/**
* 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
* @devname: string for the devname/src_name (MAY BE NULL OR ERRPTR)
* @type: string for the dev type (MAYBE NULL)
* @flags: mount flags to match
* @data: fs mount data (MAYBE NULL)
* @binary: whether @data is binary
* @devinfo: error str if (IS_ERR(@devname))
*
* Returns: 0 on success else error
*/
static int match_mnt_path_str(const struct cred *subj_cred,
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)
{
struct aa_perms perms = { };
const char *mntpnt = NULL, *info = NULL;
struct aa_ruleset *rules = profile->label.rules[0];
int pos, error;
AA_BUG(!profile);
AA_BUG(!mntpath);
AA_BUG(!buffer);
if (!RULE_MEDIATES(rules, AA_CLASS_MOUNT))
return 0;
error = aa_path_name(mntpath, path_flags(profile, mntpath), buffer,
&mntpnt, &info, profile->disconnected);
if (error)
goto audit;
if (IS_ERR(devname)) {
error = PTR_ERR(devname);
devname = NULL;
info = devinfo;
goto audit;
}
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;
audit:
return audit_mount(subj_cred, profile, OP_MOUNT, mntpnt, devname,
type, NULL,
flags, data, AA_MAY_MOUNT, &perms, info, error);
}
/**
* 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
* @devpath: path devname/src_name (MAYBE NULL)
* @devbuffer: buffer to be used to lookup devname/src_name
* @type: string for the dev type (MAYBE NULL)
* @flags: mount flags to match
* @data: fs mount data (MAYBE NULL)
* @binary: whether @data is binary
*
* Returns: 0 on success else error
*/
static int match_mnt(const struct cred *subj_cred,
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)
{
const char *devname = NULL, *info = NULL;
struct aa_ruleset *rules = profile->label.rules[0];
int error = -EACCES;
AA_BUG(!profile);
AA_BUG(devpath && !devbuffer);
if (!RULE_MEDIATES(rules, AA_CLASS_MOUNT))
return 0;
if (devpath) {
error = aa_path_name(devpath, path_flags(profile, devpath),
devbuffer, &devname, &info,
profile->disconnected);
if (error)
devname = ERR_PTR(error);
}
return match_mnt_path_str(subj_cred, profile, path, buffer, devname,
type, flags, data, binary, info);
}
int aa_remount(const struct cred *subj_cred,
struct aa_label *label, const struct path *path,
unsigned long flags, void *data)
{
struct aa_profile *profile;
char *buffer = NULL;
bool binary;
int error;
AA_BUG(!label);
AA_BUG(!path);
binary = path->dentry->d_sb->s_type->fs_flags & FS_BINARY_MOUNTDATA;
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));
aa_put_buffer(buffer);
return error;
}
int aa_bind_mount(const struct cred *subj_cred,
struct aa_label *label, const struct path *path,
const char *dev_name, unsigned long flags)
{
struct aa_profile *profile;
char *buffer = NULL, *old_buffer = NULL;
struct path old_path;
int error;
AA_BUG(!label);
AA_BUG(!path);
if (!dev_name || !*dev_name)
return -EINVAL;
flags &= MS_REC | MS_BIND;
error = kern_path(dev_name, LOOKUP_FOLLOW|LOOKUP_AUTOMOUNT, &old_path);
if (error)
return error;
buffer = aa_get_buffer(false);
old_buffer = aa_get_buffer(false);
error = -ENOMEM;
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));
out:
aa_put_buffer(buffer);
aa_put_buffer(old_buffer);
path_put(&old_path);
return error;
}
int aa_mount_change_type(const struct cred *subj_cred,
struct aa_label *label, const struct path *path,
unsigned long flags)
{
struct aa_profile *profile;
char *buffer = NULL;
int error;
AA_BUG(!label);
AA_BUG(!path);
/* These are the flags allowed by do_change_type() */
flags &= (MS_REC | MS_SILENT | MS_SHARED | MS_PRIVATE | MS_SLAVE |
MS_UNBINDABLE);
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));
aa_put_buffer(buffer);
return error;
}
int aa_move_mount(const struct cred *subj_cred,
struct aa_label *label, const struct path *from_path,
const struct path *to_path)
{
struct aa_profile *profile;
char *to_buffer = NULL, *from_buffer = NULL;
int error;
AA_BUG(!label);
AA_BUG(!from_path);
AA_BUG(!to_path);
to_buffer = aa_get_buffer(false);
from_buffer = aa_get_buffer(false);
error = -ENOMEM;
if (!to_buffer || !from_buffer)
goto out;
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));
out:
aa_put_buffer(to_buffer);
aa_put_buffer(from_buffer);
return error;
}
int aa_move_mount_old(const struct cred *subj_cred, struct aa_label *label,
const struct path *path, const char *orig_name)
{
struct path old_path;
int error;
if (!orig_name || !*orig_name)
return -EINVAL;
error = kern_path(orig_name, LOOKUP_FOLLOW, &old_path);
if (error)
return error;
error = aa_move_mount(subj_cred, label, &old_path, path);
path_put(&old_path);
return error;
}
int aa_new_mount(const struct cred *subj_cred, struct aa_label *label,
const char *dev_name, const struct path *path,
const char *type, unsigned long flags, void *data)
{
struct aa_profile *profile;
char *buffer = NULL, *dev_buffer = NULL;
bool binary = true;
int error;
int requires_dev = 0;
struct path tmp_path, *dev_path = NULL;
AA_BUG(!label);
AA_BUG(!path);
if (type) {
struct file_system_type *fstype;
fstype = get_fs_type(type);
if (!fstype)
return -ENODEV;
binary = fstype->fs_flags & FS_BINARY_MOUNTDATA;
requires_dev = fstype->fs_flags & FS_REQUIRES_DEV;
put_filesystem(fstype);
if (requires_dev) {
if (!dev_name || !*dev_name)
return -ENOENT;
error = kern_path(dev_name, LOOKUP_FOLLOW, &tmp_path);
if (error)
return error;
dev_path = &tmp_path;
}
}
buffer = aa_get_buffer(false);
if (!buffer) {
error = -ENOMEM;
goto out;
}
if (dev_path) {
dev_buffer = aa_get_buffer(false);
if (!dev_buffer) {
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));
} else {
error = fn_for_each_confined(label, profile,
match_mnt_path_str(subj_cred, profile, path,
buffer, dev_name,
type, flags, data, binary, NULL));
}
out:
aa_put_buffer(buffer);
aa_put_buffer(dev_buffer);
if (dev_path)
path_put(dev_path);
return error;
}
static int profile_umount(const struct cred *subj_cred,
struct aa_profile *profile, const struct path *path,
char *buffer)
{
struct aa_ruleset *rules = profile->label.rules[0];
struct aa_perms perms = { };
const char *name = NULL, *info = NULL;
aa_state_t state;
int error;
AA_BUG(!profile);
AA_BUG(!path);
if (!RULE_MEDIATES(rules, AA_CLASS_MOUNT))
return 0;
error = aa_path_name(path, path_flags(profile, path), buffer, &name,
&info, profile->disconnected);
if (error)
goto audit;
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);
}
int aa_umount(const struct cred *subj_cred, struct aa_label *label,
struct vfsmount *mnt, int flags)
{
struct aa_profile *profile;
char *buffer = NULL;
int error;
struct path path = { .mnt = mnt, .dentry = mnt->mnt_root };
AA_BUG(!label);
AA_BUG(!mnt);
buffer = aa_get_buffer(false);
if (!buffer)
return -ENOMEM;
error = fn_for_each_confined(label, profile,
profile_umount(subj_cred, profile, &path, buffer));
aa_put_buffer(buffer);
return error;
}
/* helper fn for transition on pivotroot
*
* 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,
const struct path *new_path,
char *new_buffer,
const struct path *old_path,
char *old_buffer)
{
struct aa_ruleset *rules = profile->label.rules[0];
const char *old_name, *new_name = NULL, *info = NULL;
const char *trans_name = NULL;
struct aa_perms perms = { };
aa_state_t state;
int error;
AA_BUG(!profile);
AA_BUG(!new_path);
AA_BUG(!old_path);
if (profile_unconfined(profile) ||
!RULE_MEDIATES(rules, AA_CLASS_MOUNT))
return aa_get_newest_label(&profile->label);
error = aa_path_name(old_path, path_flags(profile, old_path),
old_buffer, &old_name, &info,
profile->disconnected);
if (error)
goto audit;
error = aa_path_name(new_path, path_flags(profile, new_path),
new_buffer, &new_name, &info,
profile->disconnected);
if (error)
goto audit;
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);
if (AA_MAY_PIVOTROOT & perms.allow)
error = 0;
audit:
error = audit_mount(subj_cred, profile, OP_PIVOTROOT, new_name,
old_name,
NULL, trans_name, 0, NULL, AA_MAY_PIVOTROOT,
&perms, info, error);
if (error)
return ERR_PTR(error);
return aa_get_newest_label(&profile->label);
}
int aa_pivotroot(const struct cred *subj_cred, struct aa_label *label,
const struct path *old_path,
const struct path *new_path)
{
struct aa_profile *profile;
struct aa_label *target = NULL;
char *old_buffer = NULL, *new_buffer = NULL, *info = NULL;
int error;
AA_BUG(!label);
AA_BUG(!old_path);
AA_BUG(!new_path);
old_buffer = aa_get_buffer(false);
new_buffer = aa_get_buffer(false);
error = -ENOMEM;
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));
AA_BUG(!target);
if (!IS_ERR(target)) {
error = aa_replace_current_label(target);
if (error)
goto fail;
aa_put_label(target);
} else
/* already audited error */
error = PTR_ERR(target);
out:
aa_put_buffer(old_buffer);
aa_put_buffer(new_buffer);
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));
aa_put_label(target);
goto out;
}
+408
View File
@@ -0,0 +1,408 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* AppArmor security module
*
* This file contains AppArmor network mediation
*
* Copyright (C) 1998-2008 Novell/SUSE
* Copyright 2009-2017 Canonical Ltd.
*/
#include "include/af_unix.h"
#include "include/apparmor.h"
#include "include/audit.h"
#include "include/cred.h"
#include "include/label.h"
#include "include/net.h"
#include "include/policy.h"
#include "include/secid.h"
#include "net_names.h"
struct aa_sfs_entry aa_sfs_entry_network[] = {
AA_SFS_FILE_STRING("af_mask", AA_SFS_AF_MASK),
AA_SFS_FILE_BOOLEAN("tcp-fast-open", 1),
{ }
};
struct aa_sfs_entry aa_sfs_entry_networkv9[] = {
AA_SFS_FILE_STRING("af_mask", AA_SFS_AF_MASK),
AA_SFS_FILE_BOOLEAN("af_unix", 1),
AA_SFS_FILE_BOOLEAN("tcp-fast-open", 1),
{ }
};
static const char * const net_mask_names[] = {
"unknown",
"send",
"receive",
"unknown",
"create",
"shutdown",
"connect",
"unknown",
"setattr",
"getattr",
"setcred",
"getcred",
"chmod",
"chown",
"chgrp",
"lock",
"mmap",
"mprot",
"unknown",
"unknown",
"accept",
"bind",
"listen",
"unknown",
"setopt",
"getopt",
"unknown",
"unknown",
"unknown",
"unknown",
"unknown",
"unknown",
};
static void audit_unix_addr(struct audit_buffer *ab, const char *str,
struct sockaddr_un *addr, int addrlen)
{
int len = unix_addr_len(addrlen);
if (!addr || len <= 0) {
audit_log_format(ab, " %s=none", str);
} else if (addr->sun_path[0]) {
audit_log_format(ab, " %s=", str);
audit_log_untrustedstring(ab, addr->sun_path);
} else {
audit_log_format(ab, " %s=\"@", str);
if (audit_string_contains_control(&addr->sun_path[1], len - 1))
audit_log_n_hex(ab, &addr->sun_path[1], len - 1);
else
audit_log_format(ab, "%.*s", len - 1,
&addr->sun_path[1]);
audit_log_format(ab, "\"");
}
}
static void audit_unix_sk_addr(struct audit_buffer *ab, const char *str,
const struct sock *sk)
{
const struct unix_sock *u = unix_sk(sk);
if (u && u->addr) {
int addrlen;
struct sockaddr_un *addr = aa_sunaddr(u, &addrlen);
audit_unix_addr(ab, str, addr, addrlen);
} else {
audit_unix_addr(ab, str, NULL, 0);
}
}
/* audit callback for net specific fields */
void audit_net_cb(struct audit_buffer *ab, void *va)
{
struct common_audit_data *sa = va;
struct apparmor_audit_data *ad = aad(sa);
if (address_family_names[ad->common.u.net->family])
audit_log_format(ab, " family=\"%s\"",
address_family_names[ad->common.u.net->family]);
else
audit_log_format(ab, " family=\"unknown(%d)\"",
ad->common.u.net->family);
if (sock_type_names[ad->net.type])
audit_log_format(ab, " sock_type=\"%s\"",
sock_type_names[ad->net.type]);
else
audit_log_format(ab, " sock_type=\"unknown(%d)\"",
ad->net.type);
audit_log_format(ab, " protocol=%d", ad->net.protocol);
if (ad->request & NET_PERMS_MASK) {
audit_log_format(ab, " requested_mask=");
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=");
aa_audit_perm_mask(ab, ad->denied, NULL, 0,
net_mask_names, NET_PERMS_MASK);
}
}
if (ad->common.u.net->family == PF_UNIX) {
if (ad->net.addr || !ad->common.u.net->sk)
audit_unix_addr(ab, "addr",
unix_addr(ad->net.addr),
ad->net.addrlen);
else
audit_unix_sk_addr(ab, "addr", ad->common.u.net->sk);
if (ad->request & NET_PEER_MASK) {
audit_unix_addr(ab, "peer_addr",
unix_addr(ad->net.peer.addr),
ad->net.peer.addrlen);
}
}
if (ad->peer) {
audit_log_format(ab, " peer=");
aa_label_xaudit(ab, labels_ns(ad->subj_label), ad->peer,
FLAGS_NONE, GFP_ATOMIC);
}
}
/* 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)
{
struct aa_perms perms;
AA_BUG(!profile);
AA_BUG(!policy);
if (state || !p)
p = aa_lookup_perms(policy, state);
perms = *p;
aa_apply_modes_to_perms(profile, &perms);
return aa_check_perms(profile, &perms, request, ad,
audit_net_cb);
}
/* only continue match if
* insufficient current perms at current state
* indicates there are more perms in later state
* Returns: perms struct if early match
*/
static struct aa_perms *early_match(struct aa_policydb *policy,
aa_state_t state, u32 request)
{
struct aa_perms *p;
p = aa_lookup_perms(policy, state);
if (((p->allow & request) != request) && (p->allow & AA_CONT_MATCH))
return NULL;
return p;
}
static aa_state_t aa_dfa_match_be16(struct aa_dfa *dfa, aa_state_t state,
u16 data)
{
__be16 buffer = cpu_to_be16(data);
return aa_dfa_match_len(dfa, state, (char *) &buffer, 2);
}
/**
* aa_match_to_prot - match the af, type, protocol triplet
* @policy: policy being matched
* @state: state to start in
* @request: permissions being requested, ignored if @p == NULL
* @af: socket address family
* @type: socket type
* @protocol: socket protocol
* @p: output - pointer to permission associated with match
* @info: output - pointer to string describing failure
*
* RETURNS: state match stopped in.
*
* If @(p) is assigned a value the returned state will be the
* corresponding state. Will not set @p on failure or if match completes
* only if an early match occurs
*/
aa_state_t aa_match_to_prot(struct aa_policydb *policy, aa_state_t state,
u32 request, u16 af, int type, int protocol,
struct aa_perms **p, const char **info)
{
state = aa_dfa_match_be16(policy->dfa, state, (u16)af);
if (!state) {
*info = "failed af match";
return state;
}
state = aa_dfa_match_be16(policy->dfa, state, (u16)type);
if (state) {
if (p)
*p = early_match(policy, state, request);
if (!p || !*p) {
state = aa_dfa_match_be16(policy->dfa, state, (u16)protocol);
if (!state)
*info = "failed protocol match";
}
} else {
*info = "failed type match";
}
return state;
}
/* Generic af perm */
int aa_profile_af_perm(struct aa_profile *profile,
struct apparmor_audit_data *ad, u32 request, u16 family,
int type, int protocol)
{
struct aa_ruleset *rules = profile->label.rules[0];
struct aa_perms *p = NULL;
aa_state_t state;
AA_BUG(family >= AF_MAX);
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);
}
int aa_af_perm(const struct cred *subj_cred, struct aa_label *label,
const char *op, u32 request, u16 family, int type, int protocol)
{
struct aa_profile *profile;
DEFINE_AUDIT_NET(ad, op, subj_cred, NULL, family, type, protocol);
return fn_for_each_confined(label, profile,
aa_profile_af_perm(profile, &ad, request, family,
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)
{
struct aa_sk_ctx *ctx = aa_sock(sk);
int error = 0;
AA_BUG(!label);
AA_BUG(!sk);
if (rcu_access_pointer(ctx->label) != kernel_t && !unconfined(label)) {
struct aa_profile *profile;
DEFINE_AUDIT_SK(ad, op, subj_cred, sk);
ad.subj_cred = subj_cred;
error = fn_for_each_confined(label, profile,
aa_profile_af_sk_perm(profile, &ad, request, sk));
}
return error;
}
int aa_sk_perm(const char *op, u32 request, struct sock *sk)
{
struct aa_label *label;
int error;
AA_BUG(!sk);
AA_BUG(in_interrupt());
/* TODO: switch to begin_current_label ???? */
label = begin_current_label_crit_section();
error = aa_label_sk_perm(current_cred(), label, op, request, sk);
end_current_label_crit_section(label);
return error;
}
int aa_sock_file_perm(const struct cred *subj_cred, struct aa_label *label,
const char *op, u32 request, struct file *file)
{
struct socket *sock = (struct socket *) file->private_data;
AA_BUG(!label);
/* sock && sock->sk can be NULL for sockets being set up or torn down */
if (!sock || !sock->sk)
return 0;
if (sock->sk->sk_family == PF_UNIX)
return aa_unix_file_perm(subj_cred, label, op, request, file);
return aa_label_sk_perm(subj_cred, label, op, request, sock->sk);
}
#ifdef CONFIG_NETWORK_SECMARK
static int apparmor_secmark_init(struct aa_secmark *secmark)
{
struct aa_label *label;
if (secmark->label[0] == '*') {
secmark->secid = AA_SECID_WILDCARD;
return 0;
}
label = aa_label_strn_parse(&root_ns->unconfined->label,
secmark->label, strlen(secmark->label),
GFP_ATOMIC, false, false);
if (IS_ERR(label))
return PTR_ERR(label);
secmark->secid = label->secid;
aa_put_label(label);
return 0;
}
static int aa_secmark_perm(struct aa_profile *profile, u32 request, u32 secid,
struct apparmor_audit_data *ad)
{
int i, ret;
struct aa_perms perms = { };
struct aa_ruleset *rules = profile->label.rules[0];
if (rules->secmark_count == 0)
return 0;
for (i = 0; i < rules->secmark_count; i++) {
if (!rules->secmark[i].secid) {
ret = apparmor_secmark_init(&rules->secmark[i]);
if (ret)
return ret;
}
if (rules->secmark[i].secid == secid ||
rules->secmark[i].secid == AA_SECID_WILDCARD) {
if (rules->secmark[i].deny)
perms.deny = ALL_PERMS_MASK;
else
perms.allow = ALL_PERMS_MASK;
if (rules->secmark[i].audit)
perms.audit = ALL_PERMS_MASK;
}
}
aa_apply_modes_to_perms(profile, &perms);
return aa_check_perms(profile, &perms, request, ad, audit_net_cb);
}
int apparmor_secmark_check(struct aa_label *label, char *op, u32 request,
u32 secid, const struct sock *sk)
{
struct aa_profile *profile;
DEFINE_AUDIT_SK(ad, op, NULL, sk);
return fn_for_each_confined(label, profile,
aa_secmark_perm(profile, request, secid,
&ad));
}
#endif
+107
View File
@@ -0,0 +1,107 @@
0x1B, 0x5E, 0x78, 0x3D, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x04,
0x90, 0x00, 0x00, 0x6E, 0x6F, 0x74, 0x66, 0x6C, 0x65, 0x78, 0x00,
0x00, 0x00, 0x00, 0x01, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x04, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x04, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00,
0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00,
0x00, 0x00, 0x00, 0x08, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x03, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00
+222
View File
@@ -0,0 +1,222 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* AppArmor security module
*
* This file contains AppArmor function for pathnames
*
* Copyright (C) 1998-2008 Novell/SUSE
* Copyright 2009-2010 Canonical Ltd.
*/
#include <linux/magic.h>
#include <linux/mount.h>
#include <linux/namei.h>
#include <linux/nsproxy.h>
#include <linux/path.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <linux/fs_struct.h>
#include "include/apparmor.h"
#include "include/path.h"
#include "include/policy.h"
/* modified from dcache.c */
static int prepend(char **buffer, int buflen, const char *str, int namelen)
{
buflen -= namelen;
if (buflen < 0)
return -ENAMETOOLONG;
*buffer -= namelen;
memcpy(*buffer, str, namelen);
return 0;
}
#define CHROOT_NSCONNECT (PATH_CHROOT_REL | PATH_CHROOT_NSCONNECT)
/* If the path is not connected to the expected root,
* check if it is a sysctl and handle specially else remove any
* leading / that __d_path may have returned.
* Unless
* specifically directed to connect the path,
* OR
* if in a chroot and doing chroot relative paths and the path
* resolves to the namespace root (would be connected outside
* of chroot) and specifically directed to connect paths to
* namespace root.
*/
static int disconnect(const struct path *path, char *buf, char **name,
int flags, const char *disconnected)
{
int error = 0;
if (!(flags & PATH_CONNECT_PATH) &&
!(((flags & CHROOT_NSCONNECT) == CHROOT_NSCONNECT) &&
our_mnt(path->mnt))) {
/* disconnected path, don't return pathname starting
* with '/'
*/
error = -EACCES;
if (**name == '/')
*name = *name + 1;
} else {
if (**name != '/')
/* CONNECT_PATH with missing root */
error = prepend(name, *name - buf, "/", 1);
if (!error && disconnected)
error = prepend(name, *name - buf, disconnected,
strlen(disconnected));
}
return error;
}
/**
* d_namespace_path - lookup a name associated with a given path
* @path: path to lookup (NOT NULL)
* @buf: buffer to store path to (NOT NULL)
* @name: Returns - pointer for start of path name with in @buf (NOT NULL)
* @flags: flags controlling path lookup
* @disconnected: string to prefix to disconnected paths
*
* Handle path name lookup.
*
* Returns: %0 else error code if path lookup fails
* When no error the path name is returned in @name which points to
* a position in @buf
*/
static int d_namespace_path(const struct path *path, char *buf, char **name,
int flags, const char *disconnected)
{
char *res;
int error = 0;
int connected = 1;
int isdir = (flags & PATH_IS_DIR) ? 1 : 0;
int buflen = aa_g_path_max - isdir;
if (path->mnt->mnt_flags & MNT_INTERNAL) {
/* it's not mounted anywhere */
res = dentry_path(path->dentry, buf, buflen);
*name = res;
if (IS_ERR(res)) {
*name = buf;
return PTR_ERR(res);
}
if (path->dentry->d_sb->s_magic == PROC_SUPER_MAGIC &&
strncmp(*name, "/sys/", 5) == 0) {
/* TODO: convert over to using a per namespace
* control instead of hard coded /proc
*/
error = prepend(name, *name - buf, "/proc", 5);
goto out;
} else
error = disconnect(path, buf, name, flags,
disconnected);
goto out;
}
/* resolve paths relative to chroot?*/
if (flags & PATH_CHROOT_REL) {
struct path root;
get_fs_root(current->fs, &root);
res = __d_path(path, &root, buf, buflen);
path_put(&root);
} else {
res = d_absolute_path(path, buf, buflen);
if (!our_mnt(path->mnt))
connected = 0;
}
/* handle error conditions - and still allow a partial path to
* be returned.
*/
if (IS_ERR_OR_NULL(res)) {
if (PTR_ERR(res) == -ENAMETOOLONG) {
error = -ENAMETOOLONG;
*name = buf;
goto out;
}
connected = 0;
res = dentry_path_raw(path->dentry, buf, buflen);
if (IS_ERR(res)) {
error = PTR_ERR(res);
*name = buf;
goto out;
}
} else if (!our_mnt(path->mnt))
connected = 0;
*name = res;
if (!connected)
error = disconnect(path, buf, name, flags, disconnected);
/* Handle two cases:
* 1. A deleted dentry && profile is not allowing mediation of deleted
* 2. On some filesystems, newly allocated dentries appear to the
* security_path hooks as a deleted dentry except without an inode
* allocated.
*/
if (d_unlinked(path->dentry) && d_is_positive(path->dentry) &&
!(flags & (PATH_MEDIATE_DELETED | PATH_DELEGATE_DELETED))) {
error = -ENOENT;
goto out;
}
out:
/* Append "/" to directory paths and reterminate string, except for
* root "/" which already ends in a slash.
*/
if (!error && isdir) {
bool is_root = (*name)[0] == '/' && (*name)[1] == '\0';
if (!is_root) {
buf[aa_g_path_max - 2] = '/';
buf[aa_g_path_max - 1] = '\0';
}
}
return error;
}
/**
* aa_path_name - get the pathname to a buffer ensure dir / is appended
* @path: path the file (NOT NULL)
* @flags: flags controlling path name generation
* @buffer: buffer to put name in (NOT NULL)
* @name: Returns - the generated path name if !error (NOT NULL)
* @info: Returns - information on why the path lookup failed (MAYBE NULL)
* @disconnected: string to prepend to disconnected paths
*
* @name is a pointer to the beginning of the pathname (which usually differs
* from the beginning of the buffer), or NULL. If there is an error @name
* may contain a partial or invalid name that can be used for audit purposes,
* but it can not be used for mediation.
*
* We need PATH_IS_DIR to indicate whether the file is a directory or not
* because the file may not yet exist, and so we cannot check the inode's
* file type.
*
* Returns: %0 else error code if could retrieve name
*/
int aa_path_name(const struct path *path, int flags, char *buffer,
const char **name, const char **info, const char *disconnected)
{
char *str = NULL;
int error = d_namespace_path(path, buffer, &str, flags, disconnected);
if (info && error) {
if (error == -ENOENT)
*info = "Failed name lookup - deleted entry";
else if (error == -EACCES)
*info = "Failed name lookup - disconnected path";
else if (error == -ENAMETOOLONG)
*info = "Failed name lookup - name too long";
else
*info = "Failed name lookup";
}
*name = str;
return error;
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,333 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* AppArmor security module
*
* This file contains AppArmor functions for unpacking policy loaded
* from userspace.
*
* Copyright (C) 1998-2008 Novell/SUSE
* Copyright 2009-2022 Canonical Ltd.
*
* Code to provide backwards compatibility with older policy versions,
* by converting/mapping older policy formats into the newer internal
* formats.
*/
#include <linux/ctype.h>
#include <linux/errno.h>
#include "include/lib.h"
#include "include/policy_unpack.h"
#include "include/policy_compat.h"
/* remap old accept table embedded permissions to separate permission table */
static u32 dfa_map_xindex(u16 mask)
{
u16 old_index = (mask >> 10) & 0xf;
u32 index = 0;
if (mask & 0x100)
index |= AA_X_UNSAFE;
if (mask & 0x200)
index |= AA_X_INHERIT;
if (mask & 0x80)
index |= AA_X_UNCONFINED;
if (old_index == 1) {
index |= AA_X_UNCONFINED;
} else if (old_index == 2) {
index |= AA_X_NAME;
} else if (old_index == 3) {
index |= AA_X_NAME | AA_X_CHILD;
} else if (old_index) {
index |= AA_X_TABLE;
index |= old_index - 4;
}
return index;
}
/*
* map old dfa inline permissions to new format
*/
#define dfa_user_allow(dfa, state) (((ACCEPT_TABLE(dfa)[state]) & 0x7f) | \
((ACCEPT_TABLE(dfa)[state]) & 0x80000000))
#define dfa_user_xbits(dfa, state) (((ACCEPT_TABLE(dfa)[state]) >> 7) & 0x7f)
#define dfa_user_audit(dfa, state) ((ACCEPT_TABLE2(dfa)[state]) & 0x7f)
#define dfa_user_quiet(dfa, state) (((ACCEPT_TABLE2(dfa)[state]) >> 7) & 0x7f)
#define dfa_user_xindex(dfa, state) \
(dfa_map_xindex(ACCEPT_TABLE(dfa)[state] & 0x3fff))
#define dfa_other_allow(dfa, state) ((((ACCEPT_TABLE(dfa)[state]) >> 14) & \
0x7f) | \
((ACCEPT_TABLE(dfa)[state]) & 0x80000000))
#define dfa_other_xbits(dfa, state) \
((((ACCEPT_TABLE(dfa)[state]) >> 7) >> 14) & 0x7f)
#define dfa_other_audit(dfa, state) (((ACCEPT_TABLE2(dfa)[state]) >> 14) & 0x7f)
#define dfa_other_quiet(dfa, state) \
((((ACCEPT_TABLE2(dfa)[state]) >> 7) >> 14) & 0x7f)
#define dfa_other_xindex(dfa, state) \
dfa_map_xindex((ACCEPT_TABLE(dfa)[state] >> 14) & 0x3fff)
/**
* map_old_perms - map old file perms layout to the new layout
* @old: permission set in old mapping
*
* Returns: new permission mapping
*/
static u32 map_old_perms(u32 old)
{
u32 new = old & 0xf;
if (old & MAY_READ)
new |= AA_MAY_GETATTR | AA_MAY_OPEN;
if (old & MAY_WRITE)
new |= AA_MAY_SETATTR | AA_MAY_CREATE | AA_MAY_DELETE |
AA_MAY_CHMOD | AA_MAY_CHOWN | AA_MAY_OPEN;
if (old & 0x10)
new |= AA_MAY_LINK;
/* the old mapping lock and link_subset flags where overlaid
* and use was determined by part of a pair that they were in
*/
if (old & 0x20)
new |= AA_MAY_LOCK | AA_LINK_SUBSET;
if (old & 0x40) /* AA_EXEC_MMAP */
new |= AA_EXEC_MMAP;
return new;
}
static void compute_fperms_allow(struct aa_perms *perms, struct aa_dfa *dfa,
aa_state_t state)
{
perms->allow |= AA_MAY_GETATTR;
/* change_profile wasn't determined by ownership in old mapping */
if (ACCEPT_TABLE(dfa)[state] & 0x80000000)
perms->allow |= AA_MAY_CHANGE_PROFILE;
if (ACCEPT_TABLE(dfa)[state] & 0x40000000)
perms->allow |= AA_MAY_ONEXEC;
}
static struct aa_perms compute_fperms_user(struct aa_dfa *dfa,
aa_state_t state)
{
struct aa_perms perms = { };
perms.allow = map_old_perms(dfa_user_allow(dfa, state));
perms.audit = map_old_perms(dfa_user_audit(dfa, state));
perms.quiet = map_old_perms(dfa_user_quiet(dfa, state));
perms.xindex = dfa_user_xindex(dfa, state);
compute_fperms_allow(&perms, dfa, state);
return perms;
}
static struct aa_perms compute_fperms_other(struct aa_dfa *dfa,
aa_state_t state)
{
struct aa_perms perms = { };
perms.allow = map_old_perms(dfa_other_allow(dfa, state));
perms.audit = map_old_perms(dfa_other_audit(dfa, state));
perms.quiet = map_old_perms(dfa_other_quiet(dfa, state));
perms.xindex = dfa_other_xindex(dfa, state);
compute_fperms_allow(&perms, dfa, state);
return perms;
}
/**
* compute_fperms - convert dfa compressed perms to internal perms and store
* them so they can be retrieved later.
* @dfa: a dfa using fperms to remap to internal permissions
* @size: Returns the permission table size
*
* Returns: remapped perm table
*/
static struct aa_perms *compute_fperms(struct aa_dfa *dfa,
u32 *size)
{
aa_state_t state;
unsigned int state_count;
struct aa_perms *table;
AA_BUG(!dfa);
state_count = dfa->tables[YYTD_ID_BASE]->td_lolen;
/* DFAs are restricted from having a state_count of less than 2 */
table = kvzalloc_objs(struct aa_perms, state_count * 2);
if (!table)
return NULL;
*size = state_count * 2;
for (state = 0; state < state_count; state++) {
table[state * 2] = compute_fperms_user(dfa, state);
table[state * 2 + 1] = compute_fperms_other(dfa, state);
}
return table;
}
static struct aa_perms *compute_xmatch_perms(struct aa_dfa *xmatch,
u32 *size)
{
struct aa_perms *perms;
int state;
int state_count;
AA_BUG(!xmatch);
state_count = xmatch->tables[YYTD_ID_BASE]->td_lolen;
/* DFAs are restricted from having a state_count of less than 2 */
perms = kvzalloc_objs(struct aa_perms, state_count);
if (!perms)
return NULL;
*size = state_count;
/* zero init so skip the trap state (state == 0) */
for (state = 1; state < state_count; state++)
perms[state].allow = dfa_user_allow(xmatch, state);
return perms;
}
static u32 map_other(u32 x)
{
return ((x & 0x3) << 8) | /* SETATTR/GETATTR */
((x & 0x1c) << 18) | /* ACCEPT/BIND/LISTEN */
((x & 0x60) << 19); /* SETOPT/GETOPT */
}
static u32 map_xbits(u32 x)
{
return ((x & 0x1) << 7) |
((x & 0x7e) << 9);
}
static struct aa_perms compute_perms_entry(struct aa_dfa *dfa,
aa_state_t state,
u32 version)
{
struct aa_perms perms = { };
perms.allow = dfa_user_allow(dfa, state);
perms.audit = dfa_user_audit(dfa, state);
perms.quiet = dfa_user_quiet(dfa, state);
/*
* This mapping is convulated due to history.
* v1-v4: only file perms, which are handled by compute_fperms
* v5: added policydb which dropped user conditional to gain new
* perm bits, but had to map around the xbits because the
* userspace compiler was still munging them.
* v9: adds using the xbits in policydb because the compiler now
* supports treating policydb permission bits different.
* Unfortunately there is no way to force auditing on the
* perms represented by the xbits
*/
perms.allow |= map_other(dfa_other_allow(dfa, state));
if (VERSION_LE(version, v8))
perms.allow |= AA_MAY_LOCK;
else
perms.allow |= map_xbits(dfa_user_xbits(dfa, state));
/*
* for v5-v9 perm mapping in the policydb, the other set is used
* to extend the general perm set
*/
perms.audit |= map_other(dfa_other_audit(dfa, state));
perms.quiet |= map_other(dfa_other_quiet(dfa, state));
if (VERSION_GT(version, v8))
perms.quiet |= map_xbits(dfa_other_xbits(dfa, state));
return perms;
}
static struct aa_perms *compute_perms(struct aa_dfa *dfa, u32 version,
u32 *size)
{
unsigned int state;
unsigned int state_count;
struct aa_perms *table;
AA_BUG(!dfa);
state_count = dfa->tables[YYTD_ID_BASE]->td_lolen;
/* DFAs are restricted from having a state_count of less than 2 */
table = kvzalloc_objs(struct aa_perms, state_count);
if (!table)
return NULL;
*size = state_count;
/* zero init so skip the trap state (state == 0) */
for (state = 1; state < state_count; state++) {
table[state] = compute_perms_entry(dfa, state, version);
AA_DEBUG(DEBUG_UNPACK,
"[%d]: (0x%x/0x%x/0x%x//0x%x/0x%x//0x%x), converted from accept1: 0x%x, accept2: 0x%x",
state, table[state].allow, table[state].deny,
table[state].prompt, table[state].audit,
table[state].quiet, table[state].xindex,
ACCEPT_TABLE(dfa)[state], ACCEPT_TABLE2(dfa)[state]);
}
return table;
}
/**
* remap_dfa_accept - remap old dfa accept table to be an index
* @dfa: dfa to do the remapping on
* @factor: scaling factor for the index conversion.
*
* Used in conjunction with compute_Xperms, it converts old style perms
* that are encoded in the dfa accept tables to the new style where
* there is a permission table and the accept table is an index into
* the permission table.
*/
static void remap_dfa_accept(struct aa_dfa *dfa, unsigned int factor)
{
unsigned int state;
unsigned int state_count = dfa->tables[YYTD_ID_BASE]->td_lolen;
AA_BUG(!dfa);
for (state = 0; state < state_count; state++) {
ACCEPT_TABLE(dfa)[state] = state * factor;
ACCEPT_TABLE2(dfa)[state] = factor > 1 ? ACCEPT_FLAG_OWNER : 0;
}
}
/* TODO: merge different dfa mappings into single map_policy fn */
int aa_compat_map_xmatch(struct aa_policydb *policy)
{
policy->perms = compute_xmatch_perms(policy->dfa, &policy->size);
if (!policy->perms)
return -ENOMEM;
remap_dfa_accept(policy->dfa, 1);
return 0;
}
int aa_compat_map_policy(struct aa_policydb *policy, u32 version)
{
policy->perms = compute_perms(policy->dfa, version, &policy->size);
if (!policy->perms)
return -ENOMEM;
remap_dfa_accept(policy->dfa, 1);
return 0;
}
int aa_compat_map_file(struct aa_policydb *policy)
{
policy->perms = compute_fperms(policy->dfa, &policy->size);
if (!policy->perms)
return -ENOMEM;
remap_dfa_accept(policy->dfa, 2);
return 0;
}
+398
View File
@@ -0,0 +1,398 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* AppArmor security module
*
* This file contains AppArmor policy manipulation functions
*
* Copyright (C) 1998-2008 Novell/SUSE
* Copyright 2009-2017 Canonical Ltd.
*
* AppArmor policy namespaces, allow for different sets of policies
* to be loaded for tasks within the namespace.
*/
#include <linux/list.h>
#include <linux/mutex.h>
#include <linux/slab.h>
#include <linux/string.h>
#include "include/apparmor.h"
#include "include/cred.h"
#include "include/policy_ns.h"
#include "include/label.h"
#include "include/policy.h"
/* kernel label */
struct aa_label *kernel_t;
/* root profile namespace */
struct aa_ns *root_ns;
const char *aa_hidden_ns_name = "---";
/**
* aa_ns_visible - test if @view is visible from @curr
* @curr: namespace to treat as the parent (NOT NULL)
* @view: namespace to test if visible from @curr (NOT NULL)
* @subns: whether view of a subns is allowed
*
* Returns: true if @view is visible from @curr else false
*/
bool aa_ns_visible(struct aa_ns *curr, struct aa_ns *view, bool subns)
{
if (curr == view)
return true;
if (!subns)
return false;
for ( ; view; view = view->parent) {
if (view->parent == curr)
return true;
}
return false;
}
/**
* aa_ns_name - Find the ns name to display for @view from @curr
* @curr: current namespace (NOT NULL)
* @view: namespace attempting to view (NOT NULL)
* @subns: are subns visible
*
* Returns: name of @view visible from @curr
*/
const char *aa_ns_name(struct aa_ns *curr, struct aa_ns *view, bool subns)
{
/* if view == curr then the namespace name isn't displayed */
if (curr == view)
return "";
if (aa_ns_visible(curr, view, subns)) {
/* at this point if a ns is visible it is in a view ns
* thus the curr ns.hname is a prefix of its name.
* Only output the virtualized portion of the name
* Add + 2 to skip over // separating curr hname prefix
* from the visible tail of the views hname
*/
return view->base.hname + strlen(curr->base.hname) + 2;
}
return aa_hidden_ns_name;
}
static struct aa_profile *alloc_unconfined(const char *name)
{
struct aa_profile *profile;
profile = aa_alloc_null(NULL, name, GFP_KERNEL);
if (!profile)
return NULL;
profile->label.flags |= FLAG_IX_ON_NAME_ERROR |
FLAG_IMMUTIBLE | FLAG_NS_COUNT | FLAG_UNCONFINED;
profile->mode = APPARMOR_UNCONFINED;
return profile;
}
/**
* alloc_ns - allocate, initialize and return a new namespace
* @prefix: parent namespace name (MAYBE NULL)
* @name: a preallocated name (NOT NULL)
*
* Returns: refcounted namespace or NULL on failure.
*/
static struct aa_ns *alloc_ns(const char *prefix, const char *name)
{
struct aa_ns *ns;
ns = kzalloc_obj(*ns);
AA_DEBUG(DEBUG_POLICY, "%s(%p)\n", __func__, ns);
if (!ns)
return NULL;
if (!aa_policy_init(&ns->base, prefix, name, GFP_KERNEL))
goto fail_ns;
INIT_LIST_HEAD(&ns->sub_ns);
INIT_LIST_HEAD(&ns->rawdata_list);
mutex_init(&ns->lock);
init_waitqueue_head(&ns->wait);
/* released by aa_free_ns() */
ns->unconfined = alloc_unconfined("unconfined");
if (!ns->unconfined)
goto fail_unconfined;
/* ns and ns->unconfined share ns->unconfined refcount */
ns->unconfined->ns = ns;
atomic_set(&ns->uniq_null, 0);
aa_labelset_init(&ns->labels);
return ns;
fail_unconfined:
aa_policy_destroy(&ns->base);
fail_ns:
kfree_sensitive(ns);
return NULL;
}
/**
* aa_free_ns - free a profile namespace
* @ns: the namespace to free (MAYBE NULL)
*
* Requires: All references to the namespace must have been put, if the
* namespace was referenced by a profile confining a task,
*/
void aa_free_ns(struct aa_ns *ns)
{
if (!ns)
return;
aa_policy_destroy(&ns->base);
aa_labelset_destroy(&ns->labels);
aa_put_ns(ns->parent);
ns->unconfined->ns = NULL;
aa_free_profile(ns->unconfined);
kfree_sensitive(ns);
}
/**
* __aa_lookupn_ns - lookup the namespace matching @hname
* @view: namespace to search in (NOT NULL)
* @hname: hierarchical ns name (NOT NULL)
* @n: length of @hname
*
* Requires: rcu_read_lock be held
*
* Returns: unrefcounted ns pointer or NULL if not found
*
* Do a relative name lookup, recursing through profile tree.
*/
struct aa_ns *__aa_lookupn_ns(struct aa_ns *view, const char *hname, size_t n)
{
struct aa_ns *ns = view;
const char *split;
for (split = strnstr(hname, "//", n); split;
split = strnstr(hname, "//", n)) {
ns = __aa_findn_ns(&ns->sub_ns, hname, split - hname);
if (!ns)
return NULL;
n -= split + 2 - hname;
hname = split + 2;
}
if (n)
return __aa_findn_ns(&ns->sub_ns, hname, n);
return NULL;
}
/**
* aa_lookupn_ns - look up a policy namespace relative to @view
* @view: namespace to search in (NOT NULL)
* @name: name of namespace to find (NOT NULL)
* @n: length of @name
*
* Returns: a refcounted namespace on the list, or NULL if no namespace
* called @name exists.
*
* refcount released by caller
*/
struct aa_ns *aa_lookupn_ns(struct aa_ns *view, const char *name, size_t n)
{
struct aa_ns *ns = NULL;
rcu_read_lock();
ns = aa_get_ns(__aa_lookupn_ns(view, name, n));
rcu_read_unlock();
return ns;
}
static struct aa_ns *__aa_create_ns(struct aa_ns *parent, const char *name,
struct dentry *dir)
{
struct aa_ns *ns;
int error;
AA_BUG(!parent);
AA_BUG(!name);
AA_BUG(!mutex_is_locked(&parent->lock));
if (parent->level > MAX_NS_DEPTH)
return ERR_PTR(-ENOSPC);
ns = alloc_ns(parent->base.hname, name);
if (!ns)
return ERR_PTR(-ENOMEM);
ns->level = parent->level + 1;
mutex_lock_nested(&ns->lock, ns->level);
error = __aafs_ns_mkdir(ns, ns_subns_dir(parent), name, dir);
if (error) {
AA_ERROR("Failed to create interface for ns %s\n",
ns->base.name);
mutex_unlock(&ns->lock);
aa_free_ns(ns);
return ERR_PTR(error);
}
ns->parent = aa_get_ns(parent);
list_add_rcu(&ns->base.list, &parent->sub_ns);
/* add list ref */
aa_get_ns(ns);
mutex_unlock(&ns->lock);
return ns;
}
/**
* __aa_find_or_create_ns - create an ns, fail if it already exists
* @parent: the parent of the namespace being created
* @name: the name of the namespace
* @dir: if not null the dir to put the ns entries in
*
* Returns: the a refcounted ns that has been add or an ERR_PTR
*/
struct aa_ns *__aa_find_or_create_ns(struct aa_ns *parent, const char *name,
struct dentry *dir)
{
struct aa_ns *ns;
AA_BUG(!mutex_is_locked(&parent->lock));
/* try and find the specified ns */
/* released by caller */
ns = aa_get_ns(__aa_find_ns(&parent->sub_ns, name));
if (!ns)
ns = __aa_create_ns(parent, name, dir);
else
ns = ERR_PTR(-EEXIST);
/* return ref */
return ns;
}
/**
* aa_prepare_ns - find an existing or create a new namespace of @name
* @parent: ns to treat as parent
* @name: the namespace to find or add (NOT NULL)
*
* Returns: refcounted namespace or PTR_ERR if failed to create one
*/
struct aa_ns *aa_prepare_ns(struct aa_ns *parent, const char *name)
{
struct aa_ns *ns;
mutex_lock_nested(&parent->lock, parent->level);
/* try and find the specified ns and if it doesn't exist create it */
/* released by caller */
ns = aa_get_ns(__aa_find_ns(&parent->sub_ns, name));
if (!ns)
ns = __aa_create_ns(parent, name, NULL);
mutex_unlock(&parent->lock);
/* return ref */
return ns;
}
static void __ns_list_release(struct list_head *head);
/**
* destroy_ns - remove everything contained by @ns
* @ns: namespace to have it contents removed (NOT NULL)
*/
static void destroy_ns(struct aa_ns *ns)
{
if (!ns)
return;
mutex_lock_nested(&ns->lock, ns->level);
/* release all profiles in this namespace */
__aa_profile_list_release(&ns->base.profiles);
/* release all sub namespaces */
__ns_list_release(&ns->sub_ns);
if (ns->parent) {
unsigned long flags;
write_lock_irqsave(&ns->labels.lock, flags);
__aa_proxy_redirect(ns_unconfined(ns),
ns_unconfined(ns->parent));
write_unlock_irqrestore(&ns->labels.lock, flags);
}
__aafs_ns_rmdir(ns);
mutex_unlock(&ns->lock);
}
/**
* __aa_remove_ns - remove a namespace and all its children
* @ns: namespace to be removed (NOT NULL)
*
* Requires: ns->parent->lock be held and ns removed from parent.
*/
void __aa_remove_ns(struct aa_ns *ns)
{
/* remove ns from namespace list */
list_del_rcu(&ns->base.list);
destroy_ns(ns);
aa_put_ns(ns);
}
/**
* __ns_list_release - remove all profile namespaces on the list put refs
* @head: list of profile namespaces (NOT NULL)
*
* Requires: namespace lock be held
*/
static void __ns_list_release(struct list_head *head)
{
struct aa_ns *ns, *tmp;
list_for_each_entry_safe(ns, tmp, head, base.list)
__aa_remove_ns(ns);
}
/**
* aa_alloc_root_ns - allocate the root profile namespace
*
* Returns: %0 on success else error
*
*/
int __init aa_alloc_root_ns(void)
{
struct aa_profile *kernel_p;
/* released by aa_free_root_ns - used as list ref*/
root_ns = alloc_ns(NULL, "root");
if (!root_ns)
return -ENOMEM;
kernel_p = alloc_unconfined("kernel_t");
if (!kernel_p) {
destroy_ns(root_ns);
aa_free_ns(root_ns);
return -ENOMEM;
}
kernel_t = &kernel_p->label;
root_ns->unconfined->ns = aa_get_ns(root_ns);
return 0;
}
/**
* aa_free_root_ns - free the root profile namespace
*/
void __init aa_free_root_ns(void)
{
struct aa_ns *ns = root_ns;
root_ns = NULL;
aa_label_free(kernel_t);
destroy_ns(ns);
aa_put_ns(ns);
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,616 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* KUnit tests for AppArmor's policy unpack.
*/
#include <kunit/test.h>
#include <kunit/visibility.h>
#include "include/policy.h"
#include "include/policy_unpack.h"
#include <linux/unaligned.h>
#define TEST_STRING_NAME "TEST_STRING"
#define TEST_STRING_DATA "testing"
#define TEST_STRING_BUF_OFFSET \
(3 + strlen(TEST_STRING_NAME) + 1)
#define TEST_U32_NAME "U32_TEST"
#define TEST_U32_DATA ((u32)0x01020304)
#define TEST_NAMED_U32_BUF_OFFSET \
(TEST_STRING_BUF_OFFSET + 3 + strlen(TEST_STRING_DATA) + 1)
#define TEST_U32_BUF_OFFSET \
(TEST_NAMED_U32_BUF_OFFSET + 3 + strlen(TEST_U32_NAME) + 1)
#define TEST_U16_OFFSET (TEST_U32_BUF_OFFSET + 3)
#define TEST_U16_DATA ((u16)(TEST_U32_DATA >> 16))
#define TEST_U64_NAME "U64_TEST"
#define TEST_U64_DATA ((u64)0x0102030405060708)
#define TEST_NAMED_U64_BUF_OFFSET (TEST_U32_BUF_OFFSET + sizeof(u32) + 1)
#define TEST_U64_BUF_OFFSET \
(TEST_NAMED_U64_BUF_OFFSET + 3 + strlen(TEST_U64_NAME) + 1)
#define TEST_BLOB_NAME "BLOB_TEST"
#define TEST_BLOB_DATA "\xde\xad\x00\xbe\xef"
#define TEST_BLOB_DATA_SIZE (ARRAY_SIZE(TEST_BLOB_DATA))
#define TEST_NAMED_BLOB_BUF_OFFSET (TEST_U64_BUF_OFFSET + sizeof(u64) + 1)
#define TEST_BLOB_BUF_OFFSET \
(TEST_NAMED_BLOB_BUF_OFFSET + 3 + strlen(TEST_BLOB_NAME) + 1)
#define TEST_ARRAY_NAME "ARRAY_TEST"
#define TEST_ARRAY_SIZE 16
#define TEST_NAMED_ARRAY_BUF_OFFSET \
(TEST_BLOB_BUF_OFFSET + 5 + TEST_BLOB_DATA_SIZE)
#define TEST_ARRAY_BUF_OFFSET \
(TEST_NAMED_ARRAY_BUF_OFFSET + 3 + strlen(TEST_ARRAY_NAME) + 1)
MODULE_IMPORT_NS("EXPORTED_FOR_KUNIT_TESTING");
struct policy_unpack_fixture {
struct aa_ext *e;
size_t e_size;
};
static struct aa_ext *build_aa_ext_struct(struct policy_unpack_fixture *puf,
struct kunit *test, size_t buf_size)
{
char *buf;
struct aa_ext *e;
buf = kunit_kzalloc(test, buf_size, GFP_USER);
KUNIT_EXPECT_NOT_ERR_OR_NULL(test, buf);
e = kunit_kmalloc(test, sizeof(*e), GFP_USER);
KUNIT_EXPECT_NOT_ERR_OR_NULL(test, e);
e->start = buf;
e->end = e->start + buf_size;
e->pos = e->start;
*buf = AA_NAME;
*(buf + 1) = strlen(TEST_STRING_NAME) + 1;
strscpy(buf + 3, TEST_STRING_NAME, e->end - (void *)(buf + 3));
buf = e->start + TEST_STRING_BUF_OFFSET;
*buf = AA_STRING;
*(buf + 1) = strlen(TEST_STRING_DATA) + 1;
strscpy(buf + 3, TEST_STRING_DATA, e->end - (void *)(buf + 3));
buf = e->start + TEST_NAMED_U32_BUF_OFFSET;
*buf = AA_NAME;
*(buf + 1) = strlen(TEST_U32_NAME) + 1;
strscpy(buf + 3, TEST_U32_NAME, e->end - (void *)(buf + 3));
*(buf + 3 + strlen(TEST_U32_NAME) + 1) = AA_U32;
put_unaligned_le32(TEST_U32_DATA, buf + 3 + strlen(TEST_U32_NAME) + 2);
buf = e->start + TEST_NAMED_U64_BUF_OFFSET;
*buf = AA_NAME;
*(buf + 1) = strlen(TEST_U64_NAME) + 1;
strscpy(buf + 3, TEST_U64_NAME, e->end - (void *)(buf + 3));
*(buf + 3 + strlen(TEST_U64_NAME) + 1) = AA_U64;
*((__le64 *)(buf + 3 + strlen(TEST_U64_NAME) + 2)) = cpu_to_le64(TEST_U64_DATA);
buf = e->start + TEST_NAMED_BLOB_BUF_OFFSET;
*buf = AA_NAME;
*(buf + 1) = strlen(TEST_BLOB_NAME) + 1;
strscpy(buf + 3, TEST_BLOB_NAME, e->end - (void *)(buf + 3));
*(buf + 3 + strlen(TEST_BLOB_NAME) + 1) = AA_BLOB;
*(buf + 3 + strlen(TEST_BLOB_NAME) + 2) = TEST_BLOB_DATA_SIZE;
memcpy(buf + 3 + strlen(TEST_BLOB_NAME) + 6,
TEST_BLOB_DATA, TEST_BLOB_DATA_SIZE);
buf = e->start + TEST_NAMED_ARRAY_BUF_OFFSET;
*buf = AA_NAME;
*(buf + 1) = strlen(TEST_ARRAY_NAME) + 1;
strscpy(buf + 3, TEST_ARRAY_NAME, e->end - (void *)(buf + 3));
*(buf + 3 + strlen(TEST_ARRAY_NAME) + 1) = AA_ARRAY;
put_unaligned_le16(TEST_ARRAY_SIZE, buf + 3 + strlen(TEST_ARRAY_NAME) + 2);
return e;
}
static int policy_unpack_test_init(struct kunit *test)
{
size_t e_size = TEST_ARRAY_BUF_OFFSET + sizeof(u16) + 1;
struct policy_unpack_fixture *puf;
puf = kunit_kmalloc(test, sizeof(*puf), GFP_USER);
KUNIT_EXPECT_NOT_ERR_OR_NULL(test, puf);
puf->e_size = e_size;
puf->e = build_aa_ext_struct(puf, test, e_size);
test->priv = puf;
return 0;
}
static void policy_unpack_test_inbounds_when_inbounds(struct kunit *test)
{
struct policy_unpack_fixture *puf = test->priv;
KUNIT_EXPECT_TRUE(test, aa_inbounds(puf->e, 0));
KUNIT_EXPECT_TRUE(test, aa_inbounds(puf->e, puf->e_size / 2));
KUNIT_EXPECT_TRUE(test, aa_inbounds(puf->e, puf->e_size));
}
static void policy_unpack_test_inbounds_when_out_of_bounds(struct kunit *test)
{
struct policy_unpack_fixture *puf = test->priv;
KUNIT_EXPECT_FALSE(test, aa_inbounds(puf->e, puf->e_size + 1));
}
static void policy_unpack_test_unpack_array_with_null_name(struct kunit *test)
{
struct policy_unpack_fixture *puf = test->priv;
u16 array_size = 0;
puf->e->pos += TEST_ARRAY_BUF_OFFSET;
KUNIT_EXPECT_TRUE(test, aa_unpack_array(puf->e, NULL, &array_size));
KUNIT_EXPECT_EQ(test, array_size, (u16)TEST_ARRAY_SIZE);
KUNIT_EXPECT_PTR_EQ(test, puf->e->pos,
puf->e->start + TEST_ARRAY_BUF_OFFSET + sizeof(u16) + 1);
}
static void policy_unpack_test_unpack_array_with_name(struct kunit *test)
{
struct policy_unpack_fixture *puf = test->priv;
const char name[] = TEST_ARRAY_NAME;
u16 array_size = 0;
puf->e->pos += TEST_NAMED_ARRAY_BUF_OFFSET;
KUNIT_EXPECT_TRUE(test, aa_unpack_array(puf->e, name, &array_size));
KUNIT_EXPECT_EQ(test, array_size, (u16)TEST_ARRAY_SIZE);
KUNIT_EXPECT_PTR_EQ(test, puf->e->pos,
puf->e->start + TEST_ARRAY_BUF_OFFSET + sizeof(u16) + 1);
}
static void policy_unpack_test_unpack_array_out_of_bounds(struct kunit *test)
{
struct policy_unpack_fixture *puf = test->priv;
const char name[] = TEST_ARRAY_NAME;
u16 array_size;
puf->e->pos += TEST_NAMED_ARRAY_BUF_OFFSET;
puf->e->end = puf->e->start + TEST_ARRAY_BUF_OFFSET + sizeof(u16);
KUNIT_EXPECT_FALSE(test, aa_unpack_array(puf->e, name, &array_size));
KUNIT_EXPECT_PTR_EQ(test, puf->e->pos,
puf->e->start + TEST_NAMED_ARRAY_BUF_OFFSET);
}
static void policy_unpack_test_unpack_blob_with_null_name(struct kunit *test)
{
struct policy_unpack_fixture *puf = test->priv;
char *blob = NULL;
size_t size;
puf->e->pos += TEST_BLOB_BUF_OFFSET;
size = aa_unpack_blob(puf->e, &blob, NULL);
KUNIT_ASSERT_EQ(test, size, TEST_BLOB_DATA_SIZE);
KUNIT_EXPECT_TRUE(test,
memcmp(blob, TEST_BLOB_DATA, TEST_BLOB_DATA_SIZE) == 0);
}
static void policy_unpack_test_unpack_blob_with_name(struct kunit *test)
{
struct policy_unpack_fixture *puf = test->priv;
char *blob = NULL;
size_t size;
puf->e->pos += TEST_NAMED_BLOB_BUF_OFFSET;
size = aa_unpack_blob(puf->e, &blob, TEST_BLOB_NAME);
KUNIT_ASSERT_EQ(test, size, TEST_BLOB_DATA_SIZE);
KUNIT_EXPECT_TRUE(test,
memcmp(blob, TEST_BLOB_DATA, TEST_BLOB_DATA_SIZE) == 0);
}
static void policy_unpack_test_unpack_blob_out_of_bounds(struct kunit *test)
{
struct policy_unpack_fixture *puf = test->priv;
char *blob = NULL;
void *start;
int size;
puf->e->pos += TEST_NAMED_BLOB_BUF_OFFSET;
start = puf->e->pos;
puf->e->end = puf->e->start + TEST_BLOB_BUF_OFFSET
+ TEST_BLOB_DATA_SIZE - 1;
size = aa_unpack_blob(puf->e, &blob, TEST_BLOB_NAME);
KUNIT_EXPECT_EQ(test, size, 0);
KUNIT_EXPECT_PTR_EQ(test, puf->e->pos, start);
}
static void policy_unpack_test_unpack_str_with_null_name(struct kunit *test)
{
struct policy_unpack_fixture *puf = test->priv;
const char *string = NULL;
size_t size;
puf->e->pos += TEST_STRING_BUF_OFFSET;
size = aa_unpack_str(puf->e, &string, NULL);
KUNIT_EXPECT_EQ(test, size, strlen(TEST_STRING_DATA) + 1);
KUNIT_EXPECT_STREQ(test, string, TEST_STRING_DATA);
}
static void policy_unpack_test_unpack_str_with_name(struct kunit *test)
{
struct policy_unpack_fixture *puf = test->priv;
const char *string = NULL;
size_t size;
size = aa_unpack_str(puf->e, &string, TEST_STRING_NAME);
KUNIT_EXPECT_EQ(test, size, strlen(TEST_STRING_DATA) + 1);
KUNIT_EXPECT_STREQ(test, string, TEST_STRING_DATA);
}
static void policy_unpack_test_unpack_str_out_of_bounds(struct kunit *test)
{
struct policy_unpack_fixture *puf = test->priv;
const char *string = NULL;
void *start = puf->e->pos;
int size;
puf->e->end = puf->e->pos + TEST_STRING_BUF_OFFSET
+ strlen(TEST_STRING_DATA) - 1;
size = aa_unpack_str(puf->e, &string, TEST_STRING_NAME);
KUNIT_EXPECT_EQ(test, size, 0);
KUNIT_EXPECT_PTR_EQ(test, puf->e->pos, start);
}
static void policy_unpack_test_unpack_strdup_with_null_name(struct kunit *test)
{
struct policy_unpack_fixture *puf = test->priv;
char *string = NULL;
size_t size;
puf->e->pos += TEST_STRING_BUF_OFFSET;
size = aa_unpack_strdup(puf->e, &string, NULL);
KUNIT_EXPECT_EQ(test, size, strlen(TEST_STRING_DATA) + 1);
KUNIT_EXPECT_FALSE(test,
((uintptr_t)puf->e->start <= (uintptr_t)string)
&& ((uintptr_t)string <= (uintptr_t)puf->e->end));
KUNIT_EXPECT_STREQ(test, string, TEST_STRING_DATA);
kfree(string);
}
static void policy_unpack_test_unpack_strdup_with_name(struct kunit *test)
{
struct policy_unpack_fixture *puf = test->priv;
char *string = NULL;
size_t size;
size = aa_unpack_strdup(puf->e, &string, TEST_STRING_NAME);
KUNIT_EXPECT_EQ(test, size, strlen(TEST_STRING_DATA) + 1);
KUNIT_EXPECT_FALSE(test,
((uintptr_t)puf->e->start <= (uintptr_t)string)
&& ((uintptr_t)string <= (uintptr_t)puf->e->end));
KUNIT_EXPECT_STREQ(test, string, TEST_STRING_DATA);
kfree(string);
}
static void policy_unpack_test_unpack_strdup_out_of_bounds(struct kunit *test)
{
struct policy_unpack_fixture *puf = test->priv;
void *start = puf->e->pos;
char *string = NULL;
int size;
puf->e->end = puf->e->pos + TEST_STRING_BUF_OFFSET
+ strlen(TEST_STRING_DATA) - 1;
size = aa_unpack_strdup(puf->e, &string, TEST_STRING_NAME);
KUNIT_EXPECT_EQ(test, size, 0);
KUNIT_EXPECT_NULL(test, string);
KUNIT_EXPECT_PTR_EQ(test, puf->e->pos, start);
kfree(string);
}
static void policy_unpack_test_unpack_nameX_with_null_name(struct kunit *test)
{
struct policy_unpack_fixture *puf = test->priv;
bool success;
puf->e->pos += TEST_U32_BUF_OFFSET;
success = aa_unpack_nameX(puf->e, AA_U32, NULL);
KUNIT_EXPECT_TRUE(test, success);
KUNIT_EXPECT_PTR_EQ(test, puf->e->pos,
puf->e->start + TEST_U32_BUF_OFFSET + 1);
}
static void policy_unpack_test_unpack_nameX_with_wrong_code(struct kunit *test)
{
struct policy_unpack_fixture *puf = test->priv;
bool success;
puf->e->pos += TEST_U32_BUF_OFFSET;
success = aa_unpack_nameX(puf->e, AA_BLOB, NULL);
KUNIT_EXPECT_FALSE(test, success);
KUNIT_EXPECT_PTR_EQ(test, puf->e->pos,
puf->e->start + TEST_U32_BUF_OFFSET);
}
static void policy_unpack_test_unpack_nameX_with_name(struct kunit *test)
{
struct policy_unpack_fixture *puf = test->priv;
const char name[] = TEST_U32_NAME;
bool success;
puf->e->pos += TEST_NAMED_U32_BUF_OFFSET;
success = aa_unpack_nameX(puf->e, AA_U32, name);
KUNIT_EXPECT_TRUE(test, success);
KUNIT_EXPECT_PTR_EQ(test, puf->e->pos,
puf->e->start + TEST_U32_BUF_OFFSET + 1);
}
static void policy_unpack_test_unpack_nameX_with_wrong_name(struct kunit *test)
{
struct policy_unpack_fixture *puf = test->priv;
static const char name[] = "12345678";
bool success;
puf->e->pos += TEST_NAMED_U32_BUF_OFFSET;
success = aa_unpack_nameX(puf->e, AA_U32, name);
KUNIT_EXPECT_FALSE(test, success);
KUNIT_EXPECT_PTR_EQ(test, puf->e->pos,
puf->e->start + TEST_NAMED_U32_BUF_OFFSET);
}
static void policy_unpack_test_unpack_u16_chunk_basic(struct kunit *test)
{
struct policy_unpack_fixture *puf = test->priv;
char *chunk = NULL;
size_t size;
puf->e->pos += TEST_U16_OFFSET;
/*
* WARNING: For unit testing purposes, we're pushing puf->e->end past
* the end of the allocated memory. Doing anything other than comparing
* memory addresses is dangerous.
*/
puf->e->end += TEST_U16_DATA;
size = aa_unpack_u16_chunk(puf->e, &chunk);
KUNIT_EXPECT_PTR_EQ(test, chunk,
puf->e->start + TEST_U16_OFFSET + 2);
KUNIT_EXPECT_EQ(test, size, TEST_U16_DATA);
KUNIT_EXPECT_PTR_EQ(test, puf->e->pos, (chunk + TEST_U16_DATA));
}
static void policy_unpack_test_unpack_u16_chunk_out_of_bounds_1(
struct kunit *test)
{
struct policy_unpack_fixture *puf = test->priv;
char *chunk = NULL;
size_t size;
puf->e->pos = puf->e->end - 1;
size = aa_unpack_u16_chunk(puf->e, &chunk);
KUNIT_EXPECT_EQ(test, size, 0);
KUNIT_EXPECT_NULL(test, chunk);
KUNIT_EXPECT_PTR_EQ(test, puf->e->pos, puf->e->end - 1);
}
static void policy_unpack_test_unpack_u16_chunk_out_of_bounds_2(
struct kunit *test)
{
struct policy_unpack_fixture *puf = test->priv;
char *chunk = NULL;
size_t size;
puf->e->pos += TEST_U16_OFFSET;
/*
* WARNING: For unit testing purposes, we're pushing puf->e->end past
* the end of the allocated memory. Doing anything other than comparing
* memory addresses is dangerous.
*/
puf->e->end = puf->e->pos + TEST_U16_DATA - 1;
size = aa_unpack_u16_chunk(puf->e, &chunk);
KUNIT_EXPECT_EQ(test, size, 0);
KUNIT_EXPECT_NULL(test, chunk);
KUNIT_EXPECT_PTR_EQ(test, puf->e->pos, puf->e->start + TEST_U16_OFFSET);
}
static void policy_unpack_test_unpack_u32_with_null_name(struct kunit *test)
{
struct policy_unpack_fixture *puf = test->priv;
bool success;
u32 data = 0;
puf->e->pos += TEST_U32_BUF_OFFSET;
success = aa_unpack_u32(puf->e, &data, NULL);
KUNIT_EXPECT_TRUE(test, success);
KUNIT_EXPECT_EQ(test, data, TEST_U32_DATA);
KUNIT_EXPECT_PTR_EQ(test, puf->e->pos,
puf->e->start + TEST_U32_BUF_OFFSET + sizeof(u32) + 1);
}
static void policy_unpack_test_unpack_u32_with_name(struct kunit *test)
{
struct policy_unpack_fixture *puf = test->priv;
const char name[] = TEST_U32_NAME;
bool success;
u32 data = 0;
puf->e->pos += TEST_NAMED_U32_BUF_OFFSET;
success = aa_unpack_u32(puf->e, &data, name);
KUNIT_EXPECT_TRUE(test, success);
KUNIT_EXPECT_EQ(test, data, TEST_U32_DATA);
KUNIT_EXPECT_PTR_EQ(test, puf->e->pos,
puf->e->start + TEST_U32_BUF_OFFSET + sizeof(u32) + 1);
}
static void policy_unpack_test_unpack_u32_out_of_bounds(struct kunit *test)
{
struct policy_unpack_fixture *puf = test->priv;
const char name[] = TEST_U32_NAME;
bool success;
u32 data = 0;
puf->e->pos += TEST_NAMED_U32_BUF_OFFSET;
puf->e->end = puf->e->start + TEST_U32_BUF_OFFSET + sizeof(u32);
success = aa_unpack_u32(puf->e, &data, name);
KUNIT_EXPECT_FALSE(test, success);
KUNIT_EXPECT_PTR_EQ(test, puf->e->pos,
puf->e->start + TEST_NAMED_U32_BUF_OFFSET);
}
static void policy_unpack_test_unpack_u64_with_null_name(struct kunit *test)
{
struct policy_unpack_fixture *puf = test->priv;
bool success;
u64 data = 0;
puf->e->pos += TEST_U64_BUF_OFFSET;
success = aa_unpack_u64(puf->e, &data, NULL);
KUNIT_EXPECT_TRUE(test, success);
KUNIT_EXPECT_EQ(test, data, TEST_U64_DATA);
KUNIT_EXPECT_PTR_EQ(test, puf->e->pos,
puf->e->start + TEST_U64_BUF_OFFSET + sizeof(u64) + 1);
}
static void policy_unpack_test_unpack_u64_with_name(struct kunit *test)
{
struct policy_unpack_fixture *puf = test->priv;
const char name[] = TEST_U64_NAME;
bool success;
u64 data = 0;
puf->e->pos += TEST_NAMED_U64_BUF_OFFSET;
success = aa_unpack_u64(puf->e, &data, name);
KUNIT_EXPECT_TRUE(test, success);
KUNIT_EXPECT_EQ(test, data, TEST_U64_DATA);
KUNIT_EXPECT_PTR_EQ(test, puf->e->pos,
puf->e->start + TEST_U64_BUF_OFFSET + sizeof(u64) + 1);
}
static void policy_unpack_test_unpack_u64_out_of_bounds(struct kunit *test)
{
struct policy_unpack_fixture *puf = test->priv;
const char name[] = TEST_U64_NAME;
bool success;
u64 data = 0;
puf->e->pos += TEST_NAMED_U64_BUF_OFFSET;
puf->e->end = puf->e->start + TEST_U64_BUF_OFFSET + sizeof(u64);
success = aa_unpack_u64(puf->e, &data, name);
KUNIT_EXPECT_FALSE(test, success);
KUNIT_EXPECT_PTR_EQ(test, puf->e->pos,
puf->e->start + TEST_NAMED_U64_BUF_OFFSET);
}
static void policy_unpack_test_unpack_X_code_match(struct kunit *test)
{
struct policy_unpack_fixture *puf = test->priv;
bool success = aa_unpack_X(puf->e, AA_NAME);
KUNIT_EXPECT_TRUE(test, success);
KUNIT_EXPECT_TRUE(test, puf->e->pos == puf->e->start + 1);
}
static void policy_unpack_test_unpack_X_code_mismatch(struct kunit *test)
{
struct policy_unpack_fixture *puf = test->priv;
bool success = aa_unpack_X(puf->e, AA_STRING);
KUNIT_EXPECT_FALSE(test, success);
KUNIT_EXPECT_TRUE(test, puf->e->pos == puf->e->start);
}
static void policy_unpack_test_unpack_X_out_of_bounds(struct kunit *test)
{
struct policy_unpack_fixture *puf = test->priv;
bool success;
puf->e->pos = puf->e->end;
success = aa_unpack_X(puf->e, AA_NAME);
KUNIT_EXPECT_FALSE(test, success);
}
static struct kunit_case apparmor_policy_unpack_test_cases[] = {
KUNIT_CASE(policy_unpack_test_inbounds_when_inbounds),
KUNIT_CASE(policy_unpack_test_inbounds_when_out_of_bounds),
KUNIT_CASE(policy_unpack_test_unpack_array_with_null_name),
KUNIT_CASE(policy_unpack_test_unpack_array_with_name),
KUNIT_CASE(policy_unpack_test_unpack_array_out_of_bounds),
KUNIT_CASE(policy_unpack_test_unpack_blob_with_null_name),
KUNIT_CASE(policy_unpack_test_unpack_blob_with_name),
KUNIT_CASE(policy_unpack_test_unpack_blob_out_of_bounds),
KUNIT_CASE(policy_unpack_test_unpack_nameX_with_null_name),
KUNIT_CASE(policy_unpack_test_unpack_nameX_with_wrong_code),
KUNIT_CASE(policy_unpack_test_unpack_nameX_with_name),
KUNIT_CASE(policy_unpack_test_unpack_nameX_with_wrong_name),
KUNIT_CASE(policy_unpack_test_unpack_str_with_null_name),
KUNIT_CASE(policy_unpack_test_unpack_str_with_name),
KUNIT_CASE(policy_unpack_test_unpack_str_out_of_bounds),
KUNIT_CASE(policy_unpack_test_unpack_strdup_with_null_name),
KUNIT_CASE(policy_unpack_test_unpack_strdup_with_name),
KUNIT_CASE(policy_unpack_test_unpack_strdup_out_of_bounds),
KUNIT_CASE(policy_unpack_test_unpack_u16_chunk_basic),
KUNIT_CASE(policy_unpack_test_unpack_u16_chunk_out_of_bounds_1),
KUNIT_CASE(policy_unpack_test_unpack_u16_chunk_out_of_bounds_2),
KUNIT_CASE(policy_unpack_test_unpack_u32_with_null_name),
KUNIT_CASE(policy_unpack_test_unpack_u32_with_name),
KUNIT_CASE(policy_unpack_test_unpack_u32_out_of_bounds),
KUNIT_CASE(policy_unpack_test_unpack_u64_with_null_name),
KUNIT_CASE(policy_unpack_test_unpack_u64_with_name),
KUNIT_CASE(policy_unpack_test_unpack_u64_out_of_bounds),
KUNIT_CASE(policy_unpack_test_unpack_X_code_match),
KUNIT_CASE(policy_unpack_test_unpack_X_code_mismatch),
KUNIT_CASE(policy_unpack_test_unpack_X_out_of_bounds),
{},
};
static struct kunit_suite apparmor_policy_unpack_test_module = {
.name = "apparmor_policy_unpack",
.init = policy_unpack_test_init,
.test_cases = apparmor_policy_unpack_test_cases,
};
kunit_test_suite(apparmor_policy_unpack_test_module);
MODULE_DESCRIPTION("KUnit tests for AppArmor's policy unpack");
MODULE_LICENSE("GPL");
+141
View File
@@ -0,0 +1,141 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* AppArmor security module
*
* This file contains AppArmor /proc/<pid>/attr/ interface functions
*
* Copyright (C) 1998-2008 Novell/SUSE
* Copyright 2009-2010 Canonical Ltd.
*/
#include "include/apparmor.h"
#include "include/cred.h"
#include "include/policy.h"
#include "include/policy_ns.h"
#include "include/domain.h"
#include "include/procattr.h"
/**
* aa_getprocattr - Return the label information for @label
* @label: the label to print label info about (NOT NULL)
* @string: Returns - string containing the label info (NOT NULL)
* @newline: indicates that a newline should be added
*
* Requires: label != NULL && string != NULL
*
* Creates a string containing the label information for @label.
*
* Returns: size of string placed in @string else error code on failure
*/
int aa_getprocattr(struct aa_label *label, char **string, bool newline)
{
struct aa_ns *ns = labels_ns(label);
struct aa_ns *current_ns = aa_get_current_ns();
int len;
if (!aa_ns_visible(current_ns, ns, true)) {
aa_put_ns(current_ns);
return -EACCES;
}
len = aa_label_snxprint(NULL, 0, current_ns, label,
FLAG_SHOW_MODE | FLAG_VIEW_SUBNS |
FLAG_HIDDEN_UNCONFINED);
AA_BUG(len < 0);
*string = kmalloc(len + 2, GFP_KERNEL);
if (!*string) {
aa_put_ns(current_ns);
return -ENOMEM;
}
len = aa_label_snxprint(*string, len + 2, current_ns, label,
FLAG_SHOW_MODE | FLAG_VIEW_SUBNS |
FLAG_HIDDEN_UNCONFINED);
if (len < 0) {
kfree(*string);
*string = NULL;
aa_put_ns(current_ns);
return len;
}
if (newline)
(*string)[len++] = '\n';
(*string)[len] = 0;
aa_put_ns(current_ns);
return len;
}
/**
* split_token_from_name - separate a string of form <token>^<name>
* @op: operation being checked
* @args: string to parse (NOT NULL)
* @token: stores returned parsed token value (NOT NULL)
*
* Returns: start position of name after token else NULL on failure
*/
static char *split_token_from_name(const char *op, char *args, u64 *token)
{
char *name;
*token = simple_strtoull(args, &name, 16);
if ((name == args) || *name != '^') {
AA_ERROR("%s: Invalid input '%s'", op, args);
return ERR_PTR(-EINVAL);
}
name++; /* skip ^ */
if (!*name)
name = NULL;
return name;
}
/**
* aa_setprocattr_changehat - handle procattr interface to change_hat
* @args: args received from writing to /proc/<pid>/attr/current (NOT NULL)
* @size: size of the args
* @flags: set of flags governing behavior
*
* Returns: %0 or error code if change_hat fails
*/
int aa_setprocattr_changehat(char *args, size_t size, int flags)
{
char *hat;
u64 token;
const char *hats[16]; /* current hard limit on # of names */
int count = 0;
hat = split_token_from_name(OP_CHANGE_HAT, args, &token);
if (IS_ERR(hat))
return PTR_ERR(hat);
if (!hat && !token) {
AA_ERROR("change_hat: Invalid input, NULL hat and NULL magic");
return -EINVAL;
}
if (hat) {
/* set up hat name vector, args guaranteed null terminated
* at args[size] by setprocattr.
*
* If there are multiple hat names in the buffer each is
* separated by a \0. Ie. userspace writes them pre tokenized
*/
char *end = args + size;
for (count = 0; (hat < end) && count < 16; ++count) {
char *next = hat + strlen(hat) + 1;
hats[count] = hat;
AA_DEBUG(DEBUG_DOMAIN,
"%s: (pid %d) Magic 0x%llx count %d hat '%s'\n"
, __func__, current->pid, token, count, hat);
hat = next;
}
} else
AA_DEBUG(DEBUG_DOMAIN,
"%s: (pid %d) Magic 0x%llx count %d Hat '%s'\n",
__func__, current->pid, token, count, "<NULL>");
return aa_change_hat(hats, count, token, flags);
}
+206
View File
@@ -0,0 +1,206 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* AppArmor security module
*
* This file contains AppArmor resource mediation and attachment
*
* Copyright (C) 1998-2008 Novell/SUSE
* Copyright 2009-2010 Canonical Ltd.
*/
#include <linux/audit.h>
#include <linux/security.h>
#include "include/audit.h"
#include "include/cred.h"
#include "include/resource.h"
#include "include/policy.h"
/*
* Table of rlimit names: we generate it from resource.h.
*/
#include "rlim_names.h"
struct aa_sfs_entry aa_sfs_entry_rlimit[] = {
AA_SFS_FILE_STRING("mask", AA_SFS_RLIMIT_MASK),
{ }
};
/* audit callback for resource specific fields */
static void audit_cb(struct audit_buffer *ab, void *va)
{
struct common_audit_data *sa = va;
struct apparmor_audit_data *ad = aad(sa);
audit_log_format(ab, " rlimit=%s value=%lu",
rlim_names[ad->rlim.rlim], ad->rlim.max);
if (ad->peer) {
audit_log_format(ab, " peer=");
aa_label_xaudit(ab, labels_ns(ad->subj_label), ad->peer,
FLAGS_NONE, GFP_ATOMIC);
}
}
/**
* audit_resource - audit setting resource limit
* @subj_cred: cred setting the resource
* @profile: profile being enforced (NOT NULL)
* @resource: rlimit being auditing
* @value: value being set
* @peer: aa_albel of the task being set
* @info: info being auditing
* @error: error value
*
* Returns: 0 or ad->error else other error code on failure
*/
static int audit_resource(const struct cred *subj_cred,
struct aa_profile *profile, unsigned int resource,
unsigned long value, struct aa_label *peer,
const char *info, int error)
{
DEFINE_AUDIT_DATA(ad, LSM_AUDIT_DATA_NONE, AA_CLASS_RLIMITS,
OP_SETRLIMIT);
ad.subj_cred = subj_cred;
ad.rlim.rlim = resource;
ad.rlim.max = value;
ad.peer = peer;
ad.info = info;
ad.error = error;
return aa_audit(AUDIT_APPARMOR_AUTO, profile, &ad, audit_cb);
}
/**
* aa_map_resource - map compiled policy resource to internal #
* @resource: flattened policy resource number
*
* Returns: resource # for the current architecture.
*
* rlimit resource can vary based on architecture, map the compiled policy
* resource # to the internal representation for the architecture.
*/
int aa_map_resource(int resource)
{
return rlim_map[resource];
}
static int profile_setrlimit(const struct cred *subj_cred,
struct aa_profile *profile, unsigned int resource,
struct rlimit *new_rlim)
{
struct aa_ruleset *rules = profile->label.rules[0];
int e = 0;
if (rules->rlimits.mask & (1 << resource) && new_rlim->rlim_max >
rules->rlimits.limits[resource].rlim_max)
e = -EACCES;
return audit_resource(subj_cred, profile, resource, new_rlim->rlim_max,
NULL, NULL, e);
}
/**
* aa_task_setrlimit - test permission to set an rlimit
* @subj_cred: cred setting the limit
* @label: label confining the task (NOT NULL)
* @task: task the resource is being set on
* @resource: the resource being set
* @new_rlim: the new resource limit (NOT NULL)
*
* Control raising the processes hard limit.
*
* Returns: 0 or error code if setting resource failed
*/
int aa_task_setrlimit(const struct cred *subj_cred, struct aa_label *label,
struct task_struct *task,
unsigned int resource, struct rlimit *new_rlim)
{
struct aa_profile *profile;
struct aa_label *peer;
int error = 0;
rcu_read_lock();
peer = aa_get_newest_cred_label(__task_cred(task));
rcu_read_unlock();
/* TODO: extend resource control to handle other (non current)
* profiles. AppArmor rules currently have the implicit assumption
* that the task is setting the resource of a task confined with
* the same profile or that the task setting the resource of another
* task has CAP_SYS_RESOURCE.
*/
if (label != peer &&
aa_capable(subj_cred, label, CAP_SYS_RESOURCE, CAP_OPT_NOAUDIT) != 0)
error = fn_for_each(label, profile,
audit_resource(subj_cred, profile, resource,
new_rlim->rlim_max, peer,
"cap_sys_resource", -EACCES));
else
error = fn_for_each_confined(label, profile,
profile_setrlimit(subj_cred, profile, resource,
new_rlim));
aa_put_label(peer);
return error;
}
/**
* __aa_transition_rlimits - apply new profile rlimits
* @old_l: old label on task (NOT NULL)
* @new_l: new label with rlimits to apply (NOT NULL)
*/
void __aa_transition_rlimits(struct aa_label *old_l, struct aa_label *new_l)
{
unsigned int mask = 0;
struct rlimit *rlim, *initrlim;
struct aa_profile *old, *new;
struct label_it i;
old = labels_profile(old_l);
new = labels_profile(new_l);
/* for any rlimits the profile controlled, reset the soft limit
* to the lesser of the tasks hard limit and the init tasks soft limit
*/
label_for_each_confined(i, old_l, old) {
struct aa_ruleset *rules = old->label.rules[0];
if (rules->rlimits.mask) {
int j;
for (j = 0, mask = 1; j < RLIM_NLIMITS; j++,
mask <<= 1) {
if (rules->rlimits.mask & mask) {
rlim = current->signal->rlim + j;
initrlim = init_task.signal->rlim + j;
rlim->rlim_cur = min(rlim->rlim_max,
initrlim->rlim_cur);
}
}
}
}
/* set any new hard limits as dictated by the new profile */
label_for_each_confined(i, new_l, new) {
struct aa_ruleset *rules = new->label.rules[0];
int j;
if (!rules->rlimits.mask)
continue;
for (j = 0, mask = 1; j < RLIM_NLIMITS; j++, mask <<= 1) {
if (!(rules->rlimits.mask & mask))
continue;
rlim = current->signal->rlim + j;
rlim->rlim_max = min(rlim->rlim_max,
rules->rlimits.limits[j].rlim_max);
/* soft limit should not exceed hard limit */
rlim->rlim_cur = min(rlim->rlim_cur, rlim->rlim_max);
if (j == RLIMIT_CPU &&
rlim->rlim_cur != RLIM_INFINITY &&
IS_ENABLED(CONFIG_POSIX_TIMERS))
(void) update_rlimit_cpu(current->group_leader,
rlim->rlim_cur);
}
}
}
+155
View File
@@ -0,0 +1,155 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* AppArmor security module
*
* This file contains AppArmor security identifier (secid) manipulation fns
*
* Copyright 2009-2017 Canonical Ltd.
*
* AppArmor allocates a unique secid for every label used. If a label
* is replaced it receives the secid of the label it is replacing.
*/
#include <linux/errno.h>
#include <linux/err.h>
#include <linux/gfp.h>
#include <linux/slab.h>
#include <linux/spinlock.h>
#include <linux/xarray.h>
#include "include/cred.h"
#include "include/lib.h"
#include "include/secid.h"
#include "include/label.h"
#include "include/policy_ns.h"
/*
* secids - do not pin labels with a refcount. They rely on the label
* properly updating/freeing them
*/
#define AA_FIRST_SECID 2
static DEFINE_XARRAY_FLAGS(aa_secids, XA_FLAGS_LOCK_IRQ | XA_FLAGS_TRACK_FREE);
int apparmor_display_secid_mode;
/*
* TODO: allow policy to reserve a secid range?
* TODO: add secid pinning
* TODO: use secid_update in label replace
*/
/*
* see label for inverse aa_label_to_secid
*/
struct aa_label *aa_secid_to_label(u32 secid)
{
return xa_load(&aa_secids, secid);
}
static int apparmor_label_to_secctx(struct aa_label *label,
struct lsm_context *cp)
{
/* TODO: cache secctx and ref count so we don't have to recreate */
int flags = FLAG_VIEW_SUBNS | FLAG_HIDDEN_UNCONFINED | FLAG_ABS_ROOT;
int len;
if (!label)
return -EINVAL;
if (apparmor_display_secid_mode)
flags |= FLAG_SHOW_MODE;
if (cp)
len = aa_label_asxprint(&cp->context, root_ns, label,
flags, GFP_ATOMIC);
else
len = aa_label_snxprint(NULL, 0, root_ns, label, flags);
if (len < 0)
return -ENOMEM;
if (cp) {
cp->len = len;
cp->id = LSM_ID_APPARMOR;
}
return len;
}
int apparmor_secid_to_secctx(u32 secid, struct lsm_context *cp)
{
struct aa_label *label = aa_secid_to_label(secid);
return apparmor_label_to_secctx(label, cp);
}
int apparmor_lsmprop_to_secctx(struct lsm_prop *prop, struct lsm_context *cp)
{
struct aa_label *label;
label = prop->apparmor.label;
return apparmor_label_to_secctx(label, cp);
}
int apparmor_secctx_to_secid(const char *secdata, u32 seclen, u32 *secid)
{
struct aa_label *label;
label = aa_label_strn_parse(&root_ns->unconfined->label, secdata,
seclen, GFP_KERNEL, false, false);
if (IS_ERR(label))
return PTR_ERR(label);
*secid = label->secid;
return 0;
}
void apparmor_release_secctx(struct lsm_context *cp)
{
if (cp->id == LSM_ID_APPARMOR) {
kfree(cp->context);
cp->context = NULL;
cp->id = LSM_ID_UNDEF;
}
}
/**
* aa_alloc_secid - allocate a new secid for a profile
* @label: the label to allocate a secid for
* @gfp: memory allocation flags
*
* Returns: 0 with @label->secid initialized
* <0 returns error with @label->secid set to AA_SECID_INVALID
*/
int aa_alloc_secid(struct aa_label *label, gfp_t gfp)
{
unsigned long flags;
int ret;
xa_lock_irqsave(&aa_secids, flags);
ret = __xa_alloc(&aa_secids, &label->secid, label,
XA_LIMIT(AA_FIRST_SECID, INT_MAX), gfp);
xa_unlock_irqrestore(&aa_secids, flags);
if (ret < 0) {
label->secid = AA_SECID_INVALID;
return ret;
}
return 0;
}
/**
* aa_free_secid - free a secid
* @secid: secid to free
*/
void aa_free_secid(u32 secid)
{
unsigned long flags;
xa_lock_irqsave(&aa_secids, flags);
__xa_erase(&aa_secids, secid);
xa_unlock_irqrestore(&aa_secids, flags);
}
@@ -0,0 +1,114 @@
/* 0x1 [^\000]*[^/\000]//& */ 0x1B, 0x5E, 0x78, 0x3D, 0x00, 0x00,
0x00, 0x18, 0x00, 0x00, 0x04, 0xD8, 0x00, 0x00, 0x6E, 0x6F, 0x74,
0x66, 0x6C, 0x65, 0x78, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x04,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00,
0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x02, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00,
0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x02, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x02,
0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x08, 0x00,
0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x05, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x03, 0x00,
0x04, 0x00, 0x01, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x02, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x01, 0x05, 0x00, 0x00, 0x00, 0x01, 0x00,
0x02, 0x00, 0x03, 0x00, 0x04, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x03, 0x00, 0x04,
0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00
+372
View File
@@ -0,0 +1,372 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* AppArmor security module
*
* This file contains AppArmor task related definitions and mediation
*
* Copyright 2017 Canonical Ltd.
*
* TODO
* If a task uses change_hat it currently does not return to the old
* cred or task context but instead creates a new one. Ideally the task
* should return to the previous cred if it has not been modified.
*/
#include <linux/gfp.h>
#include <linux/ptrace.h>
#include "include/path.h"
#include "include/audit.h"
#include "include/cred.h"
#include "include/policy.h"
#include "include/task.h"
/**
* aa_get_task_label - Get another task's label
* @task: task to query (NOT NULL)
*
* Returns: counted reference to @task's label
*/
struct aa_label *aa_get_task_label(struct task_struct *task)
{
struct aa_label *p;
rcu_read_lock();
p = aa_get_newest_cred_label(__task_cred(task));
rcu_read_unlock();
return p;
}
/**
* aa_replace_current_label - replace the current tasks label
* @label: new label (NOT NULL)
*
* Returns: 0 or error on failure
*/
int aa_replace_current_label(struct aa_label *label)
{
struct aa_label *old = aa_current_raw_label();
struct aa_task_ctx *ctx = task_ctx(current);
struct cred *new;
AA_BUG(!label);
if (old == label)
return 0;
if (current_cred() != current_real_cred())
return -EBUSY;
new = prepare_creds();
if (!new)
return -ENOMEM;
if (ctx->nnp && label_is_stale(ctx->nnp)) {
struct aa_label *tmp = ctx->nnp;
ctx->nnp = aa_get_newest_label(tmp);
aa_put_label(tmp);
}
if (unconfined(label) || (labels_ns(old) != labels_ns(label)))
/*
* if switching to unconfined or a different label namespace
* clear out context state
*/
aa_clear_task_ctx_trans(task_ctx(current));
/*
* be careful switching cred label, when racing replacement it
* is possible that the cred labels's->proxy->label is the reference
* keeping @label valid, so make sure to get its reference before
* dropping the reference on the cred's label
*/
aa_get_label(label);
aa_put_label(cred_label(new));
set_cred_label(new, label);
commit_creds(new);
return 0;
}
/**
* aa_set_current_onexec - set the tasks change_profile to happen onexec
* @label: system label to set at exec (MAYBE NULL to clear value)
* @stack: whether stacking should be done
*/
void aa_set_current_onexec(struct aa_label *label, bool stack)
{
struct aa_task_ctx *ctx = task_ctx(current);
aa_get_label(label);
aa_put_label(ctx->onexec);
ctx->onexec = label;
ctx->token = stack;
}
/**
* aa_set_current_hat - set the current tasks hat
* @label: label to set as the current hat (NOT NULL)
* @token: token value that must be specified to change from the hat
*
* Do switch of tasks hat. If the task is currently in a hat
* validate the token to match.
*
* Returns: 0 or error on failure
*/
int aa_set_current_hat(struct aa_label *label, u64 token)
{
struct aa_task_ctx *ctx = task_ctx(current);
struct cred *new;
new = prepare_creds();
if (!new)
return -ENOMEM;
AA_BUG(!label);
if (!ctx->previous) {
/* transfer refcount */
ctx->previous = cred_label(new);
ctx->token = token;
} else if (ctx->token == token) {
aa_put_label(cred_label(new));
} else {
/* previous_profile && ctx->token != token */
abort_creds(new);
return -EACCES;
}
set_cred_label(new, aa_get_newest_label(label));
/* clear exec on switching context */
aa_put_label(ctx->onexec);
ctx->onexec = NULL;
commit_creds(new);
return 0;
}
/**
* aa_restore_previous_label - exit from hat context restoring previous label
* @token: the token that must be matched to exit hat context
*
* Attempt to return out of a hat to the previous label. The token
* must match the stored token value.
*
* Returns: 0 or error of failure
*/
int aa_restore_previous_label(u64 token)
{
struct aa_task_ctx *ctx = task_ctx(current);
struct cred *new;
if (ctx->token != token)
return -EACCES;
/* ignore restores when there is no saved label */
if (!ctx->previous)
return 0;
new = prepare_creds();
if (!new)
return -ENOMEM;
aa_put_label(cred_label(new));
set_cred_label(new, aa_get_newest_label(ctx->previous));
AA_BUG(!cred_label(new));
/* clear exec && prev information when restoring to previous context */
aa_clear_task_ctx_trans(ctx);
commit_creds(new);
return 0;
}
/**
* audit_ptrace_mask - convert mask to permission string
* @mask: permission mask to convert
*
* Returns: pointer to static string
*/
static const char *audit_ptrace_mask(u32 mask)
{
switch (mask) {
case MAY_READ:
return "read";
case MAY_WRITE:
return "trace";
case AA_MAY_BE_READ:
return "readby";
case AA_MAY_BE_TRACED:
return "tracedby";
}
return "";
}
/* call back to audit ptrace fields */
static void audit_ptrace_cb(struct audit_buffer *ab, void *va)
{
struct common_audit_data *sa = va;
struct apparmor_audit_data *ad = aad(sa);
if (ad->request & AA_PTRACE_PERM_MASK) {
audit_log_format(ab, " requested_mask=\"%s\"",
audit_ptrace_mask(ad->request));
if (ad->denied & AA_PTRACE_PERM_MASK) {
audit_log_format(ab, " denied_mask=\"%s\"",
audit_ptrace_mask(ad->denied));
}
}
audit_log_format(ab, " peer=");
aa_label_xaudit(ab, labels_ns(ad->subj_label), ad->peer,
FLAGS_NONE, GFP_ATOMIC);
}
/* assumes check for RULE_MEDIATES is already done */
/* TODO: conditionals */
static int profile_ptrace_perm(const struct cred *cred,
struct aa_profile *profile,
struct aa_label *peer, u32 request,
struct apparmor_audit_data *ad)
{
struct aa_ruleset *rules = profile->label.rules[0];
struct aa_perms perms = { };
ad->subj_cred = cred;
ad->peer = peer;
aa_profile_match_label(profile, rules, peer, AA_CLASS_PTRACE, request,
&perms);
aa_apply_modes_to_perms(profile, &perms);
return aa_check_perms(profile, &perms, request, ad, audit_ptrace_cb);
}
static int profile_tracee_perm(const struct cred *cred,
struct aa_profile *tracee,
struct aa_label *tracer, u32 request,
struct apparmor_audit_data *ad)
{
if (profile_unconfined(tracee) || unconfined(tracer) ||
!label_mediates(&tracee->label, AA_CLASS_PTRACE))
return 0;
return profile_ptrace_perm(cred, tracee, tracer, request, ad);
}
static int profile_tracer_perm(const struct cred *cred,
struct aa_profile *tracer,
struct aa_label *tracee, u32 request,
struct apparmor_audit_data *ad)
{
if (profile_unconfined(tracer))
return 0;
if (label_mediates(&tracer->label, AA_CLASS_PTRACE))
return profile_ptrace_perm(cred, tracer, tracee, request, ad);
/* profile uses the old style capability check for ptrace */
if (&tracer->label == tracee)
return 0;
ad->subj_label = &tracer->label;
ad->peer = tracee;
ad->request = 0;
ad->error = aa_capable(cred, &tracer->label, CAP_SYS_PTRACE,
CAP_OPT_NONE);
return aa_audit(AUDIT_APPARMOR_AUTO, tracer, ad, audit_ptrace_cb);
}
/**
* aa_may_ptrace - test if tracer task can trace the tracee
* @tracer_cred: cred of task doing the tracing (NOT NULL)
* @tracer: label of the task doing the tracing (NOT NULL)
* @tracee_cred: cred of task to be traced
* @tracee: task label to be traced
* @request: permission request
*
* Returns: %0 else error code if permission denied or error
*/
int aa_may_ptrace(const struct cred *tracer_cred, struct aa_label *tracer,
const struct cred *tracee_cred, struct aa_label *tracee,
u32 request)
{
struct aa_profile *profile;
u32 xrequest = request << PTRACE_PERM_SHIFT;
DEFINE_AUDIT_DATA(sa, LSM_AUDIT_DATA_NONE, AA_CLASS_PTRACE, OP_PTRACE);
return xcheck_labels(tracer, tracee, profile,
profile_tracer_perm(tracer_cred, profile, tracee,
request, &sa),
profile_tracee_perm(tracee_cred, profile, tracer,
xrequest, &sa));
}
static const char *get_current_exe_path(char *buffer, int buffer_size)
{
struct file *exe_file;
struct path p;
const char *path_str;
exe_file = get_task_exe_file(current);
if (!exe_file)
return ERR_PTR(-ENOENT);
p = exe_file->f_path;
path_get(&p);
if (aa_path_name(&p, FLAG_VIEW_SUBNS, buffer, &path_str, NULL, NULL))
path_str = ERR_PTR(-ENOMEM);
fput(exe_file);
path_put(&p);
return path_str;
}
/* call back to audit ptrace fields */
static void audit_ns_cb(struct audit_buffer *ab, void *va)
{
struct apparmor_audit_data *ad = aad_of_va(va);
char *buffer;
const char *path;
if (ad->request & AA_USERNS_CREATE)
audit_log_format(ab, " requested=\"userns_create\"");
if (ad->denied & AA_USERNS_CREATE)
audit_log_format(ab, " denied=\"userns_create\"");
buffer = aa_get_buffer(false);
if (!buffer)
return; // OOM
path = get_current_exe_path(buffer, aa_g_path_max);
if (!IS_ERR(path))
audit_log_format(ab, " execpath=\"%s\"", path);
aa_put_buffer(buffer);
}
int aa_profile_ns_perm(struct aa_profile *profile,
struct apparmor_audit_data *ad,
u32 request)
{
struct aa_perms perms = { };
int error = 0;
ad->subj_label = &profile->label;
ad->request = request;
if (!profile_unconfined(profile)) {
struct aa_ruleset *rules = profile->label.rules[0];
aa_state_t state;
state = RULE_MEDIATES(rules, ad->class);
if (!state)
/* TODO: add flag to complain about unmediated */
return 0;
perms = *aa_lookup_perms(rules->policy, state);
aa_apply_modes_to_perms(profile, &perms);
error = aa_check_perms(profile, &perms, request, ad,
audit_ns_cb);
}
return error;
}