Commit a3c6147c authored by John Johansen's avatar John Johansen Committed by Tim Gardner

UBUNTU: SAUCE: (no-up) apparmor: sync of apparmor3.5-beta1 snapshot

BugLink: http://bugs.launchpad.net/bugs/1379535

This is a sync and squash of the apparmor 3.5-beta1 snapshot. The
set of patches in this squash are available in
  git://kernel.ubuntu.com/jj/ubuntu-xenial.git
using the the tag
  apparmor-3.5-beta1-presuash-snapshot

This fixes multiple bugs and adds the policy namespace stacking features.
BugLink: http://bugs.launchpad.net/bugs/1379535Signed-off-by: default avatarJohn Johansen <john.johansen@canonical.com>
Signed-off-by: default avatarTim Gardner <tim.gardner@canonical.com>
parent 3733c356
# #
# Generated include files # Generated include files
# #
net_names.h
capability_names.h capability_names.h
rlim_names.h rlim_names.h
...@@ -30,14 +30,62 @@ config SECURITY_APPARMOR_BOOTPARAM_VALUE ...@@ -30,14 +30,62 @@ config SECURITY_APPARMOR_BOOTPARAM_VALUE
If you are unsure how to answer this question, answer 1. If you are unsure how to answer this question, answer 1.
config SECURITY_APPARMOR_STATS
bool "enable debug statistics"
depends on SECURITY_APPARMOR
select APPARMOR_LABEL_STATS
default n
help
This enables keeping statistics on various internal structures
and functions in apparmor.
If you are unsure how to answer this question, answer N.
config SECURITY_APPARMOR_UNCONFINED_INIT
bool "Set init to unconfined on boot"
depends on SECURITY_APPARMOR
default y
help
This option determines policy behavior during early boot by
placing the init process in the unconfined state, or the
'default' profile.
This option determines policy behavior during early boot by
placing the init process in the unconfined state, or the
'default' profile.
'Y' means init and its children are not confined, unless the
init process is re-execed after a policy load; loaded policy
will only apply to processes started after the load.
'N' means init and its children are confined in a profile
named 'default', which can be replaced later and thus
provide for confinement for processes started early at boot,
though not confined during early boot.
If you are unsure how to answer this question, answer Y.
config SECURITY_APPARMOR_HASH config SECURITY_APPARMOR_HASH
bool "SHA1 hash of loaded profiles" bool "enable introspection of sha1 hashes for loaded profiles"
depends on SECURITY_APPARMOR depends on SECURITY_APPARMOR
select CRYPTO select CRYPTO
select CRYPTO_SHA1 select CRYPTO_SHA1
default y default y
help help
This option selects whether sha1 hashing is done against loaded This option selects whether introspection of loaded policy
profiles and exported for inspection to user space via the apparmor is available to userspace via the apparmor filesystem.
filesystem.
config SECURITY_APPARMOR_HASH_DEFAULT
bool "Enable policy hash introspection by default"
depends on SECURITY_APPARMOR_HASH
default y
help
This option selects whether sha1 hashing of loaded policy
is enabled by default. The generation of sha1 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.
...@@ -4,11 +4,45 @@ obj-$(CONFIG_SECURITY_APPARMOR) += apparmor.o ...@@ -4,11 +4,45 @@ obj-$(CONFIG_SECURITY_APPARMOR) += apparmor.o
apparmor-y := apparmorfs.o audit.o capability.o context.o ipc.o lib.o match.o \ apparmor-y := apparmorfs.o audit.o capability.o context.o ipc.o lib.o match.o \
path.o domain.o policy.o policy_unpack.o procattr.o lsm.o \ path.o domain.o policy.o policy_unpack.o procattr.o lsm.o \
resource.o sid.o file.o resource.o sid.o file.o label.o mount.o net.o af_unix.o \
policy_ns.o
apparmor-$(CONFIG_SECURITY_APPARMOR_HASH) += crypto.o apparmor-$(CONFIG_SECURITY_APPARMOR_HASH) += crypto.o
clean-files := capability_names.h rlim_names.h 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_FS_AF_MASK "local inet"
quiet_cmd_make-af = GEN $@
cmd_make-af = echo "static const char *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 "};" >> $@ ;\
echo -n '\#define AA_FS_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 *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 # Build a lower case string table of capability names
# Transforms lines from # Transforms lines from
...@@ -61,6 +95,7 @@ cmd_make-rlim = echo "static const char *const rlim_names[RLIM_NLIMITS] = {" \ ...@@ -61,6 +95,7 @@ cmd_make-rlim = echo "static const char *const rlim_names[RLIM_NLIMITS] = {" \
tr '\n' ' ' | sed -e 's/ $$/"\n/' >> $@ tr '\n' ' ' | sed -e 's/ $$/"\n/' >> $@
$(obj)/capability.o : $(obj)/capability_names.h $(obj)/capability.o : $(obj)/capability_names.h
$(obj)/net.o : $(obj)/net_names.h
$(obj)/resource.o : $(obj)/rlim_names.h $(obj)/resource.o : $(obj)/rlim_names.h
$(obj)/capability_names.h : $(srctree)/include/uapi/linux/capability.h \ $(obj)/capability_names.h : $(srctree)/include/uapi/linux/capability.h \
$(src)/Makefile $(src)/Makefile
...@@ -68,3 +103,8 @@ $(obj)/capability_names.h : $(srctree)/include/uapi/linux/capability.h \ ...@@ -68,3 +103,8 @@ $(obj)/capability_names.h : $(srctree)/include/uapi/linux/capability.h \
$(obj)/rlim_names.h : $(srctree)/include/uapi/asm-generic/resource.h \ $(obj)/rlim_names.h : $(srctree)/include/uapi/asm-generic/resource.h \
$(src)/Makefile $(src)/Makefile
$(call cmd,make-rlim) $(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)
/*
* AppArmor security module
*
* This file contains AppArmor af_unix fine grained mediation
*
* Copyright 2014 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 <net/tcp_states.h>
#include "include/af_unix.h"
#include "include/apparmor.h"
#include "include/context.h"
#include "include/file.h"
#include "include/label.h"
#include "include/path.h"
#include "include/policy.h"
static inline struct sock *aa_sock(struct unix_sock *u)
{
return &u->sk;
}
static inline int unix_fs_perm(const char *op, u32 mask, struct aa_label *label,
struct unix_sock *u, int flags)
{
AA_BUG(!label);
AA_BUG(!u);
AA_BUG(!UNIX_FS(aa_sock(u)));
if (unconfined(label) || !LABEL_MEDIATES(label, AA_CLASS_FILE))
return 0;
mask &= NET_FS_PERMS;
if (!u->path.dentry) {
struct path_cond cond = { };
struct aa_perms perms = { };
struct aa_profile *profile;
/* socket path has been cleared because it is being shutdown
* can only fall back to original sun_path request
*/
struct aa_sk_ctx *ctx = SK_CTX(&u->sk);
if (ctx->path.dentry)
return aa_path_perm(op, label, &ctx->path, flags, mask,
&cond);
return fn_for_each_confined(label, profile,
((flags | profile->path_flags) & PATH_MEDIATE_DELETED) ?
__aa_path_perm(op, profile,
u->addr->name->sun_path, mask,
&cond, flags, &perms) :
aa_audit_file(profile, &nullperms, op, mask,
u->addr->name->sun_path, NULL,
NULL, cond.uid,
"Failed name lookup - "
"deleted entry", -EACCES));
} else {
/* the sunpath may not be valid for this ns so use the path */
struct path_cond cond = { u->path.dentry->d_inode->i_uid,
u->path.dentry->d_inode->i_mode
};
return aa_path_perm(op, label, &u->path, flags, mask, &cond);
}
return 0;
}
/* passing in state returned by PROFILE_MEDIATES_AF */
static unsigned int match_to_prot(struct aa_profile *profile,
unsigned int state, int type, int protocol,
const char **info)
{
u16 buffer[2];
buffer[0] = cpu_to_be16(type);
buffer[1] = cpu_to_be16(protocol);
state = aa_dfa_match_len(profile->policy.dfa, state, (char *) &buffer,
4);
if (!state)
*info = "failed type and protocol match";
return state;
}
static unsigned int match_addr(struct aa_profile *profile, unsigned int state,
struct sockaddr_un *addr, int addrlen)
{
if (addr)
/* include leading \0 */
state = aa_dfa_match_len(profile->policy.dfa, state,
addr->sun_path,
unix_addr_len(addrlen));
else
/* anonymous end point */
state = aa_dfa_match_len(profile->policy.dfa, state, "\x01",
1);
/* todo change to out of band */
state = aa_dfa_null_transition(profile->policy.dfa, state);
return state;
}
static unsigned int match_to_local(struct aa_profile *profile,
unsigned int state, int type, int protocol,
struct sockaddr_un *addr, int addrlen,
const char **info)
{
state = match_to_prot(profile, state, type, protocol, info);
if (state) {
state = match_addr(profile, state, addr, addrlen);
if (state) {
/* todo: local label matching */
state = aa_dfa_null_transition(profile->policy.dfa,
state);
if (!state)
*info = "failed local label match";
} else
*info = "failed local address match";
}
return state;
}
static unsigned int match_to_sk(struct aa_profile *profile,
unsigned int state, struct unix_sock *u,
const char **info)
{
struct sockaddr_un *addr = NULL;
int addrlen = 0;
if (u->addr) {
addr = u->addr->name;
addrlen = u->addr->len;
}
return match_to_local(profile, state, u->sk.sk_type, u->sk.sk_protocol,
addr, addrlen, info);
}
#define CMD_ADDR 1
#define CMD_LISTEN 2
#define CMD_OPT 4
static inline unsigned int match_to_cmd(struct aa_profile *profile,
unsigned int state, struct unix_sock *u,
char cmd, const char **info)
{
state = match_to_sk(profile, state, u, info);
if (state) {
state = aa_dfa_match_len(profile->policy.dfa, state, &cmd, 1);
if (!state)
*info = "failed cmd selection match";
}
return state;
}
static inline unsigned int match_to_peer(struct aa_profile *profile,
unsigned int state,
struct unix_sock *u,
struct sockaddr_un *peer_addr,
int peer_addrlen,
const char **info)
{
state = match_to_cmd(profile, state, u, CMD_ADDR, info);
if (state) {
state = match_addr(profile, state, peer_addr, peer_addrlen);
if (!state)
*info = "failed peer address match";
}
return state;
}
static int do_perms(struct aa_profile *profile, unsigned int state, u32 request,
struct common_audit_data *sa)
{
struct aa_perms perms;
AA_BUG(!profile);
aa_compute_perms(profile->policy.dfa, state, &perms);
aa_apply_modes_to_perms(profile, &perms);
return aa_check_perms(profile, &perms, request, sa,
audit_net_cb);
}
static int match_label(struct aa_profile *profile, struct aa_profile *peer,
unsigned int state, u32 request,
struct common_audit_data *sa)
{
AA_BUG(!profile);
AA_BUG(!peer);
aad(sa)->peer = &peer->label;
if (state) {
state = aa_dfa_match(profile->policy.dfa, state, aa_peer_name(peer));
if (!state)
aad(sa)->info = "failed peer label match";
}
return do_perms(profile, state, request, sa);
}
/* 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.
*/
static int profile_create_perm(struct aa_profile *profile, int family,
int type, int protocol)
{
unsigned int state;
DEFINE_AUDIT_NET(sa, OP_CREATE, NULL, family, type, protocol);
AA_BUG(!profile);
AA_BUG(profile_unconfined(profile));
if ((state = PROFILE_MEDIATES_AF(profile, AF_UNIX))) {
state = match_to_prot(profile, state, type, protocol,
&aad(&sa)->info);
return do_perms(profile, state, AA_MAY_CREATE, &sa);
}
return aa_profile_af_perm(profile, &sa, AA_MAY_CREATE, family, type);
}
int aa_unix_create_perm(struct aa_label *label, int family, int type,
int protocol)
{
struct aa_profile *profile;
if (unconfined(label))
return 0;
return fn_for_each_confined(label, profile,
profile_create_perm(profile, family, type, protocol));
}
static inline int profile_sk_perm(struct aa_profile *profile, const char *op,
u32 request, struct sock *sk)
{
unsigned int state;
DEFINE_AUDIT_SK(sa, op, sk);
AA_BUG(!profile);
AA_BUG(!sk);
AA_BUG(UNIX_FS(sk));
AA_BUG(profile_unconfined(profile));
state = PROFILE_MEDIATES_AF(profile, AF_UNIX);
if (state) {
state = match_to_sk(profile, state, unix_sk(sk),
&aad(&sa)->info);
return do_perms(profile, state, request, &sa);
}
return aa_profile_af_sk_perm(profile, &sa, request, sk);
}
int aa_unix_label_sk_perm(struct aa_label *label, const char *op, u32 request,
struct sock *sk)
{
struct aa_profile *profile;
return fn_for_each_confined(label, profile,
profile_sk_perm(profile, op, request, sk));
}
static int unix_label_sock_perm(struct aa_label *label, const char *op, u32 request,
struct socket *sock)
{
if (unconfined(label))
return 0;
if (UNIX_FS(sock->sk))
return unix_fs_perm(op, request, label, unix_sk(sock->sk), 0);
return aa_unix_label_sk_perm(label, op, request, sock->sk);
}
/* revaliation, get/set attr */
int aa_unix_sock_perm(const char *op, u32 request, struct socket *sock)
{
struct aa_label *label = aa_begin_current_label(DO_UPDATE);
int error = unix_label_sock_perm(label, op, request, sock);
aa_end_current_label(label);
return error;
}
static int profile_bind_perm(struct aa_profile *profile, struct sock *sk,
struct sockaddr *addr, int addrlen)
{
unsigned int state;
DEFINE_AUDIT_SK(sa, OP_BIND, sk);
AA_BUG(!profile);
AA_BUG(!sk);
AA_BUG(addr->sa_family != AF_UNIX);
AA_BUG(profile_unconfined(profile));
AA_BUG(unix_addr_fs(addr, addrlen));
state = PROFILE_MEDIATES_AF(profile, AF_UNIX);
if (state) {
/* bind for abstract socket */
aad(&sa)->net.addr = unix_addr(addr);
aad(&sa)->net.addrlen = addrlen;
state = match_to_local(profile, state,
sk->sk_type, sk->sk_protocol,
unix_addr(addr), addrlen,
&aad(&sa)->info);
return do_perms(profile, state, AA_MAY_BIND, &sa);
}
return aa_profile_af_sk_perm(profile, &sa, AA_MAY_BIND, sk);
}
int aa_unix_bind_perm(struct socket *sock, struct sockaddr *address,
int addrlen)
{
struct aa_profile *profile;
struct aa_label *label = aa_begin_current_label(DO_UPDATE);
int error = 0;
/* fs bind is handled by mknod */
if (!(unconfined(label) || unix_addr_fs(address, addrlen)))
error = fn_for_each_confined(label, profile,
profile_bind_perm(profile, sock->sk, address,
addrlen));
aa_end_current_label(label);
return error;
}
int aa_unix_connect_perm(struct socket *sock, struct sockaddr *address,
int addrlen)
{
/* unix connections are covered by the
* - unix_stream_connect (stream) and unix_may_send hooks (dgram)
* - fs connect is handled by open
*/
return 0;
}
static int profile_listen_perm(struct aa_profile *profile, struct sock *sk,
int backlog)
{
unsigned int state;
DEFINE_AUDIT_SK(sa, OP_LISTEN, sk);
AA_BUG(!profile);
AA_BUG(!sk);
AA_BUG(UNIX_FS(sk));
AA_BUG(profile_unconfined(profile));
state = PROFILE_MEDIATES_AF(profile, AF_UNIX);
if (state) {
u16 b = cpu_to_be16(backlog);
state = match_to_cmd(profile, state, unix_sk(sk), CMD_LISTEN,
&aad(&sa)->info);
if (state) {
state = aa_dfa_match_len(profile->policy.dfa, state,
(char *) &b, 2);
if (!state)
aad(&sa)->info = "failed listen backlog match";
}
return do_perms(profile, state, AA_MAY_LISTEN, &sa);
}
return aa_profile_af_sk_perm(profile, &sa, AA_MAY_LISTEN, sk);
}
int aa_unix_listen_perm(struct socket *sock, int backlog)
{
struct aa_profile *profile;
struct aa_label *label = aa_begin_current_label(DO_UPDATE);
int error = 0;
if (!(unconfined(label) || UNIX_FS(sock->sk)))
error = fn_for_each_confined(label, profile,
profile_listen_perm(profile, sock->sk,
backlog));
aa_end_current_label(label);
return error;
}
static inline int profile_accept_perm(struct aa_profile *profile,
struct sock *sk,
struct sock *newsk)
{
unsigned int state;
DEFINE_AUDIT_SK(sa, OP_ACCEPT, sk);
AA_BUG(!profile);
AA_BUG(!sk);
AA_BUG(UNIX_FS(sk));
AA_BUG(profile_unconfined(profile));
state = PROFILE_MEDIATES_AF(profile, AF_UNIX);
if (state) {
state = match_to_sk(profile, state, unix_sk(sk),
&aad(&sa)->info);
return do_perms(profile, state, AA_MAY_ACCEPT, &sa);
}
return aa_profile_af_sk_perm(profile, &sa, AA_MAY_ACCEPT, sk);
}
/* 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 = aa_begin_current_label(DO_UPDATE);
int error = 0;
if (!(unconfined(label) || UNIX_FS(sock->sk)))
error = fn_for_each_confined(label, profile,
profile_accept_perm(profile, sock->sk,
newsock->sk));
aa_end_current_label(label);
return error;
}
/* dgram handled by unix_may_sendmsg, right to send on stream done at connect
* could do per msg unix_stream here
*/
/* sendmsg, recvmsg */
int aa_unix_msg_perm(const char *op, u32 request, struct socket *sock,
struct msghdr *msg, int size)
{
return 0;
}
static int profile_opt_perm(struct aa_profile *profile, const char *op, u32 request,
struct sock *sk, int level, int optname)
{
unsigned int state;
DEFINE_AUDIT_SK(sa, op, sk);
AA_BUG(!profile);
AA_BUG(!sk);
AA_BUG(UNIX_FS(sk));
AA_BUG(profile_unconfined(profile));
state = PROFILE_MEDIATES_AF(profile, AF_UNIX);
if (state) {
u16 b = cpu_to_be16(optname);
state = match_to_cmd(profile, state, unix_sk(sk), CMD_OPT,
&aad(&sa)->info);
if (state) {
state = aa_dfa_match_len(profile->policy.dfa, state,
(char *) &b, 2);
if (!state)
aad(&sa)->info = "failed sockopt match";
}
return do_perms(profile, state, request, &sa);
}
return aa_profile_af_sk_perm(profile, &sa, request, sk);
}
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 = aa_begin_current_label(DO_UPDATE);
int error = 0;
if (!(unconfined(label) || UNIX_FS(sock->sk)))
error = fn_for_each_confined(label, profile,
profile_opt_perm(profile, op, request,
sock->sk, level, optname));
aa_end_current_label(label);
return error;
}
/* null peer_label is allowed, in which case the peer_sk label is used */
static int profile_peer_perm(struct aa_profile *profile, const char *op, u32 request,
struct sock *sk, struct sock *peer_sk,
struct aa_label *peer_label,
struct common_audit_data *sa)
{
unsigned int state;
AA_BUG(!profile);
AA_BUG(profile_unconfined(profile));
AA_BUG(!sk);
AA_BUG(!peer_sk);
AA_BUG(UNIX_FS(peer_sk));
state = PROFILE_MEDIATES_AF(profile, AF_UNIX);
if (state) {
struct aa_sk_ctx *peer_ctx = SK_CTX(peer_sk);
struct aa_profile *peerp;
struct sockaddr_un *addr = NULL;
int len = 0;
if (unix_sk(peer_sk)->addr) {
addr = unix_sk(peer_sk)->addr->name;
len = unix_sk(peer_sk)->addr->len;
}
state = match_to_peer(profile, state, unix_sk(sk),
addr, len, &aad(sa)->info);
if (!peer_label)
peer_label = peer_ctx->label;
return fn_for_each(peer_label, peerp,
match_label(profile, peerp, state, request,
sa));
}
return aa_profile_af_sk_perm(profile, sa, request, sk);
}
/**
*
* Requires: lock held on both @sk and @peer_sk
*/
int aa_unix_peer_perm(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);
AA_BUG(!label);
AA_BUG(!sk);
AA_BUG(!peer_sk);
if (UNIX_FS(aa_sock(peeru)))
return unix_fs_perm(op, request, label, peeru, 0);
else if (UNIX_FS(aa_sock(u)))
return unix_fs_perm(op, request, label, u, 0);
else {
struct aa_profile *profile;
DEFINE_AUDIT_SK(sa, op, sk);
aad(&sa)->net.peer_sk = peer_sk;
/* TODO: ns!!! */
if (!net_eq(sock_net(sk), sock_net(peer_sk))) {
;
}
if (unconfined(label))
return 0;
return fn_for_each_confined(label, profile,
profile_peer_perm(profile, op, request, sk,
peer_sk, peer_label, &sa));
}
}
/* from net/unix/af_unix.c */
static void unix_state_double_lock(struct sock *sk1, struct sock *sk2)
{
if (unlikely(sk1 == sk2) || !sk2) {
unix_state_lock(sk1);
return;
}
if (sk1 < sk2) {
unix_state_lock(sk1);
unix_state_lock_nested(sk2);
} else {
unix_state_lock(sk2);
unix_state_lock_nested(sk1);
}
}
static void unix_state_double_unlock(struct sock *sk1, struct sock *sk2)
{
if (unlikely(sk1 == sk2) || !sk2) {
unix_state_unlock(sk1);
return;
}
unix_state_unlock(sk1);
unix_state_unlock(sk2);
}
int aa_unix_file_perm(struct aa_label *label, const char *op, u32 request,
struct socket *sock)
{
struct sock *peer_sk = NULL;
u32 sk_req = request & ~NET_PEER_MASK;
int error = 0;
AA_BUG(!label);
AA_BUG(!sock);
AA_BUG(!sock->sk);
AA_BUG(sock->sk->sk_family != AF_UNIX);
/* TODO: update sock label with new task label */
unix_state_lock(sock->sk);
peer_sk = unix_peer(sock->sk);
if (peer_sk)
sock_hold(peer_sk);
if (!unix_connected(sock) && sk_req) {
error = unix_label_sock_perm(label, op, sk_req, sock);
if (!error) {
// update label
}
}
unix_state_unlock(sock->sk);
if (!peer_sk)
return error;
unix_state_double_lock(sock->sk, peer_sk);
if (UNIX_FS(sock->sk)) {
error = unix_fs_perm(op, request, label, unix_sk(sock->sk),
PATH_SOCK_COND);
} else if (UNIX_FS(peer_sk)) {
error = unix_fs_perm(op, request, label, unix_sk(peer_sk),
PATH_SOCK_COND);
} else {
struct aa_sk_ctx *pctx = SK_CTX(peer_sk);
if (sk_req)
error = aa_unix_label_sk_perm(label, op, sk_req,
sock->sk);
last_error(error,
xcheck(aa_unix_peer_perm(label, op,
MAY_READ | MAY_WRITE,
sock->sk, peer_sk, NULL),
aa_unix_peer_perm(pctx->label, op,
MAY_READ | MAY_WRITE,
peer_sk, sock->sk, label)));
}
unix_state_double_unlock(sock->sk, peer_sk);
sock_put(peer_sk);
return error;
}
...@@ -18,17 +18,23 @@ ...@@ -18,17 +18,23 @@
#include <linux/module.h> #include <linux/module.h>
#include <linux/seq_file.h> #include <linux/seq_file.h>
#include <linux/uaccess.h> #include <linux/uaccess.h>
#include <linux/mount.h>
#include <linux/namei.h> #include <linux/namei.h>
#include <linux/capability.h> #include <linux/capability.h>
#include <linux/rcupdate.h> #include <linux/rcupdate.h>
#include <uapi/linux/major.h>
#include "include/apparmor.h" #include "include/apparmor.h"
#include "include/apparmorfs.h" #include "include/apparmorfs.h"
#include "include/audit.h" #include "include/audit.h"
#include "include/context.h" #include "include/context.h"
#include "include/crypto.h" #include "include/crypto.h"
#include "include/ipc.h"
#include "include/policy_ns.h"
#include "include/label.h"
#include "include/policy.h" #include "include/policy.h"
#include "include/resource.h" #include "include/resource.h"
#include "include/lib.h"
/** /**
* aa_mangle_name - mangle a profile name to std profile layout form * aa_mangle_name - mangle a profile name to std profile layout form
...@@ -37,7 +43,7 @@ ...@@ -37,7 +43,7 @@
* *
* Returns: length of mangled name * Returns: length of mangled name
*/ */
static int mangle_name(char *name, char *target) static int mangle_name(const char *name, char *target)
{ {
char *t = target; char *t = target;
...@@ -71,7 +77,6 @@ static int mangle_name(char *name, char *target) ...@@ -71,7 +77,6 @@ static int mangle_name(char *name, char *target)
/** /**
* aa_simple_write_to_buffer - common routine for getting policy from user * aa_simple_write_to_buffer - common routine for getting policy from user
* @op: operation doing the user buffer copy
* @userbuf: user buffer to copy data from (NOT NULL) * @userbuf: user buffer to copy data from (NOT NULL)
* @alloc_size: size of user buffer (REQUIRES: @alloc_size >= @copy_size) * @alloc_size: size of user buffer (REQUIRES: @alloc_size >= @copy_size)
* @copy_size: size of data to copy from user buffer * @copy_size: size of data to copy from user buffer
...@@ -80,7 +85,7 @@ static int mangle_name(char *name, char *target) ...@@ -80,7 +85,7 @@ static int mangle_name(char *name, char *target)
* Returns: kernel buffer containing copy of user buffer data or an * Returns: kernel buffer containing copy of user buffer data or an
* ERR_PTR on failure. * ERR_PTR on failure.
*/ */
static char *aa_simple_write_to_buffer(int op, const char __user *userbuf, static char *aa_simple_write_to_buffer(const char __user *userbuf,
size_t alloc_size, size_t copy_size, size_t alloc_size, size_t copy_size,
loff_t *pos) loff_t *pos)
{ {
...@@ -92,13 +97,6 @@ static char *aa_simple_write_to_buffer(int op, const char __user *userbuf, ...@@ -92,13 +97,6 @@ static char *aa_simple_write_to_buffer(int op, const char __user *userbuf,
/* only writes from pos 0, that is complete writes */ /* only writes from pos 0, that is complete writes */
return ERR_PTR(-ESPIPE); return ERR_PTR(-ESPIPE);
/*
* Don't allow profile load/replace/remove from profiles that don't
* have CAP_MAC_ADMIN
*/
if (!aa_may_manage_policy(op))
return ERR_PTR(-EACCES);
/* freed by caller to simple_write_to_buffer */ /* freed by caller to simple_write_to_buffer */
data = kvmalloc(alloc_size); data = kvmalloc(alloc_size);
if (data == NULL) if (data == NULL)
...@@ -112,25 +110,40 @@ static char *aa_simple_write_to_buffer(int op, const char __user *userbuf, ...@@ -112,25 +110,40 @@ static char *aa_simple_write_to_buffer(int op, const char __user *userbuf,
return data; return data;
} }
static ssize_t policy_update(u32 mask, const char __user *buf, size_t size,
/* .load file hook fn to load policy */
static ssize_t profile_load(struct file *f, const char __user *buf, size_t size,
loff_t *pos) loff_t *pos)
{ {
char *data; struct aa_label *label;
ssize_t error; ssize_t error;
char *data;
data = aa_simple_write_to_buffer(OP_PROF_LOAD, buf, size, size, pos); label = aa_begin_current_label(DO_UPDATE);
/* high level check about policy management - fine grained in
* below after unpack
*/
error = aa_may_manage_policy(label, mask);
if (error)
return error;
data = aa_simple_write_to_buffer(buf, size, size, pos);
error = PTR_ERR(data); error = PTR_ERR(data);
if (!IS_ERR(data)) { if (!IS_ERR(data)) {
error = aa_replace_profiles(data, size, PROF_ADD); error = aa_replace_profiles(label, mask, data, size);
kvfree(data); kvfree(data);
} }
aa_end_current_label(label);
return error; return error;
} }
/* .load file hook fn to load policy */
static ssize_t profile_load(struct file *f, const char __user *buf, size_t size,
loff_t *pos)
{
return policy_update(AA_MAY_LOAD_POLICY, buf, size, pos);
}
static const struct file_operations aa_fs_profile_load = { static const struct file_operations aa_fs_profile_load = {
.write = profile_load, .write = profile_load,
.llseek = default_llseek, .llseek = default_llseek,
...@@ -140,17 +153,8 @@ static const struct file_operations aa_fs_profile_load = { ...@@ -140,17 +153,8 @@ static const struct file_operations aa_fs_profile_load = {
static ssize_t profile_replace(struct file *f, const char __user *buf, static ssize_t profile_replace(struct file *f, const char __user *buf,
size_t size, loff_t *pos) size_t size, loff_t *pos)
{ {
char *data; return policy_update(AA_MAY_LOAD_POLICY | AA_MAY_REPLACE_POLICY,
ssize_t error; buf, size, pos);
data = aa_simple_write_to_buffer(OP_PROF_REPL, buf, size, size, pos);
error = PTR_ERR(data);
if (!IS_ERR(data)) {
error = aa_replace_profiles(data, size, PROF_REPLACE);
kvfree(data);
}
return error;
} }
static const struct file_operations aa_fs_profile_replace = { static const struct file_operations aa_fs_profile_replace = {
...@@ -162,21 +166,31 @@ static const struct file_operations aa_fs_profile_replace = { ...@@ -162,21 +166,31 @@ static const struct file_operations aa_fs_profile_replace = {
static ssize_t profile_remove(struct file *f, const char __user *buf, static ssize_t profile_remove(struct file *f, const char __user *buf,
size_t size, loff_t *pos) size_t size, loff_t *pos)
{ {
char *data; struct aa_label *label;
ssize_t error; ssize_t error;
char *data;
label = aa_begin_current_label(DO_UPDATE);
/* high level check about policy management - fine grained in
* below after unpack
*/
error = aa_may_manage_policy(label, AA_MAY_REMOVE_POLICY);
if (error)
return error;
/* /*
* aa_remove_profile needs a null terminated string so 1 extra * aa_remove_profile needs a null terminated string so 1 extra
* byte is allocated and the copied data is null terminated. * byte is allocated and the copied data is null terminated.
*/ */
data = aa_simple_write_to_buffer(OP_PROF_RM, buf, size + 1, size, pos); data = aa_simple_write_to_buffer(buf, size + 1, size, pos);
error = PTR_ERR(data); error = PTR_ERR(data);
if (!IS_ERR(data)) { if (!IS_ERR(data)) {
data[size] = 0; data[size] = 0;
error = aa_remove_profiles(data, size); error = aa_remove_profiles(label, data, size);
kvfree(data); kvfree(data);
} }
aa_end_current_label(label);
return error; return error;
} }
...@@ -186,6 +200,176 @@ static const struct file_operations aa_fs_profile_remove = { ...@@ -186,6 +200,176 @@ static const struct file_operations aa_fs_profile_remove = {
.llseek = default_llseek, .llseek = default_llseek,
}; };
static void profile_query_cb(struct aa_profile *profile, struct aa_perms *perms,
const char *match_str, size_t match_len)
{
struct aa_perms tmp;
struct aa_dfa *dfa;
unsigned int state = 0;
if (profile_unconfined(profile))
return;
if (profile->file.dfa && *match_str == AA_CLASS_FILE) {
dfa = profile->file.dfa;
state = aa_dfa_match_len(dfa, profile->file.start,
match_str + 1, match_len - 1);
tmp = nullperms;
if (state) {
struct path_cond cond = { };
tmp = aa_compute_fperms(dfa, state, &cond);
}
} else if (profile->policy.dfa) {
if (!PROFILE_MEDIATES_SAFE(profile, *match_str))
return; /* no change to current perms */
dfa = profile->policy.dfa;
state = aa_dfa_match_len(dfa, profile->policy.start[0],
match_str, match_len);
if (state)
aa_compute_perms(dfa, state, &tmp);
else
tmp = nullperms;
}
aa_apply_modes_to_perms(profile, &tmp);
aa_perms_accum_raw(perms, &tmp);
}
/**
* query_label - queries a label and writes permissions to buf
* @buf: the resulting permissions string is stored here (NOT NULL)
* @buf_len: size of buf
* @query: binary query string to match against the dfa
* @query_len: size of query
*
* The buffers pointed to by buf and query may overlap. The query buffer is
* parsed before buf is written to.
*
* The query should look like "LABEL_NAME\0DFA_STRING" where LABEL_NAME is
* the name of the label, in the current namespace, that is to be queried and
* DFA_STRING is a binary string to match against the label(s)'s DFA.
*
* LABEL_NAME must be NUL terminated. DFA_STRING may contain NUL characters
* but must *not* be NUL terminated.
*
* Returns: number of characters written to buf or -errno on failure
*/
static ssize_t query_label(char *buf, size_t buf_len,
char *query, size_t query_len, bool ns_only)
{
struct aa_profile *profile;
struct aa_label *label, *curr;
char *label_name, *match_str;
size_t label_name_len, match_len;
struct aa_perms perms;
struct label_it i;
if (!query_len)
return -EINVAL;
label_name = query;
label_name_len = strnlen(query, query_len);
if (!label_name_len || label_name_len == query_len)
return -EINVAL;
/**
* The extra byte is to account for the null byte between the
* profile name and dfa string. profile_name_len is greater
* than zero and less than query_len, so a byte can be safely
* added or subtracted.
*/
match_str = label_name + label_name_len + 1;
match_len = query_len - label_name_len - 1;
curr = aa_begin_current_label(DO_UPDATE);
label = aa_label_parse(curr, label_name, GFP_KERNEL, false, false);
aa_end_current_label(curr);
if (IS_ERR(label))
return PTR_ERR(label);
perms = allperms;
if (ns_only) {
label_for_each_in_ns(i, labels_ns(label), label, profile) {
profile_query_cb(profile, &perms, match_str, match_len);
}
} else {
label_for_each(i, label, profile) {
profile_query_cb(profile, &perms, match_str, match_len);
}
}
aa_put_label(label);
return scnprintf(buf, buf_len,
"allow 0x%08x\ndeny 0x%08x\naudit 0x%08x\nquiet 0x%08x\n",
perms.allow, perms.deny, perms.audit, perms.quiet);
}
#define QUERY_CMD_LABEL "label\0"
#define QUERY_CMD_LABEL_LEN 6
#define QUERY_CMD_PROFILE "profile\0"
#define QUERY_CMD_PROFILE_LEN 8
#define QUERY_CMD_LABELALL "labelall\0"
#define QUERY_CMD_LABELALL_LEN 9
/**
* aa_write_access - generic permissions query
* @file: pointer to open apparmorfs/access file
* @ubuf: user buffer containing the complete query string (NOT NULL)
* @count: size of ubuf
* @ppos: position in the file (MUST BE ZERO)
*
* Allows for one permission query per open(), write(), and read() sequence.
* The only query currently supported is a label-based query. For this query
* ubuf must begin with "label\0", followed by the profile query specific
* format described in the query_label() function documentation.
*
* Returns: number of bytes written or -errno on failure
*/
static ssize_t aa_write_access(struct file *file, const char __user *ubuf,
size_t count, loff_t *ppos)
{
char *buf;
ssize_t len;
if (*ppos)
return -ESPIPE;
buf = simple_transaction_get(file, ubuf, count);
if (IS_ERR(buf))
return PTR_ERR(buf);
if (count > QUERY_CMD_PROFILE_LEN &&
!memcmp(buf, QUERY_CMD_PROFILE, QUERY_CMD_PROFILE_LEN)) {
len = query_label(buf, SIMPLE_TRANSACTION_LIMIT,
buf + QUERY_CMD_PROFILE_LEN,
count - QUERY_CMD_PROFILE_LEN, true);
} else if (count > QUERY_CMD_LABEL_LEN &&
!memcmp(buf, QUERY_CMD_LABEL, QUERY_CMD_LABEL_LEN)) {
len = query_label(buf, SIMPLE_TRANSACTION_LIMIT,
buf + QUERY_CMD_LABEL_LEN,
count - QUERY_CMD_LABEL_LEN, true);
} else if (count > QUERY_CMD_LABELALL_LEN &&
!memcmp(buf, QUERY_CMD_LABELALL, QUERY_CMD_LABELALL_LEN)) {
len = query_label(buf, SIMPLE_TRANSACTION_LIMIT,
buf + QUERY_CMD_LABELALL_LEN,
count - QUERY_CMD_LABELALL_LEN, false);
} else
len = -EINVAL;
if (len < 0)
return len;
simple_transaction_set(file, len);
return count;
}
static const struct file_operations aa_fs_access = {
.write = aa_write_access,
.read = simple_transaction_read,
.release = simple_transaction_release,
.llseek = generic_file_llseek,
};
static int aa_fs_seq_show(struct seq_file *seq, void *v) static int aa_fs_seq_show(struct seq_file *seq, void *v)
{ {
struct aa_fs_entry *fs_file = seq->private; struct aa_fs_entry *fs_file = seq->private;
...@@ -227,12 +411,12 @@ const struct file_operations aa_fs_seq_file_ops = { ...@@ -227,12 +411,12 @@ const struct file_operations aa_fs_seq_file_ops = {
static int aa_fs_seq_profile_open(struct inode *inode, struct file *file, static int aa_fs_seq_profile_open(struct inode *inode, struct file *file,
int (*show)(struct seq_file *, void *)) int (*show)(struct seq_file *, void *))
{ {
struct aa_replacedby *r = aa_get_replacedby(inode->i_private); struct aa_proxy *proxy = aa_get_proxy(inode->i_private);
int error = single_open(file, show, r); int error = single_open(file, show, proxy);
if (error) { if (error) {
file->private_data = NULL; file->private_data = NULL;
aa_put_replacedby(r); aa_put_proxy(proxy);
} }
return error; return error;
...@@ -242,16 +426,17 @@ static int aa_fs_seq_profile_release(struct inode *inode, struct file *file) ...@@ -242,16 +426,17 @@ static int aa_fs_seq_profile_release(struct inode *inode, struct file *file)
{ {
struct seq_file *seq = (struct seq_file *) file->private_data; struct seq_file *seq = (struct seq_file *) file->private_data;
if (seq) if (seq)
aa_put_replacedby(seq->private); aa_put_proxy(seq->private);
return single_release(inode, file); return single_release(inode, file);
} }
static int aa_fs_seq_profname_show(struct seq_file *seq, void *v) static int aa_fs_seq_profname_show(struct seq_file *seq, void *v)
{ {
struct aa_replacedby *r = seq->private; struct aa_proxy *proxy = seq->private;
struct aa_profile *profile = aa_get_profile_rcu(&r->profile); struct aa_label *label = aa_get_label_rcu(&proxy->label);
struct aa_profile *profile = labels_profile(label);
seq_printf(seq, "%s\n", profile->base.name); seq_printf(seq, "%s\n", profile->base.name);
aa_put_profile(profile); aa_put_label(label);
return 0; return 0;
} }
...@@ -271,10 +456,11 @@ static const struct file_operations aa_fs_profname_fops = { ...@@ -271,10 +456,11 @@ static const struct file_operations aa_fs_profname_fops = {
static int aa_fs_seq_profmode_show(struct seq_file *seq, void *v) static int aa_fs_seq_profmode_show(struct seq_file *seq, void *v)
{ {
struct aa_replacedby *r = seq->private; struct aa_proxy *proxy = seq->private;
struct aa_profile *profile = aa_get_profile_rcu(&r->profile); struct aa_label *label = aa_get_label_rcu(&proxy->label);
struct aa_profile *profile = labels_profile(label);
seq_printf(seq, "%s\n", aa_profile_mode_names[profile->mode]); seq_printf(seq, "%s\n", aa_profile_mode_names[profile->mode]);
aa_put_profile(profile); aa_put_label(label);
return 0; return 0;
} }
...@@ -294,15 +480,16 @@ static const struct file_operations aa_fs_profmode_fops = { ...@@ -294,15 +480,16 @@ static const struct file_operations aa_fs_profmode_fops = {
static int aa_fs_seq_profattach_show(struct seq_file *seq, void *v) static int aa_fs_seq_profattach_show(struct seq_file *seq, void *v)
{ {
struct aa_replacedby *r = seq->private; struct aa_proxy *proxy = seq->private;
struct aa_profile *profile = aa_get_profile_rcu(&r->profile); struct aa_label *label = aa_get_label_rcu(&proxy->label);
struct aa_profile *profile = labels_profile(label);
if (profile->attach) if (profile->attach)
seq_printf(seq, "%s\n", profile->attach); seq_printf(seq, "%s\n", profile->attach);
else if (profile->xmatch) else if (profile->xmatch)
seq_puts(seq, "<unknown>\n"); seq_puts(seq, "<unknown>\n");
else else
seq_printf(seq, "%s\n", profile->base.name); seq_printf(seq, "%s\n", profile->base.name);
aa_put_profile(profile); aa_put_label(label);
return 0; return 0;
} }
...@@ -322,8 +509,9 @@ static const struct file_operations aa_fs_profattach_fops = { ...@@ -322,8 +509,9 @@ static const struct file_operations aa_fs_profattach_fops = {
static int aa_fs_seq_hash_show(struct seq_file *seq, void *v) static int aa_fs_seq_hash_show(struct seq_file *seq, void *v)
{ {
struct aa_replacedby *r = seq->private; struct aa_proxy *proxy = seq->private;
struct aa_profile *profile = aa_get_profile_rcu(&r->profile); struct aa_label *label = aa_get_label_rcu(&proxy->label);
struct aa_profile *profile = labels_profile(label);
unsigned int i, size = aa_hash_size(); unsigned int i, size = aa_hash_size();
if (profile->hash) { if (profile->hash) {
...@@ -331,6 +519,7 @@ static int aa_fs_seq_hash_show(struct seq_file *seq, void *v) ...@@ -331,6 +519,7 @@ static int aa_fs_seq_hash_show(struct seq_file *seq, void *v)
seq_printf(seq, "%.2x", profile->hash[i]); seq_printf(seq, "%.2x", profile->hash[i]);
seq_puts(seq, "\n"); seq_puts(seq, "\n");
} }
aa_put_label(label);
return 0; return 0;
} }
...@@ -349,6 +538,11 @@ static const struct file_operations aa_fs_seq_hash_fops = { ...@@ -349,6 +538,11 @@ static const struct file_operations aa_fs_seq_hash_fops = {
}; };
/** fns to setup dynamic per profile/namespace files **/ /** fns to setup dynamic per profile/namespace files **/
/**
*
* Requires: @profile->ns->lock held
*/
void __aa_fs_profile_rmdir(struct aa_profile *profile) void __aa_fs_profile_rmdir(struct aa_profile *profile)
{ {
struct aa_profile *child; struct aa_profile *child;
...@@ -356,29 +550,40 @@ void __aa_fs_profile_rmdir(struct aa_profile *profile) ...@@ -356,29 +550,40 @@ void __aa_fs_profile_rmdir(struct aa_profile *profile)
if (!profile) if (!profile)
return; return;
AA_BUG(!mutex_is_locked(&profiles_ns(profile)->lock));
list_for_each_entry(child, &profile->base.profiles, base.list) list_for_each_entry(child, &profile->base.profiles, base.list)
__aa_fs_profile_rmdir(child); __aa_fs_profile_rmdir(child);
for (i = AAFS_PROF_SIZEOF - 1; i >= 0; --i) { for (i = AAFS_PROF_SIZEOF - 1; i >= 0; --i) {
struct aa_replacedby *r; struct aa_proxy *proxy;
if (!profile->dents[i]) if (!profile->dents[i])
continue; continue;
r = d_inode(profile->dents[i])->i_private; proxy = d_inode(profile->dents[i])->i_private;
securityfs_remove(profile->dents[i]); securityfs_remove(profile->dents[i]);
aa_put_replacedby(r); aa_put_proxy(proxy);
profile->dents[i] = NULL; profile->dents[i] = NULL;
} }
} }
/**
*
* Requires: @old->ns->lock held
*/
void __aa_fs_profile_migrate_dents(struct aa_profile *old, void __aa_fs_profile_migrate_dents(struct aa_profile *old,
struct aa_profile *new) struct aa_profile *new)
{ {
int i; int i;
AA_BUG(!old);
AA_BUG(!new);
AA_BUG(!mutex_is_locked(&profiles_ns(old)->lock));
for (i = 0; i < AAFS_PROF_SIZEOF; i++) { for (i = 0; i < AAFS_PROF_SIZEOF; i++) {
new->dents[i] = old->dents[i]; new->dents[i] = old->dents[i];
if (new->dents[i])
new->dents[i]->d_inode->i_mtime = CURRENT_TIME;
old->dents[i] = NULL; old->dents[i] = NULL;
} }
} }
...@@ -387,23 +592,29 @@ static struct dentry *create_profile_file(struct dentry *dir, const char *name, ...@@ -387,23 +592,29 @@ static struct dentry *create_profile_file(struct dentry *dir, const char *name,
struct aa_profile *profile, struct aa_profile *profile,
const struct file_operations *fops) const struct file_operations *fops)
{ {
struct aa_replacedby *r = aa_get_replacedby(profile->replacedby); struct aa_proxy *proxy = aa_get_proxy(profile->label.proxy);
struct dentry *dent; struct dentry *dent;
dent = securityfs_create_file(name, S_IFREG | 0444, dir, r, fops); dent = securityfs_create_file(name, S_IFREG | 0444, dir, proxy, fops);
if (IS_ERR(dent)) if (IS_ERR(dent))
aa_put_replacedby(r); aa_put_proxy(proxy);
return dent; return dent;
} }
/* requires lock be held */ /**
*
* Requires: @profile->ns->lock held
*/
int __aa_fs_profile_mkdir(struct aa_profile *profile, struct dentry *parent) int __aa_fs_profile_mkdir(struct aa_profile *profile, struct dentry *parent)
{ {
struct aa_profile *child; struct aa_profile *child;
struct dentry *dent = NULL, *dir; struct dentry *dent = NULL, *dir;
int error; int error;
AA_BUG(!profile);
AA_BUG(!mutex_is_locked(&profiles_ns(profile)->lock));
if (!parent) { if (!parent) {
struct aa_profile *p; struct aa_profile *p;
p = aa_deref_parent(profile); p = aa_deref_parent(profile);
...@@ -474,21 +685,26 @@ int __aa_fs_profile_mkdir(struct aa_profile *profile, struct dentry *parent) ...@@ -474,21 +685,26 @@ int __aa_fs_profile_mkdir(struct aa_profile *profile, struct dentry *parent)
return error; return error;
} }
void __aa_fs_namespace_rmdir(struct aa_namespace *ns) /**
*
* Requires: @ns->lock held
*/
void __aa_fs_ns_rmdir(struct aa_ns *ns)
{ {
struct aa_namespace *sub; struct aa_ns *sub;
struct aa_profile *child; struct aa_profile *child;
int i; int i;
if (!ns) if (!ns)
return; return;
AA_BUG(!mutex_is_locked(&ns->lock));
list_for_each_entry(child, &ns->base.profiles, base.list) list_for_each_entry(child, &ns->base.profiles, base.list)
__aa_fs_profile_rmdir(child); __aa_fs_profile_rmdir(child);
list_for_each_entry(sub, &ns->sub_ns, base.list) { list_for_each_entry(sub, &ns->sub_ns, base.list) {
mutex_lock(&sub->lock); mutex_lock(&sub->lock);
__aa_fs_namespace_rmdir(sub); __aa_fs_ns_rmdir(sub);
mutex_unlock(&sub->lock); mutex_unlock(&sub->lock);
} }
...@@ -498,14 +714,21 @@ void __aa_fs_namespace_rmdir(struct aa_namespace *ns) ...@@ -498,14 +714,21 @@ void __aa_fs_namespace_rmdir(struct aa_namespace *ns)
} }
} }
int __aa_fs_namespace_mkdir(struct aa_namespace *ns, struct dentry *parent, /**
const char *name) *
* Requires: @ns->lock held
*/
int __aa_fs_ns_mkdir(struct aa_ns *ns, struct dentry *parent, const char *name)
{ {
struct aa_namespace *sub; struct aa_ns *sub;
struct aa_profile *child; struct aa_profile *child;
struct dentry *dent, *dir; struct dentry *dent, *dir;
int error; int error;
AA_BUG(!ns);
AA_BUG(!parent);
AA_BUG(!mutex_is_locked(&ns->lock));
if (!name) if (!name)
name = ns->base.name; name = ns->base.name;
...@@ -532,7 +755,7 @@ int __aa_fs_namespace_mkdir(struct aa_namespace *ns, struct dentry *parent, ...@@ -532,7 +755,7 @@ int __aa_fs_namespace_mkdir(struct aa_namespace *ns, struct dentry *parent,
list_for_each_entry(sub, &ns->sub_ns, base.list) { list_for_each_entry(sub, &ns->sub_ns, base.list) {
mutex_lock(&sub->lock); mutex_lock(&sub->lock);
error = __aa_fs_namespace_mkdir(sub, ns_subns_dir(ns), NULL); error = __aa_fs_ns_mkdir(sub, ns_subns_dir(ns), NULL);
mutex_unlock(&sub->lock); mutex_unlock(&sub->lock);
if (error) if (error)
goto fail2; goto fail2;
...@@ -544,7 +767,7 @@ int __aa_fs_namespace_mkdir(struct aa_namespace *ns, struct dentry *parent, ...@@ -544,7 +767,7 @@ int __aa_fs_namespace_mkdir(struct aa_namespace *ns, struct dentry *parent,
error = PTR_ERR(dent); error = PTR_ERR(dent);
fail2: fail2:
__aa_fs_namespace_rmdir(ns); __aa_fs_ns_rmdir(ns);
return error; return error;
} }
...@@ -555,7 +778,7 @@ int __aa_fs_namespace_mkdir(struct aa_namespace *ns, struct dentry *parent, ...@@ -555,7 +778,7 @@ int __aa_fs_namespace_mkdir(struct aa_namespace *ns, struct dentry *parent,
#define list_entry_is_head(pos, head, member) (&pos->member == (head)) #define list_entry_is_head(pos, head, member) (&pos->member == (head))
/** /**
* __next_namespace - find the next namespace to list * __next_ns - find the next namespace to list
* @root: root namespace to stop search at (NOT NULL) * @root: root namespace to stop search at (NOT NULL)
* @ns: current ns position (NOT NULL) * @ns: current ns position (NOT NULL)
* *
...@@ -566,10 +789,13 @@ int __aa_fs_namespace_mkdir(struct aa_namespace *ns, struct dentry *parent, ...@@ -566,10 +789,13 @@ int __aa_fs_namespace_mkdir(struct aa_namespace *ns, struct dentry *parent,
* Requires: ns->parent->lock to be held * Requires: ns->parent->lock to be held
* NOTE: will not unlock root->lock * NOTE: will not unlock root->lock
*/ */
static struct aa_namespace *__next_namespace(struct aa_namespace *root, static struct aa_ns *__next_ns(struct aa_ns *root, struct aa_ns *ns)
struct aa_namespace *ns)
{ {
struct aa_namespace *parent, *next; struct aa_ns *parent, *next;
AA_BUG(!root);
AA_BUG(!ns);
AA_BUG(ns != root && !mutex_is_locked(&ns->parent->lock));
/* is next namespace a child */ /* is next namespace a child */
if (!list_empty(&ns->sub_ns)) { if (!list_empty(&ns->sub_ns)) {
...@@ -597,15 +823,17 @@ static struct aa_namespace *__next_namespace(struct aa_namespace *root, ...@@ -597,15 +823,17 @@ static struct aa_namespace *__next_namespace(struct aa_namespace *root,
/** /**
* __first_profile - find the first profile in a namespace * __first_profile - find the first profile in a namespace
* @root: namespace that is root of profiles being displayed (NOT NULL) * @root: namespace that is root of profiles being displayed (NOT NULL)
* @ns: namespace to start in (NOT NULL) * @ns: namespace to start in (MAY BE NULL)
* *
* Returns: unrefcounted profile or NULL if no profile * Returns: unrefcounted profile or NULL if no profile
* Requires: profile->ns.lock to be held * Requires: ns.lock to be held
*/ */
static struct aa_profile *__first_profile(struct aa_namespace *root, static struct aa_profile *__first_profile(struct aa_ns *root, struct aa_ns *ns)
struct aa_namespace *ns)
{ {
for (; ns; ns = __next_namespace(root, ns)) { AA_BUG(!root);
AA_BUG(ns && !mutex_is_locked(&ns->lock));
for (; ns; ns = __next_ns(root, ns)) {
if (!list_empty(&ns->base.profiles)) if (!list_empty(&ns->base.profiles))
return list_first_entry(&ns->base.profiles, return list_first_entry(&ns->base.profiles,
struct aa_profile, base.list); struct aa_profile, base.list);
...@@ -625,7 +853,9 @@ static struct aa_profile *__first_profile(struct aa_namespace *root, ...@@ -625,7 +853,9 @@ static struct aa_profile *__first_profile(struct aa_namespace *root,
static struct aa_profile *__next_profile(struct aa_profile *p) static struct aa_profile *__next_profile(struct aa_profile *p)
{ {
struct aa_profile *parent; struct aa_profile *parent;
struct aa_namespace *ns = p->ns; struct aa_ns *ns = p->ns;
AA_BUG(!mutex_is_locked(&profiles_ns(p)->lock));
/* is next profile a child */ /* is next profile a child */
if (!list_empty(&p->base.profiles)) if (!list_empty(&p->base.profiles))
...@@ -659,7 +889,7 @@ static struct aa_profile *__next_profile(struct aa_profile *p) ...@@ -659,7 +889,7 @@ static struct aa_profile *__next_profile(struct aa_profile *p)
* *
* Returns: next profile or NULL if there isn't one * Returns: next profile or NULL if there isn't one
*/ */
static struct aa_profile *next_profile(struct aa_namespace *root, static struct aa_profile *next_profile(struct aa_ns *root,
struct aa_profile *profile) struct aa_profile *profile)
{ {
struct aa_profile *next = __next_profile(profile); struct aa_profile *next = __next_profile(profile);
...@@ -667,7 +897,7 @@ static struct aa_profile *next_profile(struct aa_namespace *root, ...@@ -667,7 +897,7 @@ static struct aa_profile *next_profile(struct aa_namespace *root,
return next; return next;
/* finished all profiles in namespace move to next namespace */ /* finished all profiles in namespace move to next namespace */
return __first_profile(root, __next_namespace(root, profile->ns)); return __first_profile(root, __next_ns(root, profile->ns));
} }
/** /**
...@@ -682,10 +912,9 @@ static struct aa_profile *next_profile(struct aa_namespace *root, ...@@ -682,10 +912,9 @@ static struct aa_profile *next_profile(struct aa_namespace *root,
static void *p_start(struct seq_file *f, loff_t *pos) static void *p_start(struct seq_file *f, loff_t *pos)
{ {
struct aa_profile *profile = NULL; struct aa_profile *profile = NULL;
struct aa_namespace *root = aa_current_profile()->ns; struct aa_ns *root = aa_get_current_ns();
loff_t l = *pos; loff_t l = *pos;
f->private = aa_get_namespace(root); f->private = root;
/* find the first profile */ /* find the first profile */
mutex_lock(&root->lock); mutex_lock(&root->lock);
...@@ -711,7 +940,7 @@ static void *p_start(struct seq_file *f, loff_t *pos) ...@@ -711,7 +940,7 @@ static void *p_start(struct seq_file *f, loff_t *pos)
static void *p_next(struct seq_file *f, void *p, loff_t *pos) static void *p_next(struct seq_file *f, void *p, loff_t *pos)
{ {
struct aa_profile *profile = p; struct aa_profile *profile = p;
struct aa_namespace *ns = f->private; struct aa_ns *ns = f->private;
(*pos)++; (*pos)++;
return next_profile(ns, profile); return next_profile(ns, profile);
...@@ -727,14 +956,14 @@ static void *p_next(struct seq_file *f, void *p, loff_t *pos) ...@@ -727,14 +956,14 @@ static void *p_next(struct seq_file *f, void *p, loff_t *pos)
static void p_stop(struct seq_file *f, void *p) static void p_stop(struct seq_file *f, void *p)
{ {
struct aa_profile *profile = p; struct aa_profile *profile = p;
struct aa_namespace *root = f->private, *ns; struct aa_ns *root = f->private, *ns;
if (profile) { if (profile) {
for (ns = profile->ns; ns && ns != root; ns = ns->parent) for (ns = profile->ns; ns && ns != root; ns = ns->parent)
mutex_unlock(&ns->lock); mutex_unlock(&ns->lock);
} }
mutex_unlock(&root->lock); mutex_unlock(&root->lock);
aa_put_namespace(root); aa_put_ns(root);
} }
/** /**
...@@ -747,12 +976,11 @@ static void p_stop(struct seq_file *f, void *p) ...@@ -747,12 +976,11 @@ static void p_stop(struct seq_file *f, void *p)
static int seq_show_profile(struct seq_file *f, void *p) static int seq_show_profile(struct seq_file *f, void *p)
{ {
struct aa_profile *profile = (struct aa_profile *)p; struct aa_profile *profile = (struct aa_profile *)p;
struct aa_namespace *root = f->private; struct aa_ns *root = f->private;
if (profile->ns != root) aa_label_seq_xprint(f, root, &profile->label,
seq_printf(f, ":%s://", aa_ns_name(root, profile->ns)); FLAG_SHOW_MODE | FLAG_VIEW_SUBNS, GFP_KERNEL);
seq_printf(f, "%s (%s)\n", profile->base.hname, seq_printf(f, "\n");
aa_profile_mode_names[profile->mode]);
return 0; return 0;
} }
...@@ -766,6 +994,9 @@ static const struct seq_operations aa_fs_profiles_op = { ...@@ -766,6 +994,9 @@ static const struct seq_operations aa_fs_profiles_op = {
static int profiles_open(struct inode *inode, struct file *file) static int profiles_open(struct inode *inode, struct file *file)
{ {
if (!policy_admin_capable())
return -EACCES;
return seq_open(file, &aa_fs_profiles_op); return seq_open(file, &aa_fs_profiles_op);
} }
...@@ -789,34 +1020,76 @@ static struct aa_fs_entry aa_fs_entry_file[] = { ...@@ -789,34 +1020,76 @@ static struct aa_fs_entry aa_fs_entry_file[] = {
{ } { }
}; };
static struct aa_fs_entry aa_fs_entry_ptrace[] = {
AA_FS_FILE_STRING("mask", "read trace"),
{ }
};
static struct aa_fs_entry aa_fs_entry_signal[] = {
AA_FS_FILE_STRING("mask", AA_FS_SIG_MASK),
{ }
};
static struct aa_fs_entry aa_fs_entry_domain[] = { static struct aa_fs_entry aa_fs_entry_domain[] = {
AA_FS_FILE_BOOLEAN("change_hat", 1), AA_FS_FILE_BOOLEAN("change_hat", 1),
AA_FS_FILE_BOOLEAN("change_hatv", 1), AA_FS_FILE_BOOLEAN("change_hatv", 1),
AA_FS_FILE_BOOLEAN("change_onexec", 1), AA_FS_FILE_BOOLEAN("change_onexec", 1),
AA_FS_FILE_BOOLEAN("change_profile", 1), AA_FS_FILE_BOOLEAN("change_profile", 1),
AA_FS_FILE_BOOLEAN("stack", 1),
{ }
};
static struct aa_fs_entry aa_fs_entry_versions[] = {
AA_FS_FILE_BOOLEAN("v5", 1),
AA_FS_FILE_BOOLEAN("v6", 1),
AA_FS_FILE_BOOLEAN("v7", 1),
{ } { }
}; };
static struct aa_fs_entry aa_fs_entry_policy[] = { static struct aa_fs_entry aa_fs_entry_policy[] = {
AA_FS_DIR("versions", aa_fs_entry_versions),
AA_FS_FILE_BOOLEAN("set_load", 1), AA_FS_FILE_BOOLEAN("set_load", 1),
{} { }
};
static struct aa_fs_entry aa_fs_entry_mount[] = {
AA_FS_FILE_STRING("mask", "mount umount"),
{ }
};
static struct aa_fs_entry aa_fs_entry_ns[] = {
AA_FS_FILE_BOOLEAN("profile", 1),
AA_FS_FILE_BOOLEAN("pivot_root", 1),
{ }
};
static struct aa_fs_entry aa_fs_entry_dbus[] = {
AA_FS_FILE_STRING("mask", "acquire send receive"),
{ }
}; };
static struct aa_fs_entry aa_fs_entry_features[] = { static struct aa_fs_entry aa_fs_entry_features[] = {
AA_FS_DIR("policy", aa_fs_entry_policy), AA_FS_DIR("policy", aa_fs_entry_policy),
AA_FS_DIR("domain", aa_fs_entry_domain), AA_FS_DIR("domain", aa_fs_entry_domain),
AA_FS_DIR("file", aa_fs_entry_file), AA_FS_DIR("file", aa_fs_entry_file),
AA_FS_DIR("network", aa_fs_entry_network),
AA_FS_DIR("mount", aa_fs_entry_mount),
AA_FS_DIR("namespaces", aa_fs_entry_ns),
AA_FS_FILE_U64("capability", VFS_CAP_FLAGS_MASK), AA_FS_FILE_U64("capability", VFS_CAP_FLAGS_MASK),
AA_FS_DIR("rlimit", aa_fs_entry_rlimit), AA_FS_DIR("rlimit", aa_fs_entry_rlimit),
AA_FS_DIR("caps", aa_fs_entry_caps), AA_FS_DIR("caps", aa_fs_entry_caps),
AA_FS_DIR("ptrace", aa_fs_entry_ptrace),
AA_FS_DIR("signal", aa_fs_entry_signal),
AA_FS_DIR("dbus", aa_fs_entry_dbus),
{ } { }
}; };
static struct aa_fs_entry aa_fs_entry_apparmor[] = { static struct aa_fs_entry aa_fs_entry_apparmor[] = {
AA_FS_FILE_FOPS(".load", 0640, &aa_fs_profile_load), AA_FS_FILE_FOPS(".load", 0666, &aa_fs_profile_load),
AA_FS_FILE_FOPS(".replace", 0640, &aa_fs_profile_replace), AA_FS_FILE_FOPS(".replace", 0666, &aa_fs_profile_replace),
AA_FS_FILE_FOPS(".remove", 0640, &aa_fs_profile_remove), AA_FS_FILE_FOPS(".remove", 0666, &aa_fs_profile_remove),
AA_FS_FILE_FOPS("profiles", 0640, &aa_fs_profiles_fops), AA_FS_FILE_FOPS(".access", 0666, &aa_fs_access),
AA_FS_FILE_FOPS("profiles", 0444, &aa_fs_profiles_fops),
AA_FS_DIR("features", aa_fs_entry_features), AA_FS_DIR("features", aa_fs_entry_features),
{ } { }
}; };
...@@ -925,6 +1198,51 @@ void __init aa_destroy_aafs(void) ...@@ -925,6 +1198,51 @@ void __init aa_destroy_aafs(void)
aafs_remove_dir(&aa_fs_entry); aafs_remove_dir(&aa_fs_entry);
} }
#define NULL_FILE_NAME ".null"
struct path aa_null;
static int aa_mk_null_file(struct dentry *parent)
{
struct vfsmount *mount = NULL;
struct dentry *dentry;
struct inode *inode;
int count = 0;
int error = simple_pin_fs(parent->d_sb->s_type, &mount, &count);
if (error)
return error;
mutex_lock(&parent->d_inode->i_mutex);
dentry = lookup_one_len(NULL_FILE_NAME, parent, strlen(NULL_FILE_NAME));
if (IS_ERR(dentry)) {
error = PTR_ERR(dentry);
goto out;
}
inode = new_inode(parent->d_inode->i_sb);
if (!inode) {
error = -ENOMEM;
goto out1;
}
inode->i_ino = get_next_ino();
inode->i_mode = S_IFCHR | S_IRUGO | S_IWUGO;
inode->i_atime = inode->i_mtime = inode->i_ctime = CURRENT_TIME;
init_special_inode(inode, S_IFCHR | S_IRUGO | S_IWUGO,
MKDEV(MEM_MAJOR, 3));
d_instantiate(dentry, inode);
aa_null.dentry = dget(dentry);
aa_null.mnt = mntget(mount);
error = 0;
out1:
dput(dentry);
out:
mutex_unlock(&parent->d_inode->i_mutex);
simple_release_fs(&mount, &count);
return error;
}
/** /**
* aa_create_aafs - create the apparmor security filesystem * aa_create_aafs - create the apparmor security filesystem
* *
...@@ -949,12 +1267,20 @@ static int __init aa_create_aafs(void) ...@@ -949,12 +1267,20 @@ static int __init aa_create_aafs(void)
if (error) if (error)
goto error; goto error;
error = __aa_fs_namespace_mkdir(root_ns, aa_fs_entry.dentry, mutex_lock(&root_ns->lock);
"policy"); error = __aa_fs_ns_mkdir(root_ns, aa_fs_entry.dentry, "policy");
mutex_unlock(&root_ns->lock);
if (error)
goto error;
error = aa_mk_null_file(aa_fs_entry.dentry);
if (error) if (error)
goto error; goto error;
/* TODO: add support for apparmorfs_null and apparmorfs_mnt */ if (!aa_g_unconfined_init) {
/* TODO: add default profile to apparmorfs */
}
/* Report that AppArmor fs is enabled */ /* Report that AppArmor fs is enabled */
aa_info_message("AppArmor Filesystem Enabled"); aa_info_message("AppArmor Filesystem Enabled");
......
...@@ -18,60 +18,8 @@ ...@@ -18,60 +18,8 @@
#include "include/apparmor.h" #include "include/apparmor.h"
#include "include/audit.h" #include "include/audit.h"
#include "include/policy.h" #include "include/policy.h"
#include "include/policy_ns.h"
const char *const op_table[] = {
"null",
"sysctl",
"capable",
"unlink",
"mkdir",
"rmdir",
"mknod",
"truncate",
"link",
"symlink",
"rename_src",
"rename_dest",
"chmod",
"chown",
"getattr",
"open",
"file_perm",
"file_lock",
"file_mmap",
"file_mprotect",
"create",
"post_create",
"bind",
"connect",
"listen",
"accept",
"sendmsg",
"recvmsg",
"getsockname",
"getpeername",
"getsockopt",
"setsockopt",
"socket_shutdown",
"ptrace",
"exec",
"change_hat",
"change_profile",
"change_onexec",
"setprocattr",
"setrlimit",
"profile_replace",
"profile_load",
"profile_remove"
};
const char *const audit_mode_names[] = { const char *const audit_mode_names[] = {
"normal", "normal",
...@@ -114,34 +62,42 @@ static void audit_pre(struct audit_buffer *ab, void *ca) ...@@ -114,34 +62,42 @@ static void audit_pre(struct audit_buffer *ab, void *ca)
if (aa_g_audit_header) { if (aa_g_audit_header) {
audit_log_format(ab, "apparmor="); audit_log_format(ab, "apparmor=");
audit_log_string(ab, aa_audit_type[sa->aad->type]); audit_log_string(ab, aa_audit_type[aad(sa)->type]);
} }
if (sa->aad->op) { if (aad(sa)->op) {
audit_log_format(ab, " operation="); audit_log_format(ab, " operation=");
audit_log_string(ab, op_table[sa->aad->op]); audit_log_string(ab, aad(sa)->op);
} }
if (sa->aad->info) { if (aad(sa)->info) {
audit_log_format(ab, " info="); audit_log_format(ab, " info=");
audit_log_string(ab, sa->aad->info); audit_log_string(ab, aad(sa)->info);
if (sa->aad->error) if (aad(sa)->error)
audit_log_format(ab, " error=%d", sa->aad->error); audit_log_format(ab, " error=%d", aad(sa)->error);
} }
if (sa->aad->profile) { if (aad(sa)->label) {
struct aa_profile *profile = sa->aad->profile; struct aa_label *label = aad(sa)->label;
if (label_isprofile(label)) {
struct aa_profile *profile = labels_profile(label);
if (profile->ns != root_ns) { if (profile->ns != root_ns) {
audit_log_format(ab, " namespace="); audit_log_format(ab, " namespace=");
audit_log_untrustedstring(ab, profile->ns->base.hname); audit_log_untrustedstring(ab,
profile->ns->base.hname);
} }
audit_log_format(ab, " profile="); audit_log_format(ab, " profile=");
audit_log_untrustedstring(ab, profile->base.hname); 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 (sa->aad->name) { if (aad(sa)->name) {
audit_log_format(ab, " name="); audit_log_format(ab, " name=");
audit_log_untrustedstring(ab, sa->aad->name); audit_log_untrustedstring(ab, aad(sa)->name);
} }
} }
...@@ -153,7 +109,12 @@ static void audit_pre(struct audit_buffer *ab, void *ca) ...@@ -153,7 +109,12 @@ static void audit_pre(struct audit_buffer *ab, void *ca)
void aa_audit_msg(int type, struct common_audit_data *sa, void aa_audit_msg(int type, struct common_audit_data *sa,
void (*cb) (struct audit_buffer *, void *)) void (*cb) (struct audit_buffer *, void *))
{ {
sa->aad->type = type; /* TODO: redirect messages for profile to the correct ns
* rejects from subns should goto the audit associated
* with it, and audits from parent ns should got ns
* associated with it
*/
aad(sa)->type = type;
common_lsm_audit(sa, audit_pre, cb); common_lsm_audit(sa, audit_pre, cb);
} }
...@@ -161,7 +122,6 @@ void aa_audit_msg(int type, struct common_audit_data *sa, ...@@ -161,7 +122,6 @@ void aa_audit_msg(int type, struct common_audit_data *sa,
* aa_audit - Log a profile based audit event to the audit subsystem * aa_audit - Log a profile based audit event to the audit subsystem
* @type: audit type for the message * @type: audit type for the message
* @profile: profile to check against (NOT NULL) * @profile: profile to check against (NOT NULL)
* @gfp: allocation flags to use
* @sa: audit event (NOT NULL) * @sa: audit event (NOT NULL)
* @cb: optional callback fn for type specific fields (MAYBE NULL) * @cb: optional callback fn for type specific fields (MAYBE NULL)
* *
...@@ -169,14 +129,13 @@ void aa_audit_msg(int type, struct common_audit_data *sa, ...@@ -169,14 +129,13 @@ void aa_audit_msg(int type, struct common_audit_data *sa,
* *
* Returns: error on failure * Returns: error on failure
*/ */
int aa_audit(int type, struct aa_profile *profile, gfp_t gfp, int aa_audit(int type, struct aa_profile *profile, struct common_audit_data *sa,
struct common_audit_data *sa,
void (*cb) (struct audit_buffer *, void *)) void (*cb) (struct audit_buffer *, void *))
{ {
BUG_ON(!profile); BUG_ON(!profile);
if (type == AUDIT_APPARMOR_AUTO) { if (type == AUDIT_APPARMOR_AUTO) {
if (likely(!sa->aad->error)) { if (likely(!aad(sa)->error)) {
if (AUDIT_MODE(profile) != AUDIT_ALL) if (AUDIT_MODE(profile) != AUDIT_ALL)
return 0; return 0;
type = AUDIT_APPARMOR_AUDIT; type = AUDIT_APPARMOR_AUDIT;
...@@ -188,22 +147,22 @@ int aa_audit(int type, struct aa_profile *profile, gfp_t gfp, ...@@ -188,22 +147,22 @@ int aa_audit(int type, struct aa_profile *profile, gfp_t gfp,
if (AUDIT_MODE(profile) == AUDIT_QUIET || if (AUDIT_MODE(profile) == AUDIT_QUIET ||
(type == AUDIT_APPARMOR_DENIED && (type == AUDIT_APPARMOR_DENIED &&
AUDIT_MODE(profile) == AUDIT_QUIET)) AUDIT_MODE(profile) == AUDIT_QUIET))
return sa->aad->error; return aad(sa)->error;
if (KILL_MODE(profile) && type == AUDIT_APPARMOR_DENIED) if (KILL_MODE(profile) && type == AUDIT_APPARMOR_DENIED)
type = AUDIT_APPARMOR_KILL; type = AUDIT_APPARMOR_KILL;
if (!unconfined(profile)) aad(sa)->label = &profile->label;
sa->aad->profile = profile;
aa_audit_msg(type, sa, cb); aa_audit_msg(type, sa, cb);
if (sa->aad->type == AUDIT_APPARMOR_KILL) if (aad(sa)->type == AUDIT_APPARMOR_KILL)
(void)send_sig_info(SIGKILL, NULL, (void)send_sig_info(SIGKILL, NULL,
sa->u.tsk ? sa->u.tsk : current); sa->type == LSM_AUDIT_DATA_TASK && sa->u.tsk ?
sa->u.tsk : current);
if (sa->aad->type == AUDIT_APPARMOR_ALLOWED) if (aad(sa)->type == AUDIT_APPARMOR_ALLOWED)
return complain_error(sa->aad->error); return complain_error(aad(sa)->error);
return sa->aad->error; return aad(sa)->error;
} }
...@@ -53,6 +53,7 @@ static void audit_cb(struct audit_buffer *ab, void *va) ...@@ -53,6 +53,7 @@ static void audit_cb(struct audit_buffer *ab, void *va)
/** /**
* audit_caps - audit a capability * audit_caps - audit a capability
* @sa: audit data
* @profile: profile being tested for confinement (NOT NULL) * @profile: profile being tested for confinement (NOT NULL)
* @cap: capability tested * @cap: capability tested
* @error: error code returned by test * @error: error code returned by test
...@@ -62,17 +63,12 @@ static void audit_cb(struct audit_buffer *ab, void *va) ...@@ -62,17 +63,12 @@ static void audit_cb(struct audit_buffer *ab, void *va)
* *
* Returns: 0 or sa->error on success, error code on failure * Returns: 0 or sa->error on success, error code on failure
*/ */
static int audit_caps(struct aa_profile *profile, int cap, int error) static int audit_caps(struct common_audit_data *sa, struct aa_profile *profile,
int cap, int error)
{ {
struct audit_cache *ent; struct audit_cache *ent;
int type = AUDIT_APPARMOR_AUTO; int type = AUDIT_APPARMOR_AUTO;
struct common_audit_data sa; aad(sa)->error = error;
struct apparmor_audit_data aad = {0,};
sa.type = LSM_AUDIT_DATA_CAP;
sa.aad = &aad;
sa.u.cap = cap;
sa.aad->op = OP_CAPABLE;
sa.aad->error = error;
if (likely(!error)) { if (likely(!error)) {
/* test if auditing is being forced */ /* test if auditing is being forced */
...@@ -104,24 +100,44 @@ static int audit_caps(struct aa_profile *profile, int cap, int error) ...@@ -104,24 +100,44 @@ static int audit_caps(struct aa_profile *profile, int cap, int error)
} }
put_cpu_var(audit_cache); put_cpu_var(audit_cache);
return aa_audit(type, profile, GFP_ATOMIC, &sa, audit_cb); return aa_audit(type, profile, sa, audit_cb);
} }
/** /**
* profile_capable - test if profile allows use of capability @cap * profile_capable - test if profile allows use of capability @cap
* @profile: profile being enforced (NOT NULL, NOT unconfined) * @profile: profile being enforced (NOT NULL, NOT unconfined)
* @cap: capability to test if allowed * @cap: capability to test if allowed
* @audit: whether an audit record should be generated
* @sa: audit data (MAY BE NULL indicating no auditing)
* *
* Returns: 0 if allowed else -EPERM * Returns: 0 if allowed else -EPERM
*/ */
static int profile_capable(struct aa_profile *profile, int cap) static int profile_capable(struct aa_profile *profile, int cap, int audit,
struct common_audit_data *sa)
{ {
return cap_raised(profile->caps.allow, cap) ? 0 : -EPERM; int error;
if (cap_raised(profile->caps.allow, cap) &&
!cap_raised(profile->caps.denied, cap))
error = 0;
else
error = -EPERM;
if (audit == SECURITY_CAP_NOAUDIT) {
if (!COMPLAIN_MODE(profile))
return error;
/* audit the cap request in complain mode but note that it
* should be optional.
*/
aad(sa)->info = "optional: no audit";
}
return audit_caps(sa, profile, cap, error);
} }
/** /**
* aa_capable - test permission to use capability * aa_capable - test permission to use capability
* @profile: profile being tested against (NOT NULL) * @label: label being tested for capability (NOT NULL)
* @cap: capability to be tested * @cap: capability to be tested
* @audit: whether an audit record should be generated * @audit: whether an audit record should be generated
* *
...@@ -129,15 +145,15 @@ static int profile_capable(struct aa_profile *profile, int cap) ...@@ -129,15 +145,15 @@ static int profile_capable(struct aa_profile *profile, int cap)
* *
* Returns: 0 on success, or else an error code. * Returns: 0 on success, or else an error code.
*/ */
int aa_capable(struct aa_profile *profile, int cap, int audit) int aa_capable(struct aa_label *label, int cap, int audit)
{ {
int error = profile_capable(profile, cap); struct aa_profile *profile;
int error = 0;
DEFINE_AUDIT_DATA(sa, LSM_AUDIT_DATA_CAP, OP_CAPABLE);
sa.u.cap = cap;
if (!audit) { error = fn_for_each_confined(label, profile,
if (COMPLAIN_MODE(profile)) profile_capable(profile, cap, audit, &sa));
return complain_error(error);
return error;
}
return audit_caps(profile, cap, error); return error;
} }
...@@ -13,11 +13,11 @@ ...@@ -13,11 +13,11 @@
* License. * License.
* *
* *
* AppArmor sets confinement on every task, via the the aa_task_cxt and * AppArmor sets confinement on every task, via the the aa_task_ctx and
* the aa_task_cxt.profile, both of which are required and are not allowed * the aa_task_ctx.label, both of which are required and are not allowed
* to be NULL. The aa_task_cxt is not reference counted and is unique * to be NULL. The aa_task_ctx is not reference counted and is unique
* to each cred (which is reference count). The profile pointed to by * to each cred (which is reference count). The label pointed to by
* the task_cxt is reference counted. * the task_ctx is reference counted.
* *
* TODO * TODO
* If a task uses change_hat it currently does not return to the old * If a task uses change_hat it currently does not return to the old
...@@ -30,28 +30,28 @@ ...@@ -30,28 +30,28 @@
#include "include/policy.h" #include "include/policy.h"
/** /**
* aa_alloc_task_context - allocate a new task_cxt * aa_alloc_task_context - allocate a new task_ctx
* @flags: gfp flags for allocation * @flags: gfp flags for allocation
* *
* Returns: allocated buffer or NULL on failure * Returns: allocated buffer or NULL on failure
*/ */
struct aa_task_cxt *aa_alloc_task_context(gfp_t flags) struct aa_task_ctx *aa_alloc_task_context(gfp_t flags)
{ {
return kzalloc(sizeof(struct aa_task_cxt), flags); return kzalloc(sizeof(struct aa_task_ctx), flags);
} }
/** /**
* aa_free_task_context - free a task_cxt * aa_free_task_context - free a task_ctx
* @cxt: task_cxt to free (MAYBE NULL) * @ctx: task_ctx to free (MAYBE NULL)
*/ */
void aa_free_task_context(struct aa_task_cxt *cxt) void aa_free_task_context(struct aa_task_ctx *ctx)
{ {
if (cxt) { if (ctx) {
aa_put_profile(cxt->profile); aa_put_label(ctx->label);
aa_put_profile(cxt->previous); aa_put_label(ctx->previous);
aa_put_profile(cxt->onexec); aa_put_label(ctx->onexec);
kzfree(cxt); kzfree(ctx);
} }
} }
...@@ -60,64 +60,63 @@ void aa_free_task_context(struct aa_task_cxt *cxt) ...@@ -60,64 +60,63 @@ void aa_free_task_context(struct aa_task_cxt *cxt)
* @new: a blank task context (NOT NULL) * @new: a blank task context (NOT NULL)
* @old: the task context to copy (NOT NULL) * @old: the task context to copy (NOT NULL)
*/ */
void aa_dup_task_context(struct aa_task_cxt *new, const struct aa_task_cxt *old) void aa_dup_task_context(struct aa_task_ctx *new, const struct aa_task_ctx *old)
{ {
*new = *old; *new = *old;
aa_get_profile(new->profile); aa_get_label(new->label);
aa_get_profile(new->previous); aa_get_label(new->previous);
aa_get_profile(new->onexec); aa_get_label(new->onexec);
} }
/** /**
* aa_get_task_profile - Get another task's profile * aa_get_task_label - Get another task's label
* @task: task to query (NOT NULL) * @task: task to query (NOT NULL)
* *
* Returns: counted reference to @task's profile * Returns: counted reference to @task's label
*/ */
struct aa_profile *aa_get_task_profile(struct task_struct *task) struct aa_label *aa_get_task_label(struct task_struct *task)
{ {
struct aa_profile *p; struct aa_label *p;
rcu_read_lock(); rcu_read_lock();
p = aa_get_profile(__aa_task_profile(task)); p = aa_get_newest_label(__aa_task_raw_label(task));
rcu_read_unlock(); rcu_read_unlock();
return p; return p;
} }
/** /**
* aa_replace_current_profile - replace the current tasks profiles * aa_replace_current_label - replace the current tasks label
* @profile: new profile (NOT NULL) * @label: new label (NOT NULL)
* *
* Returns: 0 or error on failure * Returns: 0 or error on failure
*/ */
int aa_replace_current_profile(struct aa_profile *profile) int aa_replace_current_label(struct aa_label *label)
{ {
struct aa_task_cxt *cxt = current_cxt(); struct aa_task_ctx *ctx = current_ctx();
struct cred *new; struct cred *new;
BUG_ON(!profile); BUG_ON(!label);
if (cxt->profile == profile) if (ctx->label == label)
return 0; return 0;
if (current_cred() != current_real_cred())
return -EBUSY;
new = prepare_creds(); new = prepare_creds();
if (!new) if (!new)
return -ENOMEM; return -ENOMEM;
cxt = cred_cxt(new); ctx = cred_ctx(new);
if (unconfined(profile) || (cxt->profile->ns != profile->ns)) if (unconfined(label) || (labels_ns(ctx->label) != labels_ns(label)))
/* if switching to unconfined or a different profile namespace /* if switching to unconfined or a different label namespace
* clear out context state * clear out context state
*/ */
aa_clear_task_cxt_trans(cxt); aa_clear_task_ctx_trans(ctx);
/* be careful switching cxt->profile, when racing replacement it aa_get_label(label);
* is possible that cxt->profile->replacedby->profile is the reference aa_put_label(ctx->label);
* keeping @profile valid, so make sure to get its reference before ctx->label = label;
* dropping the reference on cxt->profile */
aa_get_profile(profile);
aa_put_profile(cxt->profile);
cxt->profile = profile;
commit_creds(new); commit_creds(new);
return 0; return 0;
...@@ -125,21 +124,22 @@ int aa_replace_current_profile(struct aa_profile *profile) ...@@ -125,21 +124,22 @@ int aa_replace_current_profile(struct aa_profile *profile)
/** /**
* aa_set_current_onexec - set the tasks change_profile to happen onexec * aa_set_current_onexec - set the tasks change_profile to happen onexec
* @profile: system profile to set at exec (MAYBE NULL to clear value) * @label: system label to set at exec (MAYBE NULL to clear value)
* * @stack: whether stacking should be done
* Returns: 0 or error on failure * Returns: 0 or error on failure
*/ */
int aa_set_current_onexec(struct aa_profile *profile) int aa_set_current_onexec(struct aa_label *label, bool stack)
{ {
struct aa_task_cxt *cxt; struct aa_task_ctx *ctx;
struct cred *new = prepare_creds(); struct cred *new = prepare_creds();
if (!new) if (!new)
return -ENOMEM; return -ENOMEM;
cxt = cred_cxt(new); ctx = cred_ctx(new);
aa_get_profile(profile); aa_get_label(label);
aa_put_profile(cxt->onexec); aa_clear_task_ctx_trans(ctx);
cxt->onexec = profile; ctx->onexec = label;
ctx->token = stack;
commit_creds(new); commit_creds(new);
return 0; return 0;
...@@ -147,7 +147,7 @@ int aa_set_current_onexec(struct aa_profile *profile) ...@@ -147,7 +147,7 @@ int aa_set_current_onexec(struct aa_profile *profile)
/** /**
* aa_set_current_hat - set the current tasks hat * aa_set_current_hat - set the current tasks hat
* @profile: profile to set as the current hat (NOT NULL) * @label: label to set as the current hat (NOT NULL)
* @token: token value that must be specified to change from the hat * @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 * Do switch of tasks hat. If the task is currently in a hat
...@@ -155,67 +155,67 @@ int aa_set_current_onexec(struct aa_profile *profile) ...@@ -155,67 +155,67 @@ int aa_set_current_onexec(struct aa_profile *profile)
* *
* Returns: 0 or error on failure * Returns: 0 or error on failure
*/ */
int aa_set_current_hat(struct aa_profile *profile, u64 token) int aa_set_current_hat(struct aa_label *label, u64 token)
{ {
struct aa_task_cxt *cxt; struct aa_task_ctx *ctx;
struct cred *new = prepare_creds(); struct cred *new = prepare_creds();
if (!new) if (!new)
return -ENOMEM; return -ENOMEM;
BUG_ON(!profile); BUG_ON(!label);
cxt = cred_cxt(new); ctx = cred_ctx(new);
if (!cxt->previous) { if (!ctx->previous) {
/* transfer refcount */ /* transfer refcount */
cxt->previous = cxt->profile; ctx->previous = ctx->label;
cxt->token = token; ctx->token = token;
} else if (cxt->token == token) { } else if (ctx->token == token) {
aa_put_profile(cxt->profile); aa_put_label(ctx->label);
} else { } else {
/* previous_profile && cxt->token != token */ /* previous_profile && ctx->token != token */
abort_creds(new); abort_creds(new);
return -EACCES; return -EACCES;
} }
cxt->profile = aa_get_newest_profile(profile); ctx->label = aa_get_newest_label(label);
/* clear exec on switching context */ /* clear exec on switching context */
aa_put_profile(cxt->onexec); aa_put_label(ctx->onexec);
cxt->onexec = NULL; ctx->onexec = NULL;
commit_creds(new); commit_creds(new);
return 0; return 0;
} }
/** /**
* aa_restore_previous_profile - exit from hat context restoring the profile * aa_restore_previous_label - exit from hat context restoring previous label
* @token: the token that must be matched to exit hat context * @token: the token that must be matched to exit hat context
* *
* Attempt to return out of a hat to the previous profile. The token * Attempt to return out of a hat to the previous label. The token
* must match the stored token value. * must match the stored token value.
* *
* Returns: 0 or error of failure * Returns: 0 or error of failure
*/ */
int aa_restore_previous_profile(u64 token) int aa_restore_previous_label(u64 token)
{ {
struct aa_task_cxt *cxt; struct aa_task_ctx *ctx;
struct cred *new = prepare_creds(); struct cred *new = prepare_creds();
if (!new) if (!new)
return -ENOMEM; return -ENOMEM;
cxt = cred_cxt(new); ctx = cred_ctx(new);
if (cxt->token != token) { if (ctx->token != token) {
abort_creds(new); abort_creds(new);
return -EACCES; return -EACCES;
} }
/* ignore restores when there is no saved profile */ /* ignore restores when there is no saved label */
if (!cxt->previous) { if (!ctx->previous) {
abort_creds(new); abort_creds(new);
return 0; return 0;
} }
aa_put_profile(cxt->profile); aa_put_label(ctx->label);
cxt->profile = aa_get_newest_profile(cxt->previous); ctx->label = aa_get_newest_label(ctx->previous);
BUG_ON(!cxt->profile); BUG_ON(!ctx->label);
/* clear exec && prev information when restoring to previous context */ /* clear exec && prev information when restoring to previous context */
aa_clear_task_cxt_trans(cxt); aa_clear_task_ctx_trans(ctx);
commit_creds(new); commit_creds(new);
return 0; return 0;
......
...@@ -50,76 +50,259 @@ void aa_free_domain_entries(struct aa_domain *domain) ...@@ -50,76 +50,259 @@ void aa_free_domain_entries(struct aa_domain *domain)
/** /**
* may_change_ptraced_domain - check if can change profile on ptraced task * may_change_ptraced_domain - check if can change profile on ptraced task
* @to_profile: profile to change to (NOT NULL) * @to_label: profile to change to (NOT NULL)
* @info: message if there is an error
* *
* Check if current is ptraced and if so if the tracing task is allowed * Check if current is ptraced and if so if the tracing task is allowed
* to trace the new domain * to trace the new domain
* *
* Returns: %0 or error if change not allowed * Returns: %0 or error if change not allowed
*/ */
static int may_change_ptraced_domain(struct aa_profile *to_profile) static int may_change_ptraced_domain(struct aa_label *to_label,
const char **info)
{ {
struct task_struct *tracer; struct task_struct *tracer;
struct aa_profile *tracerp = NULL; struct aa_label *tracerl = NULL;
int error = 0; int error = 0;
rcu_read_lock(); rcu_read_lock();
tracer = ptrace_parent(current); tracer = ptrace_parent(current);
if (tracer) if (tracer)
/* released below */ /* released below */
tracerp = aa_get_task_profile(tracer); tracerl = aa_get_task_label(tracer);
/* not ptraced */ /* not ptraced */
if (!tracer || unconfined(tracerp)) if (!tracer || unconfined(tracerl))
goto out; goto out;
error = aa_may_ptrace(tracerp, to_profile, PTRACE_MODE_ATTACH); error = aa_may_ptrace(tracerl, to_label, PTRACE_MODE_ATTACH);
out: out:
rcu_read_unlock(); rcu_read_unlock();
aa_put_profile(tracerp); aa_put_label(tracerl);
if (error)
*info = "ptrace prevents transition";
return error; return error;
} }
/**** TODO: dedup to aa_label_match - needs perm and dfa, merging
* specifically this is an exact copy of aa_label_match except
* aa_compute_perms is replaced with aa_compute_fperms
* and policy.dfa with file.dfa
****/
/* match a profile and its associated ns component if needed
* Assumes visibility test has already been done.
* If a subns profile is not to be matched should be prescreened with
* visibility test.
*/
/* match a profile and its associated ns component if needed
* Assumes visibility test has already been done.
* If a subns profile is not to be matched should be prescreened with
* visibility test.
*/
static inline unsigned int match_component(struct aa_profile *profile,
struct aa_profile *tp,
bool stack, unsigned int state)
{
const char *ns_name;
if (stack)
state = aa_dfa_match(profile->file.dfa, state, "&");
if (profile->ns == tp->ns)
return aa_dfa_match(profile->file.dfa, state, tp->base.hname);
/* try matching with namespace name and then profile */
ns_name = aa_ns_name(profile->ns, tp->ns, true);
state = aa_dfa_match_len(profile->file.dfa, state, ":", 1);
state = aa_dfa_match(profile->file.dfa, state, ns_name);
state = aa_dfa_match_len(profile->file.dfa, state, ":", 1);
return aa_dfa_match(profile->file.dfa, state, tp->base.hname);
}
/**
* label_component_match - find perms for full compound label
* @profile: profile to find perms for
* @label: label to check access permissions for
* @stack: whether this is a stacking request
* @start: state to start match in
* @subns: whether to do permission checks on components in a subns
* @request: permissions to request
* @perms: perms struct to set
*
* Returns: 0 on success else ERROR
*
* For the label A//&B//&C this does the perm match for A//&B//&C
* @perms should be preinitialized with allperms OR a previous permission
* check to be stacked.
*/
static int label_compound_match(struct aa_profile *profile,
struct aa_label *label, bool stack,
unsigned int state, bool subns, u32 request,
struct aa_perms *perms)
{
struct aa_profile *tp;
struct label_it i;
struct path_cond cond = { };
/* find first subcomponent that is visible */
label_for_each(i, label, tp) {
if (!aa_ns_visible(profile->ns, tp->ns, subns))
continue;
state = match_component(profile, tp, stack, state);
if (!state)
goto fail;
goto next;
}
/* no component visible */
*perms = allperms;
return 0;
next:
label_for_each_cont(i, label, tp) {
if (!aa_ns_visible(profile->ns, tp->ns, subns))
continue;
state = aa_dfa_match(profile->file.dfa, state, "//&");
state = match_component(profile, tp, false, state);
if (!state)
goto fail;
}
*perms = aa_compute_fperms(profile->file.dfa, state, &cond);
aa_apply_modes_to_perms(profile, perms);
if ((perms->allow & request) != request)
return -EACCES;
return 0;
fail:
*perms = nullperms;
return -EACCES;
}
/**
* label_component_match - find perms for all subcomponents of a label
* @profile: profile to find perms for
* @label: label to check access permissions for
* @stack: whether this is a stacking request
* @start: state to start match in
* @subns: whether to do permission checks on components in a subns
* @request: permissions to request
* @perms: an initialized perms struct to add accumulation to
*
* Returns: 0 on success else ERROR
*
* For the label A//&B//&C this does the perm match for each of A and B and C
* @perms should be preinitialized with allperms OR a previous permission
* check to be stacked.
*/
static int label_components_match(struct aa_profile *profile,
struct aa_label *label, bool stack,
unsigned int start, bool subns, u32 request,
struct aa_perms *perms)
{
struct aa_profile *tp;
struct label_it i;
struct aa_perms tmp;
struct path_cond cond = { };
unsigned int state = 0;
/* find first subcomponent to test */
label_for_each(i, label, tp) {
if (!aa_ns_visible(profile->ns, tp->ns, subns))
continue;
state = match_component(profile, tp, stack, start);
if (!state)
goto fail;
goto next;
}
/* no subcomponents visible - no change in perms */
return 0;
next:
tmp = aa_compute_fperms(profile->file.dfa, state, &cond);
aa_apply_modes_to_perms(profile, &tmp);
aa_perms_accum(perms, &tmp);
label_for_each_cont(i, label, tp) {
if (!aa_ns_visible(profile->ns, tp->ns, subns))
continue;
state = match_component(profile, tp, stack, start);
if (!state)
goto fail;
tmp = aa_compute_fperms(profile->file.dfa, state, &cond);
aa_apply_modes_to_perms(profile, &tmp);
aa_perms_accum(perms, &tmp);
}
if ((perms->allow & request) != request)
return -EACCES;
return 0;
fail:
*perms = nullperms;
return -EACCES;
}
/**
* aa_label_match - do a multi-component label match
* @profile: profile to match against (NOT NULL)
* @label: label to match (NOT NULL)
* @stack: whether this is a stacking request
* @state: state to start in
* @subns: whether to match subns components
* @request: permission request
* @perms: Returns computed perms (NOT NULL)
*
* Returns: the state the match finished in, may be the none matching state
*/
static int label_match(struct aa_profile *profile, struct aa_label *label,
bool stack, unsigned int state, bool subns, u32 request,
struct aa_perms *perms)
{
int error;
*perms = nullperms;
error = label_compound_match(profile, label, stack, state, subns,
request, perms);
if (!error)
return error;
*perms = allperms;
return label_components_match(profile, label, stack, state, subns,
request, perms);
}
/******* end TODO: dedup *****/
/** /**
* change_profile_perms - find permissions for change_profile * change_profile_perms - find permissions for change_profile
* @profile: the current profile (NOT NULL) * @profile: the current profile (NOT NULL)
* @ns: the namespace being switched to (NOT NULL) * @target: label to transition to (NOT NULL)
* @name: the name of the profile to change to (NOT NULL) * @stack: whether this is a stacking request
* @request: requested perms * @request: requested perms
* @start: state to start matching in * @start: state to start matching in
* *
*
* Returns: permission set * Returns: permission set
*
* currently only matches full label A//&B//&C or individual components A, B, C
* not arbitrary combinations. Eg. A//&B, C
*/ */
static struct file_perms change_profile_perms(struct aa_profile *profile, static int change_profile_perms(struct aa_profile *profile,
struct aa_namespace *ns, struct aa_label *target, bool stack,
const char *name, u32 request, u32 request, unsigned int start,
unsigned int start) struct aa_perms *perms)
{ {
struct file_perms perms; if (profile_unconfined(profile)) {
struct path_cond cond = { }; perms->allow = AA_MAY_CHANGE_PROFILE | AA_MAY_ONEXEC;
unsigned int state; perms->audit = perms->quiet = perms->kill = 0;
return 0;
if (unconfined(profile)) {
perms.allow = AA_MAY_CHANGE_PROFILE | AA_MAY_ONEXEC;
perms.audit = perms.quiet = perms.kill = 0;
return perms;
} else if (!profile->file.dfa) {
return nullperms;
} else if ((ns == profile->ns)) {
/* try matching against rules with out namespace prepended */
aa_str_perms(profile->file.dfa, start, name, &cond, &perms);
if (COMBINED_PERM_MASK(perms) & request)
return perms;
} }
/* try matching with namespace name and then profile */ /* TODO: add profile in ns screening */
state = aa_dfa_match(profile->file.dfa, start, ns->base.name); return label_match(profile, target, stack, start, true, request, perms);
state = aa_dfa_match_len(profile->file.dfa, state, ":", 1);
aa_str_perms(profile->file.dfa, state, name, &cond, &perms);
return perms;
} }
/** /**
...@@ -143,7 +326,7 @@ static struct aa_profile *__attach_match(const char *name, ...@@ -143,7 +326,7 @@ static struct aa_profile *__attach_match(const char *name,
struct aa_profile *profile, *candidate = NULL; struct aa_profile *profile, *candidate = NULL;
list_for_each_entry_rcu(profile, head, base.list) { list_for_each_entry_rcu(profile, head, base.list) {
if (profile->flags & PFLAG_NULL) if (profile->label.flags & FLAG_NULL)
continue; continue;
if (profile->xmatch && profile->xmatch_len > len) { if (profile->xmatch && profile->xmatch_len > len) {
unsigned int state = aa_dfa_match(profile->xmatch, unsigned int state = aa_dfa_match(profile->xmatch,
...@@ -168,10 +351,10 @@ static struct aa_profile *__attach_match(const char *name, ...@@ -168,10 +351,10 @@ static struct aa_profile *__attach_match(const char *name,
* @list: list to search (NOT NULL) * @list: list to search (NOT NULL)
* @name: the executable name to match against (NOT NULL) * @name: the executable name to match against (NOT NULL)
* *
* Returns: profile or NULL if no match found * Returns: label or NULL if no match found
*/ */
static struct aa_profile *find_attach(struct aa_namespace *ns, static struct aa_label *find_attach(struct aa_ns *ns, struct list_head *list,
struct list_head *list, const char *name) const char *name)
{ {
struct aa_profile *profile; struct aa_profile *profile;
...@@ -179,49 +362,7 @@ static struct aa_profile *find_attach(struct aa_namespace *ns, ...@@ -179,49 +362,7 @@ static struct aa_profile *find_attach(struct aa_namespace *ns,
profile = aa_get_profile(__attach_match(name, list)); profile = aa_get_profile(__attach_match(name, list));
rcu_read_unlock(); rcu_read_unlock();
return profile; return profile ? &profile->label : NULL;
}
/**
* separate_fqname - separate the namespace and profile names
* @fqname: the fqname name to split (NOT NULL)
* @ns_name: the namespace name if it exists (NOT NULL)
*
* This is the xtable equivalent routine of aa_split_fqname. It finds the
* split in an xtable fqname which contains an embedded \0 instead of a :
* if a namespace is specified. This is done so the xtable is constant and
* isn't re-split on every lookup.
*
* Either the profile or namespace name may be optional but if the namespace
* is specified the profile name termination must be present. This results
* in the following possible encodings:
* profile_name\0
* :ns_name\0profile_name\0
* :ns_name\0\0
*
* NOTE: the xtable fqname is pre-validated at load time in unpack_trans_table
*
* Returns: profile name if it is specified else NULL
*/
static const char *separate_fqname(const char *fqname, const char **ns_name)
{
const char *name;
if (fqname[0] == ':') {
/* In this case there is guaranteed to be two \0 terminators
* in the string. They are verified at load time by
* by unpack_trans_table
*/
*ns_name = fqname + 1; /* skip : */
name = *ns_name + strlen(*ns_name) + 1;
if (!*name)
name = NULL;
} else {
*ns_name = NULL;
name = fqname;
}
return name;
} }
static const char *next_name(int xtype, const char *name) static const char *next_name(int xtype, const char *name)
...@@ -233,239 +374,368 @@ static const char *next_name(int xtype, const char *name) ...@@ -233,239 +374,368 @@ static const char *next_name(int xtype, const char *name)
* x_table_lookup - lookup an x transition name via transition table * x_table_lookup - lookup an x transition name via transition table
* @profile: current profile (NOT NULL) * @profile: current profile (NOT NULL)
* @xindex: index into x transition table * @xindex: index into x transition table
* @name: returns: name tested to find label (NOT NULL)
* *
* Returns: refcounted profile, or NULL on failure (MAYBE NULL) * Returns: refcounted label, or NULL on failure (MAYBE NULL)
*/ */
static struct aa_profile *x_table_lookup(struct aa_profile *profile, u32 xindex) struct aa_label *x_table_lookup(struct aa_profile *profile, u32 xindex,
const char **name)
{ {
struct aa_profile *new_profile = NULL; struct aa_label *label = NULL;
struct aa_namespace *ns = profile->ns;
u32 xtype = xindex & AA_X_TYPE_MASK; u32 xtype = xindex & AA_X_TYPE_MASK;
int index = xindex & AA_X_INDEX_MASK; int index = xindex & AA_X_INDEX_MASK;
const char *name;
/* index is guaranteed to be in range, validated at load time */ AA_BUG(!name);
for (name = profile->file.trans.table[index]; !new_profile && name;
name = next_name(xtype, name)) {
struct aa_namespace *new_ns;
const char *xname = NULL;
new_ns = NULL; /* index is guaranteed to be in range, validated at load time */
/* TODO: move lookup parsing to unpack time so this is a straight
* index into the resultant label
*/
for (*name = profile->file.trans.table[index]; !label && *name;
*name = next_name(xtype, *name)) {
if (xindex & AA_X_CHILD) { if (xindex & AA_X_CHILD) {
struct aa_profile *new_profile;
/* release by caller */ /* release by caller */
new_profile = aa_find_child(profile, name); new_profile = aa_find_child(profile, *name);
continue; if (new_profile)
} else if (*name == ':') { label = &new_profile->label;
/* switching namespace */
const char *ns_name;
xname = name = separate_fqname(name, &ns_name);
if (!xname)
/* no name so use profile name */
xname = profile->base.hname;
if (*ns_name == '@') {
/* TODO: variable support */
;
}
/* released below */
new_ns = aa_find_namespace(ns, ns_name);
if (!new_ns)
continue; continue;
} else if (*name == '@') {
/* TODO: variable support */
continue;
} else {
/* basic namespace lookup */
xname = name;
} }
label = aa_label_parse(&profile->label, *name, GFP_ATOMIC,
/* released by caller */ true, false);
new_profile = aa_lookup_profile(new_ns ? new_ns : ns, xname); if (IS_ERR(label))
aa_put_namespace(new_ns); label = NULL;
} }
/* released by caller */ /* released by caller */
return new_profile; return label;
} }
/** /**
* x_to_profile - get target profile for a given xindex * x_to_label - get target label for a given xindex
* @profile: current profile (NOT NULL) * @profile: current profile (NOT NULL)
* @name: name to lookup (NOT NULL) * @name: name to lookup (NOT NULL)
* @xindex: index into x transition table * @xindex: index into x transition table
* @lookupname: returns: name used in lookup if one was specified (NOT NULL)
* *
* find profile for a transition index * find label for a transition index
* *
* Returns: refcounted profile or NULL if not found available * Returns: refcounted label or NULL if not found available
*/ */
static struct aa_profile *x_to_profile(struct aa_profile *profile, static struct aa_label *x_to_label(struct aa_profile *profile,
const char *name, u32 xindex) const char *name, u32 xindex,
const char **lookupname,
const char **info)
{ {
struct aa_profile *new_profile = NULL; struct aa_label *new = NULL;
struct aa_namespace *ns = profile->ns; struct aa_ns *ns = profile->ns;
u32 xtype = xindex & AA_X_TYPE_MASK; u32 xtype = xindex & AA_X_TYPE_MASK;
const char *stack = NULL;
switch (xtype) { switch (xtype) {
case AA_X_NONE: case AA_X_NONE:
/* fail exec unless ix || ux fallback - handled by caller */ /* fail exec unless ix || ux fallback - handled by caller */
return NULL; *lookupname = NULL;
break;
case AA_X_TABLE:
/* TODO: fix when perm mapping done at unload */
stack = profile->file.trans.table[xindex & AA_X_INDEX_MASK];
if (*stack != '&') {
/* released by caller */
new = x_table_lookup(profile, xindex, lookupname);
stack = NULL;
break;
}
/* fall through to X_NAME */
case AA_X_NAME: case AA_X_NAME:
if (xindex & AA_X_CHILD) if (xindex & AA_X_CHILD)
/* released by caller */ /* released by caller */
new_profile = find_attach(ns, &profile->base.profiles, new = find_attach(ns, &profile->base.profiles,
name); name);
else else
/* released by caller */ /* released by caller */
new_profile = find_attach(ns, &ns->base.profiles, new = find_attach(ns, &ns->base.profiles,
name); name);
*lookupname = name;
break; break;
case AA_X_TABLE: }
/* released by caller */
new_profile = x_table_lookup(profile, xindex); if (!new) {
break; if (xindex & AA_X_INHERIT) {
/* (p|c|n)ix - don't change profile but do
* use the newest version
*/
*info = "ix fallback";
/* no profile && no error */
new = aa_get_newest_label(&profile->label);
} else if (xindex & AA_X_UNCONFINED) {
new = aa_get_newest_label(ns_unconfined(profile->ns));
*info = "ux fallback";
}
}
if (new && stack) {
/* base the stack on post domain transition */
struct aa_label *base = new;
new = aa_label_parse(base, stack, GFP_ATOMIC, true, false);
if (IS_ERR(new))
new = NULL;
aa_put_label(base);
} }
/* released by caller */ /* released by caller */
return new_profile; return new;
} }
/** static struct aa_label *profile_transition(struct aa_profile *profile,
* apparmor_bprm_set_creds - set the new creds on the bprm struct const char *name,
* @bprm: binprm for the exec (NOT NULL) struct path_cond *cond,
* bool *secure_exec)
* Returns: %0 or error on failure
*/
int apparmor_bprm_set_creds(struct linux_binprm *bprm)
{ {
struct aa_task_cxt *cxt; struct aa_label *new = NULL;
struct aa_profile *profile, *new_profile = NULL; const char *info = NULL;
struct aa_namespace *ns; unsigned int state = profile->file.start;
char *buffer = NULL; struct aa_perms perms = {};
unsigned int state; const char *target = NULL;
struct file_perms perms = {};
struct path_cond cond = {
file_inode(bprm->file)->i_uid,
file_inode(bprm->file)->i_mode
};
const char *name = NULL, *target = NULL, *info = NULL;
int error = 0; int error = 0;
if (bprm->cred_prepared) if (profile_unconfined(profile)) {
return 0; new = find_attach(profile->ns, &profile->ns->base.profiles,
name);
if (new) {
AA_DEBUG("unconfined attached to new label");
cxt = cred_cxt(bprm->cred); return new;
BUG_ON(!cxt); }
AA_DEBUG("unconfined exec no attachment");
profile = aa_get_newest_profile(cxt->profile); return aa_get_newest_label(&profile->label);
/* }
* get the namespace from the replacement profile as replacement
* can change the namespace
*/
ns = profile->ns;
state = profile->file.start;
/* buffer freed below, name is pointer into buffer */ /* find exec permissions for name */
error = aa_path_name(&bprm->file->f_path, profile->path_flags, &buffer, state = aa_str_perms(profile->file.dfa, state, name, cond, &perms);
&name, &info); if (perms.allow & MAY_EXEC) {
if (error) { /* exec permission determine how to transition */
if (unconfined(profile) || new = x_to_label(profile, name, perms.xindex, &target, &info);
(profile->flags & PFLAG_IX_ON_NAME_ERROR)) if (new == &profile->label && info) {
error = 0; /* hack ix fallback - improve how this is detected */
name = bprm->filename;
goto audit; goto audit;
} else if (!new) {
error = -EACCES;
info = "profile transition not found";
/* remove MAY_EXEC to audit as failure */
perms.allow &= ~MAY_EXEC;
}
} else if (COMPLAIN_MODE(profile)) {
/* no exec permission - learning mode */
struct aa_profile *new_profile = aa_null_profile(profile, false,
name, GFP_ATOMIC);
if (!new_profile) {
error = -ENOMEM;
info = "could not create null profile";
} else {
error = -EACCES;
new = &new_profile->label;
} }
perms.xindex |= AA_X_UNSAFE;
} else
/* fail exec */
error = -EACCES;
/* Test for onexec first as onexec directives override other if (!new)
* x transitions. goto audit;
*/
if (unconfined(profile)) { if (!(perms.xindex & AA_X_UNSAFE)) {
/* unconfined task */ if (DEBUG_ON) {
if (cxt->onexec) dbg_printk("apparmor: scrubbing environment variables "
/* change_profile on exec already been granted */ "for %s profile=", name);
new_profile = aa_get_profile(cxt->onexec); aa_label_printk(new, GFP_ATOMIC);
else dbg_printk("\n");
new_profile = find_attach(ns, &ns->base.profiles, name); }
if (!new_profile) *secure_exec = true;
goto cleanup; }
audit:
aa_audit_file(profile, &perms, OP_EXEC, MAY_EXEC, name, target, new,
cond->uid, info, error);
if (!new)
return ERR_PTR(error);
return new;
}
static int profile_onexec(struct aa_profile *profile, struct aa_label *onexec,
bool stack, const char *xname, struct path_cond *cond,
bool *secure_exec)
{
unsigned int state = profile->file.start;
struct aa_perms perms = {};
const char *info = "change_profile onexec";
int error = -EACCES;
if (profile_unconfined(profile)) {
/* change_profile on exec already granted */
/* /*
* NOTE: Domain transitions from unconfined are allowed * NOTE: Domain transitions from unconfined are allowed
* even when no_new_privs is set because this aways results * even when no_new_privs is set because this aways results
* in a further reduction of permissions. * in a further reduction of permissions.
*/ */
goto apply; return 0;
} }
/* find exec permissions for name */ /* find exec permissions for name */
state = aa_str_perms(profile->file.dfa, state, name, &cond, &perms); state = aa_str_perms(profile->file.dfa, state, xname, cond, &perms);
if (cxt->onexec) { if (!(perms.allow & AA_MAY_ONEXEC)) {
struct file_perms cp; info = "no change_onexec valid for executable";
info = "change_profile onexec";
if (!(perms.allow & AA_MAY_ONEXEC))
goto audit; goto audit;
}
/* test if this exec can be paired with change_profile onexec. /* test if this exec can be paired with change_profile onexec.
* onexec permission is linked to exec with a standard pairing * onexec permission is linked to exec with a standard pairing
* exec\0change_profile * exec\0change_profile
*/ */
state = aa_dfa_null_transition(profile->file.dfa, state); state = aa_dfa_null_transition(profile->file.dfa, state);
cp = change_profile_perms(profile, cxt->onexec->ns, error = change_profile_perms(profile, onexec, stack, AA_MAY_ONEXEC,
cxt->onexec->base.name, state, &perms);
AA_MAY_ONEXEC, state); if (error)
if (!(cp.allow & AA_MAY_ONEXEC))
goto audit; goto audit;
new_profile = aa_get_newest_profile(cxt->onexec);
goto apply; if (!(perms.xindex & AA_X_UNSAFE)) {
if (DEBUG_ON) {
dbg_printk("appaarmor: scrubbing environment "
"variables for %s label=", xname);
aa_label_printk(onexec, GFP_ATOMIC);
dbg_printk("\n");
}
*secure_exec = true;
} }
if (perms.allow & MAY_EXEC) { audit:
/* exec permission determine how to transition */ return aa_audit_file(profile, &perms, OP_EXEC, AA_MAY_ONEXEC, xname,
new_profile = x_to_profile(profile, name, perms.xindex); NULL, onexec, cond->uid, info, error);
if (!new_profile) { }
if (perms.xindex & AA_X_INHERIT) {
/* (p|c|n)ix - don't change profile but do /* ensure none ns domain transitions are correctly applied with onexec */
* use the newest version, which was picked
* up above when getting profile static struct aa_label *handle_onexec(struct aa_label *label,
*/ struct aa_label *onexec, bool stack,
info = "ix fallback"; const char *xname,
new_profile = aa_get_profile(profile); struct path_cond *cond,
goto x_clear; bool *unsafe)
} else if (perms.xindex & AA_X_UNCONFINED) { {
new_profile = aa_get_newest_profile(ns->unconfined); struct aa_profile *profile;
info = "ux fallback"; struct aa_label *new;
int error;
if (!stack) {
error = fn_for_each_in_ns(label, profile,
profile_onexec(profile, onexec, stack,
xname, cond, unsafe));
if (error)
return ERR_PTR(error);
new = fn_label_build_in_ns(label, profile, GFP_ATOMIC,
aa_get_newest_label(onexec),
profile_transition(profile, xname,
cond, unsafe));
} else { } else {
error = -ENOENT; /* TODO: determine how much we want to losen this */
info = "profile not found"; error = fn_for_each_in_ns(label, profile,
/* remove MAY_EXEC to audit as failure */ profile_onexec(profile, onexec, stack, xname,
perms.allow &= ~MAY_EXEC; cond, unsafe));
if (error)
return ERR_PTR(error);
new = fn_label_build_in_ns(label, profile, GFP_ATOMIC,
aa_label_merge(label, onexec,
GFP_ATOMIC),
profile_transition(profile, xname,
cond, unsafe));
} }
if (new)
return new;
error = fn_for_each_in_ns(label, profile,
aa_audit_file(profile, &nullperms, OP_CHANGE_ONEXEC,
AA_MAY_ONEXEC, xname, NULL, onexec,
GLOBAL_ROOT_UID,
"failed to build target label", -ENOMEM));
return ERR_PTR(error);
}
/**
* apparmor_bprm_set_creds - set the new creds on the bprm struct
* @bprm: binprm for the exec (NOT NULL)
*
* Returns: %0 or error on failure
*
* TODO: once the other paths are done see if we can't refactor into a fn
*/
int apparmor_bprm_set_creds(struct linux_binprm *bprm)
{
struct aa_task_ctx *ctx;
struct aa_label *label, *new = NULL;
struct aa_profile *profile;
char *buffer = NULL;
const char *xname = NULL;
const char *info = NULL;
int error = 0;
bool unsafe = false;
struct path_cond cond = {
file_inode(bprm->file)->i_uid,
file_inode(bprm->file)->i_mode
};
if (bprm->cred_prepared)
return 0;
ctx = cred_ctx(bprm->cred);
AA_BUG(!ctx);
label = aa_get_newest_label(ctx->label);
profile = labels_profile(label);
/* buffer freed below, xname is pointer into buffer */
get_buffers(buffer);
error = aa_path_name(&bprm->file->f_path, profile->path_flags, buffer,
&xname, &info, profile->disconnected);
if (error) {
if (profile_unconfined(profile) ||
(profile->label.flags & FLAG_IX_ON_NAME_ERROR))
error = 0;
xname = bprm->filename;
goto audit;
} }
} else if (COMPLAIN_MODE(profile)) {
/* no exec permission - are we in learning mode */ /* Test for onexec first as onexec override other x transitions. */
new_profile = aa_new_null_profile(profile, 0); if (ctx->onexec)
if (!new_profile) { new = handle_onexec(label, ctx->onexec, ctx->token, xname,
&cond, &unsafe);
else
new = fn_label_build(label, profile, GFP_ATOMIC,
profile_transition(profile, xname, &cond,
&unsafe));
AA_BUG(!new);
if (IS_ERR(new)) {
error = PTR_ERR(new);
goto done;
} else if (!new) {
error = -ENOMEM; error = -ENOMEM;
info = "could not create null profile"; goto done;
} else {
error = -EACCES;
target = new_profile->base.hname;
} }
perms.xindex |= AA_X_UNSAFE;
} else
/* fail exec */
error = -EACCES;
/* /* Policy has specified a domain transitions. if no_new_privs and
* Policy has specified a domain transition, if no_new_privs then * confined and not transitioning to the current domain fail.
* fail the exec. *
* NOTE: Domain transitions from unconfined and to stritly stacked
* subsets are allowed even when no_new_privs is set because this
* aways results in a further reduction of permissions.
*/ */
if (bprm->unsafe & LSM_UNSAFE_NO_NEW_PRIVS) { if (bprm->unsafe & LSM_UNSAFE_NO_NEW_PRIVS &&
aa_put_profile(new_profile); !unconfined(label) && !aa_label_is_subset(new, label)) {
error = -EPERM; error = -EPERM;
goto cleanup; info = "no new privs";
}
if (!new_profile)
goto audit; goto audit;
}
if (bprm->unsafe & LSM_UNSAFE_SHARE) { if (bprm->unsafe & LSM_UNSAFE_SHARE) {
/* FIXME: currently don't mediate shared state */ /* FIXME: currently don't mediate shared state */
...@@ -473,52 +743,53 @@ int apparmor_bprm_set_creds(struct linux_binprm *bprm) ...@@ -473,52 +743,53 @@ int apparmor_bprm_set_creds(struct linux_binprm *bprm)
} }
if (bprm->unsafe & (LSM_UNSAFE_PTRACE | LSM_UNSAFE_PTRACE_CAP)) { if (bprm->unsafe & (LSM_UNSAFE_PTRACE | LSM_UNSAFE_PTRACE_CAP)) {
error = may_change_ptraced_domain(new_profile); /* TODO: test needs to be profile of label to new */
if (error) { error = may_change_ptraced_domain(new, &info);
aa_put_profile(new_profile); if (error)
goto audit; goto audit;
} }
}
/* Determine if secure exec is needed. if (unsafe) {
* Can be at this point for the following reasons: if (DEBUG_ON) {
* 1. unconfined switching to confined dbg_printk("scrubbing environment variables for %s "
* 2. confined switching to different confinement "label=", xname);
* 3. confined switching to unconfined aa_label_printk(new, GFP_ATOMIC);
* dbg_printk("\n");
* Cases 2 and 3 are marked as requiring secure exec }
* (unless policy specified "unsafe exec")
*
* bprm->unsafe is used to cache the AA_X_UNSAFE permission
* to avoid having to recompute in secureexec
*/
if (!(perms.xindex & AA_X_UNSAFE)) {
AA_DEBUG("scrubbing environment variables for %s profile=%s\n",
name, new_profile->base.hname);
bprm->unsafe |= AA_SECURE_X_NEEDED; bprm->unsafe |= AA_SECURE_X_NEEDED;
} }
apply:
target = new_profile->base.hname;
/* when transitioning profiles clear unsafe personality bits */
bprm->per_clear |= PER_CLEAR_ON_SETID;
x_clear: if (label != new) {
aa_put_profile(cxt->profile); /* when transitioning clear unsafe personality bits */
/* transfer new profile reference will be released when cxt is freed */ if (DEBUG_ON) {
cxt->profile = new_profile; dbg_printk("apparmor: clearing unsafe personality "
"bits. %s label=", xname);
aa_label_printk(new, GFP_ATOMIC);
dbg_printk("\n");
}
bprm->per_clear |= PER_CLEAR_ON_SETID;
}
aa_put_label(ctx->label);
/* transfer reference, released when ctx is freed */
ctx->label = new;
/* clear out all temporary/transitional state from the context */ done:
aa_clear_task_cxt_trans(cxt); /* clear out temporary/transitional state from the context */
aa_clear_task_ctx_trans(ctx);
audit: aa_put_label(label);
error = aa_audit_file(profile, &perms, GFP_KERNEL, OP_EXEC, MAY_EXEC, put_buffers(buffer);
name, target, cond.uid, info, error);
cleanup:
aa_put_profile(profile);
kfree(buffer);
return error; return error;
audit:
error = fn_for_each(label, profile,
aa_audit_file(profile, &nullperms, OP_EXEC, MAY_EXEC,
xname, NULL, new,
file_inode(bprm->file)->i_uid, info,
error));
aa_put_label(new);
goto done;
} }
/** /**
...@@ -538,53 +809,146 @@ int apparmor_bprm_secureexec(struct linux_binprm *bprm) ...@@ -538,53 +809,146 @@ int apparmor_bprm_secureexec(struct linux_binprm *bprm)
return 0; return 0;
} }
/** /*
* apparmor_bprm_committing_creds - do task cleanup on committing new creds * Functions for self directed profile change
* @bprm: binprm for the exec (NOT NULL)
*/ */
void apparmor_bprm_committing_creds(struct linux_binprm *bprm)
{
struct aa_profile *profile = __aa_current_profile();
struct aa_task_cxt *new_cxt = cred_cxt(bprm->cred);
/* bail out if unconfined or not changing profile */
if ((new_cxt->profile == profile) ||
(unconfined(new_cxt->profile)))
return;
current->pdeath_signal = 0;
/* reset soft limits and set hard limits for the new profile */ /* helper fn for change_hat
__aa_transition_rlimits(profile, new_cxt->profile); *
} * Returns: label for hat transition OR ERR_PTR. Does NOT return NULL
/**
* apparmor_bprm_commited_cred - do cleanup after new creds committed
* @bprm: binprm for the exec (NOT NULL)
*/ */
void apparmor_bprm_committed_creds(struct linux_binprm *bprm) static struct aa_label *build_change_hat(struct aa_profile *profile,
const char *name, bool sibling)
{ {
/* TODO: cleanup signals - ipc mediation */ struct aa_profile *root, *hat = NULL;
return; const char *info = NULL;
} int error = 0;
/* if (sibling && PROFILE_IS_HAT(profile)) {
* Functions for self directed profile change root = aa_get_profile_rcu(&profile->parent);
} else if (!sibling && !PROFILE_IS_HAT(profile)) {
root = aa_get_profile(profile);
} else {
info = "conflicting target types";
error = -EPERM;
goto audit;
}
hat = aa_find_child(root, name);
if (!hat) {
error = -ENOENT;
if (COMPLAIN_MODE(profile)) {
hat = aa_null_profile(profile, true, name, GFP_KERNEL);
if (!hat) {
info = "failed null profile create";
error = -ENOMEM;
}
}
}
aa_put_profile(root);
audit:
aa_audit_file(profile, &nullperms, OP_CHANGE_HAT, AA_MAY_CHANGEHAT,
name, hat ? hat->base.hname : NULL, hat ? &hat->label : NULL, GLOBAL_ROOT_UID,
NULL, error);
if (!hat || (error && error != -ENOENT))
return ERR_PTR(error);
/* if hat && error - complain mode, already audited and we adjust for
* complain mode allow by returning hat->label
*/ */
return &hat->label;
}
/** /* helper fn for changing into a hat
* new_compound_name - create an hname with @n2 appended to @n1
* @n1: base of hname (NOT NULL)
* @n2: name to append (NOT NULL)
* *
* Returns: new name or NULL on error * Returns: label for hat transition or ERR_PTR. Does not return NULL
*/ */
static char *new_compound_name(const char *n1, const char *n2) static struct aa_label *change_hat(struct aa_label *label, const char *hats[],
int count, bool permtest)
{ {
char *name = kmalloc(strlen(n1) + strlen(n2) + 3, GFP_KERNEL); struct aa_profile *profile, *root, *hat = NULL;
if (name) struct aa_label *new;
sprintf(name, "%s//%s", n1, n2); struct label_it it;
return name; bool sibling = false;
const char *name, *info = NULL;
int i, error;
AA_BUG(!label);
AA_BUG(!hats);
AA_BUG(count < 1);
if (PROFILE_IS_HAT(labels_profile(label)))
sibling = true;
/*find first matching hat */
for (i = 0; i < count && !hat; i++) {
name = hats[i];
label_for_each_in_ns(it, labels_ns(label), label, profile) {
if (sibling && PROFILE_IS_HAT(profile)) {
root = aa_get_profile_rcu(&profile->parent);
} else if (!sibling && !PROFILE_IS_HAT(profile)) {
root = aa_get_profile(profile);
} else { /* conflicting change type */
info = "conflicting targets types";
error = -EPERM;
goto fail;
}
hat = aa_find_child(root, name);
aa_put_profile(root);
if (!hat) {
if (!COMPLAIN_MODE(profile))
goto outer_continue;
/* complain mode succeed as if hat */
} else if (!PROFILE_IS_HAT(hat)) {
info = "target not hat";
error = -EPERM;
aa_put_profile(hat);
goto fail;
}
aa_put_profile(hat);
}
/* found a hat for all profiles in ns */
goto build;
outer_continue: ;
}
/* no hats that match, find appropriate error
*
* In complain mode audit of the failure is based off of the first
* hat supplied. This is done due how userspace interacts with
* change_hat.
*/
name = NULL;
label_for_each_in_ns(it, labels_ns(label), label, profile) {
if (!list_empty(&profile->base.profiles)) {
info = "hat not found";
error = -ENOENT;
goto fail;
}
}
info = "no hats defined";
error = -ECHILD;
fail:
fn_for_each_in_ns(label, profile,
/* no target as it has failed to be found or built */
/* TODO: get rid of GLOBAL_ROOT_UID */
aa_audit_file(profile, &nullperms, OP_CHANGE_HAT,
AA_MAY_CHANGEHAT, name, NULL, NULL,
GLOBAL_ROOT_UID, info, error));
return (ERR_PTR(error));
build:
new = fn_label_build_in_ns(label, profile, GFP_KERNEL,
build_change_hat(profile, name, sibling),
aa_get_label(&profile->label));
if (!new) {
info = "label build failed";
error = -ENOMEM;
goto fail;
} /* else if (IS_ERR) build_change_hat has logged error so return new */
return new;
} }
/** /**
...@@ -594,22 +958,24 @@ static char *new_compound_name(const char *n1, const char *n2) ...@@ -594,22 +958,24 @@ static char *new_compound_name(const char *n1, const char *n2)
* @token: magic value to validate the hat change * @token: magic value to validate the hat change
* @permtest: true if this is just a permission test * @permtest: true if this is just a permission test
* *
* Returns %0 on success, error otherwise.
*
* Change to the first profile specified in @hats that exists, and store * Change to the first profile specified in @hats that exists, and store
* the @hat_magic in the current task context. If the count == 0 and the * the @hat_magic in the current task context. If the count == 0 and the
* @token matches that stored in the current task context, return to the * @token matches that stored in the current task context, return to the
* top level profile. * top level profile.
* *
* Returns %0 on success, error otherwise. * change_hat only applies to profiles in the current ns, and each profile
* in the ns must make the same transition otherwise change_hat will fail.
*/ */
int aa_change_hat(const char *hats[], int count, u64 token, bool permtest) int aa_change_hat(const char *hats[], int count, u64 token, bool permtest)
{ {
const struct cred *cred; const struct cred *cred;
struct aa_task_cxt *cxt; struct aa_task_ctx *ctx;
struct aa_profile *profile, *previous_profile, *hat = NULL; struct aa_label *label, *previous, *new = NULL, *target = NULL;
char *name = NULL; struct aa_profile *profile;
int i; struct aa_perms perms = {};
struct file_perms perms = {}; const char *info = NULL;
const char *target = NULL, *info = NULL;
int error = 0; int error = 0;
/* /*
...@@ -617,121 +983,102 @@ int aa_change_hat(const char *hats[], int count, u64 token, bool permtest) ...@@ -617,121 +983,102 @@ int aa_change_hat(const char *hats[], int count, u64 token, bool permtest)
* There is no exception for unconfined as change_hat is not * There is no exception for unconfined as change_hat is not
* available. * available.
*/ */
if (task_no_new_privs(current)) if (task_no_new_privs(current)) {
/* not an apparmor denial per se, so don't log it */
AA_DEBUG("no_new_privs - chanage_hat denied");
return -EPERM; return -EPERM;
}
/* released below */ /* released below */
cred = get_current_cred(); cred = get_current_cred();
cxt = cred_cxt(cred); ctx = cred_ctx(cred);
profile = aa_cred_profile(cred); label = aa_get_newest_cred_label(cred);
previous_profile = cxt->previous; previous = aa_get_newest_label(ctx->previous);
if (unconfined(profile)) { if (unconfined(label)) {
info = "unconfined"; info = "unconfined can not change_hat";
error = -EPERM; error = -EPERM;
goto audit; goto fail;
} }
if (count) { if (count) {
/* attempting to change into a new hat or switch to a sibling */ new = change_hat(label, hats, count, permtest);
struct aa_profile *root; AA_BUG(!new);
if (PROFILE_IS_HAT(profile)) if (IS_ERR(new)) {
root = aa_get_profile_rcu(&profile->parent); error = PTR_ERR(new);
else new = NULL;
root = aa_get_profile(profile); /* already audited */
/* find first matching hat */
for (i = 0; i < count && !hat; i++)
/* released below */
hat = aa_find_child(root, hats[i]);
if (!hat) {
if (!COMPLAIN_MODE(root) || permtest) {
if (list_empty(&root->base.profiles))
error = -ECHILD;
else
error = -ENOENT;
aa_put_profile(root);
goto out; goto out;
} }
/* error = may_change_ptraced_domain(new, &info);
* In complain mode and failed to match any hats. if (error)
* Audit the failure is based off of the first hat goto fail;
* supplied. This is done due how userspace
* interacts with change_hat.
*
* TODO: Add logging of all failed hats
*/
/* freed below */
name = new_compound_name(root->base.hname, hats[0]);
aa_put_profile(root);
target = name;
/* released below */
hat = aa_new_null_profile(profile, 1);
if (!hat) {
info = "failed null profile create";
error = -ENOMEM;
goto audit;
}
} else {
aa_put_profile(root);
target = hat->base.hname;
if (!PROFILE_IS_HAT(hat)) {
info = "target not hat";
error = -EPERM;
goto audit;
}
}
error = may_change_ptraced_domain(hat); if (permtest)
if (error) { goto out;
info = "ptraced";
error = -EPERM;
goto audit;
}
if (!permtest) { target = new;
error = aa_set_current_hat(hat, token); error = aa_set_current_hat(new, token);
if (error == -EACCES) if (error == -EACCES)
/* kill task in case of brute force attacks */ /* kill task in case of brute force attacks */
perms.kill = AA_MAY_CHANGEHAT; goto kill;
else if (name && !error) } else if (previous && !permtest) {
/* reset error for learning of new hats */ /* Return to saved label. Kill task if restore fails
error = -ENOENT;
}
} else if (previous_profile) {
/* Return to saved profile. Kill task if restore fails
* to avoid brute force attacks * to avoid brute force attacks
*/ */
target = previous_profile->base.hname; target = previous;
error = aa_restore_previous_profile(token); error = aa_restore_previous_label(token);
if (error) {
if (error == -EACCES)
goto kill;
goto fail;
}
} /* else ignore permtest && restores when there is no saved profile */
out:
aa_put_label(new);
aa_put_label(previous);
aa_put_label(label);
put_cred(cred);
return error;
kill:
info = "failed token match";
perms.kill = AA_MAY_CHANGEHAT; perms.kill = AA_MAY_CHANGEHAT;
} else
/* ignore restores when there is no saved profile */ fail:
fn_for_each_in_ns(label, profile,
aa_audit_file(profile, &perms, OP_CHANGE_HAT, AA_MAY_CHANGEHAT,
NULL, NULL, target, GLOBAL_ROOT_UID, info, error));
goto out; goto out;
}
audit:
if (!permtest)
error = aa_audit_file(profile, &perms, GFP_KERNEL,
OP_CHANGE_HAT, AA_MAY_CHANGEHAT, NULL,
target, GLOBAL_ROOT_UID, info, error);
out: static int change_profile_perms_wrapper(const char *op, const char *name,
aa_put_profile(hat); struct aa_profile *profile,
kfree(name); struct aa_label *target, bool stack,
put_cred(cred); u32 request, struct aa_perms *perms)
{
int error = change_profile_perms(profile, target,
stack, request,
profile->file.start, perms);
if (error)
error = aa_audit_file(profile, perms, op, request, name,
NULL, target, GLOBAL_ROOT_UID, NULL,
error);
return error; return error;
} }
/** /**
* aa_change_profile - perform a one-way profile transition * aa_change_profile - perform a one-way profile transition
* @ns_name: name of the profile namespace to change to (MAYBE NULL) * @fqname: name of profile may include namespace (NOT NULL)
* @hname: name of profile to change to (MAYBE NULL)
* @onexec: whether this transition is to take place immediately or at exec * @onexec: whether this transition is to take place immediately or at exec
* @permtest: true if this is just a permission test * @permtest: true if this is just a permission test
* * @stack: true if this call is to stack on top of current domain
* Change to new profile @name. Unlike with hats, there is no way * Change to new profile @name. Unlike with hats, there is no way
* to change back. If @name isn't specified the current profile name is * to change back. If @name isn't specified the current profile name is
* used. * used.
...@@ -740,111 +1087,144 @@ int aa_change_hat(const char *hats[], int count, u64 token, bool permtest) ...@@ -740,111 +1087,144 @@ int aa_change_hat(const char *hats[], int count, u64 token, bool permtest)
* *
* Returns %0 on success, error otherwise. * Returns %0 on success, error otherwise.
*/ */
int aa_change_profile(const char *ns_name, const char *hname, bool onexec, int aa_change_profile(const char *fqname, bool onexec,
bool permtest) bool permtest, bool stack)
{ {
const struct cred *cred; struct aa_label *label, *new = NULL, *target = NULL;
struct aa_profile *profile, *target = NULL; struct aa_profile *profile;
struct aa_namespace *ns = NULL; struct aa_perms perms = {};
struct file_perms perms = {}; const char *info = NULL;
const char *name = NULL, *info = NULL; const char *auditname = fqname; /* retain leading & if stack */
int op, error = 0; int error = 0;
char *op;
u32 request; u32 request;
if (!hname && !ns_name) if (!fqname || !*fqname) {
AA_DEBUG("no profile name");
return -EINVAL; return -EINVAL;
}
if (onexec) { if (onexec) {
request = AA_MAY_ONEXEC; request = AA_MAY_ONEXEC;
if (stack)
op = OP_STACK_ONEXEC;
else
op = OP_CHANGE_ONEXEC; op = OP_CHANGE_ONEXEC;
} else { } else {
request = AA_MAY_CHANGE_PROFILE; request = AA_MAY_CHANGE_PROFILE;
if (stack)
op = OP_STACK;
else
op = OP_CHANGE_PROFILE; op = OP_CHANGE_PROFILE;
} }
cred = get_current_cred(); label = aa_get_current_label();
profile = aa_cred_profile(cred);
/* if (*fqname == '&') {
* Fail explicitly requested domain transitions if no_new_privs stack = true;
* and not unconfined. /* don't have label_parse() do stacking */
* Domain transitions from unconfined are allowed even when fqname++;
* no_new_privs is set because this aways results in a reduction
* of permissions.
*/
if (task_no_new_privs(current) && !unconfined(profile)) {
put_cred(cred);
return -EPERM;
} }
target = aa_label_parse(label, fqname, GFP_KERNEL, true, false);
if (ns_name) { if (IS_ERR(target)) {
struct aa_profile *tprofile;
info = "label not found";
error = PTR_ERR(target);
target = NULL;
/* TODO: fixme using labels_profile is not right - do profile
per complain profile ??? */
if (permtest || !COMPLAIN_MODE(labels_profile(label)))
goto audit;
/* released below */ /* released below */
ns = aa_find_namespace(profile->ns, ns_name); tprofile = aa_null_profile(labels_profile(label), false, fqname, GFP_KERNEL);
if (!ns) { if (!tprofile) {
/* we don't create new namespace in complain mode */ info = "failed null profile create";
name = ns_name; error = -ENOMEM;
info = "namespace not found";
error = -ENOENT;
goto audit; goto audit;
} }
} else target = &tprofile->label;
/* released below */ goto check;
ns = aa_get_namespace(profile->ns);
/* if the name was not specified, use the name of the current profile */
if (!hname) {
if (unconfined(profile))
hname = ns->unconfined->base.hname;
else
hname = profile->base.hname;
} }
perms = change_profile_perms(profile, ns, hname, request, /*
profile->file.start); * Fail explicitly requested domain transitions when no_new_privs
if (!(perms.allow & request)) { * and not unconfined OR the transition results in a stack on
error = -EACCES; * the current label.
* Stacking domain transitions and transitions from unconfined are
* allowed even when no_new_privs is set because this aways results
* in a reduction of permissions.
*/
if (task_no_new_privs(current) && !stack && !unconfined(label) &&
!aa_label_is_subset(target, label)) {
info = "no new privs";
error = -EPERM;
goto audit; goto audit;
} }
/* released below */ /* self directed transitions only apply to current policy ns */
target = aa_lookup_profile(ns, hname); /* TODO: currently requiring perms for stacking and straight change
if (!target) { * stacking doesn't strictly need this. Determine how much
info = "profile not found"; * we want to loosen this restriction for stacking
error = -ENOENT; */
if (permtest || !COMPLAIN_MODE(profile)) /* if (!stack) { */
goto audit; error = fn_for_each_in_ns(label, profile,
/* released below */ change_profile_perms_wrapper(op, auditname,
target = aa_new_null_profile(profile, 0); profile, target, stack,
if (!target) { request, &perms));
info = "failed null profile create"; if (error)
error = -ENOMEM; /* auditing done in change_profile_perms_wrapper */
goto audit; goto out;
}
} /* } */
check:
/* check if tracing task is allowed to trace target domain */ /* check if tracing task is allowed to trace target domain */
error = may_change_ptraced_domain(target); error = may_change_ptraced_domain(target, &info);
if (error) { if (error && !fn_for_each_in_ns(label, profile,
info = "ptrace prevents transition"; COMPLAIN_MODE(profile)))
goto audit; goto audit;
}
if (permtest) /* TODO: add permission check to allow this
if (onexec && !current_is_single_threaded()) {
info = "not a single threaded task";
error = -EACCES;
goto audit; goto audit;
}
*/
if (permtest)
goto out;
if (onexec) if (!onexec) {
error = aa_set_current_onexec(target); /* only transition profiles in the current ns */
if (stack)
new = aa_label_merge(label, target, GFP_KERNEL);
else else
error = aa_replace_current_profile(target); new = fn_label_build_in_ns(label, profile, GFP_KERNEL,
aa_get_label(target),
aa_get_label(&profile->label));
if (IS_ERR_OR_NULL(new)) {
info = "failed to build target label";
error = PTR_ERR(new);
new = NULL;
perms.allow = 0;
goto audit;
}
error = aa_replace_current_label(new);
} else
/* full transition will be built in exec path */
error = aa_set_current_onexec(target, stack);
audit: audit:
if (!permtest) error = fn_for_each_in_ns(label, profile,
error = aa_audit_file(profile, &perms, GFP_KERNEL, op, request, aa_audit_file(profile, &perms, op, request, auditname,
name, hname, GLOBAL_ROOT_UID, info, error); NULL, new ? new : target,
GLOBAL_ROOT_UID, info, error));
aa_put_namespace(ns); out:
aa_put_profile(target); aa_put_label(new);
put_cred(cred); aa_put_label(target);
aa_put_label(label);
return error; return error;
} }
...@@ -12,15 +12,30 @@ ...@@ -12,15 +12,30 @@
* License. * License.
*/ */
#include <linux/tty.h>
#include <linux/fdtable.h>
#include <linux/file.h>
#include "include/af_unix.h"
#include "include/apparmor.h" #include "include/apparmor.h"
#include "include/audit.h" #include "include/audit.h"
#include "include/context.h"
#include "include/file.h" #include "include/file.h"
#include "include/match.h" #include "include/match.h"
#include "include/path.h" #include "include/path.h"
#include "include/policy.h" #include "include/policy.h"
#include "include/label.h"
struct file_perms nullperms; 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;
}
/** /**
* audit_file_mask - convert mask to permission string * audit_file_mask - convert mask to permission string
...@@ -31,29 +46,7 @@ static void audit_file_mask(struct audit_buffer *ab, u32 mask) ...@@ -31,29 +46,7 @@ static void audit_file_mask(struct audit_buffer *ab, u32 mask)
{ {
char str[10]; char str[10];
char *m = str; aa_perm_mask_to_str(str, aa_file_perm_chrs, map_mask_to_chr_mask(mask));
if (mask & AA_EXEC_MMAP)
*m++ = 'm';
if (mask & (MAY_READ | AA_MAY_META_READ))
*m++ = 'r';
if (mask & (MAY_WRITE | AA_MAY_META_WRITE | AA_MAY_CHMOD |
AA_MAY_CHOWN))
*m++ = 'w';
else if (mask & MAY_APPEND)
*m++ = 'a';
if (mask & AA_MAY_CREATE)
*m++ = 'c';
if (mask & AA_MAY_DELETE)
*m++ = 'd';
if (mask & AA_MAY_LINK)
*m++ = 'l';
if (mask & AA_MAY_LOCK)
*m++ = 'k';
if (mask & MAY_EXEC)
*m++ = 'x';
*m = '\0';
audit_log_string(ab, str); audit_log_string(ab, str);
} }
...@@ -67,24 +60,28 @@ static void file_audit_cb(struct audit_buffer *ab, void *va) ...@@ -67,24 +60,28 @@ static void file_audit_cb(struct audit_buffer *ab, void *va)
struct common_audit_data *sa = va; struct common_audit_data *sa = va;
kuid_t fsuid = current_fsuid(); kuid_t fsuid = current_fsuid();
if (sa->aad->fs.request & AA_AUDIT_FILE_MASK) { if (aad(sa)->request & AA_AUDIT_FILE_MASK) {
audit_log_format(ab, " requested_mask="); audit_log_format(ab, " requested_mask=");
audit_file_mask(ab, sa->aad->fs.request); audit_file_mask(ab, aad(sa)->request);
} }
if (sa->aad->fs.denied & AA_AUDIT_FILE_MASK) { if (aad(sa)->denied & AA_AUDIT_FILE_MASK) {
audit_log_format(ab, " denied_mask="); audit_log_format(ab, " denied_mask=");
audit_file_mask(ab, sa->aad->fs.denied); audit_file_mask(ab, aad(sa)->denied);
} }
if (sa->aad->fs.request & AA_AUDIT_FILE_MASK) { if (aad(sa)->request & AA_AUDIT_FILE_MASK) {
audit_log_format(ab, " fsuid=%d", audit_log_format(ab, " fsuid=%d",
from_kuid(&init_user_ns, fsuid)); from_kuid(&init_user_ns, fsuid));
audit_log_format(ab, " ouid=%d", audit_log_format(ab, " ouid=%d",
from_kuid(&init_user_ns, sa->aad->fs.ouid)); from_kuid(&init_user_ns, aad(sa)->fs.ouid));
} }
if (sa->aad->fs.target) { if (aad(sa)->peer) {
audit_log_format(ab, " target=");
aa_label_xaudit(ab, labels_ns(aad(sa)->label), aad(sa)->peer,
FLAG_VIEW_SUBNS, GFP_ATOMIC);
} else if (aad(sa)->fs.target) {
audit_log_format(ab, " target="); audit_log_format(ab, " target=");
audit_log_untrustedstring(ab, sa->aad->fs.target); audit_log_untrustedstring(ab, aad(sa)->fs.target);
} }
} }
...@@ -92,65 +89,100 @@ static void file_audit_cb(struct audit_buffer *ab, void *va) ...@@ -92,65 +89,100 @@ static void file_audit_cb(struct audit_buffer *ab, void *va)
* aa_audit_file - handle the auditing of file operations * aa_audit_file - handle the auditing of file operations
* @profile: the profile being enforced (NOT NULL) * @profile: the profile being enforced (NOT NULL)
* @perms: the permissions computed for the request (NOT NULL) * @perms: the permissions computed for the request (NOT NULL)
* @gfp: allocation flags
* @op: operation being mediated * @op: operation being mediated
* @request: permissions requested * @request: permissions requested
* @name: name of object being mediated (MAYBE NULL) * @name: name of object being mediated (MAYBE NULL)
* @target: name of target (MAYBE NULL) * @target: name of target (MAYBE NULL)
* @tlabel: target label (MAY BE NULL)
* @ouid: object uid * @ouid: object uid
* @info: extra information message (MAYBE NULL) * @info: extra information message (MAYBE NULL)
* @error: 0 if operation allowed else failure error code * @error: 0 if operation allowed else failure error code
* *
* Returns: %0 or error on failure * Returns: %0 or error on failure
*/ */
int aa_audit_file(struct aa_profile *profile, struct file_perms *perms, int aa_audit_file(struct aa_profile *profile, struct aa_perms *perms,
gfp_t gfp, int op, u32 request, const char *name, const char *op, u32 request, const char *name,
const char *target, kuid_t ouid, const char *info, int error) const char *target, struct aa_label *tlabel,
kuid_t ouid, const char *info, int error)
{ {
int type = AUDIT_APPARMOR_AUTO; int type = AUDIT_APPARMOR_AUTO;
struct common_audit_data sa;
struct apparmor_audit_data aad = {0,}; DEFINE_AUDIT_DATA(sa, LSM_AUDIT_DATA_NONE, op);
sa.type = LSM_AUDIT_DATA_NONE; aad(&sa)->request = request;
sa.aad = &aad; aad(&sa)->name = name;
aad.op = op, aad(&sa)->fs.target = target;
aad.fs.request = request; aad(&sa)->peer = tlabel;
aad.name = name; aad(&sa)->fs.ouid = ouid;
aad.fs.target = target; aad(&sa)->info = info;
aad.fs.ouid = ouid; aad(&sa)->error = error;
aad.info = info; sa.u.tsk = NULL;
aad.error = error;
if (likely(!aad(&sa)->error)) {
if (likely(!sa.aad->error)) {
u32 mask = perms->audit; u32 mask = perms->audit;
if (unlikely(AUDIT_MODE(profile) == AUDIT_ALL)) if (unlikely(AUDIT_MODE(profile) == AUDIT_ALL))
mask = 0xffff; mask = 0xffff;
/* mask off perms that are not being force audited */ /* mask off perms that are not being force audited */
sa.aad->fs.request &= mask; aad(&sa)->request &= mask;
if (likely(!sa.aad->fs.request)) if (likely(!aad(&sa)->request))
return 0; return 0;
type = AUDIT_APPARMOR_AUDIT; type = AUDIT_APPARMOR_AUDIT;
} else { } else {
/* only report permissions that were denied */ /* only report permissions that were denied */
sa.aad->fs.request = sa.aad->fs.request & ~perms->allow; aad(&sa)->request = aad(&sa)->request & ~perms->allow;
AA_BUG(!aad(&sa)->request);
if (sa.aad->fs.request & perms->kill) if (aad(&sa)->request & perms->kill)
type = AUDIT_APPARMOR_KILL; type = AUDIT_APPARMOR_KILL;
/* quiet known rejects, assumes quiet and kill do not overlap */ /* quiet known rejects, assumes quiet and kill do not overlap */
if ((sa.aad->fs.request & perms->quiet) && if ((aad(&sa)->request & perms->quiet) &&
AUDIT_MODE(profile) != AUDIT_NOQUIET && AUDIT_MODE(profile) != AUDIT_NOQUIET &&
AUDIT_MODE(profile) != AUDIT_ALL) AUDIT_MODE(profile) != AUDIT_ALL)
sa.aad->fs.request &= ~perms->quiet; aad(&sa)->request &= ~perms->quiet;
if (!sa.aad->fs.request) if (!aad(&sa)->request)
return COMPLAIN_MODE(profile) ? 0 : sa.aad->error; return aad(&sa)->error;
} }
sa.aad->fs.denied = sa.aad->fs.request & ~perms->allow; aad(&sa)->denied = aad(&sa)->request & ~perms->allow;
return aa_audit(type, profile, gfp, &sa, file_audit_cb); return aa_audit(type, profile, &sa, file_audit_cb);
}
/**
* is_deleted - test if a file has been completely unlinked
* @dentry: dentry of file to test for deletion (NOT NULL)
*
* Returns: %1 if deleted else %0
*/
static inline bool is_deleted(struct dentry *dentry)
{
if (d_unlinked(dentry) && d_backing_inode(dentry)->i_nlink == 0)
return 1;
return 0;
}
static int path_name(const char *op, struct aa_label *label, struct path *path,
int flags, char *buffer, const char**name,
struct path_cond *cond, u32 request, bool delegate_deleted)
{
struct aa_profile *profile;
const char *info = NULL;
int error = aa_path_name(path, flags, buffer, name, &info,
labels_profile(label)->disconnected);
if (error) {
if (error == -ENOENT && is_deleted(path->dentry) &&
delegate_deleted)
return 0;
fn_for_each_confined(label, profile,
aa_audit_file(profile, &nullperms, op, request, *name,
NULL, NULL, cond->uid, info, error));
return error;
}
return 0;
} }
/** /**
...@@ -163,10 +195,11 @@ static u32 map_old_perms(u32 old) ...@@ -163,10 +195,11 @@ static u32 map_old_perms(u32 old)
{ {
u32 new = old & 0xf; u32 new = old & 0xf;
if (old & MAY_READ) if (old & MAY_READ)
new |= AA_MAY_META_READ; new |= AA_MAY_GETATTR | AA_MAY_OPEN;
if (old & MAY_WRITE) if (old & MAY_WRITE)
new |= AA_MAY_META_WRITE | AA_MAY_CREATE | AA_MAY_DELETE | new |= AA_MAY_SETATTR | AA_MAY_CREATE | AA_MAY_DELETE |
AA_MAY_CHMOD | AA_MAY_CHOWN; AA_MAY_CHMOD | AA_MAY_CHOWN | AA_MAY_OPEN |
AA_MAY_DELETE;
if (old & 0x10) if (old & 0x10)
new |= AA_MAY_LINK; new |= AA_MAY_LINK;
/* the old mapping lock and link_subset flags where overlaid /* the old mapping lock and link_subset flags where overlaid
...@@ -181,7 +214,7 @@ static u32 map_old_perms(u32 old) ...@@ -181,7 +214,7 @@ static u32 map_old_perms(u32 old)
} }
/** /**
* compute_perms - convert dfa compressed perms to internal perms * aa_compute_fperms - convert dfa compressed perms to internal perms
* @dfa: dfa to compute perms for (NOT NULL) * @dfa: dfa to compute perms for (NOT NULL)
* @state: state in dfa * @state: state in dfa
* @cond: conditions to consider (NOT NULL) * @cond: conditions to consider (NOT NULL)
...@@ -191,17 +224,21 @@ static u32 map_old_perms(u32 old) ...@@ -191,17 +224,21 @@ static u32 map_old_perms(u32 old)
* *
* Returns: computed permission set * Returns: computed permission set
*/ */
static struct file_perms compute_perms(struct aa_dfa *dfa, unsigned int state, struct aa_perms aa_compute_fperms(struct aa_dfa *dfa, unsigned int state,
struct path_cond *cond) struct path_cond *cond)
{ {
struct file_perms perms; struct aa_perms perms;
/* FIXME: change over to new dfa format /* FIXME: change over to new dfa format
* currently file perms are encoded in the dfa, new format * currently file perms are encoded in the dfa, new format
* splits the permissions from the dfa. This mapping can be * splits the permissions from the dfa. This mapping can be
* done at profile load * done at profile load
*/ */
perms.kill = 0; perms.deny = 0;
perms.kill = perms.stop = 0;
perms.complain = perms.cond = 0;
perms.hide = 0;
perms.prompt = 0;
if (uid_eq(current_fsuid(), cond->uid)) { if (uid_eq(current_fsuid(), cond->uid)) {
perms.allow = map_old_perms(dfa_user_allow(dfa, state)); perms.allow = map_old_perms(dfa_user_allow(dfa, state));
...@@ -214,7 +251,7 @@ static struct file_perms compute_perms(struct aa_dfa *dfa, unsigned int state, ...@@ -214,7 +251,7 @@ static struct file_perms compute_perms(struct aa_dfa *dfa, unsigned int state,
perms.quiet = map_old_perms(dfa_other_quiet(dfa, state)); perms.quiet = map_old_perms(dfa_other_quiet(dfa, state));
perms.xindex = dfa_other_xindex(dfa, state); perms.xindex = dfa_other_xindex(dfa, state);
} }
perms.allow |= AA_MAY_META_READ; perms.allow |= AA_MAY_GETATTR;
/* change_profile wasn't determined by ownership in old mapping */ /* change_profile wasn't determined by ownership in old mapping */
if (ACCEPT_TABLE(dfa)[state] & 0x80000000) if (ACCEPT_TABLE(dfa)[state] & 0x80000000)
...@@ -237,37 +274,34 @@ static struct file_perms compute_perms(struct aa_dfa *dfa, unsigned int state, ...@@ -237,37 +274,34 @@ static struct file_perms compute_perms(struct aa_dfa *dfa, unsigned int state,
*/ */
unsigned int aa_str_perms(struct aa_dfa *dfa, unsigned int start, unsigned int aa_str_perms(struct aa_dfa *dfa, unsigned int start,
const char *name, struct path_cond *cond, const char *name, struct path_cond *cond,
struct file_perms *perms) struct aa_perms *perms)
{ {
unsigned int state; unsigned int state;
if (!dfa) {
*perms = nullperms;
return DFA_NOMATCH;
}
state = aa_dfa_match(dfa, start, name); state = aa_dfa_match(dfa, start, name);
*perms = compute_perms(dfa, state, cond); *perms = aa_compute_fperms(dfa, state, cond);
return state; return state;
} }
/** int __aa_path_perm(const char *op, struct aa_profile *profile, const char *name,
* is_deleted - test if a file has been completely unlinked u32 request, struct path_cond *cond, int flags,
* @dentry: dentry of file to test for deletion (NOT NULL) struct aa_perms *perms)
*
* Returns: %1 if deleted else %0
*/
static inline bool is_deleted(struct dentry *dentry)
{ {
if (d_unlinked(dentry) && d_backing_inode(dentry)->i_nlink == 0) int e = 0;
return 1; if (profile_unconfined(profile) ||
((flags & PATH_SOCK_COND) && !PROFILE_MEDIATES_AF(profile, AF_UNIX)))
return 0; return 0;
aa_str_perms(profile->file.dfa, profile->file.start, name, cond, perms);
if (request & ~perms->allow)
e = -EACCES;
return aa_audit_file(profile, perms, op, request, name, NULL, NULL,
cond->uid, NULL, e);
} }
/** /**
* aa_path_perm - do permissions check & audit for @path * aa_path_perm - do permissions check & audit for @path
* @op: operation being checked * @op: operation being checked
* @profile: profile being enforced (NOT NULL) * @label: profile being enforced (NOT NULL)
* @path: path to check permissions of (NOT NULL) * @path: path to check permissions of (NOT NULL)
* @flags: any additional path flags beyond what the profile specifies * @flags: any additional path flags beyond what the profile specifies
* @request: requested permissions * @request: requested permissions
...@@ -275,35 +309,28 @@ static inline bool is_deleted(struct dentry *dentry) ...@@ -275,35 +309,28 @@ static inline bool is_deleted(struct dentry *dentry)
* *
* Returns: %0 else error if access denied or other error * Returns: %0 else error if access denied or other error
*/ */
int aa_path_perm(int op, struct aa_profile *profile, struct path *path, int aa_path_perm(const char *op, struct aa_label *label, struct path *path,
int flags, u32 request, struct path_cond *cond) int flags, u32 request, struct path_cond *cond)
{ {
struct aa_perms perms = {};
char *buffer = NULL; char *buffer = NULL;
struct file_perms perms = {}; const char *name;
const char *name, *info = NULL; struct aa_profile *profile;
int error; int error;
flags |= profile->path_flags | (S_ISDIR(cond->mode) ? PATH_IS_DIR : 0); /* TODO: fix path lookup flags */
error = aa_path_name(path, flags, &buffer, &name, &info); flags |= labels_profile(label)->path_flags |
if (error) { (S_ISDIR(cond->mode) ? PATH_IS_DIR : 0);
if (error == -ENOENT && is_deleted(path->dentry)) { get_buffers(buffer);
/* Access to open files that are deleted are
* give a pass (implicit delegation)
*/
error = 0;
info = NULL;
perms.allow = request;
}
} else {
aa_str_perms(profile->file.dfa, profile->file.start, name, cond,
&perms);
if (request & ~perms.allow)
error = -EACCES;
}
error = aa_audit_file(profile, &perms, GFP_KERNEL, op, request, name,
NULL, cond->uid, info, error);
kfree(buffer);
error = path_name(op, label, path, flags, buffer, &name, cond,
request, true);
if (!error)
error = fn_for_each_confined(label, profile,
__aa_path_perm(op, profile, name, request, cond,
flags, &perms));
put_buffers(buffer);
return error; return error;
} }
...@@ -327,65 +354,25 @@ static inline bool xindex_is_subset(u32 link, u32 target) ...@@ -327,65 +354,25 @@ static inline bool xindex_is_subset(u32 link, u32 target)
return 1; return 1;
} }
/** static int profile_path_link(struct aa_profile *profile, const char *lname,
* aa_path_link - Handle hard link permission check const char *tname, struct path_cond *cond)
* @profile: the profile 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(struct aa_profile *profile, struct dentry *old_dentry,
struct path *new_dir, struct dentry *new_dentry)
{ {
struct path link = { new_dir->mnt, new_dentry }; struct aa_perms lperms, perms;
struct path target = { new_dir->mnt, old_dentry }; const char *info = NULL;
struct path_cond cond = {
d_backing_inode(old_dentry)->i_uid,
d_backing_inode(old_dentry)->i_mode
};
char *buffer = NULL, *buffer2 = NULL;
const char *lname, *tname = NULL, *info = NULL;
struct file_perms lperms, perms;
u32 request = AA_MAY_LINK; u32 request = AA_MAY_LINK;
unsigned int state; unsigned int state;
int error; int e = -EACCES;
lperms = nullperms;
/* buffer freed below, lname is pointer in buffer */
error = aa_path_name(&link, profile->path_flags, &buffer, &lname,
&info);
if (error)
goto audit;
/* buffer2 freed below, tname is pointer in buffer2 */
error = aa_path_name(&target, profile->path_flags, &buffer2, &tname,
&info);
if (error)
goto audit;
error = -EACCES;
/* aa_str_perms - handles the case of the dfa being NULL */ /* aa_str_perms - handles the case of the dfa being NULL */
state = aa_str_perms(profile->file.dfa, profile->file.start, lname, state = aa_str_perms(profile->file.dfa, profile->file.start, lname,
&cond, &lperms); cond, &lperms);
if (!(lperms.allow & AA_MAY_LINK)) if (!(lperms.allow & AA_MAY_LINK))
goto audit; goto audit;
/* test to see if target can be paired with link */ /* test to see if target can be paired with link */
state = aa_dfa_null_transition(profile->file.dfa, state); state = aa_dfa_null_transition(profile->file.dfa, state);
aa_str_perms(profile->file.dfa, state, tname, &cond, &perms); aa_str_perms(profile->file.dfa, state, tname, cond, &perms);
/* force audit/quiet masks for link are stored in the second entry /* force audit/quiet masks for link are stored in the second entry
* in the link pair. * in the link pair.
...@@ -396,6 +383,7 @@ int aa_path_link(struct aa_profile *profile, struct dentry *old_dentry, ...@@ -396,6 +383,7 @@ int aa_path_link(struct aa_profile *profile, struct dentry *old_dentry,
if (!(perms.allow & AA_MAY_LINK)) { if (!(perms.allow & AA_MAY_LINK)) {
info = "target restricted"; info = "target restricted";
lperms = perms;
goto audit; goto audit;
} }
...@@ -403,10 +391,10 @@ int aa_path_link(struct aa_profile *profile, struct dentry *old_dentry, ...@@ -403,10 +391,10 @@ int aa_path_link(struct aa_profile *profile, struct dentry *old_dentry,
if (!(perms.allow & AA_LINK_SUBSET)) if (!(perms.allow & AA_LINK_SUBSET))
goto done_tests; goto done_tests;
/* Do link perm subset test requiring allowed permission on link are a /* Do link perm subset test requiring allowed permission on link are
* subset of the allowed permissions on target. * a subset of the allowed permissions on target.
*/ */
aa_str_perms(profile->file.dfa, profile->file.start, tname, &cond, aa_str_perms(profile->file.dfa, profile->file.start, tname, cond,
&perms); &perms);
/* AA_MAY_LINK is not considered in the subset test */ /* AA_MAY_LINK is not considered in the subset test */
...@@ -425,13 +413,175 @@ int aa_path_link(struct aa_profile *profile, struct dentry *old_dentry, ...@@ -425,13 +413,175 @@ int aa_path_link(struct aa_profile *profile, struct dentry *old_dentry,
} }
done_tests: done_tests:
error = 0; e = 0;
audit: audit:
error = aa_audit_file(profile, &lperms, GFP_KERNEL, OP_LINK, request, return aa_audit_file(profile, &lperms, OP_LINK, request, lname, tname,
lname, tname, cond.uid, info, error); NULL, cond->uid, info, e);
kfree(buffer); }
kfree(buffer2);
/**
* aa_path_link - Handle hard link permission check
* @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(struct aa_label *label, struct dentry *old_dentry,
struct path *new_dir, struct dentry *new_dentry)
{
struct path link = { new_dir->mnt, new_dentry };
struct path target = { new_dir->mnt, old_dentry };
struct path_cond cond = {
d_backing_inode(old_dentry)->i_uid,
d_backing_inode(old_dentry)->i_mode
};
char *buffer = NULL, *buffer2 = NULL;
const char *lname, *tname = NULL;
struct aa_profile *profile;
int error;
/* TODO: fix path lookup flags, auditing of failed path for profile */
profile = labels_profile(label);
/* buffer freed below, lname is pointer in buffer */
get_buffers(buffer, buffer2);
error = path_name(OP_LINK, label, &link,
labels_profile(label)->path_flags, buffer,
&lname, &cond, AA_MAY_LINK, false);
if (error)
goto out;
/* buffer2 freed below, tname is pointer in buffer2 */
error = path_name(OP_LINK, label, &target,
labels_profile(label)->path_flags, buffer2, &tname,
&cond, AA_MAY_LINK, false);
if (error)
goto out;
error = fn_for_each_confined(label, profile,
profile_path_link(profile, lname, tname, &cond));
out:
put_buffers(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,
spin_is_locked(&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, struct aa_label *label,
struct aa_label *flabel, struct file *file,
u32 request, u32 denied)
{
struct aa_profile *profile;
struct aa_perms perms = {};
struct path_cond cond = {
.uid = file_inode(file)->i_uid,
.mode = file_inode(file)->i_mode
};
const char *name;
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;
/* TODO: fix path lookup flags */
flags = PATH_DELEGATE_DELETED | labels_profile(label)->path_flags |
(S_ISDIR(cond.mode) ? PATH_IS_DIR : 0);
get_buffers(buffer);
error = path_name(op, label, &file->f_path, flags, buffer, &name, &cond,
request, true);
if (error) {
if (error == 1)
/* Access to open files that are deleted are
* given a pass (implicit delegation)
*/
/* TODO not needed when full perms cached */
error = 0;
goto out;
}
/* check every profile in task label not in current cache */
error = fn_for_each_not_in_set(flabel, label, profile,
__aa_path_perm(op, profile, name, request, &cond, 0,
&perms));
if (denied) {
/* 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 */
last_error(error,
fn_for_each_not_in_set(label, flabel, profile,
__aa_path_perm(op, profile, name, request,
&cond, 0, &perms)));
}
if (!error)
update_file_ctx(file_ctx(file), label, request);
out:
put_buffers(buffer);
return error;
}
static int __file_sock_perm(const char *op, struct aa_label *label,
struct aa_label *flabel, struct file *file,
u32 request, u32 denied)
{
struct socket *sock = (struct socket *) file->private_data;
int error;
AA_BUG(!sock);
/* 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(label, op, request, sock);
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(flabel, op, request, sock));
}
if (!error)
update_file_ctx(file_ctx(file), label, request);
return error; return error;
} }
...@@ -439,20 +589,117 @@ int aa_path_link(struct aa_profile *profile, struct dentry *old_dentry, ...@@ -439,20 +589,117 @@ int aa_path_link(struct aa_profile *profile, struct dentry *old_dentry,
/** /**
* aa_file_perm - do permission revalidation check & audit for @file * aa_file_perm - do permission revalidation check & audit for @file
* @op: operation being checked * @op: operation being checked
* @profile: profile being enforced (NOT NULL) * @label: label being enforced (NOT NULL)
* @file: file to revalidate access permissions on (NOT NULL) * @file: file to revalidate access permissions on (NOT NULL)
* @request: requested permissions * @request: requested permissions
* *
* Returns: %0 if access allowed else error * Returns: %0 if access allowed else error
*/ */
int aa_file_perm(int op, struct aa_profile *profile, struct file *file, int aa_file_perm(const char *op, struct aa_label *label, struct file *file,
u32 request) u32 request)
{ {
struct path_cond cond = { struct aa_file_ctx *fctx;
.uid = file_inode(file)->i_uid, struct aa_label *flabel;
.mode = file_inode(file)->i_mode u32 denied;
}; int error = 0;
AA_BUG(!label);
AA_BUG(!file);
return aa_path_perm(op, profile, &file->f_path, PATH_DELEGATE_DELETED, fctx = file_ctx(file);
request, &cond);
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) || unconfined(flabel) ||
(!denied && aa_label_is_subset(flabel, label)))
goto done;
/* TODO: label cross check */
if (file->f_path.mnt && path_mediated_fs(file->f_path.dentry)) {
error = __file_path_perm(op, label, flabel, file, request,
denied);
} else if (S_ISSOCK(file_inode(file)->i_mode)) {
error = __file_sock_perm(op, label, flabel, file, request,
denied);
}
done:
rcu_read_unlock();
return error;
}
static void revalidate_tty(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, label, file, MAY_READ | MAY_WRITE))
drop_tty = 1;
}
spin_unlock(&tty_files_lock);
tty_kref_put(tty);
if (drop_tty)
no_tty();
}
static int match_file(const void *p, struct file *file, unsigned fd)
{
struct aa_label *label = (struct aa_label *)p;
if (aa_file_perm(OP_INHERIT, label, file, aa_map_file_to_perms(file)))
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 file *devnull = NULL;
unsigned n;
revalidate_tty(label);
/* Revalidate access to inherited open files. */
n = iterate_fd(files, 0, match_file, label);
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, label)) != 0);
if (devnull)
fput(devnull);
out:
aa_put_label(label);
} }
/*
* AppArmor security module
*
* This file contains AppArmor af_unix fine grained mediation
*
* Copyright 2014 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"
//#include "include/net.h"
#define unix_addr_len(L) ((L) - sizeof(sa_family_t))
#define unix_abstract_name_len(L) (unix_addr_len(L) - 1)
#define unix_abstract_len(U) (unix_abstract_name_len((U)->addr->len))
#define addr_unix_abstract_name(B) ((B)[0] == 0)
#define addr_unix_anonymous(U) (addr_unix_len(U) <= 0)
#define addr_unix_abstract(U) (!addr_unix_anonymous(U) && addr_unix_abstract_name((U)->addr))
//#define unix_addr_fs(U) (!unix_addr_anonymous(U) && !unix_addr_abstract_name((U)->addr))
#define unix_addr(A) ((struct sockaddr_un *)(A))
#define unix_addr_anon(A, L) ((A) && unix_addr_len(L) <= 0)
#define unix_addr_fs(A, L) (!unix_addr_anon(A, L) && !addr_unix_abstract_name(unix_addr(A)->sun_path))
#define UNIX_ANONYMOUS(U) (!unix_sk(U)->addr)
/* from net/unix/af_unix.c */
#define UNIX_ABSTRACT(U) (!UNIX_ANONYMOUS(U) && \
unix_sk(U)->addr->hash < UNIX_HASH_SIZE)
#define UNIX_FS(U) (!UNIX_ANONYMOUS(U) && unix_sk(U)->addr->name->sun_path[0])
#define unix_peer(sk) (unix_sk(sk)->peer)
#define unix_connected(S) ((S)->state == SS_CONNECTED)
static inline void print_unix_addr(struct sockaddr_un *A, int L)
{
char *buf = (A) ? (char *) &(A)->sun_path : NULL;
int len = unix_addr_len(L);
if (!buf || len <= 0)
printk(" <anonymous>");
else if (buf[0])
printk(" %s", buf);
else
/* abstract name len includes leading \0 */
printk(" %d @%.*s", len - 1, len - 1, buf+1);
};
/*
printk("%s: %s: f %d, t %d, p %d", __FUNCTION__, \
#SK , \
*/
#define print_unix_sk(SK) \
do { \
struct unix_sock *u = unix_sk(SK); \
printk("%s: f %d, t %d, p %d", #SK , \
(SK)->sk_family, (SK)->sk_type, (SK)->sk_protocol); \
if (u->addr) \
print_unix_addr(u->addr->name, u->addr->len); \
else \
print_unix_addr(NULL, sizeof(sa_family_t)); \
/* printk("\n");*/ \
} while (0)
#define print_sk(SK) \
do { \
if (!(SK)) { \
printk("%s: %s is null\n", __FUNCTION__, #SK); \
} else if ((SK)->sk_family == PF_UNIX) { \
print_unix_sk(SK); \
printk("\n"); \
} else { \
printk("%s: %s: family %d\n", __FUNCTION__, #SK , \
(SK)->sk_family); \
} \
} while (0)
#define print_sock_addr(U) \
do { \
printk("%s:\n", __FUNCTION__); \
printk(" sock %s:", sock_ctx && sock_ctx->label ? aa_label_printk(sock_ctx->label, GFP_ATOMIC); : "<null>"); print_sk(sock); \
printk(" other %s:", other_ctx && other_ctx->label ? aa_label_printk(other_ctx->label, GFP_ATOMIC); : "<null>"); print_sk(other); \
printk(" new %s", new_ctx && new_ctx->label ? aa_label_printk(new_ctx->label, GFP_ATOMIC); : "<null>"); print_sk(newsk); \
} while (0)
int aa_unix_peer_perm(struct aa_label *label, const char *op, u32 request,
struct sock *sk, struct sock *peer_sk,
struct aa_label *peer_label);
int aa_unix_label_sk_perm(struct aa_label *label, const char *op, u32 request,
struct sock *sk);
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(struct aa_label *label, const char *op, u32 request,
struct socket *sock);
#endif /* __AA_AF_UNIX_H */
/* /*
* AppArmor security module * AppArmor security module
* *
* This file contains AppArmor basic global and lib definitions * This file contains AppArmor basic global
* *
* Copyright (C) 1998-2008 Novell/SUSE * Copyright (C) 1998-2008 Novell/SUSE
* Copyright 2009-2010 Canonical Ltd. * Copyright 2009-2016 Canonical Ltd.
* *
* This program is free software; you can redistribute it and/or * This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as * modify it under the terms of the GNU General Public License as
...@@ -15,10 +15,7 @@ ...@@ -15,10 +15,7 @@
#ifndef __APPARMOR_H #ifndef __APPARMOR_H
#define __APPARMOR_H #define __APPARMOR_H
#include <linux/slab.h> #include <linux/types.h>
#include <linux/fs.h>
#include "match.h"
/* /*
* Class of mediation types in the AppArmor policy db * Class of mediation types in the AppArmor policy db
...@@ -30,91 +27,22 @@ ...@@ -30,91 +27,22 @@
#define AA_CLASS_NET 4 #define AA_CLASS_NET 4
#define AA_CLASS_RLIMITS 5 #define AA_CLASS_RLIMITS 5
#define AA_CLASS_DOMAIN 6 #define AA_CLASS_DOMAIN 6
#define AA_CLASS_MOUNT 7
#define AA_CLASS_PTRACE 9
#define AA_CLASS_SIGNAL 10
#define AA_CLASS_LABEL 16
#define AA_CLASS_LAST AA_CLASS_DOMAIN #define AA_CLASS_LAST AA_CLASS_LABEL
/* Control parameters settable through module/boot flags */ /* Control parameters settable through module/boot flags */
extern enum audit_mode aa_g_audit; extern enum audit_mode aa_g_audit;
extern bool aa_g_audit_header; extern bool aa_g_audit_header;
extern bool aa_g_debug; extern bool aa_g_debug;
extern bool aa_g_hash_policy;
extern bool aa_g_lock_policy; extern bool aa_g_lock_policy;
extern bool aa_g_logsyscall; extern bool aa_g_logsyscall;
extern bool aa_g_paranoid_load; extern bool aa_g_paranoid_load;
extern unsigned int aa_g_path_max; extern unsigned int aa_g_path_max;
extern bool aa_g_unconfined_init;
/*
* DEBUG remains global (no per profile flag) since it is mostly used in sysctl
* which is not related to profile accesses.
*/
#define AA_DEBUG(fmt, args...) \
do { \
if (aa_g_debug && printk_ratelimit()) \
printk(KERN_DEBUG "AppArmor: " fmt, ##args); \
} while (0)
#define AA_ERROR(fmt, args...) \
do { \
if (printk_ratelimit()) \
printk(KERN_ERR "AppArmor: " fmt, ##args); \
} while (0)
/* Flag indicating whether initialization completed */
extern int apparmor_initialized __initdata;
/* fn's in lib */
char *aa_split_fqname(char *args, char **ns_name);
void aa_info_message(const char *str);
void *__aa_kvmalloc(size_t size, gfp_t flags);
static inline void *kvmalloc(size_t size)
{
return __aa_kvmalloc(size, 0);
}
static inline void *kvzalloc(size_t size)
{
return __aa_kvmalloc(size, __GFP_ZERO);
}
/* returns 0 if kref not incremented */
static inline int kref_get_not0(struct kref *kref)
{
return atomic_inc_not_zero(&kref->refcount);
}
/**
* 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 unsigned int aa_dfa_null_transition(struct aa_dfa *dfa,
unsigned int start)
{
/* the null transition only needs the string's null terminator byte */
return aa_dfa_next(dfa, start, 0);
}
static inline bool mediated_filesystem(struct dentry *dentry)
{
return !(dentry->d_sb->s_flags & MS_NOUSER);
}
#endif /* __APPARMOR_H */ #endif /* __APPARMOR_H */
...@@ -15,6 +15,8 @@ ...@@ -15,6 +15,8 @@
#ifndef __AA_APPARMORFS_H #ifndef __AA_APPARMORFS_H
#define __AA_APPARMORFS_H #define __AA_APPARMORFS_H
extern struct path aa_null;
enum aa_fs_type { enum aa_fs_type {
AA_FS_TYPE_BOOLEAN, AA_FS_TYPE_BOOLEAN,
AA_FS_TYPE_STRING, AA_FS_TYPE_STRING,
...@@ -62,7 +64,7 @@ extern const struct file_operations aa_fs_seq_file_ops; ...@@ -62,7 +64,7 @@ extern const struct file_operations aa_fs_seq_file_ops;
extern void __init aa_destroy_aafs(void); extern void __init aa_destroy_aafs(void);
struct aa_profile; struct aa_profile;
struct aa_namespace; struct aa_ns;
enum aafs_ns_type { enum aafs_ns_type {
AAFS_NS_DIR, AAFS_NS_DIR,
...@@ -97,8 +99,7 @@ void __aa_fs_profile_rmdir(struct aa_profile *profile); ...@@ -97,8 +99,7 @@ void __aa_fs_profile_rmdir(struct aa_profile *profile);
void __aa_fs_profile_migrate_dents(struct aa_profile *old, void __aa_fs_profile_migrate_dents(struct aa_profile *old,
struct aa_profile *new); struct aa_profile *new);
int __aa_fs_profile_mkdir(struct aa_profile *profile, struct dentry *parent); int __aa_fs_profile_mkdir(struct aa_profile *profile, struct dentry *parent);
void __aa_fs_namespace_rmdir(struct aa_namespace *ns); void __aa_fs_ns_rmdir(struct aa_ns *ns);
int __aa_fs_namespace_mkdir(struct aa_namespace *ns, struct dentry *parent, int __aa_fs_ns_mkdir(struct aa_ns *ns, struct dentry *parent, const char *name);
const char *name);
#endif /* __AA_APPARMORFS_H */ #endif /* __AA_APPARMORFS_H */
...@@ -22,8 +22,7 @@ ...@@ -22,8 +22,7 @@
#include <linux/slab.h> #include <linux/slab.h>
#include "file.h" #include "file.h"
#include "label.h"
struct aa_profile;
extern const char *const audit_mode_names[]; extern const char *const audit_mode_names[];
#define AUDIT_MAX_INDEX 5 #define AUDIT_MAX_INDEX 5
...@@ -46,97 +45,140 @@ enum audit_type { ...@@ -46,97 +45,140 @@ enum audit_type {
AUDIT_APPARMOR_AUTO AUDIT_APPARMOR_AUTO
}; };
extern const char *const op_table[]; #define OP_NULL NULL
enum aa_ops {
OP_NULL, #define OP_SYSCTL "sysctl"
#define OP_CAPABLE "capable"
OP_SYSCTL,
OP_CAPABLE, #define OP_UNLINK "unlink"
#define OP_MKDIR "mkdir"
OP_UNLINK, #define OP_RMDIR "rmdir"
OP_MKDIR, #define OP_MKNOD "mknod"
OP_RMDIR, #define OP_TRUNC "truncate"
OP_MKNOD, #define OP_LINK "link"
OP_TRUNC, #define OP_SYMLINK "symlink"
OP_LINK, #define OP_RENAME_SRC "rename_src"
OP_SYMLINK, #define OP_RENAME_DEST "rename_dest"
OP_RENAME_SRC, #define OP_CHMOD "chmod"
OP_RENAME_DEST, #define OP_CHOWN "chown"
OP_CHMOD, #define OP_GETATTR "getattr"
OP_CHOWN, #define OP_OPEN "open"
OP_GETATTR,
OP_OPEN, #define OP_FRECEIVE "file_receive"
#define OP_FPERM "file_perm"
OP_FPERM, #define OP_FLOCK "file_lock"
OP_FLOCK, #define OP_FMMAP "file_mmap"
OP_FMMAP, #define OP_FMPROT "file_mprotect"
OP_FMPROT, #define OP_INHERIT "file_inherit"
OP_CREATE, #define OP_PIVOTROOT "pivotroot"
OP_POST_CREATE, #define OP_MOUNT "mount"
OP_BIND, #define OP_UMOUNT "umount"
OP_CONNECT,
OP_LISTEN, #define OP_CREATE "create"
OP_ACCEPT, #define OP_POST_CREATE "post_create"
OP_SENDMSG, #define OP_BIND "bind"
OP_RECVMSG, #define OP_CONNECT "connect"
OP_GETSOCKNAME, #define OP_LISTEN "listen"
OP_GETPEERNAME, #define OP_ACCEPT "accept"
OP_GETSOCKOPT, #define OP_SENDMSG "sendmsg"
OP_SETSOCKOPT, #define OP_RECVMSG "recvmsg"
OP_SOCK_SHUTDOWN, #define OP_GETSOCKNAME "getsockname"
#define OP_GETPEERNAME "getpeername"
OP_PTRACE, #define OP_GETSOCKOPT "getsockopt"
#define OP_SETSOCKOPT "setsockopt"
OP_EXEC, #define OP_SHUTDOWN "socket_shutdown"
OP_CHANGE_HAT,
OP_CHANGE_PROFILE, #define OP_PTRACE "ptrace"
OP_CHANGE_ONEXEC, #define OP_SIGNAL "signal"
OP_SETPROCATTR, #define OP_EXEC "exec"
OP_SETRLIMIT,
#define OP_CHANGE_HAT "change_hat"
OP_PROF_REPL, #define OP_CHANGE_PROFILE "change_profile"
OP_PROF_LOAD, #define OP_CHANGE_ONEXEC "change_onexec"
OP_PROF_RM, #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"
struct apparmor_audit_data { struct apparmor_audit_data {
int error; int error;
int op;
int type; int type;
void *profile; const char *op;
struct aa_label *label;
const char *name; const char *name;
const char *info; const char *info;
u32 request;
u32 denied;
union {
/* these entries require a custom callback fn */
struct {
struct aa_label *peer;
union { union {
void *target;
struct { struct {
kuid_t ouid;
const char *target;
} fs;
struct {
int type, protocol;
struct sock *peer_sk;
void *addr;
int addrlen;
} net;
int signal;
};
};
struct {
struct aa_profile *profile;
const char *ns;
long pos; long pos;
void *target;
} iface; } iface;
struct { struct {
int rlim; int rlim;
unsigned long max; unsigned long max;
} rlim; } rlim;
struct { struct {
const char *target; const char *src_name;
u32 request; const char *type;
u32 denied; const char *trans;
kuid_t ouid; const char *data;
} fs; unsigned long flags;
} mnt;
}; };
}; };
/* define a short hand for apparmor_audit_data structure */ /* macros for dealing with apparmor_audit_data structure */
#define aad apparmor_audit_data #define aad(SA) (SA)->apparmor_audit_data
#define DEFINE_AUDIT_DATA(NAME, T, X) \
/* TODO: cleanup audit init so we don't need _aad = {0,} */ \
struct apparmor_audit_data NAME ## _aad = { .op = (X), }; \
struct common_audit_data NAME = \
{ \
.type = (T), \
.u.tsk = NULL, \
}; \
NAME.apparmor_audit_data = &(NAME ## _aad)
void aa_audit_msg(int type, struct common_audit_data *sa, void aa_audit_msg(int type, struct common_audit_data *sa,
void (*cb) (struct audit_buffer *, void *)); void (*cb) (struct audit_buffer *, void *));
int aa_audit(int type, struct aa_profile *profile, gfp_t gfp, int aa_audit(int type, struct aa_profile *profile, struct common_audit_data *sa,
struct common_audit_data *sa,
void (*cb) (struct audit_buffer *, void *)); void (*cb) (struct audit_buffer *, void *));
#define aa_audit_error(ERROR, SA, CB) \
({ \
aad((SA))->error = (ERROR); \
aa_audit_msg(AUDIT_APPARMOR_ERROR, (SA), (CB)); \
aad((SA))->error; \
})
static inline int complain_error(int error) static inline int complain_error(int error)
{ {
if (error == -EPERM || error == -EACCES) if (error == -EPERM || error == -EACCES)
......
...@@ -19,11 +19,12 @@ ...@@ -19,11 +19,12 @@
#include "apparmorfs.h" #include "apparmorfs.h"
struct aa_profile; struct aa_label;
/* aa_caps - confinement data for capabilities /* aa_caps - confinement data for capabilities
* @allowed: capabilities mask * @allowed: capabilities mask
* @audit: caps that are to be audited * @audit: caps that are to be audited
* @denied: caps that are explicitly denied
* @quiet: caps that should not be audited * @quiet: caps that should not be audited
* @kill: caps that when requested will result in the task being killed * @kill: caps that when requested will result in the task being killed
* @extended: caps that are subject finer grained mediation * @extended: caps that are subject finer grained mediation
...@@ -31,6 +32,7 @@ struct aa_profile; ...@@ -31,6 +32,7 @@ struct aa_profile;
struct aa_caps { struct aa_caps {
kernel_cap_t allow; kernel_cap_t allow;
kernel_cap_t audit; kernel_cap_t audit;
kernel_cap_t denied;
kernel_cap_t quiet; kernel_cap_t quiet;
kernel_cap_t kill; kernel_cap_t kill;
kernel_cap_t extended; kernel_cap_t extended;
...@@ -38,7 +40,7 @@ struct aa_caps { ...@@ -38,7 +40,7 @@ struct aa_caps {
extern struct aa_fs_entry aa_fs_entry_caps[]; extern struct aa_fs_entry aa_fs_entry_caps[];
int aa_capable(struct aa_profile *profile, int cap, int audit); int aa_capable(struct aa_label *label, int cap, int audit);
static inline void aa_free_cap_rules(struct aa_caps *caps) static inline void aa_free_cap_rules(struct aa_caps *caps)
{ {
......
...@@ -19,99 +19,79 @@ ...@@ -19,99 +19,79 @@
#include <linux/slab.h> #include <linux/slab.h>
#include <linux/sched.h> #include <linux/sched.h>
#include "policy.h" #include "label.h"
#include "policy_ns.h"
#define cred_cxt(X) (X)->security #define cred_ctx(X) (X)->security
#define current_cxt() cred_cxt(current_cred()) #define current_ctx() cred_ctx(current_cred())
/* struct aa_file_cxt - the AppArmor context the file was opened in
* @perms: the permission the file was opened with
*
* The file_cxt could currently be directly stored in file->f_security
* as the profile reference is now stored in the f_cred. However the
* cxt struct will expand in the future so we keep the struct.
*/
struct aa_file_cxt {
u16 allow;
};
/**
* aa_alloc_file_context - allocate file_cxt
* @gfp: gfp flags for allocation
*
* Returns: file_cxt or NULL on failure
*/
static inline struct aa_file_cxt *aa_alloc_file_context(gfp_t gfp)
{
return kzalloc(sizeof(struct aa_file_cxt), gfp);
}
/**
* aa_free_file_context - free a file_cxt
* @cxt: file_cxt to free (MAYBE_NULL)
*/
static inline void aa_free_file_context(struct aa_file_cxt *cxt)
{
if (cxt)
kzfree(cxt);
}
/** /**
* struct aa_task_cxt - primary label for confined tasks * struct aa_task_ctx - primary label for confined tasks
* @profile: the current profile (NOT NULL) * @label: the current label (NOT NULL)
* @exec: profile to transition to on next exec (MAYBE NULL) * @exec: label to transition to on next exec (MAYBE NULL)
* @previous: profile the task may return to (MAYBE NULL) * @previous: label the task may return to (MAYBE NULL)
* @token: magic value the task must know for returning to @previous_profile * @token: magic value the task must know for returning to @previous
* *
* Contains the task's current profile (which could change due to * Contains the task's current label (which could change due to
* change_hat). Plus the hat_magic needed during change_hat. * change_hat). Plus the hat_magic needed during change_hat.
* *
* TODO: make so a task can be confined by a stack of contexts * TODO: make so a task can be confined by a stack of contexts
*/ */
struct aa_task_cxt { struct aa_task_ctx {
struct aa_profile *profile; struct aa_label *label;
struct aa_profile *onexec; struct aa_label *onexec;
struct aa_profile *previous; struct aa_label *previous;
u64 token; u64 token;
}; };
struct aa_task_cxt *aa_alloc_task_context(gfp_t flags); struct aa_task_ctx *aa_alloc_task_context(gfp_t flags);
void aa_free_task_context(struct aa_task_cxt *cxt); void aa_free_task_context(struct aa_task_ctx *ctx);
void aa_dup_task_context(struct aa_task_cxt *new, void aa_dup_task_context(struct aa_task_ctx *new,
const struct aa_task_cxt *old); const struct aa_task_ctx *old);
int aa_replace_current_profile(struct aa_profile *profile); int aa_replace_current_label(struct aa_label *label);
int aa_set_current_onexec(struct aa_profile *profile); int aa_set_current_onexec(struct aa_label *label, bool stack);
int aa_set_current_hat(struct aa_profile *profile, u64 token); int aa_set_current_hat(struct aa_label *label, u64 token);
int aa_restore_previous_profile(u64 cookie); int aa_restore_previous_label(u64 cookie);
struct aa_profile *aa_get_task_profile(struct task_struct *task); struct aa_label *aa_get_task_label(struct task_struct *task);
/** /**
* aa_cred_profile - obtain cred's profiles * aa_cred_raw_label - obtain cred's label
* @cred: cred to obtain profiles from (NOT NULL) * @cred: cred to obtain label from (NOT NULL)
* *
* Returns: confining profile * Returns: confining label
* *
* does NOT increment reference count * does NOT increment reference count
*/ */
static inline struct aa_profile *aa_cred_profile(const struct cred *cred) static inline struct aa_label *aa_cred_raw_label(const struct cred *cred)
{ {
struct aa_task_cxt *cxt = cred_cxt(cred); struct aa_task_ctx *ctx = cred_ctx(cred);
BUG_ON(!cxt || !cxt->profile); BUG_ON(!ctx || !ctx->label);
return cxt->profile; return ctx->label;
} }
/** /**
* __aa_task_profile - retrieve another task's profile * aa_get_newest_cred_label - obtain the newest version of the 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(aa_cred_raw_label(cred));
}
/**
* __aa_task_raw_label - retrieve another task's label
* @task: task to query (NOT NULL) * @task: task to query (NOT NULL)
* *
* Returns: @task's profile without incrementing its ref count * Returns: @task's label without incrementing its ref count
* *
* If @task != current needs to be called in RCU safe critical section * If @task != current needs to be called in RCU safe critical section
*/ */
static inline struct aa_profile *__aa_task_profile(struct task_struct *task) static inline struct aa_label *__aa_task_raw_label(struct task_struct *task)
{ {
return aa_cred_profile(__task_cred(task)); return aa_cred_raw_label(__task_cred(task));
} }
/** /**
...@@ -122,57 +102,105 @@ static inline struct aa_profile *__aa_task_profile(struct task_struct *task) ...@@ -122,57 +102,105 @@ static inline struct aa_profile *__aa_task_profile(struct task_struct *task)
*/ */
static inline bool __aa_task_is_confined(struct task_struct *task) static inline bool __aa_task_is_confined(struct task_struct *task)
{ {
return !unconfined(__aa_task_profile(task)); return !unconfined(__aa_task_raw_label(task));
} }
/** /**
* __aa_current_profile - find the current tasks confining profile * aa_current_raw_label - find the current tasks confining label
* *
* Returns: up to date confining profile or the ns unconfined profile (NOT NULL) * 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 * This fn will not update the tasks cred to the most up to date version
* of the profile so it is safe to call when inside of locks. * of the label so it is safe to call when inside of locks.
*/ */
static inline struct aa_profile *__aa_current_profile(void) static inline struct aa_label *aa_current_raw_label(void)
{ {
return aa_cred_profile(current_cred()); return aa_cred_raw_label(current_cred());
} }
/** /**
* aa_current_profile - find the current tasks confining profile and do updates * aa_get_current_label - get the newest version of the current tasks label
*
* Returns: newest version of confining label (NOT NULL)
* *
* Returns: up to date confining profile or the ns unconfined profile (NOT NULL) * This fn will not update the tasks cred, so it is safe inside of locks
* *
* This fn will update the tasks cred structure if the profile has been * The returned reference must be put with aa_put_label()
* replaced. Not safe to call inside locks
*/ */
static inline struct aa_profile *aa_current_profile(void) static inline struct aa_label *aa_get_current_label(void)
{ {
const struct aa_task_cxt *cxt = current_cxt(); struct aa_label *l = aa_current_raw_label();
struct aa_profile *profile;
BUG_ON(!cxt || !cxt->profile); if (label_is_stale(l))
return aa_get_newest_label(l);
if (PROFILE_INVALID(cxt->profile)) { return aa_get_label(l);
profile = aa_get_newest_profile(cxt->profile); }
aa_replace_current_profile(profile);
aa_put_profile(profile); /**
cxt = current_cxt(); * aa_end_current_label - put a reference found with aa_begin_current_label
* @label: label reference to put
*
* Should only be used with a reference obtained with aa_begin_current_label
* and never used in situations where the task cred may be updated
*/
static inline void aa_end_current_label(struct aa_label *label)
{
if (label != aa_current_raw_label())
aa_put_label(label);
}
/**
* aa_begin_current_label - find the current tasks confining label and update it
* @update: whether the current label can be updated
*
* Returns: up to date confining label or the ns unconfined label (NOT NULL)
*
* If @update is true this fn will update the tasks cred structure if the
* label has been replaced. Not safe to call inside locks
* else
* just return the up to date label
*
* The returned reference must be put with aa_end_current_label()
* This must NOT be used if the task cred could be updated within the
* critical section between aa_begin_current_label() .. aa_end_current_label()
*/
static inline struct aa_label *aa_begin_current_label(bool update)
{
struct aa_label *label = aa_current_raw_label();
if (label_is_stale(label)) {
label = aa_get_newest_label(label);
if (update && aa_replace_current_label(label) == 0)
/* task cred will keep the reference */
aa_put_label(label);
} }
return cxt->profile; return label;
}
#define NO_UPDATE false
#define DO_UPDATE true
static inline struct aa_ns *aa_get_current_ns(void)
{
struct aa_label *label = aa_begin_current_label(NO_UPDATE);
struct aa_ns *ns = aa_get_ns(labels_ns(label));
aa_end_current_label(label);
return ns;
} }
/** /**
* aa_clear_task_cxt_trans - clear transition tracking info from the cxt * aa_clear_task_ctx_trans - clear transition tracking info from the ctx
* @cxt: task context to clear (NOT NULL) * @ctx: task context to clear (NOT NULL)
*/ */
static inline void aa_clear_task_cxt_trans(struct aa_task_cxt *cxt) static inline void aa_clear_task_ctx_trans(struct aa_task_ctx *ctx)
{ {
aa_put_profile(cxt->previous); aa_put_label(ctx->previous);
aa_put_profile(cxt->onexec); aa_put_label(ctx->onexec);
cxt->previous = NULL; ctx->previous = NULL;
cxt->onexec = NULL; ctx->onexec = NULL;
cxt->token = 0; ctx->token = 0;
} }
#endif /* __AA_CONTEXT_H */ #endif /* __AA_CONTEXT_H */
...@@ -15,6 +15,8 @@ ...@@ -15,6 +15,8 @@
#include <linux/binfmts.h> #include <linux/binfmts.h>
#include <linux/types.h> #include <linux/types.h>
#include "include/label.h"
#ifndef __AA_DOMAIN_H #ifndef __AA_DOMAIN_H
#define __AA_DOMAIN_H #define __AA_DOMAIN_H
...@@ -23,6 +25,9 @@ struct aa_domain { ...@@ -23,6 +25,9 @@ struct aa_domain {
char **table; char **table;
}; };
struct aa_label *x_table_lookup(struct aa_profile *profile, u32 xindex,
const char **name);
int apparmor_bprm_set_creds(struct linux_binprm *bprm); int apparmor_bprm_set_creds(struct linux_binprm *bprm);
int apparmor_bprm_secureexec(struct linux_binprm *bprm); int apparmor_bprm_secureexec(struct linux_binprm *bprm);
void apparmor_bprm_committing_creds(struct linux_binprm *bprm); void apparmor_bprm_committing_creds(struct linux_binprm *bprm);
...@@ -30,7 +35,7 @@ void apparmor_bprm_committed_creds(struct linux_binprm *bprm); ...@@ -30,7 +35,7 @@ void apparmor_bprm_committed_creds(struct linux_binprm *bprm);
void aa_free_domain_entries(struct aa_domain *domain); void aa_free_domain_entries(struct aa_domain *domain);
int aa_change_hat(const char *hats[], int count, u64 token, bool permtest); int aa_change_hat(const char *hats[], int count, u64 token, bool permtest);
int aa_change_profile(const char *ns_name, const char *name, bool onexec, int aa_change_profile(const char *fqname, bool onexec, bool permtest,
bool permtest); bool stack);
#endif /* __AA_DOMAIN_H */ #endif /* __AA_DOMAIN_H */
...@@ -15,38 +15,75 @@ ...@@ -15,38 +15,75 @@
#ifndef __AA_FILE_H #ifndef __AA_FILE_H
#define __AA_FILE_H #define __AA_FILE_H
#include <linux/spinlock.h>
#include "domain.h" #include "domain.h"
#include "match.h" #include "match.h"
#include "label.h"
#include "perms.h"
struct aa_profile; struct aa_profile;
struct path; struct path;
/* #define mask_mode_t(X) (X & (MAY_EXEC | MAY_WRITE | MAY_READ | MAY_APPEND))
* We use MAY_EXEC, MAY_WRITE, MAY_READ, MAY_APPEND and the following flags
* for profile permissions
*/
#define AA_MAY_CREATE 0x0010
#define AA_MAY_DELETE 0x0020
#define AA_MAY_META_WRITE 0x0040
#define AA_MAY_META_READ 0x0080
#define AA_MAY_CHMOD 0x0100
#define AA_MAY_CHOWN 0x0200
#define AA_MAY_LOCK 0x0400
#define AA_EXEC_MMAP 0x0800
#define AA_MAY_LINK 0x1000
#define AA_LINK_SUBSET AA_MAY_LOCK /* overlaid */
#define AA_MAY_ONEXEC 0x40000000 /* exec allows onexec */
#define AA_MAY_CHANGE_PROFILE 0x80000000
#define AA_MAY_CHANGEHAT 0x80000000 /* ctrl auditing only */
#define AA_AUDIT_FILE_MASK (MAY_READ | MAY_WRITE | MAY_EXEC | MAY_APPEND |\ #define AA_AUDIT_FILE_MASK (MAY_READ | MAY_WRITE | MAY_EXEC | MAY_APPEND |\
AA_MAY_CREATE | AA_MAY_DELETE | \ AA_MAY_CREATE | AA_MAY_DELETE | \
AA_MAY_META_READ | AA_MAY_META_WRITE | \ AA_MAY_GETATTR | AA_MAY_SETATTR | \
AA_MAY_CHMOD | AA_MAY_CHOWN | AA_MAY_LOCK | \ AA_MAY_CHMOD | AA_MAY_CHOWN | AA_MAY_LOCK | \
AA_EXEC_MMAP | AA_MAY_LINK) AA_EXEC_MMAP | AA_MAY_LINK)
#define file_ctx(X) ((struct aa_file_ctx *)(X)->f_security)
/* 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;
};
/**
* aa_alloc_file_ctx - allocate file_ctx
* @label: initial label of task creating the file
* @gfp: gfp flags for allocation
*
* Returns: file_ctx or NULL on failure
*/
static inline struct aa_file_ctx *aa_alloc_file_ctx(struct aa_label *label, gfp_t gfp)
{
struct aa_file_ctx *ctx;
ctx = kzalloc(sizeof(struct aa_file_ctx), gfp);
if (ctx) {
spin_lock_init(&ctx->lock);
rcu_assign_pointer(ctx->label, aa_get_label(label));
}
return ctx;
}
/**
* aa_free_file_ctx - free a file_ctx
* @ctx: file_ctx to free (MAYBE_NULL)
*/
static inline void aa_free_file_ctx(struct aa_file_ctx *ctx)
{
if (ctx) {
aa_put_label(rcu_access_pointer(ctx->label));
kzfree(ctx);
}
}
static inline struct aa_label *aa_get_file_label(struct aa_file_ctx *ctx)
{
return aa_get_label_rcu(&ctx->label);
}
#define inode_ctx(X) (X)->i_security
/* /*
* The xindex is broken into 3 parts * The xindex is broken into 3 parts
* - index - an index into either the exec name table or the variable table * - index - an index into either the exec name table or the variable table
...@@ -75,25 +112,6 @@ struct path_cond { ...@@ -75,25 +112,6 @@ struct path_cond {
umode_t mode; umode_t mode;
}; };
/* struct file_perms - file permission
* @allow: mask of permissions that are allowed
* @audit: mask of permissions to force an audit message for
* @quiet: mask of permissions to quiet audit messages for
* @kill: mask of permissions that when matched will kill the task
* @xindex: exec transition index if @allow contains MAY_EXEC
*
* The @audit and @queit mask should be mutually exclusive.
*/
struct file_perms {
u32 allow;
u32 audit;
u32 quiet;
u32 kill;
u16 xindex;
};
extern struct file_perms nullperms;
#define COMBINED_PERM_MASK(X) ((X).allow | (X).audit | (X).quiet | (X).kill) #define COMBINED_PERM_MASK(X) ((X).allow | (X).audit | (X).quiet | (X).kill)
/* FIXME: split perms from dfa and match this to description /* FIXME: split perms from dfa and match this to description
...@@ -144,9 +162,9 @@ static inline u16 dfa_map_xindex(u16 mask) ...@@ -144,9 +162,9 @@ static inline u16 dfa_map_xindex(u16 mask)
#define dfa_other_xindex(dfa, state) \ #define dfa_other_xindex(dfa, state) \
dfa_map_xindex((ACCEPT_TABLE(dfa)[state] >> 14) & 0x3fff) dfa_map_xindex((ACCEPT_TABLE(dfa)[state] >> 14) & 0x3fff)
int aa_audit_file(struct aa_profile *profile, struct file_perms *perms, int aa_audit_file(struct aa_profile *profile, struct aa_perms *perms,
gfp_t gfp, int op, u32 request, const char *name, const char *op, u32 request, const char *name, const char *target, struct aa_label *tlabel,
const char *target, kuid_t ouid, const char *info, int error); kuid_t ouid, const char *info, int error);
/** /**
* struct aa_file_rules - components used for file rule permissions * struct aa_file_rules - components used for file rule permissions
...@@ -167,19 +185,26 @@ struct aa_file_rules { ...@@ -167,19 +185,26 @@ struct aa_file_rules {
/* TODO: add delegate table */ /* TODO: add delegate table */
}; };
struct aa_perms aa_compute_fperms(struct aa_dfa *dfa, unsigned int state,
struct path_cond *cond);
unsigned int aa_str_perms(struct aa_dfa *dfa, unsigned int start, unsigned int aa_str_perms(struct aa_dfa *dfa, unsigned int start,
const char *name, struct path_cond *cond, const char *name, struct path_cond *cond,
struct file_perms *perms); struct aa_perms *perms);
int aa_path_perm(int op, struct aa_profile *profile, struct path *path, int __aa_path_perm(const char *op, 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, struct aa_label *label, struct path *path,
int flags, u32 request, struct path_cond *cond); int flags, u32 request, struct path_cond *cond);
int aa_path_link(struct aa_profile *profile, struct dentry *old_dentry, int aa_path_link(struct aa_label *label, struct dentry *old_dentry,
struct path *new_dir, struct dentry *new_dentry); struct path *new_dir, struct dentry *new_dentry);
int aa_file_perm(int op, struct aa_profile *profile, struct file *file, int aa_file_perm(const char *op, struct aa_label *label, struct file *file,
u32 request); u32 request);
void aa_inherit_files(const struct cred *cred, struct files_struct *files);
static inline void aa_free_file_rules(struct aa_file_rules *rules) static inline void aa_free_file_rules(struct aa_file_rules *rules)
{ {
aa_put_dfa(rules->dfa); aa_put_dfa(rules->dfa);
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
* This file contains AppArmor ipc mediation function definitions. * This file contains AppArmor ipc mediation function definitions.
* *
* Copyright (C) 1998-2008 Novell/SUSE * Copyright (C) 1998-2008 Novell/SUSE
* Copyright 2009-2010 Canonical Ltd. * Copyright 2009-2013 Canonical Ltd.
* *
* This program is free software; you can redistribute it and/or * This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as * modify it under the terms of the GNU General Public License as
...@@ -19,10 +19,22 @@ ...@@ -19,10 +19,22 @@
struct aa_profile; struct aa_profile;
int aa_may_ptrace(struct aa_profile *tracer, struct aa_profile *tracee, #define AA_PTRACE_TRACE MAY_WRITE
unsigned int mode); #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
int aa_ptrace(struct task_struct *tracer, struct task_struct *tracee, #define AA_PTRACE_PERM_MASK (AA_PTRACE_READ | AA_PTRACE_TRACE | \
unsigned int mode); AA_MAY_BE_READ | AA_MAY_BE_TRACED)
#define AA_SIGNAL_PERM_MASK (MAY_READ | MAY_WRITE)
#define AA_FS_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(struct aa_label *tracer, struct aa_label *tracee,
u32 request);
int aa_may_signal(struct aa_label *sender, struct aa_label *target, int sig);
#endif /* __AA_IPC_H */ #endif /* __AA_IPC_H */
/*
* AppArmor security module
*
* This file contains AppArmor label definitions
*
* Copyright 2013 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_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;
#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 labelset_stats {
atomic_t sread;
atomic_t fread;
atomic_t msread;
atomic_t mfread;
atomic_t insert;
atomic_t existing;
atomic_t minsert;
atomic_t mexisting;
atomic_t stale; /* outstanding stale */
};
struct label_stats {
struct labelset_stats set_stats;
atomic_t allocated;
atomic_t failed;
atomic_t freed;
atomic_t printk_name_alloc;
atomic_t printk_name_fail;
atomic_t seq_print_name_alloc;
atomic_t seq_print_name_fail;
atomic_t audit_name_alloc;
atomic_t audit_name_fail;
};
#ifdef AA_LABEL_STATS
#define labelstats_inc(X) atomic_inc(stats.(X))
#define labelstats_dec(X) atomic_dec(stats.(X))
#define labelsetstats_inc(LS, X) \
do { \
labelstats_inc(set_stats.##X); \
atomic_inc((LS)->stats.(X)); \
} while (0)
#define labelsetstats_dec(LS, X) \
do { \
labelstats_dec(set_stats.##X); \
atomic_dec((LS)->stats.(X)); \
} while (0)
#else
#define labelstats_inc(X)
#define labelstats_dec(X)
#define labelsetstats_inc(LS, X)
#define labelsetstats_dec(LS, X)
#endif
#define labelstats_init(X)
/* 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;
/* stats */
#ifdef APPARMOR_LABEL_STATS
struct labelset_stats stats;
#endif
};
#define __labelset_for_each(LS, N) \
for((N) = rb_first(&(LS)->root); (N); (N) = rb_next(N))
void aa_labelset_destroy(struct aa_labelset *ls);
void aa_labelset_init(struct aa_labelset *ls);
enum label_flags {
FLAG_HAT = 1, /* profile is a hat */
FLAG_UNCONFINED = 2, /* label unconfined only if all
* constituant profiles unconfined */
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, /* explict static label */
FLAG_STALE = 0x800, /* replaced/removed */
FLAG_RENAMED = 0x1000, /* label has renaming in it */
FLAG_REVOKED = 0x2000, /* label has revocation in it */
/* These flags must correspond with PATH_flags */
/* TODO: add new path flags */
};
struct aa_label;
struct aa_proxy {
struct kref count;
struct aa_label __rcu *label;
};
struct label_it {
int i, j;
};
/* struct aa_label - lazy labeling struct
* @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
* @sid: sid that references this label
* @size: number of entries in @ent[]
* @ent: set of profiles for label, actual size determined by @size
*/
struct aa_label {
struct kref count;
struct rb_node node;
struct rcu_head rcu;
struct aa_proxy *proxy;
__counted char *hname;
long flags;
u32 sid;
int size;
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) do { \
labelsetstats_inc(labels_set(X), stale); \
((X)->flags |= FLAG_STALE); \
} while (0)
#define labels_ns(X) (vec_ns(&((X)->vec[0]), (X)->size))
#define labels_set(X) (&labels_ns(X)->labels)
#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_init(I) ((I).i = 0);
#define label_for_each_next(I) (++((I).i))
#define label_for_each_curr(I, L) ({ (L)->vec[(I).i] ; })
#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))
#define next_comb(I, L1, L2) \
do { \
(I).j++; \
if ((I).j >= (L2)->size) { \
(I).i++; \
(I).j = 0; \
} \
} while (0)
/* TODO: label_for_each_ns_comb */
/* for each combination of P1 in L1, and P2 in L2 */
#define label_for_each_comb(I, L1, L2, P1, P2) \
for ((I).i = (I).j = 0; \
((P1) = (L1)->vec[(I).i]) && ((P2) = (L2)->vec[(I).j]); \
(I) = next_comb(I, L1, L2))
#define fn_for_each_comb(L1, L2, P1, P2, FN) \
({ \
struct label_it i; \
int __E = 0; \
label_for_each_comb(i, (L1), (L2), (P1), (P2)) { \
last_error(__E, (FN)); \
} \
__E; \
})
/* internal cross check */
//fn_for_each_comb(L1, L2, P1, P2, xcheck(...));
/* external cross check */
// xcheck(fn_for_each_comb(L1, L2, ...),
// fn_for_each_comb(L2, L1, ...));
/* 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)
#define LABEL_MEDIATES(L, C) \
({ \
struct aa_profile *profile; \
struct label_it i; \
int ret = 0; \
label_for_each(i, (L), profile) { \
if (PROFILE_MEDIATES(profile, (C))) { \
ret = 1; \
break; \
} \
} \
ret; \
})
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_free(struct aa_label *label);
void aa_label_kref(struct kref *kref);
bool aa_label_init(struct aa_label *label, int size);
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);
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_label *aa_label_find(struct aa_label *l);
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
int aa_profile_snxprint(char *str, size_t size, struct aa_ns *ns,
struct aa_profile *profile, int flags);
int aa_label_snxprint(char *str, size_t size, struct aa_ns *ns,
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_audit(struct audit_buffer *ab, struct aa_label *label, gfp_t gfp);
void aa_label_seq_print(struct seq_file *f, struct aa_label *label, gfp_t gfp);
void aa_label_printk(struct aa_label *label, gfp_t gfp);
struct aa_label *aa_label_parse(struct aa_label *base, const char *str,
gfp_t gfp, bool create, bool force_stack);
struct aa_perms;
int aa_label_match(struct aa_profile *profile, struct aa_label *label,
unsigned int state, bool subns, u32 request,
struct aa_perms *perms);
static inline struct aa_label *aa_get_label(struct aa_label *l)
{
if (l)
kref_get(&(l->count));
return l;
}
static inline struct aa_label *aa_get_label_not0(struct aa_label *l)
{
if (l && kref_get_not0(&l->count))
return l;
return NULL;
}
/**
* 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_not0(&c->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
*/
AA_BUG(!(tmp = aa_get_label_rcu(&l->proxy->label)));
return tmp;
}
return aa_get_label(l);
}
static inline void aa_put_label(struct aa_label *l)
{
if (l)
kref_put(&l->count, aa_label_kref);
}
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));
return proxy;
}
static inline void aa_put_proxy(struct aa_proxy *proxy)
{
if (proxy)
kref_put(&proxy->count, aa_proxy_kref);
}
void __aa_proxy_redirect(struct aa_label *orig, struct aa_label *new);
#endif /* __AA_LABEL_H */
/*
* AppArmor security module
*
* This file contains AppArmor lib definitions
*
* 2016 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_LIB_H
#define __AA_LIB_H
#include <linux/slab.h>
#include <linux/fs.h>
#include "match.h"
/* Provide our own test for whether a write lock is held for asserts
* this is because on none SMP systems write_can_lock will always
* resolve to true, which is what you want for code making decisions
* based on it, but wrong for asserts checking that the lock is held
*/
#ifdef CONFIG_SMP
#define write_is_locked(X) !write_can_lock(X)
#else
#define write_is_locked(X) (1)
#endif /* CONFIG_SMP */
/*
* DEBUG remains global (no per profile flag) since it is mostly used in sysctl
* which is not related to profile accesses.
*/
#define DEBUG_ON (aa_g_debug && printk_ratelimit())
#define dbg_printk(__fmt, __args...) printk(KERN_DEBUG __fmt, ##__args)
#define AA_DEBUG(fmt, args...) \
do { \
if (DEBUG_ON) \
dbg_printk("AppArmor: " fmt, ##args); \
} while (0)
#define AA_WARN(X) WARN((X), "APPARMOR WARN %s: %s\n", __FUNCTION__, #X)
#define AA_BUG(X, args...) AA_BUG_FMT((X), "" args )
#define AA_BUG_FMT(X, fmt, args...) \
WARN((X), "AppArmor WARN %s: (" #X "): " fmt, __FUNCTION__ , ##args )
#define AA_ERROR(fmt, args...) \
do { \
if (printk_ratelimit()) \
printk(KERN_ERR "AppArmor: " fmt, ##args); \
} while (0)
/* Flag indicating whether initialization completed */
extern int apparmor_initialized __initdata;
/* fn's in lib */
char *aa_split_fqname(char *args, char **ns_name);
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);
void *__aa_kvmalloc(size_t size, gfp_t flags);
static inline void *kvmalloc(size_t size)
{
return __aa_kvmalloc(size, 0);
}
static inline void *kvzalloc(size_t size)
{
return __aa_kvmalloc(size, __GFP_ZERO);
}
/* returns 0 if kref not incremented */
static inline int kref_get_not0(struct kref *kref)
{
return atomic_inc_not_zero(&kref->refcount);
}
/**
* 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 unsigned int aa_dfa_null_transition(struct aa_dfa *dfa,
unsigned int 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 & MS_NOUSER);
}
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);
}
const char *aa_imode_name(umode_t mode);
/* struct aa_policy - common part of both namespaces and profiles
* @name: name of the object
* @hname - The hierarchical name, NOTE: is .name of struct counted_str
* @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;
};
#define aa_peer_name(peer) (peer)->base.hname
/**
* basename - find the last component of an hname
* @name: 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
* ERR_PTR if build @FN fails
* NULL if label_build fails due to low memory conditions
*
* @FN must return a label or ERR_PTR on failure. NULL is not allowed
*/
#define fn_label_build(L, P, GFP, FN) \
({ \
__label__ __cleanup, __done; \
struct aa_label *__new_; \
\
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_ = NULL; \
goto __done; \
} \
__j = 0; \
label_for_each(__i, (L), (P)) { \
__new_ = (FN); \
AA_BUG(!__new_); \
if (IS_ERR(__new_)) \
goto __cleanup; \
__lvec[__j++] = __new_; \
} \
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)); \
/* only fails if out of Mem */ \
if (!__new_) \
__new_ = NULL; \
} else \
__new_ = aa_get_label(&__pvec[0]->label); \
vec_cleanup(profile, __pvec, __count); \
} else \
__new_ = NULL; \
__cleanup: \
vec_cleanup(label, __lvec, (L)->size); \
} else { \
(P) = labels_profile(L); \
__new_ = (FN); \
} \
__done: \
if (!__new_) \
AA_DEBUG("label build failed\n"); \
(__new_); \
})
#define __fn_build_in_ns(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_ns(L, P, GFP, NS_FN, OTHER_FN) \
({ \
fn_label_build((L), (P), (GFP), \
__fn_build_in_ns(labels_ns(L), (P), (NS_FN), (OTHER_FN))); \
})
#endif /* __AA_LIB_H */
...@@ -99,6 +99,8 @@ struct aa_dfa { ...@@ -99,6 +99,8 @@ struct aa_dfa {
struct table_header *tables[YYTD_ID_TSIZE]; struct table_header *tables[YYTD_ID_TSIZE];
}; };
extern struct aa_dfa *nulldfa;
#define byte_to_byte(X) (X) #define byte_to_byte(X) (X)
#define UNPACK_ARRAY(TABLE, BLOB, LEN, TYPE, NTOHX) \ #define UNPACK_ARRAY(TABLE, BLOB, LEN, TYPE, NTOHX) \
...@@ -116,6 +118,9 @@ static inline size_t table_size(size_t len, size_t el_size) ...@@ -116,6 +118,9 @@ static inline size_t table_size(size_t len, size_t el_size)
return ALIGN(sizeof(struct table_header) + len * el_size, 8); return ALIGN(sizeof(struct table_header) + len * el_size, 8);
} }
int aa_setup_dfa_engine(void);
void aa_teardown_dfa_engine(void);
struct aa_dfa *aa_dfa_unpack(void *blob, size_t size, int flags); struct aa_dfa *aa_dfa_unpack(void *blob, size_t size, int flags);
unsigned int aa_dfa_match_len(struct aa_dfa *dfa, unsigned int start, unsigned int aa_dfa_match_len(struct aa_dfa *dfa, unsigned int start,
const char *str, int len); const char *str, int len);
...@@ -126,6 +131,21 @@ unsigned int aa_dfa_next(struct aa_dfa *dfa, unsigned int state, ...@@ -126,6 +131,21 @@ unsigned int aa_dfa_next(struct aa_dfa *dfa, unsigned int state,
void aa_dfa_free_kref(struct kref *kref); void aa_dfa_free_kref(struct kref *kref);
/**
* 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 * aa_put_dfa - put a dfa refcount
* @dfa: dfa to put refcount (MAYBE NULL) * @dfa: dfa to put refcount (MAYBE NULL)
......
/*
* AppArmor security module
*
* This file contains AppArmor file mediation function definitions.
*
* Copyright 2012 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_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(struct aa_label *label, struct path *path, unsigned long flags,
void *data);
int aa_bind_mount(struct aa_label *label, struct path *path,
const char *old_name, unsigned long flags);
int aa_mount_change_type(struct aa_label *label, struct path *path,
unsigned long flags);
int aa_move_mount(struct aa_label *label, struct path *path,
const char *old_name);
int aa_new_mount(struct aa_label *label, const char *dev_name,
struct path *path, const char *type, unsigned long flags,
void *data);
int aa_umount(struct aa_label *label, struct vfsmount *mnt, int flags);
int aa_pivotroot(struct aa_label *label, struct path *old_path,
struct path *new_path);
#endif /* __AA_MOUNT_H */
/*
* AppArmor security module
*
* This file contains AppArmor network mediation definitions.
*
* Copyright (C) 1998-2008 Novell/SUSE
* Copyright 2009-2014 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_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 *label;
struct aa_label *peer;
struct path path;
};
#define SK_CTX(X) (X)->sk_security
#define SOCK_ctx(X) SOCK_INODE(X)->i_security
#define DEFINE_AUDIT_NET(NAME, OP, 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, \
OP); \
NAME.u.net = &(NAME ## _net); \
aad(&NAME)->net.type = (T); \
aad(&NAME)->net.protocol = (P)
#define DEFINE_AUDIT_SK(NAME, OP, SK) \
DEFINE_AUDIT_NET(NAME, OP, SK, (SK)->sk_family, (SK)->sk_type, \
(SK)->sk_protocol)
/* struct aa_net - network confinement data
* @allowed: basic network families permissions
* @audit_network: which network permissions to force audit
* @quiet_network: which network permissions to quiet rejects
*/
struct aa_net {
u16 allow[AF_MAX];
u16 audit[AF_MAX];
u16 quiet[AF_MAX];
};
extern struct aa_fs_entry aa_fs_entry_network[];
void audit_net_cb(struct audit_buffer *ab, void *va);
int aa_profile_af_perm(struct aa_profile *profile, struct common_audit_data *sa,
u32 request, u16 family, int type);
static inline int aa_profile_af_sk_perm(struct aa_profile *profile,
struct common_audit_data *sa,
u32 request,
struct sock *sk)
{
return aa_profile_af_perm(profile, sa, request, sk->sk_family,
sk->sk_type);
}
int aa_sock_perm(const char *op, u32 request, struct socket *sock);
int aa_sock_create_perm(struct aa_label *label, int family, int type,
int protocol);
int aa_sock_bind_perm(struct socket *sock, struct sockaddr *address,
int addrlen);
int aa_sock_connect_perm(struct socket *sock, struct sockaddr *address,
int addrlen);
int aa_sock_listen_perm(struct socket *sock, int backlog);
int aa_sock_accept_perm(struct socket *sock, struct socket *newsock);
int aa_sock_msg_perm(const char *op, u32 request, struct socket *sock,
struct msghdr *msg, int size);
int aa_sock_opt_perm(const char *op, u32 request, struct socket *sock, int level,
int optname);
int aa_sock_file_perm(struct aa_label *label, const char *op, u32 request,
struct socket *sock);
static inline void aa_free_net_rules(struct aa_net *new)
{
/* NOP */
}
#endif /* __AA_NET_H */
...@@ -18,6 +18,7 @@ ...@@ -18,6 +18,7 @@
enum path_flags { enum path_flags {
PATH_IS_DIR = 0x1, /* path is a directory */ PATH_IS_DIR = 0x1, /* path is a directory */
PATH_SOCK_COND = 0x2,
PATH_CONNECT_PATH = 0x4, /* connect disconnected paths to / */ PATH_CONNECT_PATH = 0x4, /* connect disconnected paths to / */
PATH_CHROOT_REL = 0x8, /* do path lookup relative to chroot */ PATH_CHROOT_REL = 0x8, /* do path lookup relative to chroot */
PATH_CHROOT_NSCONNECT = 0x10, /* connect paths that are at ns root */ PATH_CHROOT_NSCONNECT = 0x10, /* connect paths that are at ns root */
...@@ -26,7 +27,63 @@ enum path_flags { ...@@ -26,7 +27,63 @@ enum path_flags {
PATH_MEDIATE_DELETED = 0x10000, /* mediate deleted paths */ PATH_MEDIATE_DELETED = 0x10000, /* mediate deleted paths */
}; };
int aa_path_name(struct path *path, int flags, char **buffer, int aa_path_name(struct path *path, int flags, char *buffer,
const char **name, const char **info); const char **name, const char **info, const char *disconnect);
#define MAX_PATH_BUFFERS 2
/* Per cpu buffers used during mediation */
/* preallocated buffers to use during path lookups */
struct aa_buffers {
char *buf[MAX_PATH_BUFFERS];
};
#include <linux/percpu.h>
#include <linux/preempt.h>
DECLARE_PER_CPU(struct aa_buffers, aa_buffers);
#define COUNT_ARGS(X...) COUNT_ARGS_HELPER ( , ##X ,9,8,7,6,5,4,3,2,1,0)
#define COUNT_ARGS_HELPER(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,n,X...) n
#define CONCAT(X, Y) X ## Y
#define CONCAT_AFTER(X, Y) CONCAT(X, Y)
#define ASSIGN(FN, X, N) do { (X) = FN(N); } while (0)
#define EVAL1(FN, X) ASSIGN(FN, X, 0) /*X = FN(0)*/
#define EVAL2(FN, X, Y...) ASSIGN(FN, X, 1); /*X = FN(1);*/ EVAL1(FN, Y)
#define EVAL(FN, X...) CONCAT_AFTER(EVAL, COUNT_ARGS(X))(FN, X)
#define for_each_cpu_buffer(I) for ((I) = 0; (I) < MAX_PATH_BUFFERS; (I)++)
#ifdef CONFIG_DEBUG_PREEMPT
#define AA_BUG_PREEMPT_ENABLED(X) AA_BUG(preempt_count() <= 0, X)
#else
#define AA_BUG_PREEMPT_ENABLED(X) /* nop */
#endif
#define __get_buffer(N) ({ \
struct aa_buffers *__cpu_var; \
AA_BUG_PREEMPT_ENABLED("__get_buffer without preempt disabled"); \
__cpu_var = this_cpu_ptr(&aa_buffers); \
__cpu_var->buf[(N)]; })
#define __get_buffers(X...) \
do { \
EVAL(__get_buffer, X); \
} while (0)
#define __put_buffers(X, Y...) (void)&(X)
#define get_buffers(X...) \
do { \
preempt_disable(); \
__get_buffers(X); \
} while (0)
#define put_buffers(X, Y...) \
do { \
__put_buffers(X, Y); \
preempt_enable(); \
} while (0)
#endif /* __AA_PATH_H */ #endif /* __AA_PATH_H */
/*
* AppArmor security module
*
* This file contains AppArmor basic permission sets definitions.
*
* Copyright 2013 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_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 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 audit; /* set only when allow is set */
u32 deny; /* explicit deny, or conflict if allow also set */
u32 quiet; /* set only when ~allow | deny */
u32 kill; /* set only when ~allow | deny */
u32 stop; /* set only when ~allow | deny */
u32 complain; /* accumulates only used when ~allow & ~deny */
u32 cond; /* set only when ~allow and ~deny */
u32 hide; /* set only when ~allow | deny */
u32 prompt; /* accumulates only used when ~allow & ~deny */
/* Reserved:
* u32 subtree; / * set only when allow is set * /
*/
u16 xindex;
};
#define ALL_PERMS_MASK 0xffffffff
extern struct aa_perms nullperms;
extern struct aa_perms allperms;
#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
* Note: this only works for profiles from a single namespace
*/
#define xcheck_profile_label(P, L, FN, args...) \
({ \
struct aa_profile *__p2; \
fn_for_each((L), __p2, FN((P), __p2, args)); \
})
#define xcheck_ns_labels(L1, L2, FN, args...) \
({ \
struct aa_profile *__p1; \
fn_for_each((L1), __p1, FN(__p1, (L2), args)); \
})
/* todo: fix to handle multiple namespaces */
#define xcheck_labels(L1, L2, FN, args...) \
xcheck_ns_labels((L1), (L2), FN, 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_profile_label, (FN), args)
#define FINAL_CHECK true
void aa_perm_mask_to_str(char *str, const char *chrs, u32 mask);
void aa_audit_perm_names(struct audit_buffer *ab, const char **names, u32 mask);
void aa_audit_perm_mask(struct audit_buffer *ab, u32 mask, const char *chrs,
u32 chrsmask, const char **names, u32 namesmask);
void aa_apply_modes_to_perms(struct aa_profile *profile,
struct aa_perms *perms);
void aa_compute_perms(struct aa_dfa *dfa, unsigned int state,
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_label *label,
int type, u32 request, struct aa_perms *perms);
int aa_profile_label_perm(struct aa_profile *profile, struct aa_profile *target,
u32 request, int type, u32 *deny,
struct common_audit_data *sa);
int aa_check_perms(struct aa_profile *profile, struct aa_perms *perms,
u32 request, struct common_audit_data *sa,
void (*cb) (struct audit_buffer *, void *));
static inline int aa_xlabel_perm(struct aa_profile *profile,
struct aa_profile *target,
int type, u32 request, u32 reverse,
u32 * deny, struct common_audit_data *sa)
{
/* TODO: ??? 2nd aa_profile_label_perm needs to reverse perms */
return xcheck(aa_profile_label_perm(profile, target, request, type,
deny, sa),
aa_profile_label_perm(target, profile, request /*??*/, type,
deny, sa));
}
#endif /* __AA_PERM_H */
...@@ -27,8 +27,13 @@ ...@@ -27,8 +27,13 @@
#include "capability.h" #include "capability.h"
#include "domain.h" #include "domain.h"
#include "file.h" #include "file.h"
#include "label.h"
#include "net.h"
#include "perms.h"
#include "resource.h" #include "resource.h"
struct aa_ns;
extern const char *const aa_profile_mode_names[]; extern const char *const aa_profile_mode_names[];
#define APPARMOR_MODE_NAMES_MAX_INDEX 4 #define APPARMOR_MODE_NAMES_MAX_INDEX 4
...@@ -40,9 +45,9 @@ extern const char *const aa_profile_mode_names[]; ...@@ -40,9 +45,9 @@ extern const char *const aa_profile_mode_names[];
#define KILL_MODE(_profile) PROFILE_MODE((_profile), APPARMOR_KILL) #define KILL_MODE(_profile) PROFILE_MODE((_profile), APPARMOR_KILL)
#define PROFILE_IS_HAT(_profile) ((_profile)->flags & PFLAG_HAT) #define PROFILE_IS_HAT(_profile) ((_profile)->label.flags & FLAG_HAT)
#define PROFILE_INVALID(_profile) ((_profile)->flags & PFLAG_INVALID) #define profile_is_stale(_profile) (label_is_stale(&(_profile)->label))
#define on_list_rcu(X) (!list_empty(X) && (X)->prev != LIST_POISON2) #define on_list_rcu(X) (!list_empty(X) && (X)->prev != LIST_POISON2)
...@@ -59,86 +64,6 @@ enum profile_mode { ...@@ -59,86 +64,6 @@ enum profile_mode {
APPARMOR_UNCONFINED, /* profile set to unconfined */ APPARMOR_UNCONFINED, /* profile set to unconfined */
}; };
enum profile_flags {
PFLAG_HAT = 1, /* profile is a hat */
PFLAG_NULL = 4, /* profile is null learning profile */
PFLAG_IX_ON_NAME_ERROR = 8, /* fallback to ix on name lookup fail */
PFLAG_IMMUTABLE = 0x10, /* don't allow changes/replacement */
PFLAG_USER_DEFINED = 0x20, /* user based profile - lower privs */
PFLAG_NO_LIST_REF = 0x40, /* list doesn't keep profile ref */
PFLAG_OLD_NULL_TRANS = 0x100, /* use // as the null transition */
PFLAG_INVALID = 0x200, /* profile replaced/removed */
PFLAG_NS_COUNT = 0x400, /* carries NS ref count */
/* These flags must correspond with PATH_flags */
PFLAG_MEDIATE_DELETED = 0x10000, /* mediate instead delegate deleted */
};
struct aa_profile;
/* 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 {
char *name;
char *hname;
struct list_head list;
struct list_head profiles;
};
/* 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_namespace - 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
* @dents: dentries for the namespaces file entries in apparmorfs
*
* An aa_namespace defines the set profiles that are searched to determine
* which profile to attach to a task. Profiles can not be shared between
* aa_namespaces 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
*
* FIXME TODO: add vserver support of namespaces (can it all be done in
* userspace?)
*/
struct aa_namespace {
struct aa_policy base;
struct aa_namespace *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;
struct dentry *dents[AAFS_NS_SIZEOF];
};
/* struct aa_policydb - match engine for a policy /* struct aa_policydb - match engine for a policy
* dfa: dfa pattern match * dfa: dfa pattern match
...@@ -151,31 +76,24 @@ struct aa_policydb { ...@@ -151,31 +76,24 @@ struct aa_policydb {
}; };
struct aa_replacedby {
struct kref count;
struct aa_profile __rcu *profile;
};
/* struct aa_profile - basic confinement data /* struct aa_profile - basic confinement data
* @base - base components of the profile (name, refcount, lists, lock ...) * @base - base components of the profile (name, refcount, lists, lock ...)
* @count: reference count of the obj * @label - label this profile is an extension of
* @rcu: rcu head used when removing from @list
* @parent: parent of profile * @parent: parent of profile
* @ns: namespace the profile is in * @ns: namespace the profile is in
* @replacedby: is set to the profile that replaced this profile
* @rename: optional profile name that this profile renamed * @rename: optional profile name that this profile renamed
* @attach: human readable attachment string * @attach: human readable attachment string
* @xmatch: optional extended matching for unconfined executables names * @xmatch: optional extended matching for unconfined executables names
* @xmatch_len: xmatch prefix len, used to determine xmatch priority * @xmatch_len: xmatch prefix len, used to determine xmatch priority
* @audit: the auditing mode of the profile * @audit: the auditing mode of the profile
* @mode: the enforcement mode of the profile * @mode: the enforcement mode of the profile
* @flags: flags controlling profile behavior
* @path_flags: flags controlling path generation behavior * @path_flags: flags controlling path generation behavior
* @disconnected: what to prepend if attach_disconnected is specified
* @size: the memory consumed by this profiles rules * @size: the memory consumed by this profiles rules
* @policy: general match rules governing policy * @policy: general match rules governing policy
* @file: The set of rules governing basic file access and domain transitions * @file: The set of rules governing basic file access and domain transitions
* @caps: capabilities for the profile * @caps: capabilities for the profile
* @net: network controls for the profile
* @rlimits: rlimits for the profile * @rlimits: rlimits for the profile
* *
* @dents: dentries for the profiles file entries in apparmorfs * @dents: dentries for the profiles file entries in apparmorfs
...@@ -186,8 +104,6 @@ struct aa_replacedby { ...@@ -186,8 +104,6 @@ struct aa_replacedby {
* used to determine profile attachment against unconfined tasks. All other * used to determine profile attachment against unconfined tasks. All other
* attachments are determined by profile X transition rules. * attachments are determined by profile X transition rules.
* *
* The @replacedby struct is write protected by the profile lock.
*
* Profiles have a hierarchy where hats and children profiles keep * Profiles have a hierarchy where hats and children profiles keep
* a reference to their parent. * a reference to their parent.
* *
...@@ -197,12 +113,9 @@ struct aa_replacedby { ...@@ -197,12 +113,9 @@ struct aa_replacedby {
*/ */
struct aa_profile { struct aa_profile {
struct aa_policy base; struct aa_policy base;
struct kref count;
struct rcu_head rcu;
struct aa_profile __rcu *parent; struct aa_profile __rcu *parent;
struct aa_namespace *ns; struct aa_ns *ns;
struct aa_replacedby *replacedby;
const char *rename; const char *rename;
const char *attach; const char *attach;
...@@ -210,57 +123,91 @@ struct aa_profile { ...@@ -210,57 +123,91 @@ struct aa_profile {
int xmatch_len; int xmatch_len;
enum audit_mode audit; enum audit_mode audit;
long mode; long mode;
long flags;
u32 path_flags; u32 path_flags;
const char *disconnected;
int size; int size;
struct aa_policydb policy; struct aa_policydb policy;
struct aa_file_rules file; struct aa_file_rules file;
struct aa_caps caps; struct aa_caps caps;
struct aa_net net;
struct aa_rlimit rlimits; struct aa_rlimit rlimits;
unsigned char *hash; unsigned char *hash;
char *dirname; char *dirname;
struct dentry *dents[AAFS_PROF_SIZEOF]; struct dentry *dents[AAFS_PROF_SIZEOF];
struct aa_label label;
}; };
extern struct aa_namespace *root_ns;
extern enum profile_mode aa_g_profile_mode; extern enum profile_mode aa_g_profile_mode;
void aa_add_profile(struct aa_policy *common, struct aa_profile *profile); #define AA_MAY_LOAD_POLICY AA_MAY_APPEND
#define AA_MAY_REPLACE_POLICY AA_MAY_WRITE
#define AA_MAY_REMOVE_POLICY AA_MAY_DELETE
bool aa_ns_visible(struct aa_namespace *curr, struct aa_namespace *view); #define profiles_ns(P) ((P)->ns)
const char *aa_ns_name(struct aa_namespace *parent, struct aa_namespace *child); #define name_is_shared(A, B) ((A)->hname && (A)->hname == (B)->hname)
int aa_alloc_root_ns(void);
void aa_free_root_ns(void); void aa_add_profile(struct aa_policy *common, struct aa_profile *profile);
void aa_free_namespace_kref(struct kref *kref);
struct aa_namespace *aa_find_namespace(struct aa_namespace *root,
const char *name);
struct aa_label *aa_setup_default_label(void);
void aa_free_replacedby_kref(struct kref *kref); struct aa_profile *aa_alloc_profile(const char *name, struct aa_proxy *proxy,
struct aa_profile *aa_alloc_profile(const char *name); gfp_t gfp);
struct aa_profile *aa_new_null_profile(struct aa_profile *parent, int hat); struct aa_profile *aa_null_profile(struct aa_profile *parent, bool hat,
const char *base, gfp_t gfp);
void aa_free_profile(struct aa_profile *profile); void aa_free_profile(struct aa_profile *profile);
void aa_free_profile_kref(struct kref *kref); void aa_free_profile_kref(struct kref *kref);
struct aa_profile *aa_find_child(struct aa_profile *parent, const char *name); struct aa_profile *aa_find_child(struct aa_profile *parent, const char *name);
struct aa_profile *aa_lookup_profile(struct aa_namespace *ns, const char *name); struct aa_profile *aa_lookupn_profile(struct aa_ns *ns, const char *hname,
struct aa_profile *aa_match_profile(struct aa_namespace *ns, const char *name); size_t n);
struct aa_profile *aa_lookup_profile(struct aa_ns *ns, const char *name);
ssize_t aa_replace_profiles(void *udata, size_t size, bool noreplace); struct aa_profile *aa_fqlookupn_profile(struct aa_label *base,
ssize_t aa_remove_profiles(char *name, size_t size); const char *fqname, size_t n);
struct aa_profile *aa_match_profile(struct aa_ns *ns, const char *name);
ssize_t aa_replace_profiles(struct aa_label *label, u32 mask, void *udata,
size_t size);
ssize_t aa_remove_profiles(struct aa_label *label, char *name, size_t size);
void __aa_profile_list_release(struct list_head *head);
#define PROF_ADD 1 #define PROF_ADD 1
#define PROF_REPLACE 0 #define PROF_REPLACE 0
#define unconfined(X) ((X)->mode == APPARMOR_UNCONFINED) #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 struct aa_profile *aa_deref_parent(struct aa_profile *p) #define PROFILE_MEDIATES(P, T) ((P)->policy.start[(T)])
/* safe version of POLICY_MEDIATES for full range input */
static inline unsigned int PROFILE_MEDIATES_SAFE(struct aa_profile *profile,
unsigned char class)
{ {
return rcu_dereference_protected(p->parent, if (profile->policy.dfa)
mutex_is_locked(&p->ns->lock)); return aa_dfa_match_len(profile->policy.dfa,
profile->policy.start[0], &class, 1);
return 0;
}
static inline unsigned int PROFILE_MEDIATES_AF(struct aa_profile *profile,
u16 AF) {
unsigned int state = PROFILE_MEDIATES(profile, AA_CLASS_NET);
u16 be_af = cpu_to_be16(AF);
if (!state)
return 0;
return aa_dfa_match_len(profile->policy.dfa, state, (char *) &be_af, 2);
} }
/** /**
...@@ -273,7 +220,7 @@ static inline struct aa_profile *aa_deref_parent(struct aa_profile *p) ...@@ -273,7 +220,7 @@ static inline struct aa_profile *aa_deref_parent(struct aa_profile *p)
static inline struct aa_profile *aa_get_profile(struct aa_profile *p) static inline struct aa_profile *aa_get_profile(struct aa_profile *p)
{ {
if (p) if (p)
kref_get(&(p->count)); kref_get(&(p->label.count));
return p; return p;
} }
...@@ -287,7 +234,7 @@ static inline struct aa_profile *aa_get_profile(struct aa_profile *p) ...@@ -287,7 +234,7 @@ static inline struct aa_profile *aa_get_profile(struct aa_profile *p)
*/ */
static inline struct aa_profile *aa_get_profile_not0(struct aa_profile *p) static inline struct aa_profile *aa_get_profile_not0(struct aa_profile *p)
{ {
if (p && kref_get_not0(&p->count)) if (p && kref_get_not0(&p->label.count))
return p; return p;
return NULL; return NULL;
...@@ -307,31 +254,12 @@ static inline struct aa_profile *aa_get_profile_rcu(struct aa_profile __rcu **p) ...@@ -307,31 +254,12 @@ static inline struct aa_profile *aa_get_profile_rcu(struct aa_profile __rcu **p)
rcu_read_lock(); rcu_read_lock();
do { do {
c = rcu_dereference(*p); c = rcu_dereference(*p);
} while (c && !kref_get_not0(&c->count)); } while (c && !kref_get_not0(&c->label.count));
rcu_read_unlock(); rcu_read_unlock();
return c; return c;
} }
/**
* aa_get_newest_profile - find the newest version of @profile
* @profile: the profile to check for newer versions of
*
* Returns: refcounted newest version of @profile taking into account
* replacement, renames and removals
* return @profile.
*/
static inline struct aa_profile *aa_get_newest_profile(struct aa_profile *p)
{
if (!p)
return NULL;
if (PROFILE_INVALID(p))
return aa_get_profile_rcu(&p->replacedby->profile);
return aa_get_profile(p);
}
/** /**
* aa_put_profile - decrement refcount on profile @p * aa_put_profile - decrement refcount on profile @p
* @p: profile (MAYBE NULL) * @p: profile (MAYBE NULL)
...@@ -339,60 +267,7 @@ static inline struct aa_profile *aa_get_newest_profile(struct aa_profile *p) ...@@ -339,60 +267,7 @@ static inline struct aa_profile *aa_get_newest_profile(struct aa_profile *p)
static inline void aa_put_profile(struct aa_profile *p) static inline void aa_put_profile(struct aa_profile *p)
{ {
if (p) if (p)
kref_put(&p->count, aa_free_profile_kref); kref_put(&p->label.count, aa_label_kref);
}
static inline struct aa_replacedby *aa_get_replacedby(struct aa_replacedby *p)
{
if (p)
kref_get(&(p->count));
return p;
}
static inline void aa_put_replacedby(struct aa_replacedby *p)
{
if (p)
kref_put(&p->count, aa_free_replacedby_kref);
}
/* requires profile list write lock held */
static inline void __aa_update_replacedby(struct aa_profile *orig,
struct aa_profile *new)
{
struct aa_profile *tmp;
tmp = rcu_dereference_protected(orig->replacedby->profile,
mutex_is_locked(&orig->ns->lock));
rcu_assign_pointer(orig->replacedby->profile, aa_get_profile(new));
orig->flags |= PFLAG_INVALID;
aa_put_profile(tmp);
}
/**
* aa_get_namespace - 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_namespace *aa_get_namespace(struct aa_namespace *ns)
{
if (ns)
aa_get_profile(ns->unconfined);
return ns;
}
/**
* aa_put_namespace - 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_namespace(struct aa_namespace *ns)
{
if (ns)
aa_put_profile(ns->unconfined);
} }
static inline int AUDIT_MODE(struct aa_profile *profile) static inline int AUDIT_MODE(struct aa_profile *profile)
...@@ -403,6 +278,7 @@ static inline int AUDIT_MODE(struct aa_profile *profile) ...@@ -403,6 +278,7 @@ static inline int AUDIT_MODE(struct aa_profile *profile)
return profile->audit; return profile->audit;
} }
bool aa_may_manage_policy(int op); bool policy_admin_capable(void);
int aa_may_manage_policy(struct aa_label *label, u32 mask);
#endif /* __AA_POLICY_H */ #endif /* __AA_POLICY_H */
/*
* AppArmor security module
*
* This file contains AppArmor policy definitions.
*
* Copyright (C) 1998-2008 Novell/SUSE
* Copyright 2009-2015 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_NAMESPACE_H
#define __AA_NAMESPACE_H
#include <linux/kref.h>
#include "apparmor.h"
#include "apparmorfs.h"
#include "label.h"
#include "policy.h"
/* 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
* @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;
struct aa_labelset labels;
struct dentry *dents[AAFS_NS_SIZEOF];
};
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);
void aa_free_ns_kref(struct kref *kref);
struct aa_ns *aa_find_ns(struct aa_ns *root, const char *name);
struct aa_ns *aa_findn_ns(struct aa_ns *root, const char *name, size_t n);
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: ns 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);
}
#endif /* AA_NAMESPACE_H */
...@@ -22,6 +22,7 @@ struct aa_load_ent { ...@@ -22,6 +22,7 @@ struct aa_load_ent {
struct aa_profile *new; struct aa_profile *new;
struct aa_profile *old; struct aa_profile *old;
struct aa_profile *rename; struct aa_profile *rename;
const char *ns_name;
}; };
void aa_load_ent_free(struct aa_load_ent *ent); void aa_load_ent_free(struct aa_load_ent *ent);
......
...@@ -18,8 +18,7 @@ ...@@ -18,8 +18,7 @@
#define AA_DO_TEST 1 #define AA_DO_TEST 1
#define AA_ONEXEC 1 #define AA_ONEXEC 1
int aa_getprocattr(struct aa_profile *profile, char **string); int aa_getprocattr(struct aa_label *label, char **string);
int aa_setprocattr_changehat(char *args, size_t size, int test); int aa_setprocattr_changehat(char *args, size_t size, int test);
int aa_setprocattr_changeprofile(char *fqname, bool onexec, int test);
#endif /* __AA_PROCATTR_H */ #endif /* __AA_PROCATTR_H */
...@@ -37,10 +37,10 @@ struct aa_rlimit { ...@@ -37,10 +37,10 @@ struct aa_rlimit {
extern struct aa_fs_entry aa_fs_entry_rlimit[]; extern struct aa_fs_entry aa_fs_entry_rlimit[];
int aa_map_resource(int resource); int aa_map_resource(int resource);
int aa_task_setrlimit(struct aa_profile *profile, struct task_struct *, int aa_task_setrlimit(struct aa_label *label, struct task_struct *,
unsigned int resource, struct rlimit *new_rlim); unsigned int resource, struct rlimit *new_rlim);
void __aa_transition_rlimits(struct aa_profile *old, struct aa_profile *new); void __aa_transition_rlimits(struct aa_label *old, struct aa_label *new);
static inline void aa_free_rlimit_rules(struct aa_rlimit *rlims) static inline void aa_free_rlimit_rules(struct aa_rlimit *rlims)
{ {
......
#include <linux/signal.h>
#define SIGUNKNOWN 0
#define MAXMAPPED_SIG 35
/* 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, /* existance 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,
[SIGSTKFLT] = 16, /* -, 16, - */
[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(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_SIG + 1] = {
"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 existance test mapped to MAXMAPPED_SIG */
};
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
* This file contains AppArmor ipc mediation * This file contains AppArmor ipc mediation
* *
* Copyright (C) 1998-2008 Novell/SUSE * Copyright (C) 1998-2008 Novell/SUSE
* Copyright 2009-2010 Canonical Ltd. * Copyright 2009-2013 Canonical Ltd.
* *
* This program is free software; you can redistribute it and/or * This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as * modify it under the terms of the GNU General Public License as
...@@ -20,92 +20,200 @@ ...@@ -20,92 +20,200 @@
#include "include/context.h" #include "include/context.h"
#include "include/policy.h" #include "include/policy.h"
#include "include/ipc.h" #include "include/ipc.h"
#include "include/sig_names.h"
/**
* audit_ptrace_mask - convert mask to permission string
* @buffer: buffer to write string to (NOT NULL)
* @mask: permission mask to convert
*/
static void audit_ptrace_mask(struct audit_buffer *ab, u32 mask)
{
switch (mask) {
case MAY_READ:
audit_log_string(ab, "read");
break;
case MAY_WRITE:
audit_log_string(ab, "trace");
break;
case AA_MAY_BE_READ:
audit_log_string(ab, "readby");
break;
case AA_MAY_BE_TRACED:
audit_log_string(ab, "tracedby");
break;
}
}
/* call back to audit ptrace fields */ /* call back to audit ptrace fields */
static void audit_cb(struct audit_buffer *ab, void *va) static void audit_ptrace_cb(struct audit_buffer *ab, void *va)
{ {
struct common_audit_data *sa = va; struct common_audit_data *sa = va;
audit_log_format(ab, " target=");
audit_log_untrustedstring(ab, sa->aad->target); if (aad(sa)->request & AA_PTRACE_PERM_MASK) {
audit_log_format(ab, " requested_mask=");
audit_ptrace_mask(ab, aad(sa)->request);
if (aad(sa)->denied & AA_PTRACE_PERM_MASK) {
audit_log_format(ab, " denied_mask=");
audit_ptrace_mask(ab, aad(sa)->denied);
}
}
audit_log_format(ab, " peer=");
aa_label_xaudit(ab, labels_ns(aad(sa)->label), aad(sa)->peer,
FLAGS_NONE, GFP_ATOMIC);
} }
/** /* TODO: conditionals */
* aa_audit_ptrace - do auditing for ptrace static int profile_ptrace_perm(struct aa_profile *profile,
* @profile: profile being enforced (NOT NULL) struct aa_profile *peer, u32 request,
* @target: profile being traced (NOT NULL) struct common_audit_data *sa)
* @error: error condition {
* struct aa_perms perms = { };
* Returns: %0 or error code
*/ /* need because of peer in cross check */
static int aa_audit_ptrace(struct aa_profile *profile, if (profile_unconfined(profile) ||
struct aa_profile *target, int error) !PROFILE_MEDIATES(profile, AA_CLASS_PTRACE))
return 0;
aad(sa)->peer = &peer->label;
aa_profile_match_label(profile, &peer->label, AA_CLASS_PTRACE, request,
&perms);
aa_apply_modes_to_perms(profile, &perms);
return aa_check_perms(profile, &perms, request, sa, audit_ptrace_cb);
}
static int cross_ptrace_perm(struct aa_profile *tracer,
struct aa_profile *tracee, u32 request,
struct common_audit_data *sa)
{ {
struct common_audit_data sa; if (PROFILE_MEDIATES(tracer, AA_CLASS_PTRACE))
struct apparmor_audit_data aad = {0,}; return xcheck(profile_ptrace_perm(tracer, tracee, request, sa),
sa.type = LSM_AUDIT_DATA_NONE; profile_ptrace_perm(tracee, tracer,
sa.aad = &aad; request << PTRACE_PERM_SHIFT,
aad.op = OP_PTRACE; sa));
aad.target = target; /* policy uses the old style capability check for ptrace */
aad.error = error; if (profile_unconfined(tracer) || tracer == tracee)
return 0;
return aa_audit(AUDIT_APPARMOR_AUTO, profile, GFP_ATOMIC, &sa,
audit_cb); aad(sa)->label = &tracer->label;
aad(sa)->peer = &tracee->label;
aad(sa)->request = 0;
aad(sa)->error = aa_capable(&tracer->label, CAP_SYS_PTRACE, 1);
return aa_audit(AUDIT_APPARMOR_AUTO, tracer, sa, audit_ptrace_cb);
} }
/** /**
* aa_may_ptrace - test if tracer task can trace the tracee * aa_may_ptrace - test if tracer task can trace the tracee
* @tracer: profile of the task doing the tracing (NOT NULL) * @tracer: label of the task doing the tracing (NOT NULL)
* @tracee: task to be traced * @tracee: task label to be traced
* @mode: whether PTRACE_MODE_READ || PTRACE_MODE_ATTACH * @request: permission request
* *
* Returns: %0 else error code if permission denied or error * Returns: %0 else error code if permission denied or error
*/ */
int aa_may_ptrace(struct aa_profile *tracer, struct aa_profile *tracee, int aa_may_ptrace(struct aa_label *tracer, struct aa_label *tracee,
unsigned int mode) u32 request)
{ {
/* TODO: currently only based on capability, not extended ptrace DEFINE_AUDIT_DATA(sa, LSM_AUDIT_DATA_NONE, OP_PTRACE);
* rules,
* Test mode for PTRACE_MODE_READ || PTRACE_MODE_ATTACH
*/
if (unconfined(tracer) || tracer == tracee) return xcheck_labels_profiles(tracer, tracee, cross_ptrace_perm,
return 0; request, &sa);
/* log this capability request */ }
return aa_capable(tracer, CAP_SYS_PTRACE, 1);
static inline int map_signal_num(int sig)
{
if (sig > SIGRTMAX)
return SIGUNKNOWN;
else if (sig >= SIGRTMIN)
return sig - SIGRTMIN + 128; /* rt sigs mapped to 128 */
else if (sig <= MAXMAPPED_SIG)
return sig_map[sig];
return SIGUNKNOWN;
} }
/** /**
* aa_ptrace - do ptrace permission check and auditing * audit_file_mask - convert mask to permission string
* @tracer: task doing the tracing (NOT NULL) * @buffer: buffer to write string to (NOT NULL)
* @tracee: task being traced (NOT NULL) * @mask: permission mask to convert
* @mode: ptrace mode either PTRACE_MODE_READ || PTRACE_MODE_ATTACH
*
* Returns: %0 else error code if permission denied or error
*/ */
int aa_ptrace(struct task_struct *tracer, struct task_struct *tracee, static void audit_signal_mask(struct audit_buffer *ab, u32 mask)
unsigned int mode)
{ {
/* if (mask & MAY_READ)
* tracer can ptrace tracee when audit_log_string(ab, "receive");
* - tracer is unconfined || if (mask & MAY_WRITE)
* - tracer is in complain mode audit_log_string(ab, "send");
* - tracer has rules allowing it to trace tracee currently this is: }
* - confined by the same profile ||
* - tracer profile has CAP_SYS_PTRACE /**
* audit_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;
if (aad(sa)->request & AA_SIGNAL_PERM_MASK) {
audit_log_format(ab, " requested_mask=");
audit_signal_mask(ab, aad(sa)->request);
if (aad(sa)->denied & AA_SIGNAL_PERM_MASK) {
audit_log_format(ab, " denied_mask=");
audit_signal_mask(ab, aad(sa)->denied);
}
}
if (aad(sa)->signal <= MAXMAPPED_SIG)
audit_log_format(ab, " signal=%s", sig_names[aad(sa)->signal]);
else
audit_log_format(ab, " signal=rtmin+%d",
aad(sa)->signal - 128);
audit_log_format(ab, " peer=");
aa_label_xaudit(ab, labels_ns(aad(sa)->label), aad(sa)->peer,
FLAGS_NONE, GFP_ATOMIC);
}
/* TODO: update to handle compound name&name2, conditionals */
static void profile_match_signal(struct aa_profile *profile, const char *label,
int signal, struct aa_perms *perms)
{
unsigned int state;
/* TODO: secondary cache check <profile, profile, perm> */
state = aa_dfa_next(profile->policy.dfa,
profile->policy.start[AA_CLASS_SIGNAL],
signal);
state = aa_dfa_match(profile->policy.dfa, state, label);
aa_compute_perms(profile->policy.dfa, state, perms);
}
struct aa_profile *tracer_p = aa_get_task_profile(tracer); static int profile_signal_perm(struct aa_profile *profile,
int error = 0; struct aa_profile *peer, u32 request,
struct common_audit_data *sa)
{
struct aa_perms perms;
if (!unconfined(tracer_p)) { if (profile_unconfined(profile) ||
struct aa_profile *tracee_p = aa_get_task_profile(tracee); !PROFILE_MEDIATES(profile, AA_CLASS_SIGNAL))
return 0;
error = aa_may_ptrace(tracer_p, tracee_p, mode); aad(sa)->peer = &peer->label;
error = aa_audit_ptrace(tracer_p, tracee_p, error); profile_match_signal(profile, aa_peer_name(peer), aad(sa)->signal,
&perms);
aa_apply_modes_to_perms(profile, &perms);
return aa_check_perms(profile, &perms, request, sa, audit_signal_cb);
}
aa_put_profile(tracee_p); static int aa_signal_cross_perm(struct aa_profile *sender,
} struct aa_profile *target,
aa_put_profile(tracer_p); struct common_audit_data *sa)
{
return xcheck(profile_signal_perm(sender, target, MAY_WRITE, sa),
profile_signal_perm(target, sender, MAY_READ, sa));
}
return error; int aa_may_signal(struct aa_label *sender, struct aa_label *target, int sig)
{
DEFINE_AUDIT_DATA(sa, LSM_AUDIT_DATA_NONE, OP_SIGNAL);
aad(&sa)->signal = map_signal_num(sig);
return xcheck_labels_profiles(sender, target, aa_signal_cross_perm,
&sa);
} }
/*
* AppArmor security module
*
* This file contains AppArmor label definitions
*
* Copyright 2013 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/audit.h>
#include <linux/seq_file.h>
#include <linux/sort.h>
#include "include/apparmor.h"
#include "include/context.h"
#include "include/label.h"
#include "include/policy.h"
#include "include/sid.h"
/*
* the aa_label represents the set of profiles confining an object
*
* Labels maintain a reference count to the set of pointers they reference
* Labels are ref counted by
* tasks and object via the security field/security context off the field
* code - will take a ref count on a label if it needs the label
* beyond what is possible with an rcu_read_lock.
* profiles - each profile is a label
* sids - a pinned sid will keep a refcount of the label it is
* referencing
* objects - inode, files, sockets, ...
*
* Labels are not ref counted by the label set, so they maybe removed and
* freed when no longer in use.
*
*/
#define PROXY_POISON 97
#define LABEL_POISON 100
static void free_proxy(struct aa_proxy *proxy)
{
if (proxy) {
/* p->label will not updated any more as p is dead */
aa_put_label(rcu_dereference_protected(proxy->label, true));
memset(proxy, 0, sizeof(*proxy));
proxy->label = (struct aa_label *) PROXY_POISON;
kfree(proxy);
}
}
void aa_proxy_kref(struct kref *kref)
{
struct aa_proxy *proxy = container_of(kref, struct aa_proxy, count);
free_proxy(proxy);
}
struct aa_proxy *aa_alloc_proxy(struct aa_label *label, gfp_t gfp)
{
struct aa_proxy *new;
new = kzalloc(sizeof(struct aa_proxy), gfp);
if (new) {
kref_init(&new->count);
rcu_assign_pointer(new->label, aa_get_label(label));
}
return new;
}
/* requires profile list write lock held */
void __aa_proxy_redirect(struct aa_label *orig, struct aa_label *new)
{
struct aa_label *tmp;
AA_BUG(!orig);
AA_BUG(!new);
AA_BUG(!write_is_locked(&labels_set(orig)->lock));
tmp = rcu_dereference_protected(orig->proxy->label,
&labels_ns(orig)->lock);
rcu_assign_pointer(orig->proxy->label, aa_get_label(new));
orig->flags |= FLAG_STALE;
aa_put_label(tmp);
}
static void __proxy_share(struct aa_label *old, struct aa_label *new)
{
struct aa_proxy *proxy = new->proxy;
new->proxy = aa_get_proxy(old->proxy);
__aa_proxy_redirect(old, new);
aa_put_proxy(proxy);
}
/**
* ns_cmp - compare ns for label set ordering
* @a: ns to compare (NOT NULL)
* @b: ns to compare (NOT NULL)
*
* Returns: <0 if a < b
* ==0 if a == b
* >0 if a > b
*/
static int ns_cmp(struct aa_ns *a, struct aa_ns *b)
{
int res;
AA_BUG(!a);
AA_BUG(!b);
AA_BUG(!a->base.name);
AA_BUG(!b->base.name);
if (a == b)
return 0;
res = a->level - b->level;
if (res)
return res;
return strcmp(a->base.name, b->base.name);
}
/**
* profile_cmp - profile comparision for set ordering
* @a: profile to compare (NOT NULL)
* @b: profile to compare (NOT NULL)
*
* Returns: <0 if a < b
* ==0 if a == b
* >0 if a > b
*/
static int profile_cmp(struct aa_profile *a, struct aa_profile *b)
{
int res;
AA_BUG(!a);
AA_BUG(!b);
AA_BUG(!a->ns);
AA_BUG(!b->ns);
AA_BUG(!a->base.hname);
AA_BUG(!b->base.hname);
if (a == b || a->base.hname == b->base.hname)
return 0;
res = ns_cmp(a->ns, b->ns);
if (res)
return res;
return strcmp(a->base.hname, b->base.hname);
}
/**
* vec_cmp - label comparision for set ordering
* @a: label to compare (NOT NULL)
* @vec: vector of profiles to compare (NOT NULL)
* @n: length of @vec
*
* Returns: <0 if a < vec
* ==0 if a == vec
* >0 if a > vec
*/
static int vec_cmp(struct aa_profile **a, int an, struct aa_profile **b, int bn)
{
int i;
AA_BUG(!a);
AA_BUG(!*a);
AA_BUG(!b);
AA_BUG(!*b);
AA_BUG(an <= 0);
AA_BUG(bn <= 0);
for (i = 0; i < an && i < bn; i++) {
int res = profile_cmp(a[i], b[i]);
if (res != 0)
return res;
}
return an - bn;
}
static bool vec_is_stale(struct aa_profile **vec, int n)
{
int i;
AA_BUG(!vec);
for (i = 0; i < n; i++) {
if (profile_is_stale(vec[i]))
return true;
}
return false;
}
static bool vec_unconfined(struct aa_profile **vec, int n)
{
int i;
AA_BUG(!vec);
for (i = 0; i < n; i++) {
if (!profile_unconfined(vec[i]))
return false;
}
return true;
}
static int sort_cmp(const void *a, const void *b)
{
return profile_cmp(*(struct aa_profile **)a, *(struct aa_profile **)b);
}
/* assumes vec is sorted
* Assumes @vec has null terminator at vec[n], and will null terminate
* vec[n - dups]
*/
static inline int unique(struct aa_profile **vec, int n)
{
int i, pos, dups = 0;
AA_BUG(n < 1);
AA_BUG(!vec);
pos = 0;
for (i = 1; 1 < n; i++) {
int res = profile_cmp(vec[pos], vec[i]);
AA_BUG(res > 0, "vec not sorted");
if (res == 0) {
/* drop duplicate */
aa_put_profile(vec[i]);
dups++;
continue;
}
pos++;
if (dups)
vec[pos] = vec[i];
}
AA_BUG(dups < 0);
return dups;
}
/**
* vec_unique - canonical sort and unique a list of profiles
* @n: number of refcounted profiles in the list (@n > 0)
* @vec: list of profiles to sort and merge
*
* Returns: the number of duplicates eliminated == references put
*
* If @flags & VEC_FLAG_TERMINATE @vec has null terminator at vec[n], and will
* null terminate vec[n - dups]
*/
int aa_vec_unique(struct aa_profile **vec, int n, int flags)
{
int i, dups = 0;
AA_BUG(n < 1);
AA_BUG(!vec);
/* vecs are usually small and inorder, have a fallback for larger */
if (n > 8) {
sort(vec, n, sizeof(struct aa_profile *), sort_cmp, NULL);
dups = unique(vec, n);
goto out;
}
/* insertion sort + unique in one */
for (i = 1; i < n; i++) {
struct aa_profile *tmp = vec[i];
int pos, j;
for (pos = i - 1 - dups; pos >= 0; pos--) {
int res = profile_cmp(vec[pos], tmp);
if (res == 0) {
/* drop duplicate entry */
aa_put_profile(tmp);
dups++;
goto continue_outer;
} else if (res < 0)
break;
}
/* pos is at entry < tmp, or index -1. Set to insert pos */
pos++;
for (j = i - dups; j > pos; j--)
vec[j] = vec[j - 1];
vec[pos] = tmp;
continue_outer: ;
}
AA_BUG(dups < 0);
out:
if (flags & VEC_FLAG_TERMINATE)
vec[n - dups] = NULL;
return dups;
}
static void label_destroy(struct aa_label *label)
{
AA_BUG(!label);
if (label_is_stale(label))
labelsetstats_dec(labels_set(label), stale);
if (!label_isprofile(label)) {
struct aa_profile *profile;
struct label_it i;
aa_put_str(label->hname);
label_for_each(i, label, profile) {
aa_put_profile(profile);
label->vec[i.i] = (struct aa_profile *) (LABEL_POISON + (long) i.i);
}
}
if (rcu_dereference_protected(label->proxy->label, true) == label)
rcu_assign_pointer(label->proxy->label, NULL);
aa_free_sid(label->sid);
aa_put_proxy(label->proxy);
label->proxy = (struct aa_proxy *) PROXY_POISON + 1;
}
void aa_label_free(struct aa_label *label)
{
if (!label)
return;
label_destroy(label);
labelstats_inc(freed);
kfree(label);
}
static void label_free_switch(struct aa_label *label)
{
if (label->flags & FLAG_NS_COUNT)
aa_free_ns(labels_ns(label));
else if (label_isprofile(label))
aa_free_profile(labels_profile(label));
else
aa_label_free(label);
}
static void label_free_rcu(struct rcu_head *head)
{
struct aa_label *label = container_of(head, struct aa_label, rcu);
if (label->flags & FLAG_IN_TREE)
(void) aa_label_remove(label);
label_free_switch(label);
}
void aa_label_kref(struct kref *kref)
{
struct aa_label *label = container_of(kref, struct aa_label, count);
struct aa_ns *ns = labels_ns(label);
if (!ns) {
/* never live, no rcu callback needed, just using the fn */
label_free_switch(label);
return;
}
/* TODO: update labels_profile macro so it works here */
AA_BUG(label_isprofile(label) && on_list_rcu(&label->vec[0]->base.profiles));
AA_BUG(label_isprofile(label) && on_list_rcu(&label->vec[0]->base.list));
/* TODO: if compound label and not stale add to reclaim cache */
call_rcu(&label->rcu, label_free_rcu);
}
bool aa_label_init(struct aa_label *label, int size)
{
AA_BUG(!label);
AA_BUG(size < 1);
label->sid = aa_alloc_sid();
if (label->sid == AA_SID_INVALID)
return false;
label->size = size; /* doesn't include null */
label->vec[size] = NULL; /* null terminate */
kref_init(&label->count);
RB_CLEAR_NODE(&label->node);
return true;
}
/**
* aa_label_alloc - allocate a label with a profile vector of @size length
* @size: size of profile vector in the label
* @proxy: proxy to use OR null if to allocate a new one
* @gfp: memory allocation type
*
* Returns: new label
* else NULL if failed
*/
struct aa_label *aa_label_alloc(int size, struct aa_proxy *proxy, gfp_t gfp)
{
struct aa_label *new;
AA_BUG(size < 1);
/* + 1 for null terminator entry on vec */
new = kzalloc(sizeof(*new) + sizeof(struct aa_profile *) * (size + 1),
gfp);
AA_DEBUG("%s (%p)\n", __func__, new);
if (!new)
goto fail;
if (!aa_label_init(new, size))
goto fail;
if (!proxy) {
proxy = aa_alloc_proxy(new, gfp);
if (!proxy)
goto fail;
} else
aa_get_proxy(proxy);
/* just set new's proxy, don't redirect proxy here if it was passed in*/
new->proxy = proxy;
labelstats_inc(allocated);
return new;
fail:
kfree(new);
labelstats_inc(failed);
return NULL;
}
/**
* label_cmp - label comparision for set ordering
* @a: label to compare (NOT NULL)
* @b: label to compare (NOT NULL)
*
* Returns: <0 if a < b
* ==0 if a == b
* >0 if a > b
*/
static int label_cmp(struct aa_label *a, struct aa_label *b)
{
AA_BUG(!b);
if (a == b)
return 0;
return vec_cmp(a->vec, a->size, b->vec, b->size);
}
/* helper fn for label_for_each_confined */
int aa_label_next_confined(struct aa_label *label, int i)
{
AA_BUG(!label);
AA_BUG(i < 0);
for (; i < label->size; i++) {
if (!profile_unconfined(label->vec[i]))
return i;
}
return i;
}
/**
* aa_label_next_not_in_set - return the next profile of @sub not in @set
* @I: label iterator
* @set: label to test against
* @sub: label to if is subset of @set
*
* Returns: profile in @sub that is not in @set, with iterator set pos after
* else NULL if @sub is a subset of @set
*/
struct aa_profile *__aa_label_next_not_in_set(struct label_it *I,
struct aa_label *set,
struct aa_label *sub)
{
AA_BUG(!set);
AA_BUG(!I);
AA_BUG(I->i < 0);
AA_BUG(I->i > set->size);
AA_BUG(!sub);
AA_BUG(I->j < 0);
AA_BUG(I->j > sub->size);
while (I->j < sub->size && I->i < set->size) {
int res = profile_cmp(sub->vec[I->j], set->vec[I->i]);
if (res == 0) {
(I->j)++;
(I->i)++;
} else if (res > 0)
(I->i)++;
else
return sub->vec[(I->j)++];
}
if (I->j < sub->size)
return sub->vec[(I->j)++];
return NULL;
}
/**
* aa_label_is_subset - test if @sub is a subset of @set
* @set: label to test against
* @sub: label to test if is subset of @set
*
* Returns: true if @sub is subset of @set
* else false
*/
bool aa_label_is_subset(struct aa_label *set, struct aa_label *sub)
{
struct label_it i = { };
AA_BUG(!set);
AA_BUG(!sub);
if (sub == set)
return true;
return __aa_label_next_not_in_set(&i, set, sub) == NULL;
}
/**
* __label_remove - remove @label from the label set
* @l: label to remove
* @new: label to redirect to
*
* Requires: labels_set(@label)->lock write_lock
* Returns: true if the label was in the tree and removed
*/
static bool __label_remove(struct aa_label *label, struct aa_label *new)
{
struct aa_labelset *ls = labels_set(label);
AA_BUG(!ls);
AA_BUG(!label);
AA_BUG(!write_is_locked(&ls->lock));
if (new)
__aa_proxy_redirect(label, new);
if (label_is_stale(label))
labelstats_dec(stale_intree);
else
__label_make_stale(label);
if (label->flags & FLAG_IN_TREE) {
labelsetstats_dec(ls, intree);
rb_erase(&label->node, &ls->root);
label->flags &= ~FLAG_IN_TREE;
return true;
}
return false;
}
/**
* __label_replace - replace @old with @new in label set
* @old: label to remove from label set
* @new: label to replace @old with
*
* Requires: labels_set(@old)->lock write_lock
* valid ref count be held on @new
* Returns: true if @old was in set and replaced by @new
*
* Note: current implementation requires label set be order in such a way
* that @new directly replaces @old position in the set (ie.
* using pointer comparison of the label address would not work)
*/
static bool __label_replace(struct aa_label *old, struct aa_label *new)
{
struct aa_labelset *ls = labels_set(old);
AA_BUG(!ls);
AA_BUG(!old);
AA_BUG(!new);
AA_BUG(!write_is_locked(&ls->lock));
AA_BUG(new->flags & FLAG_IN_TREE);
if (label_is_stale(old))
labelstats_dec(stale_intree);
else
__label_make_stale(old);
if (old->flags & FLAG_IN_TREE) {
rb_replace_node(&old->node, &new->node, &ls->root);
old->flags &= ~FLAG_IN_TREE;
new->flags |= FLAG_IN_TREE;
return true;
}
return false;
}
/**
* __label_insert - attempt to insert @l into a label set
* @ls: set of labels to insert @l into (NOT NULL)
* @label: new label to insert (NOT NULL)
* @replace: whether insertion should replace existing entry that is not stale
*
* Requires: @ls->lock
* caller to hold a valid ref on l
* if @replace is true l has a preallocated proxy associated
* Returns: @l if successful in inserting @l - with additional refcount
* else ref counted equivalent label that is already in the set,
the else condition only happens if @replace is false
*/
static struct aa_label *__label_insert(struct aa_labelset *ls,
struct aa_label *label, bool replace)
{
struct rb_node **new, *parent = NULL;
AA_BUG(!ls);
AA_BUG(!label);
AA_BUG(labels_set(label) != ls);
AA_BUG(!write_is_locked(&ls->lock));
AA_BUG(label->flags & FLAG_IN_TREE);
/* Figure out where to put new node */
new = &ls->root.rb_node;
while (*new) {
struct aa_label *this = rb_entry(*new, struct aa_label, node);
int result = label_cmp(label, this);
parent = *new;
if (result == 0) {
labelsetstats_inc(ls, existing);
/* !aa_get_label_not0 means queued for destruction,
* so replace in place, however the label has
* died before the replacement so do not share
* the proxy
*/
if (!replace && !label_is_stale(this)) {
if (aa_get_label_not0(this))
return this;
} else
__proxy_share(this, label);
AA_BUG(!__label_replace(this, label));
return aa_get_label(label);
} else if (result < 0)
new = &((*new)->rb_left);
else /* (result > 0) */
new = &((*new)->rb_right);
}
/* Add new node and rebalance tree. */
rb_link_node(&label->node, parent, new);
rb_insert_color(&label->node, &ls->root);
label->flags |= FLAG_IN_TREE;
labelsetstats_inc(ls, insert);
labelsetstats_inc(ls, intree);
return aa_get_label(label);
}
/**
* __vec_find - find label that matches @vec in label set
* @vec: vec of profiles to find matching label for (NOT NULL)
* @n: length of @vec
*
* Requires: @vec_labelset(vec) lock held
* caller to hold a valid ref on l
*
* Returns: ref counted @label if matching label is in tree
* ref counted label that is equiv to @l in tree
* else NULL if @vec equiv is not in tree
*/
static struct aa_label *__vec_find(struct aa_profile **vec, int n)
{
struct rb_node *node;
AA_BUG(!vec);
AA_BUG(!*vec);
AA_BUG(n <= 0);
node = vec_labelset(vec, n)->root.rb_node;
while (node) {
struct aa_label *this = rb_entry(node, struct aa_label, node);
int result = vec_cmp(this->vec, this->size, vec, n);
if (result > 0)
node = node->rb_left;
else if (result < 0)
node = node->rb_right;
else
return aa_get_label_not0(this);
}
return NULL;
}
/**
* __label_find - find label @label in label set
* @label: label to find (NOT NULL)
*
* Requires: labels_set(@label)->lock held
* caller to hold a valid ref on l
*
* Returns: ref counted @label if @label is in tree OR
* ref counted label that is equiv to @label in tree
* else NULL if @label or equiv is not in tree
*/
static struct aa_label *__label_find(struct aa_label *label)
{
AA_BUG(!label);
return __vec_find(label->vec, label->size);
}
/**
* aa_label_remove - remove a label from the labelset
* @label: label to remove
*
* Returns: true if @label was removed from the tree
* else @label was not in tree so it could not be removed
*/
bool aa_label_remove(struct aa_label *label)
{
struct aa_labelset *ls = labels_set(label);
unsigned long flags;
bool res;
AA_BUG(!ls);
write_lock_irqsave(&ls->lock, flags);
res = __label_remove(label, ns_unconfined(labels_ns(label)));
write_unlock_irqrestore(&ls->lock, flags);
return res;
}
/**
* aa_label_replace - replace a label @old with a new version @new
* @old: label to replace
* @new: label replacing @old
*
* Returns: true if @old was in tree and replaced
* else @old was not in tree, and @new was not inserted
*/
bool aa_label_replace(struct aa_label *old, struct aa_label *new)
{
unsigned long flags;
bool res;
if (name_is_shared(old, new) && labels_ns(old) == labels_ns(new)) {
write_lock_irqsave(&labels_set(old)->lock, flags);
if (old->proxy != new->proxy) {
__proxy_share(old, new);
} else
__aa_proxy_redirect(old, new);
res = __label_replace(old, new);
write_unlock_irqrestore(&labels_set(old)->lock, flags);
} else {
struct aa_label *l;
struct aa_labelset *ls = labels_set(old);
write_lock_irqsave(&ls->lock, flags);
res = __label_remove(old, new);
if (labels_ns(old) != labels_ns(new)) {
write_unlock_irqrestore(&ls->lock, flags);
ls = labels_set(new);
write_lock_irqsave(&ls->lock, flags);
}
l = __label_insert(ls, new, true);
res = (l == new);
write_unlock_irqrestore(&ls->lock, flags);
aa_put_label(l);
}
return res;
}
/**
* vec_find - find label @l in label set
* @vec: array of profiles to find equiv label for (NOT NULL)
* @n: length of @vec
*
* Returns: refcounted label if @vec equiv is in tree
* else NULL if @vec equiv is not in tree
*/
static struct aa_label *vec_find(struct aa_profile **vec, int n)
{
struct aa_labelset *ls;
struct aa_label *label;
unsigned long flags;
AA_BUG(!vec);
AA_BUG(!*vec);
AA_BUG(n <= 0);
ls = vec_labelset(vec, n);
read_lock_irqsave(&ls->lock, flags);
label = __vec_find(vec, n);
labelstats_inc(sread);
read_unlock_irqrestore(&ls->lock, flags);
return label;
}
/* requires sort and merge done first */
static struct aa_label *vec_create_and_insert_label(struct aa_profile **vec,
int len, gfp_t gfp)
{
struct aa_label *label = NULL;
struct aa_labelset *ls;
unsigned long flags;
struct aa_label *new;
int i;
AA_BUG(!vec);
if (len == 1)
return aa_get_label(&vec[0]->label);
ls = labels_set(&vec[len - 1]->label);
/* TODO: enable when read side is lockless
* check if label exists before taking locks
*/
new = aa_label_alloc(len, NULL, gfp);
if (!new)
return NULL;
for (i = 0; i < len; i++) {
new->vec[i] = aa_get_profile(vec[i]);
}
write_lock_irqsave(&ls->lock, flags);
label = __label_insert(ls, new, false);
write_unlock_irqrestore(&ls->lock, flags);
aa_put_label(new);
return label;
}
struct aa_label *aa_vec_find_or_create_label(struct aa_profile **vec, int len,
gfp_t gfp)
{
struct aa_label *label = vec_find(vec, len);
if (label)
return label;
return vec_create_and_insert_label(vec, len, gfp);
}
/**
* aa_label_find - find label @label in label set
* @label: label to find (NOT NULL)
*
* Requires: caller to hold a valid ref on l
*
* Returns: refcounted @label if @label is in tree
* refcounted label that is equiv to @label in tree
* else NULL if @label or equiv is not in tree
*/
struct aa_label *aa_label_find(struct aa_label *label)
{
AA_BUG(!label);
return vec_find(label->vec, label->size);
}
/**
* aa_label_insert - insert label @label into @ls or return existing label
* @ls - labelset to insert @label into
* @label - label to insert
*
* Requires: caller to hold a valid ref on @label
*
* Returns: ref counted @label if successful in inserting @label
* else ref counted equivalent label that is already in the set
*/
struct aa_label *aa_label_insert(struct aa_labelset *ls, struct aa_label *label)
{
struct aa_label *l;
unsigned long flags;
AA_BUG(!ls);
AA_BUG(!label);
/* check if label exists before taking lock */
if (!label_is_stale(label)) {
read_lock_irqsave(&ls->lock, flags);
l = __label_find(label);
read_unlock_irqrestore(&ls->lock, flags);
labelstats_inc(fread);
if (l)
return l;
}
write_lock_irqsave(&ls->lock, flags);
l = __label_insert(ls, label, false);
write_unlock_irqrestore(&ls->lock, flags);
return l;
}
/**
* aa_label_next_in_merge - find the next profile when merging @a and @b
* @I: label iterator
* @a: label to merge
* @b: label to merge
*
* Returns: next profile
* else null if no more profiles
*/
struct aa_profile *aa_label_next_in_merge(struct label_it *I,
struct aa_label *a,
struct aa_label *b)
{
AA_BUG(!a);
AA_BUG(!b);
AA_BUG(!I);
AA_BUG(I->i < 0);
AA_BUG(I->i > a->size);
AA_BUG(I->j < 0);
AA_BUG(I->j > b->size);
if (I->i < a->size) {
if (I->j < b->size) {
int res = profile_cmp(a->vec[I->i], b->vec[I->j]);
if (res > 0)
return b->vec[(I->j)++];
if (res == 0)
(I->j)++;
}
return a->vec[(I->i)++];
}
if (I->j < b->size)
return b->vec[(I->j)++];
return NULL;
}
/**
* label_merge_cmp - cmp of @a merging with @b against @z for set ordering
* @a: label to merge then compare (NOT NULL)
* @b: label to merge then compare (NOT NULL)
* @z: label to compare merge against (NOT NULL)
*
* Assumes: using the most recent versions of @a, @b, and @z
*
* Returns: <0 if a < b
* ==0 if a == b
* >0 if a > b
*/
static int label_merge_cmp(struct aa_label *a, struct aa_label *b,
struct aa_label *z)
{
struct aa_profile *p = NULL;
struct label_it i = { };
int k;
AA_BUG(!a);
AA_BUG(!b);
AA_BUG(!z);
for (k = 0;
k < z->size && (p = aa_label_next_in_merge(&i, a, b));
k++) {
int res = profile_cmp(p, z->vec[k]);
if (res != 0)
return res;
}
if (p)
return 1;
else if (k < z->size)
return -1;
return 0;
}
#if 0
/**
* label_merge_len - find the length of the merge of @a and @b
* @a: label to merge (NOT NULL)
* @b: label to merge (NOT NULL)
*
* Assumes: using newest versions of labels @a and @b
*
* Returns: length of a label vector for merge of @a and @b
*/
static int label_merge_len(struct aa_label *a, struct aa_label *b)
{
int len = a->size + b->size;
int i, j;
AA_BUG(!a);
AA_BUG(!b);
/* find entries in common and remove from count */
for (i = j = 0; i < a->size && j < b->size; ) {
int res = profile_cmp(a->vec[i], b->vec[j]);
if (res == 0) {
len--;
i++;
j++;
} else if (res < 0)
i++;
else
j++;
}
return len;
}
#endif
/**
* label_merge_insert - create a new label by merging @a and @b
* @new: preallocated label to merge into (NOT NULL)
* @a: label to merge with @b (NOT NULL)
* @b: label to merge with @a (NOT NULL)
*
* Requires: preallocated proxy
*
* Returns: ref counted label either @new if merge is unique
* @a if @b is a subset of @a
* @b if @a is a subset of @b
*
* NOTE: will not use @new if the merge results in @new == @a or @b
*
* Must be used within labelset write lock to avoid racing with
* setting labels stale.
*/
static struct aa_label *label_merge_insert(struct aa_label *new,
struct aa_label *a,
struct aa_label *b)
{
struct aa_label *label;
struct aa_labelset *ls;
struct aa_profile *next;
struct label_it i;
unsigned long flags;
int k = 0, invcount = 0;
bool stale = false;
AA_BUG(!a);
AA_BUG(a->size < 0);
AA_BUG(!b);
AA_BUG(b->size < 0);
AA_BUG(!new);
AA_BUG(new->size < a->size + b->size);
label_for_each_in_merge(i, a, b, next) {
if (profile_is_stale(next)) {
new->vec[k] = aa_get_newest_profile(next);
if (next->label.proxy != new->vec[k]->label.proxy)
invcount++;
k++;
stale = true;
} else
new->vec[k++] = aa_get_profile(next);
}
/* set to actual size which is <= allocated len */
new->size = k;
new->vec[k] = NULL;
if (invcount) {
new->size -= aa_vec_unique(&new->vec[0], new->size,
VEC_FLAG_TERMINATE);
} else if (!stale) {
/* merge could be same as a || b, note: it is not possible
* for new->size == a->size == b->size unless a == b */
if (k == a->size)
return aa_get_label(a);
else if (k == b->size)
return aa_get_label(b);
}
if (vec_unconfined(new->vec, new->size))
new->flags |= FLAG_UNCONFINED;
ls = labels_set(new);
write_lock_irqsave(&ls->lock, flags);
label = __label_insert(labels_set(new), new, false);
write_unlock_irqrestore(&ls->lock, flags);
return label;
}
/**
* labelset_of_merge - find into which labelset a merged label should be inserted
* @a: label to merge and insert
* @b: label to merge and insert
*
* Returns: labelset that the merged label should be inserted into
*/
static struct aa_labelset *labelset_of_merge(struct aa_label *a, struct aa_label *b)
{
struct aa_ns *nsa = labels_ns(a);
struct aa_ns *nsb = labels_ns(b);
if (ns_cmp(nsa, nsb) <= 0)
return &nsa->labels;
return &nsb->labels;
}
/**
* __label_find_merge - find label that is equiv to merge of @a and @b
* @ls: set of labels to search (NOT NULL)
* @a: label to merge with @b (NOT NULL)
* @b: label to merge with @a (NOT NULL)
*
* Requires: ls->lock read_lock held
*
* Returns: ref counted label that is equiv to merge of @a and @b
* else NULL if merge of @a and @b is not in set
*/
static struct aa_label *__label_find_merge(struct aa_labelset *ls,
struct aa_label *a,
struct aa_label *b)
{
struct rb_node *node;
AA_BUG(!ls);
AA_BUG(!a);
AA_BUG(!b);
if (a == b)
return __label_find(a);
node = ls->root.rb_node;
while (node) {
struct aa_label *this = container_of(node, struct aa_label,
node);
int result = label_merge_cmp(a, b, this);
if (result < 0)
node = node->rb_left;
else if (result > 0)
node = node->rb_right;
else
return aa_get_label_not0(this);
}
return NULL;
}
/**
* aa_label_find_merge - find label that is equiv to merge of @a and @b
* @a: label to merge with @b (NOT NULL)
* @b: label to merge with @a (NOT NULL)
*
* Requires: labels be fully constructed with a valid ns
*
* Returns: ref counted label that is equiv to merge of @a and @b
* else NULL if merge of @a and @b is not in set
*/
struct aa_label *aa_label_find_merge(struct aa_label *a, struct aa_label *b)
{
struct aa_labelset *ls;
struct aa_label *label, *ar = NULL, *br = NULL;
unsigned long flags;
AA_BUG(!a);
AA_BUG(!b);
if (label_is_stale(a))
a = ar = aa_get_newest_label(a);
if (label_is_stale(b))
b = br = aa_get_newest_label(b);
ls = labelset_of_merge(a, b);
read_lock_irqsave(&ls->lock, flags);
label = __label_find_merge(ls, a, b);
read_unlock_irqrestore(&ls->lock, flags);
aa_put_label(ar);
aa_put_label(br);
labelsetstats_inc(ls, msread);
return label;
}
/**
* aa_label_merge - attempt to insert new merged label of @a and @b
* @ls: set of labels to insert label into (NOT NULL)
* @a: label to merge with @b (NOT NULL)
* @b: label to merge with @a (NOT NULL)
* @gfp: memory allocation type
*
* Requires: caller to hold valid refs on @a and @b
* labels be fully constructed with a valid ns
*
* Returns: ref counted new label if successful in inserting merge of a & b
* else ref counted equivalent label that is already in the set.
* else NULL if could not create label (-ENOMEM)
*/
struct aa_label *aa_label_merge(struct aa_label *a, struct aa_label *b,
gfp_t gfp)
{
struct aa_label *label = NULL;
AA_BUG(!a);
AA_BUG(!b);
if (a == b)
return aa_get_newest_label(a);
/* TODO: enable when read side is lockless
* check if label exists before taking locks
if (!label_is_stale(a) && !label_is_stale(b))
label = aa_label_find_merge(a, b);
*/
if (!label) {
struct aa_label *new;
a = aa_get_newest_label(a);
b = aa_get_newest_label(b);
/* could use label_merge_len(a, b), but requires double
* comparison for small savings
*/
new = aa_label_alloc(a->size + b->size, NULL, gfp);
if (!new)
goto out;
label = label_merge_insert(new, a, b);
aa_put_label(new);
out:
aa_put_label(a);
aa_put_label(b);
}
return label;
}
static inline bool label_is_visible(struct aa_profile *profile,
struct aa_label *label)
{
return aa_ns_visible(profile->ns, labels_ns(label), true);
}
/* match a profile and its associated ns component if needed
* Assumes visibility test has already been done.
* If a subns profile is not to be matched should be prescreened with
* visibility test.
*/
static inline unsigned int match_component(struct aa_profile *profile,
struct aa_profile *tp,
unsigned int state)
{
const char *ns_name;
if (profile->ns == tp->ns)
return aa_dfa_match(profile->policy.dfa, state, tp->base.hname);
/* try matching with namespace name and then profile */
ns_name = aa_ns_name(profile->ns, tp->ns, true);
state = aa_dfa_match_len(profile->policy.dfa, state, ":", 1);
state = aa_dfa_match(profile->policy.dfa, state, ns_name);
state = aa_dfa_match_len(profile->policy.dfa, state, ":", 1);
return aa_dfa_match(profile->policy.dfa, state, tp->base.hname);
}
/**
* label_component_match - find perms for full compound label
* @profile: profile to find perms for
* @label: label to check access permissions for
* @start: state to start match in
* @subns: whether to do permission checks on components in a subns
* @request: permissions to request
* @perms: perms struct to set
*
* Returns: 0 on success else ERROR
*
* For the label A//&B//&C this does the perm match for A//&B//&C
* @perms should be preinitialized with allperms OR a previous permission
* check to be stacked.
*/
static int label_compound_match(struct aa_profile *profile,
struct aa_label *label,
unsigned int state, bool subns, u32 request,
struct aa_perms *perms)
{
struct aa_profile *tp;
struct label_it i;
/* find first subcomponent that is visible */
label_for_each(i, label, tp) {
if (!aa_ns_visible(profile->ns, tp->ns, subns))
continue;
state = match_component(profile, tp, state);
if (!state)
goto fail;
goto next;
}
/* no component visible */
*perms = allperms;
return 0;
next:
label_for_each_cont(i, label, tp) {
if (!aa_ns_visible(profile->ns, tp->ns, subns))
continue;
state = aa_dfa_match(profile->policy.dfa, state, "//&");
state = match_component(profile, tp, state);
if (!state)
goto fail;
}
aa_compute_perms(profile->policy.dfa, state, perms);
aa_apply_modes_to_perms(profile, perms);
if ((perms->allow & request) != request)
return -EACCES;
return 0;
fail:
*perms = nullperms;
return state;
}
/**
* label_component_match - find perms for all subcomponents of a label
* @profile: profile to find perms for
* @label: label to check access permissions for
* @start: state to start match in
* @subns: whether to do permission checks on components in a subns
* @request: permissions to request
* @perms: an initialized perms struct to add accumulation to
*
* Returns: 0 on success else ERROR
*
* For the label A//&B//&C this does the perm match for each of A and B and C
* @perms should be preinitialized with allperms OR a previous permission
* check to be stacked.
*/
static int label_components_match(struct aa_profile *profile,
struct aa_label *label, unsigned int start,
bool subns, u32 request,
struct aa_perms *perms)
{
struct aa_profile *tp;
struct label_it i;
struct aa_perms tmp;
unsigned int state = 0;
/* find first subcomponent to test */
label_for_each(i, label, tp) {
if (!aa_ns_visible(profile->ns, tp->ns, subns))
continue;
state = match_component(profile, tp, start);
if (!state)
goto fail;
goto next;
}
/* no subcomponents visible - no change in perms */
return 0;
next:
aa_compute_perms(profile->policy.dfa, state, &tmp);
aa_apply_modes_to_perms(profile, &tmp);
aa_perms_accum(perms, &tmp);
label_for_each_cont(i, label, tp) {
if (!aa_ns_visible(profile->ns, tp->ns, subns))
continue;
state = match_component(profile, tp, start);
if (!state)
goto fail;
aa_compute_perms(profile->policy.dfa, state, &tmp);
aa_apply_modes_to_perms(profile, &tmp);
aa_perms_accum(perms, &tmp);
}
if ((perms->allow & request) != request)
return -EACCES;
return 0;
fail:
*perms = nullperms;
return -EACCES;
}
/**
* aa_label_match - do a multi-component label match
* @profile: profile to match against (NOT NULL)
* @label: label to match (NOT NULL)
* @state: state to start in
* @subns: whether to match subns components
* @request: permission request
* @perms: Returns computed perms (NOT NULL)
*
* Returns: the state the match finished in, may be the none matching state
*/
int aa_label_match(struct aa_profile *profile, struct aa_label *label,
unsigned int state, bool subns, u32 request,
struct aa_perms *perms)
{
int error = label_compound_match(profile, label, state, subns, request,
perms);
if (!error)
return error;
*perms = allperms;
return label_components_match(profile, label, state, subns, request,
perms);
}
/**
* aa_update_label_name - update a label to have a stored name
* @ns: ns being viewed from (NOT NULL)
* @label: label to update (NOT NULL)
* @gfp: type of memory allocation
*
* Requires: labels_set(label) not locked in caller
*
* note: only updates the label name if it does not have a name already
* and if it is in the labelset
*/
bool aa_update_label_name(struct aa_ns *ns, struct aa_label *label, gfp_t gfp)
{
struct aa_labelset *ls;
unsigned long flags;
char __counted *name;
bool res = false;
AA_BUG(!ns);
AA_BUG(!label);
if (label->hname || labels_ns(label) != ns)
return res;
if (aa_label_acntsxprint(&name, ns, label, FLAGS_NONE, gfp) == -1)
return res;
ls = labels_set(label);
write_lock_irqsave(&ls->lock, flags);
if (!label->hname && label->flags & FLAG_IN_TREE) {
label->hname = name;
res = true;
} else
aa_put_str(name);
write_unlock_irqrestore(&ls->lock, flags);
return res;
}
/* cached label name is present and visible
* @label->hname only exists if label is namespace hierachical */
static inline bool use_label_hname(struct aa_ns *ns, struct aa_label *label)
{
if (label->hname && labels_ns(label) == ns)
return true;
return false;
}
/* helper macro for snprint routines */
#define update_for_len(total, len, size, str) \
do { \
AA_BUG(len < 0); \
total += len; \
len = min(len, size); \
size -= len; \
str += len; \
} while (0)
/**
* aa_profile_snxprint_profile - print a profile name to a buffer
* @str: buffer to write to. (MAY BE NULL if @size == 0)
* @size: size of buffer
* @ns: namespace profile is being viewed from
* @profile: profile to view (NOT NULL)
* @flags: whether to include the mode string
*
* Returns: size of name written or would be written if larger than
* available buffer
*
* Note: will not print anything if the profile is not visible
*/
int aa_profile_snxprint(char *str, size_t size, struct aa_ns *ns,
struct aa_profile *profile, int flags)
{
const char *ns_name = "";
AA_BUG(!str && size != 0);
AA_BUG(!profile);
if (!ns)
ns = profiles_ns(profile);
if (ns != profile->ns) {
ns_name = aa_ns_name(ns, profile->ns, flags & FLAG_VIEW_SUBNS);
if (ns_name == aa_hidden_ns_name) {
if (flags & FLAG_HIDDEN_UNCONFINED)
return snprintf(str, size, "%s", "unconfined");
return snprintf(str, size, "%s", ns_name);
}
}
if ((flags & FLAG_SHOW_MODE) && profile != profile->ns->unconfined) {
const char *modestr = aa_profile_mode_names[profile->mode];
if (strlen(ns_name))
return snprintf(str, size, ":%s://%s (%s)", ns_name,
profile->base.hname, modestr);
return snprintf(str, size, "%s (%s)", profile->base.hname,
modestr);
}
if (strlen(ns_name))
return snprintf(str, size, ":%s://%s", ns_name,
profile->base.hname);
return snprintf(str, size, "%s", profile->base.hname);
}
static const char *label_modename(struct aa_ns *ns, struct aa_label *label,
int flags)
{
struct aa_profile *profile;
struct label_it i;
const char *modestr = NULL;
int count = 0;
label_for_each(i, label, profile) {
if (aa_ns_visible(ns, profile->ns, flags & FLAG_VIEW_SUBNS)) {
const char *tmp_modestr;
count++;
tmp_modestr = aa_profile_mode_names[profile->mode];
if (!modestr)
modestr = tmp_modestr;
else if (modestr != tmp_modestr)
return "mixed";
}
}
if (count == 0)
return "-";
return modestr;
}
/* if any visible label is not unconfined the display_mode returns true */
static inline bool display_mode(struct aa_ns *ns, struct aa_label *label,
int flags)
{
if ((flags & FLAG_SHOW_MODE)) {
struct aa_profile *profile;
struct label_it i;
label_for_each(i, label, profile) {
if (aa_ns_visible(ns, profile->ns,
flags & FLAG_VIEW_SUBNS) &&
profile != profile->ns->unconfined)
return true;
}
/* only ns->unconfined in set of profiles in ns */
return false;
}
return false;
}
/**
* aa_label_snxprint - print a label name to a string buffer
* @str: buffer to write to. (MAY BE NULL if @size == 0)
* @size: size of buffer
* @ns: namespace profile is being viewed from
* @label: label to view (NOT NULL)
* @flags: whether to include the mode string
*
* Returns: size of name written or would be written if larger than
* available buffer
*
* Note: labels do not have to be strictly hierarchical to the ns as
* objects may be shared across different namespaces and thus
* pickup labeling from each ns. If a particular part of the
* label is not visible it will just be excluded. And if none
* of the label is visible "---" will be used.
*/
int aa_label_snxprint(char *str, size_t size, struct aa_ns *ns,
struct aa_label *label, int flags)
{
struct aa_profile *profile;
struct label_it i;
int count = 0, total = 0;
size_t len;
AA_BUG(!str && size != 0);
AA_BUG(!label);
if (!ns)
ns = labels_ns(label);
label_for_each(i, label, profile) {
if (aa_ns_visible(ns, profile->ns, flags & FLAG_VIEW_SUBNS)) {
if (count > 0) {
len = snprintf(str, size, "//&");
update_for_len(total, len, size, str);
}
len = aa_profile_snxprint(str, size, ns, profile,
flags & FLAG_VIEW_SUBNS);
update_for_len(total, len, size, str);
count++;
}
}
if (count == 0) {
if (flags & FLAG_HIDDEN_UNCONFINED)
return snprintf(str, size, "%s", "unconfined");
return snprintf(str, size, "%s", aa_hidden_ns_name);
}
/* count == 1 && ... is for backwards compat where the mode
* is not displayed for 'unconfined' in the current ns
*/
if (display_mode(ns, label, flags)) {
len = snprintf(str, size, " (%s)",
label_modename(ns, label, flags));
update_for_len(total, len, size, str);
}
return total;
}
#undef update_for_len
/**
* aa_label_asxprint - allocate a string buffer and print label into it
* @strp: Returns - the allocated buffer with the label name. (NOT NULL)
* @ns: namespace profile is being viewed from
* @label: label to view (NOT NULL)
* @flags: flags controlling what label info is printed
* @gfp: kernel memory allocation type
*
* Returns: size of name written or would be written if larger than
* available buffer
*/
int aa_label_asxprint(char **strp, struct aa_ns *ns, struct aa_label *label,
int flags, gfp_t gfp)
{
int size;
AA_BUG(!strp);
AA_BUG(!label);
size = aa_label_snxprint(NULL, 0, ns, label, flags);
if (size < 0)
return size;
*strp = kmalloc(size + 1, gfp);
if (!*strp)
return -ENOMEM;
return aa_label_snxprint(*strp, size + 1, ns, label, flags);
}
/**
* aa_label_acntsxprint - allocate a __counted string buffer and print label
* @strp: buffer to write to. (MAY BE NULL if @size == 0)
* @ns: namespace profile is being viewed from
* @label: label to view (NOT NULL)
* @flags: flags controlling what label info is printed
* @gfp: kernel memory allocation type
*
* Returns: size of name written or would be written if larger than
* available buffer
*/
int aa_label_acntsxprint(char __counted **strp, struct aa_ns *ns,
struct aa_label *label, int flags, gfp_t gfp)
{
int size;
AA_BUG(!strp);
AA_BUG(!label);
size = aa_label_snxprint(NULL, 0, ns, label, flags);
if (size < 0)
return size;
*strp = aa_str_alloc(size + 1, gfp);
if (!*strp)
return -ENOMEM;
return aa_label_snxprint(*strp, size + 1, ns, label, flags);
}
void aa_label_xaudit(struct audit_buffer *ab, struct aa_ns *ns,
struct aa_label *label, int flags, gfp_t gfp)
{
const char *str;
char *name = NULL;
int len;
AA_BUG(!ab);
AA_BUG(!label);
if (!ns)
ns = labels_ns(label);
if (!use_label_hname(ns, label) || display_mode(ns, label, flags)) {
labelstats_inc(audit_name_alloc);
len = aa_label_asxprint(&name, ns, label, flags, gfp);
if (len == -1) {
labelstats_inc(audit_name_fail);
AA_DEBUG("label print error");
return;
}
str = name;
} else {
str = (char *) label->hname;
len = strlen(str);
}
if (audit_string_contains_control(str, len))
audit_log_n_hex(ab, str, len);
else
audit_log_n_string(ab, str, len);
kfree(name);
}
void aa_label_seq_xprint(struct seq_file *f, struct aa_ns *ns,
struct aa_label *label, int flags, gfp_t gfp)
{
AA_BUG(!f);
AA_BUG(!label);
if (!ns)
ns = labels_ns(label);
if (!use_label_hname(ns, label)) {
char *str;
int len;
labelstats_inc(seq_print_name_alloc);
len = aa_label_asxprint(&str, ns, label, flags, gfp);
if (len == -1) {
labelstats_inc(seq_print_name_fail);
AA_DEBUG("label print error");
return;
}
seq_printf(f, "%s", str);
kfree(str);
} else if (display_mode(ns, label, flags))
seq_printf(f, "%s (%s)", label->hname,
label_modename(ns, label, flags));
else
seq_printf(f, "%s", label->hname);
}
void aa_label_xprintk(struct aa_ns *ns, struct aa_label *label, int flags,
gfp_t gfp)
{
AA_BUG(!label);
if (!ns)
ns = labels_ns(label);
if (!use_label_hname(ns, label)) {
char *str;
int len;
labelstats_inc(printk_name_alloc);
len = aa_label_asxprint(&str, ns, label, flags, gfp);
if (len == -1) {
labelstats_inc(printk_name_fail);
AA_DEBUG("label print error");
return;
}
printk("%s", str);
kfree(str);
} else if (display_mode(ns, label, flags))
printk("%s (%s)", label->hname,
label_modename(ns, label, flags));
else
printk("%s", label->hname);
}
void aa_label_audit(struct audit_buffer *ab, struct aa_label *label, gfp_t gfp)
{
struct aa_ns *ns = aa_get_current_ns();
aa_label_xaudit(ab, ns, label, FLAG_VIEW_SUBNS, gfp);
aa_put_ns(ns);
}
void aa_label_seq_print(struct seq_file *f, struct aa_label *label, gfp_t gfp)
{
struct aa_ns *ns = aa_get_current_ns();
aa_label_seq_xprint(f, ns, label, FLAG_VIEW_SUBNS, gfp);
aa_put_ns(ns);
}
void aa_label_printk(struct aa_label *label, gfp_t gfp)
{
struct aa_ns *ns = aa_get_current_ns();
aa_label_xprintk(ns, label, FLAG_VIEW_SUBNS, gfp);
aa_put_ns(ns);
}
static int label_count_str_entries(const char *str)
{
const char *split;
int count = 1;
AA_BUG(!str);
for (split = strstr(str, "//&"); split; split = strstr(str, "//&")) {
count++;
str = split + 3;
}
return count;
}
/**
* aa_label_parse - parse, validate and convert a text string to a label
* @base: base label to use for lookups (NOT NULL)
* @str: null terminated text string (NOT NULL)
* @gfp: allocation type
* @create: true if should create compound labels if they don't exist
* @force_stack: true if should stack even if no leading &
*
* Returns: the matching refcounted label if present
* else ERRPTR
*/
struct aa_label *aa_label_parse(struct aa_label *base, const char *str,
gfp_t gfp, bool create, bool force_stack)
{
DEFINE_VEC(profile, vec);
struct aa_label *label;
int i, len, stack = 0, error;
char *split;
AA_BUG(!base);
AA_BUG(!str);
str = skip_spaces(str);
len = label_count_str_entries(str);
if (*str == '&' || force_stack) {
/* stack on top of base */
stack = base->size;
len += stack;
if (*str == '&')
str++;
}
error = vec_setup(profile, vec, len, gfp);
if (error)
return ERR_PTR(error);
for (i = 0; i < stack; i++)
vec[i] = aa_get_profile(base->vec[i]);
for (split = strstr(str, "//&"), i = stack; split && i < len; i++) {
vec[i] = aa_fqlookupn_profile(base, str, split - str);
if (!vec[i])
goto fail;
str = split + 3;
split = strstr(str, "//&");
}
/* last element doesn't have a split so this should be the case but just to be safe */
if (i < len) {
vec[i] = aa_fqlookupn_profile(base, str, strlen(str));
if (!vec[i])
goto fail;
}
if (len == 1)
/* no need to free vec as len < LOCAL_VEC_ENTRIES */
return &vec[0]->label;
len -= aa_vec_unique(vec, len, VEC_FLAG_TERMINATE);
if (create)
label = aa_vec_find_or_create_label(vec, len, gfp);
else
label = vec_find(vec, len);
if (!label)
goto fail;
out:
/* use adjusted len from after vec_unique, not original */
vec_cleanup(profile, vec, len);
return label;
fail:
label = ERR_PTR(-ENOENT);
goto out;
}
/**
* aa_labelset_destroy - remove all labels from the label set
* @ls: label set to cleanup (NOT NULL)
*
* Labels that are removed from the set may still exist beyond the set
* being destroyed depending on their reference counting
*/
void aa_labelset_destroy(struct aa_labelset *ls)
{
struct rb_node *node;
unsigned long flags;
AA_BUG(!ls);
write_lock_irqsave(&ls->lock, flags);
for (node = rb_first(&ls->root); node; node = rb_first(&ls->root)) {
struct aa_label *this = rb_entry(node, struct aa_label, node);
if (labels_ns(this) != root_ns)
__label_remove(this,
ns_unconfined(labels_ns(this)->parent));
else
__label_remove(this, NULL);
}
write_unlock_irqrestore(&ls->lock, flags);
}
/*
* @ls: labelset to init (NOT NULL)
*/
void aa_labelset_init(struct aa_labelset *ls)
{
AA_BUG(!ls);
rwlock_init(&ls->lock);
ls->root = RB_ROOT;
labelstats_init(&ls);
}
static struct aa_label *labelset_next_stale(struct aa_labelset *ls)
{
struct aa_label *label;
struct rb_node *node;
unsigned long flags;
AA_BUG(!ls);
read_lock_irqsave(&ls->lock, flags);
__labelset_for_each(ls, node) {
label = rb_entry(node, struct aa_label, node);
if ((label_is_stale(label) || vec_is_stale(label->vec, label->size)) &&
aa_get_label_not0(label))
goto out;
}
label = NULL;
out:
read_unlock_irqrestore(&ls->lock, flags);
return label;
}
/**
* __label_update - insert updated version of @label into labelset
* @label - the label to update/repace
*
* Returns: new label that is up to date
* else NULL on failure
*
* Requires: @ns lock be held
*
* Note: worst case is the stale @label does not get updated and has
* to be updated at a later time.
*/
static struct aa_label *__label_update(struct aa_label *label)
{
struct aa_label *new, *tmp;
struct aa_labelset *ls;
struct aa_profile *p;
struct label_it i;
unsigned long flags;
int invcount = 0;
AA_BUG(!label);
AA_BUG(!mutex_is_locked(&labels_ns(label)->lock));
new = aa_label_alloc(label->size, label->proxy, GFP_KERNEL);
if (!new)
return NULL;
/* while holding the ns_lock will stop profile replacement, removal,
* and label updates, label merging and removal can be occuring
*/
ls = labels_set(label);
write_lock_irqsave(&ls->lock, flags);
label_for_each(i, label, p) {
new->vec[i.i] = aa_get_newest_profile(p);
if (&new->vec[i.i]->label.proxy != &p->label.proxy)
invcount++;
}
/* updated stale label by being removed/renamed from labelset */
if (invcount) {
new->size -= aa_vec_unique(&new->vec[0], new->size,
VEC_FLAG_TERMINATE);
if (labels_set(label) != labels_set(new)) {
write_unlock_irqrestore(&ls->lock, flags);
tmp = aa_label_insert(labels_set(new), new);
write_lock_irqsave(&ls->lock, flags);
goto remove;
}
} else
AA_BUG(labels_ns(label) != labels_ns(new));
tmp = __label_insert(labels_set(label), new, true);
remove:
/* ensure label is removed, and redirected correctly */
__label_remove(label, tmp);
write_unlock_irqrestore(&ls->lock, flags);
aa_put_label(new);
return tmp;
}
/**
* __labelset_update - update labels in @ns
* @ns: namespace to update labels in (NOT NULL)
*
* Requires: @ns lock be held
*
* Walk the labelset ensuring that all labels are up to date and valid
* Any label that has a stale component is marked stale and replaced and
* by an updated version.
*
* If failures happen due to memory pressures then stale labels will
* be left in place until the next pass.
*/
static void __labelset_update(struct aa_ns *ns)
{
struct aa_label *label;
AA_BUG(!ns);
AA_BUG(!mutex_is_locked(&ns->lock));
do {
label = labelset_next_stale(&ns->labels);
if (label) {
struct aa_label *l;
l = __label_update(label);
aa_put_label(l);
aa_put_label(label);
}
} while (label);
}
/**
* __aa_labelset_udate_subtree - update all labels with a stale component
* @ns: ns to start update at (NOT NULL)
*
* Requires: @ns lock be held
*
* Invalidates labels based on @p in @ns and any children namespaces.
*/
void __aa_labelset_update_subtree(struct aa_ns *ns)
{
struct aa_ns *child;
AA_BUG(!ns);
AA_BUG(!mutex_is_locked(&ns->lock));
__labelset_update(ns);
list_for_each_entry(child, &ns->sub_ns, base.list) {
mutex_lock(&child->lock);
__aa_labelset_update_subtree(child);
mutex_unlock(&child->lock);
}
}
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
* This file contains basic common functions used in AppArmor * This file contains basic common functions used in AppArmor
* *
* Copyright (C) 1998-2008 Novell/SUSE * Copyright (C) 1998-2008 Novell/SUSE
* Copyright 2009-2010 Canonical Ltd. * Copyright 2009-2013 Canonical Ltd.
* *
* This program is free software; you can redistribute it and/or * This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as * modify it under the terms of the GNU General Public License as
...@@ -12,14 +12,23 @@ ...@@ -12,14 +12,23 @@
* License. * License.
*/ */
#include <linux/ctype.h>
#include <linux/mm.h> #include <linux/mm.h>
#include <linux/slab.h> #include <linux/slab.h>
#include <linux/string.h> #include <linux/string.h>
#include <linux/vmalloc.h> #include <linux/vmalloc.h>
#include "include/audit.h"
#include "include/apparmor.h" #include "include/apparmor.h"
#include "include/audit.h"
#include "include/label.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 };
/** /**
* aa_split_fqname - split a fqname into a profile and namespace name * aa_split_fqname - split a fqname into a profile and namespace name
...@@ -59,6 +68,57 @@ char *aa_split_fqname(char *fqname, char **ns_name) ...@@ -59,6 +68,57 @@ char *aa_split_fqname(char *fqname, char **ns_name)
return name; return name;
} }
/**
* skipn_spaces - Removes leading whitespace from @str.
* @str: The string to be stripped.
*
* Returns a pointer to the first non-whitespace character in @str.
* if all whitespace will return NULL
*/
static 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);
if (!name)
return NULL;
*ns_name = NULL;
*ns_len = 0;
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 * aa_info_message - log a none profile related status message
* @str: message to log * @str: message to log
...@@ -66,11 +126,8 @@ char *aa_split_fqname(char *fqname, char **ns_name) ...@@ -66,11 +126,8 @@ char *aa_split_fqname(char *fqname, char **ns_name)
void aa_info_message(const char *str) void aa_info_message(const char *str)
{ {
if (audit_enabled) { if (audit_enabled) {
struct common_audit_data sa; DEFINE_AUDIT_DATA(sa, LSM_AUDIT_DATA_NONE, NULL);
struct apparmor_audit_data aad = {0,}; aad(&sa)->info = str;
sa.type = LSM_AUDIT_DATA_NONE;
sa.aad = &aad;
aad.info = str;
aa_audit_msg(AUDIT_APPARMOR_STATUS, &sa, NULL); aa_audit_msg(AUDIT_APPARMOR_STATUS, &sa, NULL);
} }
printk(KERN_INFO "AppArmor: %s\n", str); printk(KERN_INFO "AppArmor: %s\n", str);
...@@ -104,3 +161,405 @@ void *__aa_kvmalloc(size_t size, gfp_t flags) ...@@ -104,3 +161,405 @@ void *__aa_kvmalloc(size_t size, gfp_t flags)
} }
return buffer; return buffer;
} }
__counted char *aa_str_alloc(int size, gfp_t gfp)
{
struct counted_str *str;
str = kmalloc(sizeof(struct counted_str) + 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)
* @mask: permission mask to convert
*/
void aa_perm_mask_to_str(char *str, const char *chrs, u32 mask)
{
unsigned int i, perm = 1;
for (i = 0; i < 32; perm <<= 1, i++) {
if (mask & perm)
*str++ = chrs[i];
}
*str = '\0';
}
void aa_audit_perm_names(struct audit_buffer *ab, const char **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 **names, u32 namesmask)
{
char str[33];
audit_log_format(ab, "\"");
if ((mask & chrsmask) && chrs) {
aa_perm_mask_to_str(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_audit_perms_cb - generic callback fn for auditing perms
* @ab: audit buffer (NOT NULL)
* @va: audit struct to audit values of (NOT NULL)
*/
static void aa_audit_perms_cb(struct audit_buffer *ab, void *va)
{
struct common_audit_data *sa = va;
if (aad(sa)->request) {
audit_log_format(ab, " requested_mask=");
aa_audit_perm_mask(ab, aad(sa)->request, aa_file_perm_chrs,
PERMS_CHRS_MASK, aa_file_perm_names,
PERMS_NAMES_MASK);
}
if (aad(sa)->denied) {
audit_log_format(ab, "denied_mask=");
aa_audit_perm_mask(ab, aad(sa)->denied, aa_file_perm_chrs,
PERMS_CHRS_MASK, aa_file_perm_names,
PERMS_NAMES_MASK);
}
audit_log_format(ab, " peer=");
aa_label_xaudit(ab, labels_ns(aad(sa)->label), aad(sa)->peer,
FLAGS_NONE, GFP_ATOMIC);
}
/**
* 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;
/* fall through */
case AUDIT_NOQUIET:
perms->quiet = 0;
break;
case AUDIT_QUIET:
perms->audit = 0;
/* fall through */
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;
/* TODO:
else if (PROMPT_MODE(profile))
perms->prompt = ALL_PERMS_MASK;
*/
}
static u32 map_other(u32 x)
{
return ((x & 0x3) << 8) | /* SETATTR/GETATTR */
((x & 0x1c) << 18) | /* ACCEPT/BIND/LISTEN */
((x & 0x60) << 19); /* SETOPT/GETOPT */
}
void aa_compute_perms(struct aa_dfa *dfa, unsigned int state,
struct aa_perms *perms)
{
perms->deny = 0;
perms->kill = perms->stop = 0;
perms->complain = perms->cond = 0;
perms->hide = 0;
perms->prompt = 0;
perms->allow = dfa_user_allow(dfa, state);
perms->audit = dfa_user_audit(dfa, state);
perms->quiet = dfa_user_quiet(dfa, state);
/* for v5 perm mapping in the policydb, the other set is used
* to extend the general perm set
*/
perms->allow |= map_other(dfa_other_allow(dfa, state));
perms->audit |= map_other(dfa_other_audit(dfa, state));
perms->quiet |= map_other(dfa_other_quiet(dfa, state));
// perms->xindex = dfa_user_xindex(dfa, state);
}
/**
* 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
*/
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->stop |= addend->stop & ~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;
}
/**
* aa_perms_accum - accumulate perms, masking off overlapping perms
* @accum - perms struct to accumulate into
* @addend - perms struct to add to @accum
*/
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->stop |= addend->stop & ~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;
}
void aa_profile_match_label(struct aa_profile *profile, struct aa_label *label,
int type, u32 request, struct aa_perms *perms)
{
/* TODO: doesn't yet handle extended types */
unsigned int state;
state = aa_dfa_next(profile->policy.dfa,
profile->policy.start[AA_CLASS_LABEL],
type);
aa_label_match(profile, label, state, false, request, perms);
}
/* currently unused */
int aa_profile_label_perm(struct aa_profile *profile, struct aa_profile *target,
u32 request, int type, u32 *deny,
struct common_audit_data *sa)
{
struct aa_perms perms;
aad(sa)->label = &profile->label;
aad(sa)->peer = &target->label;
aad(sa)->request = request;
aa_profile_match_label(profile, &target->label, type, request, &perms);
aa_apply_modes_to_perms(profile, &perms);
*deny |= request & perms.deny;
return aa_check_perms(profile, &perms, request, sa, aa_audit_perms_cb);
}
/**
* aa_check_perms - do audit mode selection based on perms set
* @profile: profile being checked
* @perms: perms computed for the request
* @request: requested perms
* @deny: Returns: explicit deny set
* @sa: initialized audit structure (MAY BE NULL if not auditing)
* @cb: callback fn for tpye 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 common_audit_data *sa,
void (*cb) (struct audit_buffer *, void *))
{
int type, error;
bool stop = false;
u32 denied = request & (~perms->allow | perms->deny);
if (likely(!denied)) {
/* mask off perms that are not being force audited */
request &= perms->audit;
if (!request || !sa)
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 & perms->stop)
stop = true;
if (denied == (denied & perms->hide))
error = -ENOENT;
denied &= ~perms->quiet;
if (!sa || !denied)
return error;
}
if (sa) {
aad(sa)->label = &profile->label;
aad(sa)->request = request;
aad(sa)->denied = denied;
aad(sa)->error = error;
aa_audit_msg(type, sa, cb);
}
if (type == AUDIT_APPARMOR_ALLOWED)
error = 0;
return error;
}
const char *aa_imode_name(umode_t mode)
{
switch(mode & S_IFMT) {
case S_IFSOCK:
return "sock";
case S_IFLNK:
return "link";
case S_IFREG:
return "reg";
case S_IFBLK:
return "blkdev";
case S_IFDIR:
return "dir";
case S_IFCHR:
return "chrdev";
case S_IFIFO:
return "fifo";
}
return "unknown";
}
/**
* 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;
/* freed by policy_free */
if (prefix) {
hname = aa_str_alloc(strlen(prefix) + strlen(name) + 3, gfp);
if (hname)
sprintf(hname, "%s//%s", prefix, name);
} else {
hname = aa_str_alloc(strlen(name) + 1, gfp);
if (hname)
strcpy(hname, name);
}
if (!hname)
return 0;
policy->hname = hname;
/* base.name is a substring of fqname */
policy->name = (char *) basename(policy->hname);
INIT_LIST_HEAD(&policy->list);
INIT_LIST_HEAD(&policy->profiles);
return 1;
}
/**
* 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);
}
...@@ -25,6 +25,7 @@ ...@@ -25,6 +25,7 @@
#include <linux/user_namespace.h> #include <linux/user_namespace.h>
#include <net/sock.h> #include <net/sock.h>
#include "include/af_unix.h"
#include "include/apparmor.h" #include "include/apparmor.h"
#include "include/apparmorfs.h" #include "include/apparmorfs.h"
#include "include/audit.h" #include "include/audit.h"
...@@ -32,24 +33,29 @@ ...@@ -32,24 +33,29 @@
#include "include/context.h" #include "include/context.h"
#include "include/file.h" #include "include/file.h"
#include "include/ipc.h" #include "include/ipc.h"
#include "include/net.h"
#include "include/path.h" #include "include/path.h"
#include "include/policy.h" #include "include/policy.h"
#include "include/procattr.h" #include "include/procattr.h"
#include "include/mount.h"
/* Flag indicating whether initialization completed */ /* Flag indicating whether initialization completed */
int apparmor_initialized __initdata; int apparmor_initialized __initdata;
DEFINE_PER_CPU(struct aa_buffers, aa_buffers);
/* /*
* LSM hook functions * LSM hook functions
*/ */
/* /*
* free the associated aa_task_cxt and put its profiles * free the associated aa_task_ctx and put its labels
*/ */
static void apparmor_cred_free(struct cred *cred) static void apparmor_cred_free(struct cred *cred)
{ {
aa_free_task_context(cred_cxt(cred)); aa_free_task_context(cred_ctx(cred));
cred_cxt(cred) = NULL; cred_ctx(cred) = NULL;
} }
/* /*
...@@ -58,27 +64,27 @@ static void apparmor_cred_free(struct cred *cred) ...@@ -58,27 +64,27 @@ static void apparmor_cred_free(struct cred *cred)
static int apparmor_cred_alloc_blank(struct cred *cred, gfp_t gfp) static int apparmor_cred_alloc_blank(struct cred *cred, gfp_t gfp)
{ {
/* freed by apparmor_cred_free */ /* freed by apparmor_cred_free */
struct aa_task_cxt *cxt = aa_alloc_task_context(gfp); struct aa_task_ctx *ctx = aa_alloc_task_context(gfp);
if (!cxt) if (!ctx)
return -ENOMEM; return -ENOMEM;
cred_cxt(cred) = cxt; cred_ctx(cred) = ctx;
return 0; return 0;
} }
/* /*
* prepare new aa_task_cxt for modification by prepare_cred block * prepare new aa_task_ctx for modification by prepare_cred block
*/ */
static int apparmor_cred_prepare(struct cred *new, const struct cred *old, static int apparmor_cred_prepare(struct cred *new, const struct cred *old,
gfp_t gfp) gfp_t gfp)
{ {
/* freed by apparmor_cred_free */ /* freed by apparmor_cred_free */
struct aa_task_cxt *cxt = aa_alloc_task_context(gfp); struct aa_task_ctx *ctx = aa_alloc_task_context(gfp);
if (!cxt) if (!ctx)
return -ENOMEM; return -ENOMEM;
aa_dup_task_context(cxt, cred_cxt(old)); aa_dup_task_context(ctx, cred_ctx(old));
cred_cxt(new) = cxt; cred_ctx(new) = ctx;
return 0; return 0;
} }
...@@ -87,43 +93,71 @@ static int apparmor_cred_prepare(struct cred *new, const struct cred *old, ...@@ -87,43 +93,71 @@ static int apparmor_cred_prepare(struct cred *new, const struct cred *old,
*/ */
static void apparmor_cred_transfer(struct cred *new, const struct cred *old) static void apparmor_cred_transfer(struct cred *new, const struct cred *old)
{ {
const struct aa_task_cxt *old_cxt = cred_cxt(old); const struct aa_task_ctx *old_ctx = cred_ctx(old);
struct aa_task_cxt *new_cxt = cred_cxt(new); struct aa_task_ctx *new_ctx = cred_ctx(new);
aa_dup_task_context(new_cxt, old_cxt); aa_dup_task_context(new_ctx, old_ctx);
} }
static int apparmor_ptrace_access_check(struct task_struct *child, static int apparmor_ptrace_access_check(struct task_struct *child,
unsigned int mode) unsigned int mode)
{ {
return aa_ptrace(current, child, mode); struct aa_label *tracer, *tracee;
int error;
tracer = aa_begin_current_label(DO_UPDATE);
tracee = aa_get_task_label(child);
error = aa_may_ptrace(tracer, tracee,
mode == PTRACE_MODE_READ ? AA_PTRACE_READ : AA_PTRACE_TRACE);
aa_put_label(tracee);
aa_end_current_label(tracer);
return error;
} }
static int apparmor_ptrace_traceme(struct task_struct *parent) static int apparmor_ptrace_traceme(struct task_struct *parent)
{ {
return aa_ptrace(parent, current, PTRACE_MODE_ATTACH); struct aa_label *tracer, *tracee;
int error;
tracee = aa_begin_current_label(DO_UPDATE);
tracer = aa_get_task_label(parent);
error = aa_may_ptrace(tracer, tracee, AA_PTRACE_TRACE);
aa_put_label(tracer);
aa_end_current_label(tracee);
return error;
} }
/* Derived from security/commoncap.c:cap_capget */ /* Derived from security/commoncap.c:cap_capget */
static int apparmor_capget(struct task_struct *target, kernel_cap_t *effective, static int apparmor_capget(struct task_struct *target, kernel_cap_t *effective,
kernel_cap_t *inheritable, kernel_cap_t *permitted) kernel_cap_t *inheritable, kernel_cap_t *permitted)
{ {
struct aa_profile *profile; struct aa_label *label;
const struct cred *cred; const struct cred *cred;
rcu_read_lock(); rcu_read_lock();
cred = __task_cred(target); cred = __task_cred(target);
profile = aa_cred_profile(cred); label = aa_get_newest_cred_label(cred);
/* /*
* cap_capget is stacked ahead of this and will * cap_capget is stacked ahead of this and will
* initialize effective and permitted. * initialize effective and permitted.
*/ */
if (!unconfined(profile) && !COMPLAIN_MODE(profile)) { if (!unconfined(label)) {
*effective = cap_intersect(*effective, profile->caps.allow); struct aa_profile *profile;
*permitted = cap_intersect(*permitted, profile->caps.allow); struct label_it i;
label_for_each_confined(i, label, profile) {
if (COMPLAIN_MODE(profile))
continue;
*effective = cap_intersect(*effective,
profile->caps.allow);
*permitted = cap_intersect(*permitted,
profile->caps.allow);
}
} }
rcu_read_unlock(); rcu_read_unlock();
aa_put_label(label);
return 0; return 0;
} }
...@@ -131,12 +165,14 @@ static int apparmor_capget(struct task_struct *target, kernel_cap_t *effective, ...@@ -131,12 +165,14 @@ static int apparmor_capget(struct task_struct *target, kernel_cap_t *effective,
static int apparmor_capable(const struct cred *cred, struct user_namespace *ns, static int apparmor_capable(const struct cred *cred, struct user_namespace *ns,
int cap, int audit) int cap, int audit)
{ {
struct aa_profile *profile; struct aa_label *label;
int error = 0; int error = 0;
profile = aa_cred_profile(cred); label = aa_get_newest_cred_label(cred);
if (!unconfined(profile)) if (!unconfined(label))
error = aa_capable(profile, cap, audit); error = aa_capable(label, cap, audit);
aa_put_label(label);
return error; return error;
} }
...@@ -149,19 +185,39 @@ static int apparmor_capable(const struct cred *cred, struct user_namespace *ns, ...@@ -149,19 +185,39 @@ static int apparmor_capable(const struct cred *cred, struct user_namespace *ns,
* *
* Returns: %0 else error code if error or permission denied * Returns: %0 else error code if error or permission denied
*/ */
static int common_perm(int op, struct path *path, u32 mask, static int common_perm(const char *op, struct path *path, u32 mask,
struct path_cond *cond) struct path_cond *cond)
{ {
struct aa_profile *profile; struct aa_label *label;
int error = 0; int error = 0;
profile = __aa_current_profile(); label = aa_begin_current_label(NO_UPDATE);
if (!unconfined(profile)) if (!unconfined(label))
error = aa_path_perm(op, profile, path, 0, mask, cond); error = aa_path_perm(op, label, path, 0, mask, cond);
aa_end_current_label(label);
return error; return error;
} }
static int common_perm_cond(const char *op, struct path *path, u32 mask)
{
struct path_cond cond = { d_backing_inode(path->dentry)->i_uid,
d_backing_inode(path->dentry)->i_mode
};
return common_perm(op, path, mask, &cond);
}
static void apparmor_inode_free_security(struct inode *inode)
{
struct aa_label *ctx = inode_ctx(inode);
if (ctx) {
inode_ctx(inode) = NULL;
aa_put_label(ctx);
}
}
/** /**
* common_perm_dir_dentry - common permission wrapper when path is dir, dentry * common_perm_dir_dentry - common permission wrapper when path is dir, dentry
* @op: operation being checked * @op: operation being checked
...@@ -172,7 +228,7 @@ static int common_perm(int op, struct path *path, u32 mask, ...@@ -172,7 +228,7 @@ static int common_perm(int op, struct path *path, u32 mask,
* *
* Returns: %0 else error code if error or permission denied * Returns: %0 else error code if error or permission denied
*/ */
static int common_perm_dir_dentry(int op, struct path *dir, static int common_perm_dir_dentry(const char *op, struct path *dir,
struct dentry *dentry, u32 mask, struct dentry *dentry, u32 mask,
struct path_cond *cond) struct path_cond *cond)
{ {
...@@ -190,15 +246,12 @@ static int common_perm_dir_dentry(int op, struct path *dir, ...@@ -190,15 +246,12 @@ static int common_perm_dir_dentry(int op, struct path *dir,
* *
* Returns: %0 else error code if error or permission denied * Returns: %0 else error code if error or permission denied
*/ */
static int common_perm_mnt_dentry(int op, struct vfsmount *mnt, static int common_perm_mnt_dentry(const char *op, struct vfsmount *mnt,
struct dentry *dentry, u32 mask) struct dentry *dentry, u32 mask)
{ {
struct path path = { mnt, dentry }; struct path path = { mnt, dentry };
struct path_cond cond = { d_backing_inode(dentry)->i_uid,
d_backing_inode(dentry)->i_mode
};
return common_perm(op, &path, mask, &cond); return common_perm_cond(op, &path, mask);
} }
/** /**
...@@ -210,13 +263,13 @@ static int common_perm_mnt_dentry(int op, struct vfsmount *mnt, ...@@ -210,13 +263,13 @@ static int common_perm_mnt_dentry(int op, struct vfsmount *mnt,
* *
* Returns: %0 else error code if error or permission denied * Returns: %0 else error code if error or permission denied
*/ */
static int common_perm_rm(int op, struct path *dir, static int common_perm_rm(const char *op, struct path *dir,
struct dentry *dentry, u32 mask) struct dentry *dentry, u32 mask)
{ {
struct inode *inode = d_backing_inode(dentry); struct inode *inode = d_backing_inode(dentry);
struct path_cond cond = { }; struct path_cond cond = { };
if (!inode || !dir->mnt || !mediated_filesystem(dentry)) if (!inode || !dir->mnt || !path_mediated_fs(dentry))
return 0; return 0;
cond.uid = inode->i_uid; cond.uid = inode->i_uid;
...@@ -235,12 +288,12 @@ static int common_perm_rm(int op, struct path *dir, ...@@ -235,12 +288,12 @@ static int common_perm_rm(int op, struct path *dir,
* *
* Returns: %0 else error code if error or permission denied * Returns: %0 else error code if error or permission denied
*/ */
static int common_perm_create(int op, struct path *dir, struct dentry *dentry, static int common_perm_create(const char *op, struct path *dir, struct dentry *dentry,
u32 mask, umode_t mode) u32 mask, umode_t mode)
{ {
struct path_cond cond = { current_fsuid(), mode }; struct path_cond cond = { current_fsuid(), mode };
if (!dir->mnt || !mediated_filesystem(dir->dentry)) if (!dir->mnt || !path_mediated_fs(dir->dentry))
return 0; return 0;
return common_perm_dir_dentry(op, dir, dentry, mask, &cond); return common_perm_dir_dentry(op, dir, dentry, mask, &cond);
...@@ -271,15 +324,10 @@ static int apparmor_path_mknod(struct path *dir, struct dentry *dentry, ...@@ -271,15 +324,10 @@ static int apparmor_path_mknod(struct path *dir, struct dentry *dentry,
static int apparmor_path_truncate(struct path *path) static int apparmor_path_truncate(struct path *path)
{ {
struct path_cond cond = { d_backing_inode(path->dentry)->i_uid, if (!path->mnt || !path_mediated_fs(path->dentry))
d_backing_inode(path->dentry)->i_mode
};
if (!path->mnt || !mediated_filesystem(path->dentry))
return 0; return 0;
return common_perm(OP_TRUNC, path, MAY_WRITE | AA_MAY_META_WRITE, return common_perm_cond(OP_TRUNC, path, MAY_WRITE | AA_MAY_SETATTR);
&cond);
} }
static int apparmor_path_symlink(struct path *dir, struct dentry *dentry, static int apparmor_path_symlink(struct path *dir, struct dentry *dentry,
...@@ -292,84 +340,84 @@ static int apparmor_path_symlink(struct path *dir, struct dentry *dentry, ...@@ -292,84 +340,84 @@ static int apparmor_path_symlink(struct path *dir, struct dentry *dentry,
static int apparmor_path_link(struct dentry *old_dentry, struct path *new_dir, static int apparmor_path_link(struct dentry *old_dentry, struct path *new_dir,
struct dentry *new_dentry) struct dentry *new_dentry)
{ {
struct aa_profile *profile; struct aa_label *label;
int error = 0; int error = 0;
if (!mediated_filesystem(old_dentry)) if (!path_mediated_fs(old_dentry))
return 0; return 0;
profile = aa_current_profile(); label = aa_begin_current_label(DO_UPDATE);
if (!unconfined(profile)) if (!unconfined(label))
error = aa_path_link(profile, old_dentry, new_dir, new_dentry); error = aa_path_link(label, old_dentry, new_dir, new_dentry);
aa_end_current_label(label);
return error; return error;
} }
static int apparmor_path_rename(struct path *old_dir, struct dentry *old_dentry, static int apparmor_path_rename(struct path *old_dir, struct dentry *old_dentry,
struct path *new_dir, struct dentry *new_dentry) struct path *new_dir, struct dentry *new_dentry)
{ {
struct aa_profile *profile; struct aa_label *label;
int error = 0; int error = 0;
if (!mediated_filesystem(old_dentry)) if (!path_mediated_fs(old_dentry))
return 0; return 0;
profile = aa_current_profile(); label = aa_begin_current_label(DO_UPDATE);
if (!unconfined(profile)) { if (!unconfined(label)) {
struct path old_path = { old_dir->mnt, old_dentry }; struct path old_path = { old_dir->mnt, old_dentry };
struct path new_path = { new_dir->mnt, new_dentry }; struct path new_path = { new_dir->mnt, new_dentry };
struct path_cond cond = { d_backing_inode(old_dentry)->i_uid, struct path_cond cond = { d_backing_inode(old_dentry)->i_uid,
d_backing_inode(old_dentry)->i_mode d_backing_inode(old_dentry)->i_mode
}; };
error = aa_path_perm(OP_RENAME_SRC, profile, &old_path, 0, error = aa_path_perm(OP_RENAME_SRC, label, &old_path, 0,
MAY_READ | AA_MAY_META_READ | MAY_WRITE | MAY_READ | AA_MAY_GETATTR | MAY_WRITE |
AA_MAY_META_WRITE | AA_MAY_DELETE, AA_MAY_SETATTR | AA_MAY_DELETE,
&cond); &cond);
if (!error) if (!error)
error = aa_path_perm(OP_RENAME_DEST, profile, &new_path, error = aa_path_perm(OP_RENAME_DEST, label, &new_path,
0, MAY_WRITE | AA_MAY_META_WRITE | 0, MAY_WRITE | AA_MAY_SETATTR |
AA_MAY_CREATE, &cond); AA_MAY_CREATE, &cond);
} }
aa_end_current_label(label);
return error; return error;
} }
static int apparmor_path_chmod(struct path *path, umode_t mode) static int apparmor_path_chmod(struct path *path, umode_t mode)
{ {
if (!mediated_filesystem(path->dentry)) if (!path_mediated_fs(path->dentry))
return 0; return 0;
return common_perm_mnt_dentry(OP_CHMOD, path->mnt, path->dentry, AA_MAY_CHMOD); return common_perm_cond(OP_CHMOD, path, AA_MAY_CHMOD);
} }
static int apparmor_path_chown(struct path *path, kuid_t uid, kgid_t gid) static int apparmor_path_chown(struct path *path, kuid_t uid, kgid_t gid)
{ {
struct path_cond cond = { d_backing_inode(path->dentry)->i_uid, if (!path_mediated_fs(path->dentry))
d_backing_inode(path->dentry)->i_mode
};
if (!mediated_filesystem(path->dentry))
return 0; return 0;
return common_perm(OP_CHOWN, path, AA_MAY_CHOWN, &cond); return common_perm_cond(OP_CHOWN, path, AA_MAY_CHOWN);
} }
static int apparmor_inode_getattr(const struct path *path) static int apparmor_inode_getattr(const struct path *path)
{ {
if (!mediated_filesystem(path->dentry)) if (!path_mediated_fs(path->dentry))
return 0; return 0;
return common_perm_mnt_dentry(OP_GETATTR, path->mnt, path->dentry, return common_perm_mnt_dentry(OP_GETATTR, path->mnt, path->dentry,
AA_MAY_META_READ); AA_MAY_GETATTR);
} }
static int apparmor_file_open(struct file *file, const struct cred *cred) static int apparmor_file_open(struct file *file, const struct cred *cred)
{ {
struct aa_file_cxt *fcxt = file->f_security; struct aa_file_ctx *fctx = file_ctx(file);
struct aa_profile *profile; struct aa_label *label;
int error = 0; int error = 0;
if (!mediated_filesystem(file->f_path.dentry)) if (!path_mediated_fs(file->f_path.dentry))
return 0; return 0;
/* If in exec, permission is handled by bprm hooks. /* If in exec, permission is handled by bprm hooks.
...@@ -378,69 +426,61 @@ static int apparmor_file_open(struct file *file, const struct cred *cred) ...@@ -378,69 +426,61 @@ static int apparmor_file_open(struct file *file, const struct cred *cred)
* actually execute the image. * actually execute the image.
*/ */
if (current->in_execve) { if (current->in_execve) {
fcxt->allow = MAY_EXEC | MAY_READ | AA_EXEC_MMAP; fctx->allow = MAY_EXEC | MAY_READ | AA_EXEC_MMAP;
return 0; return 0;
} }
profile = aa_cred_profile(cred); label = aa_get_newest_cred_label(cred);
if (!unconfined(profile)) { if (!unconfined(label)) {
struct inode *inode = file_inode(file); struct inode *inode = file_inode(file);
struct path_cond cond = { inode->i_uid, inode->i_mode }; struct path_cond cond = { inode->i_uid, inode->i_mode };
error = aa_path_perm(OP_OPEN, profile, &file->f_path, 0, error = aa_path_perm(OP_OPEN, label, &file->f_path, 0,
aa_map_file_to_perms(file), &cond); aa_map_file_to_perms(file), &cond);
/* todo cache full allowed permissions set and state */ /* todo cache full allowed permissions set and state */
fcxt->allow = aa_map_file_to_perms(file); fctx->allow = aa_map_file_to_perms(file);
} }
aa_put_label(label);
return error; return error;
} }
static int apparmor_file_alloc_security(struct file *file) static int apparmor_file_alloc_security(struct file *file)
{ {
int error = 0;
/* freed by apparmor_file_free_security */ /* freed by apparmor_file_free_security */
file->f_security = aa_alloc_file_context(GFP_KERNEL); struct aa_label *label = aa_begin_current_label(DO_UPDATE);
if (!file->f_security) file->f_security = aa_alloc_file_ctx(label, GFP_KERNEL);
return -ENOMEM; if (!file_ctx(file))
return 0; error = -ENOMEM;
aa_end_current_label(label);
return error;
} }
static void apparmor_file_free_security(struct file *file) static void apparmor_file_free_security(struct file *file)
{ {
struct aa_file_cxt *cxt = file->f_security; aa_free_file_ctx(file_ctx(file));
aa_free_file_context(cxt);
} }
static int common_file_perm(int op, struct file *file, u32 mask) static int common_file_perm(const char *op, struct file *file, u32 mask)
{ {
struct aa_file_cxt *fcxt = file->f_security; struct aa_label *label;
struct aa_profile *profile, *fprofile = aa_cred_profile(file->f_cred);
int error = 0; int error = 0;
BUG_ON(!fprofile); label = aa_begin_current_label(NO_UPDATE);
error = aa_file_perm(op, label, file, mask);
if (!file->f_path.mnt || aa_end_current_label(label);
!mediated_filesystem(file->f_path.dentry))
return 0;
profile = __aa_current_profile();
/* 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(fprofile) is to handle file
* delegation from unconfined tasks
*/
if (!unconfined(profile) && !unconfined(fprofile) &&
((fprofile != profile) || (mask & ~fcxt->allow)))
error = aa_file_perm(op, profile, file, mask);
return error; return error;
} }
static int apparmor_file_receive(struct file *file)
{
return common_file_perm(OP_FRECEIVE, file, aa_map_file_to_perms(file));
}
static int apparmor_file_permission(struct file *file, int mask) static int apparmor_file_permission(struct file *file, int mask)
{ {
return common_file_perm(OP_FPERM, file, mask); return common_file_perm(OP_FPERM, file, mask);
...@@ -456,12 +496,12 @@ static int apparmor_file_lock(struct file *file, unsigned int cmd) ...@@ -456,12 +496,12 @@ static int apparmor_file_lock(struct file *file, unsigned int cmd)
return common_file_perm(OP_FLOCK, file, mask); return common_file_perm(OP_FLOCK, file, mask);
} }
static int common_mmap(int op, struct file *file, unsigned long prot, static int common_mmap(const char *op, struct file *file, unsigned long prot,
unsigned long flags) unsigned long flags)
{ {
int mask = 0; int mask = 0;
if (!file || !file->f_security) if (!file || !file_ctx(file))
return 0; return 0;
if (prot & PROT_READ) if (prot & PROT_READ)
...@@ -491,28 +531,86 @@ static int apparmor_file_mprotect(struct vm_area_struct *vma, ...@@ -491,28 +531,86 @@ static int apparmor_file_mprotect(struct vm_area_struct *vma,
!(vma->vm_flags & VM_SHARED) ? MAP_PRIVATE : 0); !(vma->vm_flags & VM_SHARED) ? MAP_PRIVATE : 0);
} }
static int apparmor_sb_mount(const char *dev_name, struct path *path,
const char *type, unsigned long flags, void *data)
{
struct aa_label *label;
int error = 0;
/* Discard magic */
if ((flags & MS_MGC_MSK) == MS_MGC_VAL)
flags &= ~MS_MGC_MSK;
flags &= ~AA_MS_IGNORE_MASK;
label = aa_begin_current_label(NO_UPDATE);
if (!unconfined(label)) {
if (flags & MS_REMOUNT)
error = aa_remount(label, path, flags, data);
else if (flags & MS_BIND)
error = aa_bind_mount(label, path, dev_name, flags);
else if (flags & (MS_SHARED | MS_PRIVATE | MS_SLAVE |
MS_UNBINDABLE))
error = aa_mount_change_type(label, path, flags);
else if (flags & MS_MOVE)
error = aa_move_mount(label, path, dev_name);
else
error = aa_new_mount(label, dev_name, path, type,
flags, data);
}
aa_end_current_label(label);
return error;
}
static int apparmor_sb_umount(struct vfsmount *mnt, int flags)
{
struct aa_label *label;
int error = 0;
label = aa_begin_current_label(NO_UPDATE);
if (!unconfined(label))
error = aa_umount(label, mnt, flags);
aa_end_current_label(label);
return error;
}
static int apparmor_sb_pivotroot(struct path *old_path, struct path *new_path)
{
struct aa_label *label;
int error = 0;
label = aa_get_current_label();
if (!unconfined(label))
error = aa_pivotroot(label, old_path, new_path);
aa_put_label(label);
return error;
}
static int apparmor_getprocattr(struct task_struct *task, char *name, static int apparmor_getprocattr(struct task_struct *task, char *name,
char **value) char **value)
{ {
int error = -ENOENT; int error = -ENOENT;
/* released below */ /* released below */
const struct cred *cred = get_task_cred(task); const struct cred *cred = get_task_cred(task);
struct aa_task_cxt *cxt = cred_cxt(cred); struct aa_task_ctx *ctx = cred_ctx(cred);
struct aa_profile *profile = NULL; struct aa_label *label = NULL;
if (strcmp(name, "current") == 0) if (strcmp(name, "current") == 0)
profile = aa_get_newest_profile(cxt->profile); label = aa_get_newest_label(ctx->label);
else if (strcmp(name, "prev") == 0 && cxt->previous) else if (strcmp(name, "prev") == 0 && ctx->previous)
profile = aa_get_newest_profile(cxt->previous); label = aa_get_newest_label(ctx->previous);
else if (strcmp(name, "exec") == 0 && cxt->onexec) else if (strcmp(name, "exec") == 0 && ctx->onexec)
profile = aa_get_newest_profile(cxt->onexec); label = aa_get_newest_label(ctx->onexec);
else else
error = -EINVAL; error = -EINVAL;
if (profile) if (label)
error = aa_getprocattr(profile, value); error = aa_getprocattr(label, value);
aa_put_profile(profile); aa_put_label(label);
put_cred(cred); put_cred(cred);
return error; return error;
...@@ -521,11 +619,10 @@ static int apparmor_getprocattr(struct task_struct *task, char *name, ...@@ -521,11 +619,10 @@ static int apparmor_getprocattr(struct task_struct *task, char *name,
static int apparmor_setprocattr(struct task_struct *task, char *name, static int apparmor_setprocattr(struct task_struct *task, char *name,
void *value, size_t size) void *value, size_t size)
{ {
struct common_audit_data sa;
struct apparmor_audit_data aad = {0,};
char *command, *args = value; char *command, *args = value;
size_t arg_size; size_t arg_size;
int error; int error;
DEFINE_AUDIT_DATA(sa, LSM_AUDIT_DATA_NONE, OP_SETPROCATTR);
if (size == 0) if (size == 0)
return -EINVAL; return -EINVAL;
...@@ -561,17 +658,23 @@ static int apparmor_setprocattr(struct task_struct *task, char *name, ...@@ -561,17 +658,23 @@ static int apparmor_setprocattr(struct task_struct *task, char *name,
error = aa_setprocattr_changehat(args, arg_size, error = aa_setprocattr_changehat(args, arg_size,
AA_DO_TEST); AA_DO_TEST);
} else if (strcmp(command, "changeprofile") == 0) { } else if (strcmp(command, "changeprofile") == 0) {
error = aa_setprocattr_changeprofile(args, !AA_ONEXEC, error = aa_change_profile(args, !AA_ONEXEC,
!AA_DO_TEST); !AA_DO_TEST, false);
} else if (strcmp(command, "permprofile") == 0) { } else if (strcmp(command, "permprofile") == 0) {
error = aa_setprocattr_changeprofile(args, !AA_ONEXEC, error = aa_change_profile(args, !AA_ONEXEC, AA_DO_TEST,
AA_DO_TEST); false);
} else if (strcmp(command, "stack") == 0) {
error = aa_change_profile(args, !AA_ONEXEC, !AA_DO_TEST,
true);
} else } else
goto fail; goto fail;
} else if (strcmp(name, "exec") == 0) { } else if (strcmp(name, "exec") == 0) {
if (strcmp(command, "exec") == 0) if (strcmp(command, "exec") == 0)
error = aa_setprocattr_changeprofile(args, AA_ONEXEC, error = aa_change_profile(args, AA_ONEXEC, !AA_DO_TEST,
!AA_DO_TEST); false);
else if (strcmp(command, "stack") == 0)
error = aa_change_profile(args, AA_ONEXEC, !AA_DO_TEST,
true);
else else
goto fail; goto fail;
} else } else
...@@ -583,34 +686,500 @@ static int apparmor_setprocattr(struct task_struct *task, char *name, ...@@ -583,34 +686,500 @@ static int apparmor_setprocattr(struct task_struct *task, char *name,
return error; return error;
fail: fail:
sa.type = LSM_AUDIT_DATA_NONE; aad(&sa)->label = aa_begin_current_label(DO_UPDATE);
sa.aad = &aad; aad(&sa)->info = name;
aad.profile = aa_current_profile(); aad(&sa)->error = -EINVAL;
aad.op = OP_SETPROCATTR;
aad.info = name;
aad.error = -EINVAL;
aa_audit_msg(AUDIT_APPARMOR_DENIED, &sa, NULL); aa_audit_msg(AUDIT_APPARMOR_DENIED, &sa, NULL);
aa_end_current_label(aad(&sa)->label);
return -EINVAL; return -EINVAL;
} }
/**
* apparmor_bprm_committing_creds - do task cleanup on committing new creds
* @bprm: binprm for the exec (NOT NULL)
*/
void apparmor_bprm_committing_creds(struct linux_binprm *bprm)
{
struct aa_label *label = aa_current_raw_label();
struct aa_task_ctx *new_ctx = cred_ctx(bprm->cred);
/* bail out if unconfined or not changing profile */
if ((new_ctx->label->proxy == label->proxy) ||
(unconfined(new_ctx->label)))
return;
aa_inherit_files(bprm->cred, current->files);
current->pdeath_signal = 0;
/* reset soft limits and set hard limits for the new label */
__aa_transition_rlimits(label, new_ctx->label);
}
/**
* apparmor_bprm_commited_cred - do cleanup after new creds committed
* @bprm: binprm for the exec (NOT NULL)
*/
void apparmor_bprm_committed_creds(struct linux_binprm *bprm)
{
/* TODO: cleanup signals - ipc mediation */
return;
}
static int apparmor_task_setrlimit(struct task_struct *task, static int apparmor_task_setrlimit(struct task_struct *task,
unsigned int resource, struct rlimit *new_rlim) unsigned int resource, struct rlimit *new_rlim)
{ {
struct aa_profile *profile = __aa_current_profile(); struct aa_label *label = aa_begin_current_label(NO_UPDATE);
int error = 0;
if (!unconfined(label))
error = aa_task_setrlimit(label, task, resource, new_rlim);
aa_end_current_label(label);
return error;
}
/**
* apparmor_sk_alloc_security - allocate and attach the sk_security field
*/
static int apparmor_sk_alloc_security(struct sock *sk, int family, gfp_t flags)
{
struct aa_sk_ctx *ctx;
ctx = kzalloc(sizeof(*ctx), flags);
if (!ctx)
return -ENOMEM;
SK_CTX(sk) = ctx;
//??? set local too current???
return 0;
}
/**
* apparmor_sk_free_security - free the sk_security field
*/
static void apparmor_sk_free_security(struct sock *sk)
{
struct aa_sk_ctx *ctx = SK_CTX(sk);
SK_CTX(sk) = NULL;
aa_put_label(ctx->label);
aa_put_label(ctx->peer);
path_put(&ctx->path);
kfree(ctx);
}
/**
* apparmor_clone_security - clone the sk_security field
*/
static void apparmor_sk_clone_security(const struct sock *sk,
struct sock *newsk)
{
struct aa_sk_ctx *ctx = SK_CTX(sk);
struct aa_sk_ctx *new = SK_CTX(newsk);
new->label = aa_get_label(ctx->label);
new->peer = aa_get_label(ctx->peer);
new->path = ctx->path;
path_get(&new->path);
}
static struct path *UNIX_FS_CONN_PATH(struct sock *sk, struct sock *newsk)
{
if (sk->sk_family == PF_UNIX && UNIX_FS(sk))
return &unix_sk(sk)->path;
else if (newsk->sk_family == PF_UNIX && UNIX_FS(newsk))
return &unix_sk(newsk)->path;
return NULL;
}
/**
* apparmor_unix_stream_connect - check perms before making unix domain conn
*
* peer is locked when this hook is called
*/
static int apparmor_unix_stream_connect(struct sock *sk, struct sock *peer_sk,
struct sock *newsk)
{
struct aa_sk_ctx *sk_ctx = SK_CTX(sk);
struct aa_sk_ctx *peer_ctx = SK_CTX(peer_sk);
struct aa_sk_ctx *new_ctx = SK_CTX(newsk);
struct aa_label *label;
struct path *path;
int error;
label = aa_begin_current_label(NO_UPDATE);
error = aa_unix_peer_perm(label, OP_CONNECT,
(AA_MAY_CONNECT | AA_MAY_SEND | AA_MAY_RECEIVE),
sk, peer_sk, NULL);
if (!UNIX_FS(peer_sk)) {
last_error(error,
aa_unix_peer_perm(peer_ctx->label, OP_CONNECT,
(AA_MAY_ACCEPT | AA_MAY_SEND | AA_MAY_RECEIVE),
peer_sk, sk, label));
}
aa_end_current_label(label);
if (error)
return error;
/* label newsk if it wasn't labeled in post_create. Normally this
* would be done in sock_graft, but because we are directly looking
* at the peer_sk to obtain peer_labeling for unix socks this
* does not work
*/
if (!new_ctx->label)
new_ctx->label = aa_get_label(peer_ctx->label);
/* Cross reference the peer labels for SO_PEERSEC */
if (new_ctx->peer)
aa_put_label(new_ctx->peer);
if (sk_ctx->peer)
aa_put_label(sk_ctx->peer);
new_ctx->peer = aa_get_label(sk_ctx->label);
sk_ctx->peer = aa_get_label(peer_ctx->label);
path = UNIX_FS_CONN_PATH(sk, peer_sk);
if (path) {
new_ctx->path = *path;
sk_ctx->path = *path;
path_get(path);
path_get(path);
}
return 0;
}
/**
* apparmor_unix_may_send - check perms before conn or sending unix dgrams
*
* other is locked when this hook is called
*
* dgram connect calls may_send, peer setup but path not copied?????
*/
static int apparmor_unix_may_send(struct socket *sock, struct socket *peer)
{
struct aa_sk_ctx *peer_ctx = SK_CTX(peer->sk);
struct aa_label *label = aa_begin_current_label(NO_UPDATE);
int error;
error = xcheck(aa_unix_peer_perm(label, OP_SENDMSG, AA_MAY_SEND,
sock->sk, peer->sk, NULL),
aa_unix_peer_perm(peer_ctx->label, OP_SENDMSG, AA_MAY_RECEIVE,
peer->sk, sock->sk, label));
aa_end_current_label(label);
return error;
}
/**
* apparmor_socket_create - check perms before creating a new socket
*/
static int apparmor_socket_create(int family, int type, int protocol, int kern)
{
struct aa_label *label;
int error = 0; int error = 0;
if (!unconfined(profile)) label = aa_begin_current_label(DO_UPDATE);
error = aa_task_setrlimit(profile, task, resource, new_rlim); if (!(kern || unconfined(label)))
error = aa_sock_create_perm(label, family, type, protocol);
aa_end_current_label(label);
return error;
}
/**
* apparmor_socket_post_create - setup the per-socket security struct
*
* Note:
* - kernel sockets currently labeled unconfined but we may want to
* move to a special kernel label
* - socket may not have sk here if created with sock_create_lite or
* sock_alloc. These should be accept cases which will be handled in
* sock_graft.
*/
static int apparmor_socket_post_create(struct socket *sock, int family,
int type, int protocol, int kern)
{
struct aa_label *label;
if (kern) {
struct aa_ns *ns = aa_get_current_ns();
label = aa_get_label(ns_unconfined(ns));
aa_put_ns(ns);
} else
label = aa_get_current_label();
if (sock->sk) {
struct aa_sk_ctx *ctx = SK_CTX(sock->sk);
aa_put_label(ctx->label);
ctx->label = aa_get_label(label);
}
aa_put_label(label);
return 0;
}
/**
* apparmor_socket_bind - check perms before bind addr to socket
*/
static int apparmor_socket_bind(struct socket *sock,
struct sockaddr *address, int addrlen)
{
return aa_sock_bind_perm(sock, address, addrlen);
}
/**
* apparmor_socket_connect - check perms before connecting @sock to @address
*/
static int apparmor_socket_connect(struct socket *sock,
struct sockaddr *address, int addrlen)
{
return aa_sock_connect_perm(sock, address, addrlen);
}
/**
* apparmor_socket_list - check perms before allowing listen
*/
static int apparmor_socket_listen(struct socket *sock, int backlog)
{
return aa_sock_listen_perm(sock, backlog);
}
/**
* apparmor_socket_accept - check perms before accepting a new connection.
*
* Note: while @newsock is created and has some information, the accept
* has not been done.
*/
static int apparmor_socket_accept(struct socket *sock, struct socket *newsock)
{
return aa_sock_accept_perm(sock, newsock);
}
/**
* apparmor_socket_sendmsg - check perms before sending msg to another socket
*/
static int apparmor_socket_sendmsg(struct socket *sock,
struct msghdr *msg, int size)
{
int error = aa_sock_msg_perm(OP_SENDMSG, AA_MAY_SEND, sock, msg, size);
if (!error) {
/* TODO: setup delegation on scm rights
see smack for AF_INET, AF_INET6 */
;
}
return error; return error;
} }
/**
* apparmor_socket_recvmsg - check perms before receiving a message
*/
static int apparmor_socket_recvmsg(struct socket *sock,
struct msghdr *msg, int size, int flags)
{
return aa_sock_msg_perm(OP_RECVMSG, AA_MAY_RECEIVE, sock, msg, size);
}
/**
* apparmor_socket_getsockname - check perms before getting the local address
*/
static int apparmor_socket_getsockname(struct socket *sock)
{
return aa_sock_perm(OP_GETSOCKNAME, AA_MAY_GETATTR, sock);
}
/**
* apparmor_socket_getpeername - check perms before getting remote address
*/
static int apparmor_socket_getpeername(struct socket *sock)
{
return aa_sock_perm(OP_GETPEERNAME, AA_MAY_GETATTR, sock);
}
/**
* apparmor_getsockopt - check perms before getting socket options
*/
static int apparmor_socket_getsockopt(struct socket *sock, int level,
int optname)
{
return aa_sock_opt_perm(OP_GETSOCKOPT, AA_MAY_GETOPT, sock,
level, optname);
}
/**
* apparmor_setsockopt - check perms before setting socket options
*/
static int apparmor_socket_setsockopt(struct socket *sock, int level,
int optname)
{
return aa_sock_opt_perm(OP_SETSOCKOPT, AA_MAY_SETOPT, sock,
level, optname);
}
/**
* apparmor_socket_shutdown - check perms before shutting down @sock conn
*/
static int apparmor_socket_shutdown(struct socket *sock, int how)
{
return aa_sock_perm(OP_SHUTDOWN, AA_MAY_SHUTDOWN, sock);
}
/**
* apparmor_socket_sock_recv_skb - check perms before associating skb to sk
*
* Note: can not sleep maybe called with locks held
dont want protocol specific in __skb_recv_datagram()
to deny an incoming connection socket_sock_rcv_skb()
*/
static int apparmor_socket_sock_rcv_skb(struct sock *sk, struct sk_buff *skb)
{
/* TODO: */
return 0;
}
static struct aa_label *sk_peer_label(struct sock *sk)
{
struct sock *peer_sk;
struct aa_sk_ctx *ctx = SK_CTX(sk);
if (ctx->peer)
return ctx->peer;
if (sk->sk_family != PF_UNIX)
return ERR_PTR(-ENOPROTOOPT);
/* check for sockpair peering which does not go through
* security_unix_stream_connect
*/
peer_sk = unix_peer(sk);
if (peer_sk) {
ctx = SK_CTX(peer_sk);
if (ctx->label)
return ctx->label;
}
return ERR_PTR(-ENOPROTOOPT);
}
/**
* apparmor_socket_getpeersec_stream - get security context of peer
*
* Note: for tcp only valid if using ipsec or cipso on lan
*/
static int apparmor_socket_getpeersec_stream(struct socket *sock,
char __user *optval,
int __user *optlen, unsigned len)
{
char *name;
int slen, error = 0;
struct aa_label *label = aa_begin_current_label(DO_UPDATE);
struct aa_label *peer = sk_peer_label(sock->sk);
if (IS_ERR(peer))
return PTR_ERR(peer);
slen = aa_label_asxprint(&name, labels_ns(label), peer,
FLAG_SHOW_MODE | FLAG_VIEW_SUBNS |
FLAG_HIDDEN_UNCONFINED, GFP_KERNEL);
/* don't include terminating \0 in slen, it breaks some apps */
if (slen < 0) {
error = -ENOMEM;
} else {
if (slen > len) {
error = -ERANGE;
} else if (copy_to_user(optval, name, slen)) {
error = -EFAULT;
goto out;
}
if (put_user(slen, optlen))
error = -EFAULT;
out:
kfree(name);
}
aa_end_current_label(label);
return error;
}
/**
* apparmor_socket_getpeersec_dgram - get security label of packet
* @sock: the peer socket
* @skb: packet data
* @secid: pointer to where to put the secid of the packet
*
* Sets the netlabel socket state on sk from parent
*/
static int apparmor_socket_getpeersec_dgram(struct socket *sock,
struct sk_buff *skb, u32 *secid)
{
/* TODO: requires secid support, and netlabel */
return -ENOPROTOOPT;
}
/**
* apparmor_sock_graft - Initialize newly created socket
* @sk: child sock
* @parent: parent socket
*
* Note: could set off of SOCK_CTX(parent) but need to track inode and we can
* just set sk security information off of current creating process label
* Labeling of sk for accept case - probably should be sock based
* instead of task, because of the case where an implicitly labeled
* socket is shared by different tasks.
*/
static void apparmor_sock_graft(struct sock *sk, struct socket *parent)
{
struct aa_sk_ctx *ctx = SK_CTX(sk);
if (!ctx->label)
ctx->label = aa_get_current_label();
}
static int apparmor_task_kill(struct task_struct *target, struct siginfo *info,
int sig, u32 secid)
{
struct aa_label *cl, *tl;
int error;
if (secid)
/* TODO: after secid to label mapping is done.
* Dealing with USB IO specific behavior
*/
return 0;
cl = aa_begin_current_label(NO_UPDATE);
tl = aa_get_task_label(target);
error = aa_may_signal(cl, tl, sig);
aa_put_label(tl);
aa_end_current_label(cl);
return error;
}
#ifndef LSM_HOOKS_NAME
#define LSM_HOOKS_NAME(X) //.name = (X),
#endif
static struct security_hook_list apparmor_hooks[] = { static struct security_hook_list apparmor_hooks[] = {
LSM_HOOKS_NAME("apparmor")
LSM_HOOK_INIT(ptrace_access_check, apparmor_ptrace_access_check), LSM_HOOK_INIT(ptrace_access_check, apparmor_ptrace_access_check),
LSM_HOOK_INIT(ptrace_traceme, apparmor_ptrace_traceme), LSM_HOOK_INIT(ptrace_traceme, apparmor_ptrace_traceme),
LSM_HOOK_INIT(capget, apparmor_capget), LSM_HOOK_INIT(capget, apparmor_capget),
LSM_HOOK_INIT(capable, apparmor_capable), LSM_HOOK_INIT(capable, apparmor_capable),
LSM_HOOK_INIT(inode_free_security, apparmor_inode_free_security),
LSM_HOOK_INIT(sb_mount, apparmor_sb_mount),
LSM_HOOK_INIT(sb_umount, apparmor_sb_umount),
LSM_HOOK_INIT(sb_pivotroot, apparmor_sb_pivotroot),
LSM_HOOK_INIT(path_link, apparmor_path_link), LSM_HOOK_INIT(path_link, apparmor_path_link),
LSM_HOOK_INIT(path_unlink, apparmor_path_unlink), LSM_HOOK_INIT(path_unlink, apparmor_path_unlink),
LSM_HOOK_INIT(path_symlink, apparmor_path_symlink), LSM_HOOK_INIT(path_symlink, apparmor_path_symlink),
...@@ -624,16 +1193,43 @@ static struct security_hook_list apparmor_hooks[] = { ...@@ -624,16 +1193,43 @@ static struct security_hook_list apparmor_hooks[] = {
LSM_HOOK_INIT(inode_getattr, apparmor_inode_getattr), LSM_HOOK_INIT(inode_getattr, apparmor_inode_getattr),
LSM_HOOK_INIT(file_open, apparmor_file_open), LSM_HOOK_INIT(file_open, apparmor_file_open),
LSM_HOOK_INIT(file_receive, apparmor_file_receive),
LSM_HOOK_INIT(file_permission, apparmor_file_permission), LSM_HOOK_INIT(file_permission, apparmor_file_permission),
LSM_HOOK_INIT(file_alloc_security, apparmor_file_alloc_security), LSM_HOOK_INIT(file_alloc_security, apparmor_file_alloc_security),
LSM_HOOK_INIT(file_free_security, apparmor_file_free_security), LSM_HOOK_INIT(file_free_security, apparmor_file_free_security),
LSM_HOOK_INIT(mmap_file, apparmor_mmap_file), LSM_HOOK_INIT(mmap_file, apparmor_mmap_file),
LSM_HOOK_INIT(mmap_addr, cap_mmap_addr),
LSM_HOOK_INIT(file_mprotect, apparmor_file_mprotect), LSM_HOOK_INIT(file_mprotect, apparmor_file_mprotect),
LSM_HOOK_INIT(file_lock, apparmor_file_lock), LSM_HOOK_INIT(file_lock, apparmor_file_lock),
LSM_HOOK_INIT(getprocattr, apparmor_getprocattr), LSM_HOOK_INIT(getprocattr, apparmor_getprocattr),
LSM_HOOK_INIT(setprocattr, apparmor_setprocattr), LSM_HOOK_INIT(setprocattr, apparmor_setprocattr),
LSM_HOOK_INIT(sk_alloc_security, apparmor_sk_alloc_security),
LSM_HOOK_INIT(sk_free_security, apparmor_sk_free_security),
LSM_HOOK_INIT(sk_clone_security, apparmor_sk_clone_security),
LSM_HOOK_INIT(unix_stream_connect, apparmor_unix_stream_connect),
LSM_HOOK_INIT(unix_may_send, apparmor_unix_may_send),
LSM_HOOK_INIT(socket_create, apparmor_socket_create),
LSM_HOOK_INIT(socket_post_create, apparmor_socket_post_create),
LSM_HOOK_INIT(socket_bind, apparmor_socket_bind),
LSM_HOOK_INIT(socket_connect, apparmor_socket_connect),
LSM_HOOK_INIT(socket_listen, apparmor_socket_listen),
LSM_HOOK_INIT(socket_accept, apparmor_socket_accept),
LSM_HOOK_INIT(socket_sendmsg, apparmor_socket_sendmsg),
LSM_HOOK_INIT(socket_recvmsg, apparmor_socket_recvmsg),
LSM_HOOK_INIT(socket_getsockname, apparmor_socket_getsockname),
LSM_HOOK_INIT(socket_getpeername, apparmor_socket_getpeername),
LSM_HOOK_INIT(socket_getsockopt, apparmor_socket_getsockopt),
LSM_HOOK_INIT(socket_setsockopt, apparmor_socket_setsockopt),
LSM_HOOK_INIT(socket_shutdown, apparmor_socket_shutdown),
LSM_HOOK_INIT(socket_sock_rcv_skb, apparmor_socket_sock_rcv_skb),
LSM_HOOK_INIT(socket_getpeersec_stream, apparmor_socket_getpeersec_stream),
LSM_HOOK_INIT(socket_getpeersec_dgram, apparmor_socket_getpeersec_dgram),
LSM_HOOK_INIT(sock_graft, apparmor_sock_graft),
LSM_HOOK_INIT(cred_alloc_blank, apparmor_cred_alloc_blank), LSM_HOOK_INIT(cred_alloc_blank, apparmor_cred_alloc_blank),
LSM_HOOK_INIT(cred_free, apparmor_cred_free), LSM_HOOK_INIT(cred_free, apparmor_cred_free),
LSM_HOOK_INIT(cred_prepare, apparmor_cred_prepare), LSM_HOOK_INIT(cred_prepare, apparmor_cred_prepare),
...@@ -645,6 +1241,7 @@ static struct security_hook_list apparmor_hooks[] = { ...@@ -645,6 +1241,7 @@ static struct security_hook_list apparmor_hooks[] = {
LSM_HOOK_INIT(bprm_secureexec, apparmor_bprm_secureexec), LSM_HOOK_INIT(bprm_secureexec, apparmor_bprm_secureexec),
LSM_HOOK_INIT(task_setrlimit, apparmor_task_setrlimit), LSM_HOOK_INIT(task_setrlimit, apparmor_task_setrlimit),
LSM_HOOK_INIT(task_kill, apparmor_task_kill),
}; };
/* /*
...@@ -690,23 +1287,26 @@ static int param_get_mode(char *buffer, struct kernel_param *kp); ...@@ -690,23 +1287,26 @@ static int param_get_mode(char *buffer, struct kernel_param *kp);
/* AppArmor global enforcement switch - complain, enforce, kill */ /* AppArmor global enforcement switch - complain, enforce, kill */
enum profile_mode aa_g_profile_mode = APPARMOR_ENFORCE; enum profile_mode aa_g_profile_mode = APPARMOR_ENFORCE;
module_param_call(mode, param_set_mode, param_get_mode, module_param_call(mode, param_set_mode, param_get_mode,
&aa_g_profile_mode, S_IRUSR | S_IWUSR); &aa_g_profile_mode, S_IRUGO | S_IWUSR);
/* whether policy verification hashing is enabled */
bool aa_g_hash_policy = CONFIG_SECURITY_APPARMOR_HASH_DEFAULT;
module_param_named(hash_policy, aa_g_hash_policy, aabool, S_IRUGO | S_IWUSR);
/* Debug mode */ /* Debug mode */
bool aa_g_debug; bool aa_g_debug;
module_param_named(debug, aa_g_debug, aabool, S_IRUSR | S_IWUSR); module_param_named(debug, aa_g_debug, aabool, S_IRUGO | S_IWUSR);
/* Audit mode */ /* Audit mode */
enum audit_mode aa_g_audit; enum audit_mode aa_g_audit;
module_param_call(audit, param_set_audit, param_get_audit, module_param_call(audit, param_set_audit, param_get_audit, &aa_g_audit,
&aa_g_audit, S_IRUSR | S_IWUSR); S_IRUGO | S_IWUSR);
/* Determines if audit header is included in audited messages. This /* Determines if audit header is included in audited messages. This
* provides more context if the audit daemon is not running * provides more context if the audit daemon is not running
*/ */
bool aa_g_audit_header = 1; bool aa_g_audit_header = 1;
module_param_named(audit_header, aa_g_audit_header, aabool, module_param_named(audit_header, aa_g_audit_header, aabool, S_IRUGO | S_IWUSR);
S_IRUSR | S_IWUSR);
/* lock out loading/removal of policy /* lock out loading/removal of policy
* TODO: add in at boot loading of policy, which is the only way to * TODO: add in at boot loading of policy, which is the only way to
...@@ -714,27 +1314,33 @@ module_param_named(audit_header, aa_g_audit_header, aabool, ...@@ -714,27 +1314,33 @@ module_param_named(audit_header, aa_g_audit_header, aabool,
*/ */
bool aa_g_lock_policy; bool aa_g_lock_policy;
module_param_named(lock_policy, aa_g_lock_policy, aalockpolicy, module_param_named(lock_policy, aa_g_lock_policy, aalockpolicy,
S_IRUSR | S_IWUSR); S_IRUGO | S_IWUSR);
/* Syscall logging mode */ /* Syscall logging mode */
bool aa_g_logsyscall; bool aa_g_logsyscall;
module_param_named(logsyscall, aa_g_logsyscall, aabool, S_IRUSR | S_IWUSR); module_param_named(logsyscall, aa_g_logsyscall, aabool, S_IRUGO | S_IWUSR);
/* Maximum pathname length before accesses will start getting rejected */ /* Maximum pathname length before accesses will start getting rejected */
unsigned int aa_g_path_max = 2 * PATH_MAX; unsigned int aa_g_path_max = 2 * PATH_MAX;
module_param_named(path_max, aa_g_path_max, aauint, S_IRUSR | S_IWUSR); module_param_named(path_max, aa_g_path_max, aauint, S_IRUGO | S_IWUSR);
/* Determines how paranoid loading of policy is and how much verification /* Determines how paranoid loading of policy is and how much verification
* on the loaded policy is done. * on the loaded policy is done.
* DEPRECATED: read only as strict checking of load is always done now
* that none root users (user namespaces) can load policy.
*/ */
bool aa_g_paranoid_load = 1; bool aa_g_paranoid_load = 1;
module_param_named(paranoid_load, aa_g_paranoid_load, aabool, module_param_named(paranoid_load, aa_g_paranoid_load, aabool, S_IRUGO);
S_IRUSR | S_IWUSR);
/* Boot time disable flag */ /* Boot time disable flag */
static bool apparmor_enabled = CONFIG_SECURITY_APPARMOR_BOOTPARAM_VALUE; static bool apparmor_enabled = CONFIG_SECURITY_APPARMOR_BOOTPARAM_VALUE;
module_param_named(enabled, apparmor_enabled, bool, S_IRUGO); module_param_named(enabled, apparmor_enabled, bool, S_IRUGO);
/* Boot time to set use of default or unconfined as initial profile */
bool aa_g_unconfined_init = CONFIG_SECURITY_APPARMOR_UNCONFINED_INIT;
module_param_named(unconfined, aa_g_unconfined_init, aabool, S_IRUGO);
static int __init apparmor_enabled_setup(char *str) static int __init apparmor_enabled_setup(char *str)
{ {
unsigned long enabled; unsigned long enabled;
...@@ -749,7 +1355,7 @@ __setup("apparmor=", apparmor_enabled_setup); ...@@ -749,7 +1355,7 @@ __setup("apparmor=", apparmor_enabled_setup);
/* set global flag turning off the ability to load policy */ /* set global flag turning off the ability to load policy */
static int param_set_aalockpolicy(const char *val, const struct kernel_param *kp) static int param_set_aalockpolicy(const char *val, const struct kernel_param *kp)
{ {
if (!capable(CAP_MAC_ADMIN)) if (!policy_admin_capable())
return -EPERM; return -EPERM;
if (aa_g_lock_policy) if (aa_g_lock_policy)
return -EACCES; return -EACCES;
...@@ -758,59 +1364,65 @@ static int param_set_aalockpolicy(const char *val, const struct kernel_param *kp ...@@ -758,59 +1364,65 @@ static int param_set_aalockpolicy(const char *val, const struct kernel_param *kp
static int param_get_aalockpolicy(char *buffer, const struct kernel_param *kp) static int param_get_aalockpolicy(char *buffer, const struct kernel_param *kp)
{ {
if (!capable(CAP_MAC_ADMIN)) if (!policy_admin_capable())
return -EPERM; return -EPERM;
if (!apparmor_enabled)
return -EINVAL;
return param_get_bool(buffer, kp); return param_get_bool(buffer, kp);
} }
static int param_set_aabool(const char *val, const struct kernel_param *kp) static int param_set_aabool(const char *val, const struct kernel_param *kp)
{ {
if (!capable(CAP_MAC_ADMIN)) if (!policy_admin_capable())
return -EPERM; return -EPERM;
if (!apparmor_enabled)
return -EINVAL;
return param_set_bool(val, kp); return param_set_bool(val, kp);
} }
static int param_get_aabool(char *buffer, const struct kernel_param *kp) static int param_get_aabool(char *buffer, const struct kernel_param *kp)
{ {
if (!capable(CAP_MAC_ADMIN)) if (!policy_admin_capable())
return -EPERM; return -EPERM;
if (!apparmor_enabled)
return -EINVAL;
return param_get_bool(buffer, kp); return param_get_bool(buffer, kp);
} }
static int param_set_aauint(const char *val, const struct kernel_param *kp) static int param_set_aauint(const char *val, const struct kernel_param *kp)
{ {
if (!capable(CAP_MAC_ADMIN)) if (!policy_admin_capable())
return -EPERM; return -EPERM;
if (!apparmor_enabled)
return -EINVAL;
return param_set_uint(val, kp); return param_set_uint(val, kp);
} }
static int param_get_aauint(char *buffer, const struct kernel_param *kp) static int param_get_aauint(char *buffer, const struct kernel_param *kp)
{ {
if (!capable(CAP_MAC_ADMIN)) if (!policy_admin_capable())
return -EPERM; return -EPERM;
if (!apparmor_enabled)
return -EINVAL;
return param_get_uint(buffer, kp); return param_get_uint(buffer, kp);
} }
static int param_get_audit(char *buffer, struct kernel_param *kp) static int param_get_audit(char *buffer, struct kernel_param *kp)
{ {
if (!capable(CAP_MAC_ADMIN)) if (!policy_admin_capable())
return -EPERM; return -EPERM;
if (!apparmor_enabled) if (!apparmor_enabled)
return -EINVAL; return -EINVAL;
return sprintf(buffer, "%s", audit_mode_names[aa_g_audit]); return sprintf(buffer, "%s", audit_mode_names[aa_g_audit]);
} }
static int param_set_audit(const char *val, struct kernel_param *kp) static int param_set_audit(const char *val, struct kernel_param *kp)
{ {
int i; int i;
if (!capable(CAP_MAC_ADMIN)) if (!policy_admin_capable())
return -EPERM; return -EPERM;
if (!apparmor_enabled) if (!apparmor_enabled)
return -EINVAL; return -EINVAL;
if (!val) if (!val)
return -EINVAL; return -EINVAL;
...@@ -826,9 +1438,8 @@ static int param_set_audit(const char *val, struct kernel_param *kp) ...@@ -826,9 +1438,8 @@ static int param_set_audit(const char *val, struct kernel_param *kp)
static int param_get_mode(char *buffer, struct kernel_param *kp) static int param_get_mode(char *buffer, struct kernel_param *kp)
{ {
if (!capable(CAP_MAC_ADMIN)) if (!policy_admin_capable())
return -EPERM; return -EPERM;
if (!apparmor_enabled) if (!apparmor_enabled)
return -EINVAL; return -EINVAL;
...@@ -838,12 +1449,10 @@ static int param_get_mode(char *buffer, struct kernel_param *kp) ...@@ -838,12 +1449,10 @@ static int param_get_mode(char *buffer, struct kernel_param *kp)
static int param_set_mode(const char *val, struct kernel_param *kp) static int param_set_mode(const char *val, struct kernel_param *kp)
{ {
int i; int i;
if (!capable(CAP_MAC_ADMIN)) if (!policy_admin_capable())
return -EPERM; return -EPERM;
if (!apparmor_enabled) if (!apparmor_enabled)
return -EINVAL; return -EINVAL;
if (!val) if (!val)
return -EINVAL; return -EINVAL;
...@@ -862,21 +1471,63 @@ static int param_set_mode(const char *val, struct kernel_param *kp) ...@@ -862,21 +1471,63 @@ static int param_set_mode(const char *val, struct kernel_param *kp)
*/ */
/** /**
* set_init_cxt - set a task context and profile on the first task. * set_init_ctx - set a task context and profile on the first task.
*
* TODO: allow setting an alternate profile than unconfined
*/ */
static int __init set_init_cxt(void) static int __init set_init_ctx(void)
{ {
struct cred *cred = (struct cred *)current->real_cred; struct cred *cred = (struct cred *)current->real_cred;
struct aa_task_cxt *cxt; struct aa_task_ctx *ctx;
ctx = aa_alloc_task_context(GFP_KERNEL);
if (!ctx)
return -ENOMEM;
cxt = aa_alloc_task_context(GFP_KERNEL); if (!aa_g_unconfined_init) {
if (!cxt) ctx->label = aa_setup_default_label();
if (!ctx->label) {
aa_free_task_context(ctx);
return -ENOMEM; return -ENOMEM;
}
/* fs setup of default is done in aa_create_aafs() */
} else
ctx->label = aa_get_label(ns_unconfined(root_ns));
cred_ctx(cred) = ctx;
return 0;
}
cxt->profile = aa_get_profile(root_ns->unconfined); static void destroy_buffers(void)
cred_cxt(cred) = cxt; {
u32 i, j;
for_each_possible_cpu(i) {
for_each_cpu_buffer(j) {
kfree(per_cpu(aa_buffers, i).buf[j]);
per_cpu(aa_buffers, i).buf[j] = NULL;
}
}
}
static int __init alloc_buffers(void)
{
u32 i, j;
for_each_possible_cpu(i) {
for_each_cpu_buffer(j) {
char *buffer;
if (cpu_to_node(i) > num_online_nodes())
/* fallback to kmalloc for offline nodes */
buffer = kmalloc(aa_g_path_max, GFP_KERNEL);
else
buffer = kmalloc_node(aa_g_path_max, GFP_KERNEL,
cpu_to_node(i));
if (!buffer) {
destroy_buffers();
return -ENOMEM;
}
per_cpu(aa_buffers, i).buf[j] = buffer;
}
}
return 0; return 0;
} }
...@@ -891,17 +1542,29 @@ static int __init apparmor_init(void) ...@@ -891,17 +1542,29 @@ static int __init apparmor_init(void)
return 0; return 0;
} }
error = aa_setup_dfa_engine();
if (error) {
AA_ERROR("Unable to setup dfa engine\n");
goto alloc_out;
}
error = aa_alloc_root_ns(); error = aa_alloc_root_ns();
if (error) { if (error) {
AA_ERROR("Unable to allocate default profile namespace\n"); AA_ERROR("Unable to allocate default profile namespace\n");
goto alloc_out; goto alloc_out;
} }
error = set_init_cxt(); error = alloc_buffers();
if (error) {
AA_ERROR("Unable to allocate work buffers\n");
goto buffers_out;
}
error = set_init_ctx();
if (error) { if (error) {
AA_ERROR("Failed to set context on init task\n"); AA_ERROR("Failed to set context on init task\n");
aa_free_root_ns(); aa_free_root_ns();
goto alloc_out; goto buffers_out;
} }
security_add_hooks(apparmor_hooks, ARRAY_SIZE(apparmor_hooks)); security_add_hooks(apparmor_hooks, ARRAY_SIZE(apparmor_hooks));
...@@ -916,8 +1579,12 @@ static int __init apparmor_init(void) ...@@ -916,8 +1579,12 @@ static int __init apparmor_init(void)
return error; return error;
buffers_out:
destroy_buffers();
alloc_out: alloc_out:
aa_destroy_aafs(); aa_destroy_aafs();
aa_teardown_dfa_engine();
apparmor_enabled = 0; apparmor_enabled = 0;
return error; return error;
......
...@@ -20,11 +20,38 @@ ...@@ -20,11 +20,38 @@
#include <linux/err.h> #include <linux/err.h>
#include <linux/kref.h> #include <linux/kref.h>
#include "include/apparmor.h" #include "include/lib.h"
#include "include/match.h" #include "include/match.h"
#define base_idx(X) ((X) & 0xffffff) #define base_idx(X) ((X) & 0xffffff)
static char nulldfa_src[] = {
#include "nulldfa.in"
};
struct aa_dfa *nulldfa;
int aa_setup_dfa_engine(void)
{
int error;
nulldfa = aa_dfa_unpack(nulldfa_src, sizeof(nulldfa_src),
TO_ACCEPT1_FLAG(YYTD_DATA32) |
TO_ACCEPT2_FLAG(YYTD_DATA32));
if (!IS_ERR(nulldfa))
return 0;
error = PTR_ERR(nulldfa);
nulldfa = NULL;
return error;
}
void aa_teardown_dfa_engine(void)
{
aa_put_dfa(nulldfa);
nulldfa = NULL;
}
/** /**
* unpack_table - unpack a dfa table (one of accept, default, base, next check) * unpack_table - unpack a dfa table (one of accept, default, base, next check)
* @blob: data to unpack (NOT NULL) * @blob: data to unpack (NOT NULL)
......
/*
* AppArmor security module
*
* This file contains AppArmor mediation of files
*
* Copyright (C) 1998-2008 Novell/SUSE
* Copyright 2009-2012 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 <linux/mount.h>
#include <linux/namei.h>
#include "include/apparmor.h"
#include "include/audit.h"
#include "include/context.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_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;
if (aad(sa)->mnt.type) {
audit_log_format(ab, " fstype=");
audit_log_untrustedstring(ab, aad(sa)->mnt.type);
}
if (aad(sa)->mnt.src_name) {
audit_log_format(ab, " srcname=");
audit_log_untrustedstring(ab, aad(sa)->mnt.src_name);
}
if (aad(sa)->mnt.trans) {
audit_log_format(ab, " trans=");
audit_log_untrustedstring(ab, aad(sa)->mnt.trans);
}
if (aad(sa)->mnt.flags) {
audit_log_format(ab, " flags=\"");
audit_mnt_flags(ab, aad(sa)->mnt.flags);
audit_log_format(ab, "\"");
}
if (aad(sa)->mnt.data) {
audit_log_format(ab, " options=");
audit_log_untrustedstring(ab, aad(sa)->mnt.data);
}
}
/**
* audit_mount - handle the auditing of mount operations
* @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 idependent 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(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(sa, LSM_AUDIT_DATA_NONE, 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;
}
aad(&sa)->name = name;
aad(&sa)->mnt.src_name = src_name;
aad(&sa)->mnt.type = type;
aad(&sa)->mnt.trans = trans;
aad(&sa)->mnt.flags = flags;
if (data && (perms->audit & AA_AUDIT_DATA))
aad(&sa)->mnt.data = data;
aad(&sa)->info = info;
aad(&sa)->error = error;
return aa_audit(audit_type, profile, &sa, 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 unsigned int match_mnt_flags(struct aa_dfa *dfa, unsigned int 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;
}
/**
* compute_mnt_perms - compute mount permission associated with @state
* @dfa: dfa to match against (NOT NULL)
* @state: state match finished in
*
* Returns: mount permissions
*/
static struct aa_perms compute_mnt_perms(struct aa_dfa *dfa,
unsigned int state)
{
struct aa_perms perms;
perms.kill = 0;
perms.allow = dfa_user_allow(dfa, state);
perms.audit = dfa_user_audit(dfa, state);
perms.quiet = dfa_user_quiet(dfa, state);
perms.xindex = dfa_user_xindex(dfa, state);
return perms;
}
static const char *mnt_info_table[] = {
"match succeeded",
"failed mntpnt match",
"failed srcname match",
"failed type match",
"failed flags match",
"failed data match"
};
/*
* 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_dfa *dfa, unsigned int start,
const char *mntpnt, const char *devname,
const char *type, unsigned long flags,
void *data, bool binary, struct aa_perms *perms)
{
unsigned int state;
AA_BUG(!dfa);
AA_BUG(!perms);
state = aa_dfa_match(dfa, start, mntpnt);
state = aa_dfa_null_transition(dfa, state);
if (!state)
return 1;
if (devname)
state = aa_dfa_match(dfa, state, devname);
state = aa_dfa_null_transition(dfa, state);
if (!state)
return 2;
if (type)
state = aa_dfa_match(dfa, state, type);
state = aa_dfa_null_transition(dfa, state);
if (!state)
return 3;
state = match_mnt_flags(dfa, state, flags);
if (!state)
return 4;
*perms = compute_mnt_perms(dfa, 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(dfa, state);
if (!state)
return 4;
state = aa_dfa_match(dfa, state, data);
if (!state)
return 5;
*perms = compute_mnt_perms(dfa, state);
if (perms->allow & AA_MAY_MOUNT)
return 0;
}
/* failed at end of flags match */
return 4;
}
/**
* match_mnt - handle path matching for mount
* @profile: the confining profile
* @mntpnt: string for the mntpnt (NOT NULL)
* @devname: string for the devname/src_name (MAYBE NULL)
* @type: string for the dev type (MAYBE NULL)
* @flags: mount flags to match
* @data: fs mount data (MAYBE NULL)
* @binary: whether @data is binary
* @perms: Returns: permission found by the match
* @info: Returns: infomation string about the match for logging
*
* Returns: 0 on success else error
*/
static int match_mnt(struct aa_profile *profile, const char *mntpnt,
const char *devname, const char *type,
unsigned long flags, void *data, bool binary)
{
struct aa_perms perms = { };
const char *info = NULL;
int pos, error = -EACCES;
AA_BUG(!profile);
pos = do_match_mnt(profile->policy.dfa,
profile->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(profile, OP_MOUNT, mntpnt, devname, type, NULL,
flags, data, AA_MAY_MOUNT, &perms, info, error);
}
static int path_flags(struct aa_profile *profile, 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);
}
int aa_remount(struct aa_label *label, struct path *path, unsigned long flags,
void *data)
{
struct aa_profile *profile;
const char *name, *info = NULL;
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;
get_buffers(buffer);
error = aa_path_name(path, path_flags(labels_profile(label), path),
buffer, &name, &info,
labels_profile(label)->disconnected);
if (error) {
error = audit_mount(labels_profile(label), OP_MOUNT, name, NULL,
NULL, NULL, flags, data, AA_MAY_MOUNT,
&nullperms, info, error);
goto out;
}
error = fn_for_each_confined(label, profile,
match_mnt(profile, name, NULL, NULL, flags, data,
binary));
out:
put_buffers(buffer);
return error;
}
int aa_bind_mount(struct aa_label *label, struct path *path,
const char *dev_name, unsigned long flags)
{
struct aa_profile *profile;
char *buffer = NULL, *old_buffer = NULL;
const char *name, *old_name = NULL, *info = 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;
get_buffers(buffer, old_buffer);
error = aa_path_name(path, path_flags(labels_profile(label), path), buffer, &name,
&info, labels_profile(label)->disconnected);
if (error)
goto error;
error = aa_path_name(&old_path, path_flags(labels_profile(label),
&old_path),
old_buffer, &old_name, &info,
labels_profile(label)->disconnected);
path_put(&old_path);
if (error)
goto error;
error = fn_for_each_confined(label, profile,
match_mnt(profile, name, old_name, NULL, flags, NULL,
false));
out:
put_buffers(buffer, old_buffer);
return error;
error:
error = fn_for_each(label, profile,
audit_mount(profile, OP_MOUNT, name, old_name, NULL,
NULL, flags, NULL, AA_MAY_MOUNT, &nullperms,
info, error));
goto out;
}
int aa_mount_change_type(struct aa_label *label, struct path *path,
unsigned long flags)
{
struct aa_profile *profile;
char *buffer = NULL;
const char *name, *info = 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);
get_buffers(buffer);
error = aa_path_name(path, path_flags(labels_profile(label), path),
buffer, &name, &info,
labels_profile(label)->disconnected);
if (error) {
error = fn_for_each(label, profile,
audit_mount(profile, OP_MOUNT, name, NULL,
NULL, NULL, flags, NULL,
AA_MAY_MOUNT, &nullperms, info,
error));
goto out;
}
error = fn_for_each_confined(label, profile,
match_mnt(profile, name, NULL, NULL, flags, NULL,
false));
out:
put_buffers(buffer);
return error;
}
int aa_move_mount(struct aa_label *label, struct path *path,
const char *orig_name)
{
struct aa_profile *profile;
char *buffer = NULL, *old_buffer = NULL;
const char *name, *old_name = NULL, *info = NULL;
struct path old_path;
int error;
AA_BUG(!label);
AA_BUG(!path);
if (!orig_name || !*orig_name)
return -EINVAL;
error = kern_path(orig_name, LOOKUP_FOLLOW, &old_path);
if (error)
return error;
get_buffers(buffer, old_buffer);
error = aa_path_name(path, path_flags(labels_profile(label), path),
buffer, &name, &info,
labels_profile(label)->disconnected);
if (error)
goto error;
error = aa_path_name(&old_path, path_flags(labels_profile(label),
&old_path),
old_buffer, &old_name, &info,
labels_profile(label)->disconnected);
path_put(&old_path);
if (error)
goto error;
error = fn_for_each_confined(label, profile,
match_mnt(profile, name, old_name, NULL, MS_MOVE, NULL,
false));
out:
put_buffers(buffer, old_buffer);
return error;
error:
error = fn_for_each(label, profile,
audit_mount(profile, OP_MOUNT, name, old_name, NULL,
NULL, MS_MOVE, NULL, AA_MAY_MOUNT,
&nullperms, info, error));
goto out;
}
int aa_new_mount(struct aa_label *label, const char *orig_dev_name,
struct path *path, const char *type, unsigned long flags,
void *data)
{
struct aa_profile *profile;
char *buffer = NULL, *dev_buffer = NULL;
const char *name = NULL, *dev_name = NULL, *info = NULL;
bool binary = true;
int error;
int requires_dev = 0;
struct path dev_path;
AA_BUG(!label);
AA_BUG(!path);
dev_name = orig_dev_name;
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, &dev_path);
if (error)
return error;
}
}
get_buffers(buffer, dev_buffer);
if (type && requires_dev) {
error = aa_path_name(&dev_path,
path_flags(labels_profile(label),
&dev_path),
dev_buffer, &dev_name, &info,
labels_profile(label)->disconnected);
path_put(&dev_path);
if (error)
goto error;
}
error = aa_path_name(path, path_flags(labels_profile(label), path),
buffer, &name, &info,
labels_profile(label)->disconnected);
if (error)
goto error;
error = fn_for_each_confined(label, profile,
match_mnt(profile, name, dev_name, type, flags, data,
binary));
cleanup:
put_buffers(buffer, dev_buffer);
return error;
error:
error = fn_for_each(label, profile,
audit_mount(labels_profile(label), OP_MOUNT, name,
dev_name, type, NULL, flags, data,
AA_MAY_MOUNT, &nullperms, info, error));
goto cleanup;
}
static int profile_umount(struct aa_profile *profile, const char *name)
{
struct aa_perms perms = { };
const char *info = NULL;
unsigned int state;
int e = 0;
AA_BUG(!profile);
AA_BUG(!name);
state = aa_dfa_match(profile->policy.dfa,
profile->policy.start[AA_CLASS_MOUNT],
name);
perms = compute_mnt_perms(profile->policy.dfa, state);
if (AA_MAY_UMOUNT & ~perms.allow)
e = -EACCES;
return audit_mount(profile, OP_UMOUNT, name, NULL, NULL, NULL, 0, NULL,
AA_MAY_UMOUNT, &perms, info, e);
}
int aa_umount(struct aa_label *label, struct vfsmount *mnt, int flags)
{
struct aa_profile *profile;
char *buffer = NULL;
const char *name, *info = NULL;
int error;
struct path path = { mnt, mnt->mnt_root };
AA_BUG(!label);
AA_BUG(!mnt);
get_buffers(buffer);
error = aa_path_name(&path, path_flags(labels_profile(label), &path),
buffer, &name, &info,
labels_profile(label)->disconnected);
if (error) {
error = fn_for_each(label, profile,
audit_mount(profile, OP_UMOUNT, name, NULL,
NULL, NULL, 0, NULL, AA_MAY_UMOUNT,
&nullperms, info, error));
goto out;
}
error = fn_for_each_confined(label, profile,
profile_umount(profile, name));
out:
put_buffers(buffer);
return error;
}
static int profile_pivotroot(struct aa_profile *profile, const char *new_name,
const char *old_name, struct aa_label **trans)
{
struct aa_label *target = NULL;
const char *trans_name = NULL;
struct aa_perms perms = { };
const char *info = NULL;
unsigned int state;
int error = -EACCES;
AA_BUG(!profile);
AA_BUG(!new_name);
AA_BUG(!old_name);
AA_BUG(!trans);
/* TODO: actual domain transition computation for multiple
* profiles
*/
state = aa_dfa_match(profile->policy.dfa,
profile->policy.start[AA_CLASS_MOUNT],
new_name);
state = aa_dfa_null_transition(profile->policy.dfa, state);
state = aa_dfa_match(profile->policy.dfa, state, old_name);
perms = compute_mnt_perms(profile->policy.dfa, state);
if (AA_MAY_PIVOTROOT & perms.allow) {
if ((perms.xindex & AA_X_TYPE_MASK) == AA_X_TABLE) {
target = x_table_lookup(profile, perms.xindex,
&trans_name);
if (!target)
error = -ENOENT;
else
*trans = target;
} else
error = 0;
}
error = audit_mount(profile, OP_PIVOTROOT, new_name, old_name,
NULL, trans_name, 0, NULL, AA_MAY_PIVOTROOT,
&perms, info, error);
if (!*trans)
aa_put_label(target);
return error;
}
int aa_pivotroot(struct aa_label *label, struct path *old_path,
struct path *new_path)
{
struct aa_profile *profile;
struct aa_label *target = NULL;
char *old_buffer = NULL, *new_buffer = NULL;
const char *old_name, *new_name = NULL, *info = NULL;
int error;
AA_BUG(!label);
AA_BUG(!old_path);
AA_BUG(!new_path);
get_buffers(old_buffer, new_buffer);
error = aa_path_name(old_path, path_flags(labels_profile(label),
old_path),
old_buffer, &old_name, &info,
labels_profile(label)->disconnected);
if (error)
goto error;
error = aa_path_name(new_path, path_flags(labels_profile(label),
new_path),
new_buffer, &new_name, &info,
labels_profile(label)->disconnected);
if (error)
goto error;
error = fn_for_each(label, profile,
profile_pivotroot(profile, new_name, old_name,
&target));
out:
put_buffers(old_buffer, new_buffer);
if (target)
error = aa_replace_current_label(target);
return error;
error:
error = fn_for_each(label, profile,
audit_mount(profile, OP_PIVOTROOT, new_name, old_name,
NULL, NULL,
0, NULL, AA_MAY_PIVOTROOT, &nullperms, info,
error));
goto out;
}
/*
* AppArmor security module
*
* This file contains AppArmor network mediation
*
* Copyright (C) 1998-2008 Novell/SUSE
* Copyright 2009-2014 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 "include/af_unix.h"
#include "include/apparmor.h"
#include "include/audit.h"
#include "include/context.h"
#include "include/label.h"
#include "include/net.h"
#include "include/policy.h"
#include "net_names.h"
struct aa_fs_entry aa_fs_entry_network[] = {
AA_FS_FILE_STRING("af_mask", AA_FS_AF_MASK),
AA_FS_FILE_BOOLEAN("af_unix", 1),
{ }
};
static const char *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,
struct sock *sk)
{
struct unix_sock *u = unix_sk(sk);
if (u && u->addr)
audit_unix_addr(ab, str, u->addr->name, u->addr->len);
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;
audit_log_format(ab, " family=");
if (address_family_names[sa->u.net->family]) {
audit_log_string(ab, address_family_names[sa->u.net->family]);
} else {
audit_log_format(ab, "\"unknown(%d)\"", sa->u.net->family);
}
audit_log_format(ab, " sock_type=");
if (sock_type_names[aad(sa)->net.type]) {
audit_log_string(ab, sock_type_names[aad(sa)->net.type]);
} else {
audit_log_format(ab, "\"unknown(%d)\"", aad(sa)->net.type);
}
audit_log_format(ab, " protocol=%d", aad(sa)->net.protocol);
if (aad(sa)->request & NET_PERMS_MASK) {
audit_log_format(ab, " requested_mask=");
aa_audit_perm_mask(ab, aad(sa)->request, NULL, 0,
net_mask_names, NET_PERMS_MASK);
if (aad(sa)->denied & NET_PERMS_MASK) {
audit_log_format(ab, " denied_mask=");
aa_audit_perm_mask(ab, aad(sa)->denied, NULL, 0,
net_mask_names, NET_PERMS_MASK);
}
}
if (sa->u.net->family == AF_UNIX) {
if ((aad(sa)->request & ~NET_PEER_MASK) && aad(sa)->net.addr)
audit_unix_addr(ab, "addr",
unix_addr(aad(sa)->net.addr),
aad(sa)->net.addrlen);
else
audit_unix_sk_addr(ab, "addr", sa->u.net->sk);
if (aad(sa)->request & NET_PEER_MASK) {
if (aad(sa)->net.addr)
audit_unix_addr(ab, "peer_addr",
unix_addr(aad(sa)->net.addr),
aad(sa)->net.addrlen);
else
audit_unix_sk_addr(ab, "peer_addr",
aad(sa)->net.peer_sk);
}
}
if (aad(sa)->peer) {
audit_log_format(ab, " peer=");
aa_label_xaudit(ab, labels_ns(aad(sa)->label), aad(sa)->peer,
FLAGS_NONE, GFP_ATOMIC);
}
}
/* Generic af perm */
int aa_profile_af_perm(struct aa_profile *profile, struct common_audit_data *sa,
u32 request, u16 family, int type)
{
struct aa_perms perms = { };
AA_BUG(family >= AF_MAX);
AA_BUG(type < 0 && type >= SOCK_MAX);
if (profile_unconfined(profile))
return 0;
perms.allow = (profile->net.allow[family] & (1 << type)) ?
ALL_PERMS_MASK : 0;
perms.audit = (profile->net.audit[family] & (1 << type)) ?
ALL_PERMS_MASK : 0;
perms.quiet = (profile->net.quiet[family] & (1 << type)) ?
ALL_PERMS_MASK : 0;
aa_apply_modes_to_perms(profile, &perms);
return aa_check_perms(profile, &perms, request, sa, audit_net_cb);
}
static int aa_af_perm(struct aa_label *label, const char *op, u32 request,
u16 family, int type, int protocol)
{
struct aa_profile *profile;
DEFINE_AUDIT_NET(sa, op, NULL, family, type, protocol);
return fn_for_each_confined(label, profile,
aa_profile_af_perm(profile, &sa, request, family, type));
}
static int aa_label_sk_perm(struct aa_label *label, const char *op, u32 request,
struct sock *sk)
{
struct aa_profile *profile;
DEFINE_AUDIT_SK(sa, op, sk);
AA_BUG(!label);
AA_BUG(!sk);
if (unconfined(label))
return 0;
return fn_for_each_confined(label, profile,
aa_profile_af_sk_perm(profile, &sa, request, sk));
}
static 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 = aa_begin_current_label(DO_UPDATE);
error = aa_label_sk_perm(label, op, request, sk);
aa_end_current_label(label);
return error;
}
#define af_select(FAMILY, FN, DEF_FN) \
({ \
int __e; \
switch ((FAMILY)) { \
case AF_UNIX: \
__e = aa_unix_ ## FN; \
break; \
default: \
__e = DEF_FN; \
} \
__e; \
})
/* TODO: push into lsm.c ???? */
/* revaliation, get/set attr, shutdown */
int aa_sock_perm(const char *op, u32 request, struct socket *sock)
{
AA_BUG(!sock);
AA_BUG(!sock->sk);
AA_BUG(in_interrupt());
return af_select(sock->sk->sk_family,
sock_perm(op, request, sock),
aa_sk_perm(op, request, sock->sk));
}
int aa_sock_create_perm(struct aa_label *label, int family, int type,
int protocol)
{
AA_BUG(!label);
/* TODO: .... */
AA_BUG(in_interrupt());
return af_select(family,
create_perm(label, family, type, protocol),
aa_af_perm(label, OP_CREATE, AA_MAY_CREATE, family,
type, protocol));
}
int aa_sock_bind_perm(struct socket *sock, struct sockaddr *address,
int addrlen)
{
AA_BUG(!sock);
AA_BUG(!sock->sk);
AA_BUG(!address);
/* TODO: .... */
AA_BUG(in_interrupt());
return af_select(sock->sk->sk_family,
bind_perm(sock, address, addrlen),
aa_sk_perm(OP_BIND, AA_MAY_BIND, sock->sk));
}
int aa_sock_connect_perm(struct socket *sock, struct sockaddr *address,
int addrlen)
{
AA_BUG(!sock);
AA_BUG(!sock->sk);
AA_BUG(!address);
/* TODO: .... */
AA_BUG(in_interrupt());
return af_select(sock->sk->sk_family,
connect_perm(sock, address, addrlen),
aa_sk_perm(OP_CONNECT, AA_MAY_CONNECT, sock->sk));
}
int aa_sock_listen_perm(struct socket *sock, int backlog)
{
AA_BUG(!sock);
AA_BUG(!sock->sk);
/* TODO: .... */
AA_BUG(in_interrupt());
return af_select(sock->sk->sk_family,
listen_perm(sock, backlog),
aa_sk_perm(OP_LISTEN, AA_MAY_LISTEN, sock->sk));
}
/* ability of sock to connect, not peer address binding */
int aa_sock_accept_perm(struct socket *sock, struct socket *newsock)
{
AA_BUG(!sock);
AA_BUG(!sock->sk);
AA_BUG(!newsock);
/* TODO: .... */
AA_BUG(in_interrupt());
return af_select(sock->sk->sk_family,
accept_perm(sock, newsock),
aa_sk_perm(OP_ACCEPT, AA_MAY_ACCEPT, sock->sk));
}
/* sendmsg, recvmsg */
int aa_sock_msg_perm(const char *op, u32 request, struct socket *sock,
struct msghdr *msg, int size)
{
AA_BUG(!sock);
AA_BUG(!sock->sk);
AA_BUG(!msg);
/* TODO: .... */
AA_BUG(in_interrupt());
return af_select(sock->sk->sk_family,
msg_perm(op, request, sock, msg, size),
aa_sk_perm(op, request, sock->sk));
}
/* revaliation, get/set attr, opt */
int aa_sock_opt_perm(const char *op, u32 request, struct socket *sock, int level,
int optname)
{
AA_BUG(!sock);
AA_BUG(!sock->sk);
AA_BUG(in_interrupt());
return af_select(sock->sk->sk_family,
opt_perm(op, request, sock, level, optname),
aa_sk_perm(op, request, sock->sk));
}
int aa_sock_file_perm(struct aa_label *label, const char *op, u32 request,
struct socket *sock)
{
AA_BUG(!label);
AA_BUG(!sock);
AA_BUG(!sock->sk);
return af_select(sock->sk->sk_family,
file_perm(label, op, request, sock),
aa_label_sk_perm(label, op, request, sock->sk));
}
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
...@@ -25,7 +25,6 @@ ...@@ -25,7 +25,6 @@
#include "include/path.h" #include "include/path.h"
#include "include/policy.h" #include "include/policy.h"
/* modified from dcache.c */ /* modified from dcache.c */
static int prepend(char **buffer, int buflen, const char *str, int namelen) static int prepend(char **buffer, int buflen, const char *str, int namelen)
{ {
...@@ -39,13 +38,50 @@ static int prepend(char **buffer, int buflen, const char *str, int namelen) ...@@ -39,13 +38,50 @@ static int prepend(char **buffer, int buflen, const char *str, int namelen)
#define CHROOT_NSCONNECT (PATH_CHROOT_REL | PATH_CHROOT_NSCONNECT) #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(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 * d_namespace_path - lookup a name associated with a given path
* @path: path to lookup (NOT NULL) * @path: path to lookup (NOT NULL)
* @buf: buffer to store path to (NOT NULL) * @buf: buffer to store path to (NOT NULL)
* @buflen: length of @buf
* @name: Returns - pointer for start of path name with in @buf (NOT NULL) * @name: Returns - pointer for start of path name with in @buf (NOT NULL)
* @flags: flags controlling path lookup * @flags: flags controlling path lookup
* @disconnected: string to prefix to disconnected paths
* *
* Handle path name lookup. * Handle path name lookup.
* *
...@@ -53,12 +89,14 @@ static int prepend(char **buffer, int buflen, const char *str, int namelen) ...@@ -53,12 +89,14 @@ static int prepend(char **buffer, int buflen, const char *str, int namelen)
* When no error the path name is returned in @name which points to * When no error the path name is returned in @name which points to
* to a position in @buf * to a position in @buf
*/ */
static int d_namespace_path(struct path *path, char *buf, int buflen, static int d_namespace_path(struct path *path, char *buf, char **name,
char **name, int flags) int flags, const char *disconnected)
{ {
char *res; char *res;
int error = 0; int error = 0;
int connected = 1; 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) { if (path->mnt->mnt_flags & MNT_INTERNAL) {
/* it's not mounted anywhere */ /* it's not mounted anywhere */
...@@ -73,9 +111,12 @@ static int d_namespace_path(struct path *path, char *buf, int buflen, ...@@ -73,9 +111,12 @@ static int d_namespace_path(struct path *path, char *buf, int buflen,
/* TODO: convert over to using a per namespace /* TODO: convert over to using a per namespace
* control instead of hard coded /proc * control instead of hard coded /proc
*/ */
return prepend(name, *name - buf, "/proc", 5); error = prepend(name, *name - buf, "/proc", 5);
} goto out;
return 0; } else
error = disconnect(path, buf, name, flags,
disconnected);
goto out;
} }
/* resolve paths relative to chroot?*/ /* resolve paths relative to chroot?*/
...@@ -94,8 +135,11 @@ static int d_namespace_path(struct path *path, char *buf, int buflen, ...@@ -94,8 +135,11 @@ static int d_namespace_path(struct path *path, char *buf, int buflen,
* be returned. * be returned.
*/ */
if (!res || IS_ERR(res)) { if (!res || IS_ERR(res)) {
if (PTR_ERR(res) == -ENAMETOOLONG) if (PTR_ERR(res) == -ENAMETOOLONG) {
return -ENAMETOOLONG; error = -ENAMETOOLONG;
*name = buf;
goto out;
}
connected = 0; connected = 0;
res = dentry_path_raw(path->dentry, buf, buflen); res = dentry_path_raw(path->dentry, buf, buflen);
if (IS_ERR(res)) { if (IS_ERR(res)) {
...@@ -120,78 +164,28 @@ static int d_namespace_path(struct path *path, char *buf, int buflen, ...@@ -120,78 +164,28 @@ static int d_namespace_path(struct path *path, char *buf, int buflen,
goto out; goto out;
} }
/* If the path is not connected to the expected root, if (!connected)
* check if it is a sysctl and handle specially else remove any error = disconnect(path, buf, name, flags, disconnected);
* 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.
*/
if (!connected) {
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 (*res == '/')
*name = res + 1;
}
}
out: out:
return error;
}
/**
* get_name_to_buffer - get the pathname to a buffer ensure dir / is appended
* @path: path to get name for (NOT NULL)
* @flags: flags controlling path lookup
* @buffer: buffer to put name in (NOT NULL)
* @size: size of buffer
* @name: Returns - contains position of path name in @buffer (NOT NULL)
*
* Returns: %0 else error on failure
*/
static int get_name_to_buffer(struct path *path, int flags, char *buffer,
int size, char **name, const char **info)
{
int adjust = (flags & PATH_IS_DIR) ? 1 : 0;
int error = d_namespace_path(path, buffer, size - adjust, name, flags);
if (!error && (flags & PATH_IS_DIR) && (*name)[1] != '\0')
/* /*
* Append "/" to the pathname. The root directory is a special * Append "/" to the pathname. The root directory is a special
* case; it already ends in slash. * case; it already ends in slash.
*/ */
strcpy(&buffer[size - 2], "/"); if (!error && isdir && ((*name)[1] != '\0' || (*name)[0] != '/'))
strcpy(&buf[aa_g_path_max - 2], "/");
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";
}
return error; return error;
} }
/** /**
* aa_path_name - compute the pathname of a file * aa_path_name - get the pathname to a buffer ensure dir / is appended
* @path: path the file (NOT NULL) * @path: path the file (NOT NULL)
* @flags: flags controlling path name generation * @flags: flags controlling path name generation
* @buffer: buffer that aa_get_name() allocated (NOT NULL) * @buffer: buffer to put name in (NOT NULL)
* @name: Returns - the generated path name if !error (NOT NULL) * @name: Returns - the generated path name if !error (NOT NULL)
* @info: Returns - information on why the path lookup failed (MAYBE 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 * @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 * from the beginning of the buffer), or NULL. If there is an error @name
...@@ -204,33 +198,24 @@ static int get_name_to_buffer(struct path *path, int flags, char *buffer, ...@@ -204,33 +198,24 @@ static int get_name_to_buffer(struct path *path, int flags, char *buffer,
* *
* Returns: %0 else error code if could retrieve name * Returns: %0 else error code if could retrieve name
*/ */
int aa_path_name(struct path *path, int flags, char **buffer, const char **name, int aa_path_name(struct path *path, int flags, char *buffer, const char **name,
const char **info) const char **info, const char *disconnected)
{ {
char *buf, *str = NULL; char *str = NULL;
int size = 256; int error = d_namespace_path(path, buffer, &str, flags, disconnected);
int error;
*name = NULL; if (info && error) {
*buffer = NULL; if (error == -ENOENT)
for (;;) { *info = "Failed name lookup - deleted entry";
/* freed by caller */ else if (error == -EACCES)
buf = kmalloc(size, GFP_KERNEL); *info = "Failed name lookup - disconnected path";
if (!buf) else if (error == -ENAMETOOLONG)
return -ENOMEM; *info = "Failed name lookup - name too long";
else
error = get_name_to_buffer(path, flags, buf, size, &str, info); *info = "Failed name lookup";
if (error != -ENAMETOOLONG)
break;
kfree(buf);
size <<= 1;
if (size > aa_g_path_max)
return -ENAMETOOLONG;
*info = NULL;
} }
*buffer = buf;
*name = str;
*name = str;
return error; return error;
} }
...@@ -76,12 +76,14 @@ ...@@ -76,12 +76,14 @@
#include <linux/slab.h> #include <linux/slab.h>
#include <linux/spinlock.h> #include <linux/spinlock.h>
#include <linux/string.h> #include <linux/string.h>
#include <linux/user_namespace.h>
#include "include/apparmor.h" #include "include/apparmor.h"
#include "include/capability.h" #include "include/capability.h"
#include "include/context.h" #include "include/context.h"
#include "include/file.h" #include "include/file.h"
#include "include/ipc.h" #include "include/ipc.h"
#include "include/label.h"
#include "include/match.h" #include "include/match.h"
#include "include/path.h" #include "include/path.h"
#include "include/policy.h" #include "include/policy.h"
...@@ -89,9 +91,9 @@ ...@@ -89,9 +91,9 @@
#include "include/resource.h" #include "include/resource.h"
/* root profile namespace */ /* Note: mode names must be unique in the first character because of
struct aa_namespace *root_ns; * modechrs used to print modes on compound labels on some interfaces
*/
const char *const aa_profile_mode_names[] = { const char *const aa_profile_mode_names[] = {
"enforce", "enforce",
"complain", "complain",
...@@ -99,322 +101,9 @@ const char *const aa_profile_mode_names[] = { ...@@ -99,322 +101,9 @@ const char *const aa_profile_mode_names[] = {
"unconfined", "unconfined",
}; };
/**
* hname_tail - find the last component of an hname
* @name: hname to find the base profile name component of (NOT NULL)
*
* Returns: the tail (base profile name) name component of an hname
*/
static const char *hname_tail(const char *hname)
{
char *split;
hname = strim((char *)hname);
for (split = strstr(hname, "//"); split; split = strstr(hname, "//"))
hname = split + 2;
return hname;
}
/**
* 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)
*
* Note: this fn creates a copy of strings passed in
*
* Returns: true if policy init successful
*/
static bool policy_init(struct aa_policy *policy, const char *prefix,
const char *name)
{
/* freed by policy_free */
if (prefix) {
policy->hname = kmalloc(strlen(prefix) + strlen(name) + 3,
GFP_KERNEL);
if (policy->hname)
sprintf(policy->hname, "%s//%s", prefix, name);
} else
policy->hname = kstrdup(name, GFP_KERNEL);
if (!policy->hname)
return 0;
/* base.name is a substring of fqname */
policy->name = (char *)hname_tail(policy->hname);
INIT_LIST_HEAD(&policy->list);
INIT_LIST_HEAD(&policy->profiles);
return 1;
}
/**
* policy_destroy - free the elements referenced by @policy
* @policy: policy that is to have its elements freed (NOT NULL)
*/
static void policy_destroy(struct aa_policy *policy)
{
/* still contains profiles -- invalid */
if (on_list_rcu(&policy->profiles)) {
AA_ERROR("%s: internal error, "
"policy '%s' still contains profiles\n",
__func__, policy->name);
BUG();
}
if (on_list_rcu(&policy->list)) {
AA_ERROR("%s: internal error, policy '%s' still on list\n",
__func__, policy->name);
BUG();
}
/* don't free name as its a subset of hname */
kzfree(policy->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 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 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;
}
/*
* Routines for AppArmor namespaces
*/
static const char *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)
*
* Returns: true if @view is visible from @curr else false
*/
bool aa_ns_visible(struct aa_namespace *curr, struct aa_namespace *view)
{
if (curr == view)
return true;
for ( ; view; view = view->parent) {
if (view->parent == curr)
return true;
}
return false;
}
/**
* aa_na_name - Find the ns name to display for @view from @curr
* @curr - current namespace (NOT NULL)
* @view - namespace attempting to view (NOT NULL)
*
* Returns: name of @view visible from @curr
*/
const char *aa_ns_name(struct aa_namespace *curr, struct aa_namespace *view)
{
/* if view == curr then the namespace name isn't displayed */
if (curr == view)
return "";
if (aa_ns_visible(curr, view)) {
/* 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;
} else
return hidden_ns_name;
}
/**
* alloc_namespace - 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_namespace *alloc_namespace(const char *prefix,
const char *name)
{
struct aa_namespace *ns;
ns = kzalloc(sizeof(*ns), GFP_KERNEL);
AA_DEBUG("%s(%p)\n", __func__, ns);
if (!ns)
return NULL;
if (!policy_init(&ns->base, prefix, name))
goto fail_ns;
INIT_LIST_HEAD(&ns->sub_ns);
mutex_init(&ns->lock);
/* released by free_namespace */
ns->unconfined = aa_alloc_profile("unconfined");
if (!ns->unconfined)
goto fail_unconfined;
ns->unconfined->flags = PFLAG_IX_ON_NAME_ERROR |
PFLAG_IMMUTABLE | PFLAG_NS_COUNT;
ns->unconfined->mode = APPARMOR_UNCONFINED;
/* ns and ns->unconfined share ns->unconfined refcount */
ns->unconfined->ns = ns;
atomic_set(&ns->uniq_null, 0);
return ns;
fail_unconfined:
kzfree(ns->base.hname);
fail_ns:
kzfree(ns);
return NULL;
}
/**
* free_namespace - 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,
*/
static void free_namespace(struct aa_namespace *ns)
{
if (!ns)
return;
policy_destroy(&ns->base);
aa_put_namespace(ns->parent);
ns->unconfined->ns = NULL;
aa_free_profile(ns->unconfined);
kzfree(ns);
}
/**
* __aa_find_namespace - 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)
*
* Returns: unrefcounted namespace
*
* Requires: rcu_read_lock be held
*/
static struct aa_namespace *__aa_find_namespace(struct list_head *head,
const char *name)
{
return (struct aa_namespace *)__policy_find(head, name);
}
/**
* aa_find_namespace - look up a profile namespace on the namespace list
* @root: namespace to search in (NOT NULL)
* @name: name of namespace to find (NOT NULL)
*
* Returns: a refcounted namespace on the list, or NULL if no namespace
* called @name exists.
*
* refcount released by caller
*/
struct aa_namespace *aa_find_namespace(struct aa_namespace *root,
const char *name)
{
struct aa_namespace *ns = NULL;
rcu_read_lock();
ns = aa_get_namespace(__aa_find_namespace(&root->sub_ns, name));
rcu_read_unlock();
return ns;
}
/**
* aa_prepare_namespace - find an existing or create a new namespace of @name
* @name: the namespace to find or add (MAYBE NULL)
*
* Returns: refcounted namespace or NULL if failed to create one
*/
static struct aa_namespace *aa_prepare_namespace(const char *name)
{
struct aa_namespace *ns, *root;
root = aa_current_profile()->ns;
mutex_lock(&root->lock);
/* if name isn't specified the profile is loaded to the current ns */
if (!name) {
/* released by caller */
ns = aa_get_namespace(root);
goto out;
}
/* try and find the specified ns and if it doesn't exist create it */
/* released by caller */
ns = aa_get_namespace(__aa_find_namespace(&root->sub_ns, name));
if (!ns) {
ns = alloc_namespace(root->base.hname, name);
if (!ns)
goto out;
if (__aa_fs_namespace_mkdir(ns, ns_subns_dir(root), name)) {
AA_ERROR("Failed to create interface for ns %s\n",
ns->base.name);
free_namespace(ns);
ns = NULL;
goto out;
}
ns->parent = aa_get_namespace(root);
list_add_rcu(&ns->base.list, &root->sub_ns);
/* add list ref */
aa_get_namespace(ns);
}
out:
mutex_unlock(&root->lock);
/* return ref */
return ns;
}
/** /**
* __list_add_profile - add a profile to a list * __add_profile - add a profiles to list and label tree
* @list: list to add it to (NOT NULL) * @list: list to add it to (NOT NULL)
* @profile: the profile to add (NOT NULL) * @profile: the profile to add (NOT NULL)
* *
...@@ -422,12 +111,21 @@ static struct aa_namespace *aa_prepare_namespace(const char *name) ...@@ -422,12 +111,21 @@ static struct aa_namespace *aa_prepare_namespace(const char *name)
* *
* Requires: namespace lock be held, or list not be shared * Requires: namespace lock be held, or list not be shared
*/ */
static void __list_add_profile(struct list_head *list, static void __add_profile(struct list_head *list, struct aa_profile *profile)
struct aa_profile *profile)
{ {
struct aa_label *l;
AA_BUG(!list);
AA_BUG(!profile);
AA_BUG(!profile->ns);
AA_BUG(!mutex_is_locked(&profile->ns->lock));
list_add_rcu(&profile->base.list, list); list_add_rcu(&profile->base.list, list);
/* get list reference */ /* get list reference */
aa_get_profile(profile); aa_get_profile(profile);
l = aa_label_insert(&profile->ns->labels, &profile->label);
AA_BUG(l != &profile->label);
aa_put_label(l);
} }
/** /**
...@@ -444,11 +142,15 @@ static void __list_add_profile(struct list_head *list, ...@@ -444,11 +142,15 @@ static void __list_add_profile(struct list_head *list,
*/ */
static void __list_remove_profile(struct aa_profile *profile) static void __list_remove_profile(struct aa_profile *profile)
{ {
AA_BUG(!profile);
AA_BUG(!profile->ns);
AA_BUG(!mutex_is_locked(&profile->ns->lock));
list_del_rcu(&profile->base.list); list_del_rcu(&profile->base.list);
aa_put_profile(profile); aa_put_profile(profile);
} }
static void __profile_list_release(struct list_head *head); void __aa_profile_list_release(struct list_head *head);
/** /**
* __remove_profile - remove old profile, and children * __remove_profile - remove old profile, and children
...@@ -458,124 +160,31 @@ static void __profile_list_release(struct list_head *head); ...@@ -458,124 +160,31 @@ static void __profile_list_release(struct list_head *head);
*/ */
static void __remove_profile(struct aa_profile *profile) static void __remove_profile(struct aa_profile *profile)
{ {
AA_BUG(!profile);
AA_BUG(!profile->ns);
AA_BUG(!mutex_is_locked(&profile->ns->lock));
/* release any children lists first */ /* release any children lists first */
__profile_list_release(&profile->base.profiles); __aa_profile_list_release(&profile->base.profiles);
/* released by free_profile */ /* released by free_profile */
__aa_update_replacedby(profile, profile->ns->unconfined); aa_label_remove(&profile->label);
__aa_fs_profile_rmdir(profile); __aa_fs_profile_rmdir(profile);
__list_remove_profile(profile); __list_remove_profile(profile);
} }
/** /**
* __profile_list_release - remove all profiles on the list and put refs * __aa_profile_list_release - remove all profiles on the list and put refs
* @head: list of profiles (NOT NULL) * @head: list of profiles (NOT NULL)
* *
* Requires: namespace lock be held * Requires: namespace lock be held
*/ */
static void __profile_list_release(struct list_head *head) void __aa_profile_list_release(struct list_head *head)
{ {
struct aa_profile *profile, *tmp; struct aa_profile *profile, *tmp;
list_for_each_entry_safe(profile, tmp, head, base.list) list_for_each_entry_safe(profile, tmp, head, base.list)
__remove_profile(profile); __remove_profile(profile);
} }
static void __ns_list_release(struct list_head *head);
/**
* destroy_namespace - remove everything contained by @ns
* @ns: namespace to have it contents removed (NOT NULL)
*/
static void destroy_namespace(struct aa_namespace *ns)
{
if (!ns)
return;
mutex_lock(&ns->lock);
/* release all profiles in this namespace */
__profile_list_release(&ns->base.profiles);
/* release all sub namespaces */
__ns_list_release(&ns->sub_ns);
if (ns->parent)
__aa_update_replacedby(ns->unconfined, ns->parent->unconfined);
__aa_fs_namespace_rmdir(ns);
mutex_unlock(&ns->lock);
}
/**
* __remove_namespace - 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.
*/
static void __remove_namespace(struct aa_namespace *ns)
{
/* remove ns from namespace list */
list_del_rcu(&ns->base.list);
destroy_namespace(ns);
aa_put_namespace(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_namespace *ns, *tmp;
list_for_each_entry_safe(ns, tmp, head, base.list)
__remove_namespace(ns);
}
/**
* aa_alloc_root_ns - allocate the root profile namespace
*
* Returns: %0 on success else error
*
*/
int __init aa_alloc_root_ns(void)
{
/* released by aa_free_root_ns - used as list ref*/
root_ns = alloc_namespace(NULL, "root");
if (!root_ns)
return -ENOMEM;
return 0;
}
/**
* aa_free_root_ns - free the root profile namespace
*/
void __init aa_free_root_ns(void)
{
struct aa_namespace *ns = root_ns;
root_ns = NULL;
destroy_namespace(ns);
aa_put_namespace(ns);
}
static void free_replacedby(struct aa_replacedby *r)
{
if (r) {
/* r->profile will not be updated any more as r is dead */
aa_put_profile(rcu_dereference_protected(r->profile, true));
kzfree(r);
}
}
void aa_free_replacedby_kref(struct kref *kref)
{
struct aa_replacedby *r = container_of(kref, struct aa_replacedby,
count);
free_replacedby(r);
}
/** /**
* aa_free_profile - free a profile * aa_free_profile - free a profile
...@@ -595,89 +204,81 @@ void aa_free_profile(struct aa_profile *profile) ...@@ -595,89 +204,81 @@ void aa_free_profile(struct aa_profile *profile)
return; return;
/* free children profiles */ /* free children profiles */
policy_destroy(&profile->base); aa_policy_destroy(&profile->base);
aa_put_profile(rcu_access_pointer(profile->parent)); aa_put_profile(rcu_access_pointer(profile->parent));
aa_put_namespace(profile->ns); aa_put_ns(profile->ns);
kzfree(profile->rename); kzfree(profile->rename);
aa_free_file_rules(&profile->file); aa_free_file_rules(&profile->file);
aa_free_cap_rules(&profile->caps); aa_free_cap_rules(&profile->caps);
aa_free_net_rules(&profile->net);
aa_free_rlimit_rules(&profile->rlimits); aa_free_rlimit_rules(&profile->rlimits);
kzfree(profile->dirname); kzfree(profile->dirname);
aa_put_dfa(profile->xmatch); aa_put_dfa(profile->xmatch);
aa_put_dfa(profile->policy.dfa); aa_put_dfa(profile->policy.dfa);
aa_put_replacedby(profile->replacedby);
kzfree(profile->hash); kzfree(profile->hash);
kzfree(profile); kzfree(profile);
} }
/**
* aa_free_profile_rcu - free aa_profile by rcu (called by aa_free_profile_kref)
* @head: rcu_head callback for freeing of a profile (NOT NULL)
*/
static void aa_free_profile_rcu(struct rcu_head *head)
{
struct aa_profile *p = container_of(head, struct aa_profile, rcu);
if (p->flags & PFLAG_NS_COUNT)
free_namespace(p->ns);
else
aa_free_profile(p);
}
/**
* aa_free_profile_kref - free aa_profile by kref (called by aa_put_profile)
* @kr: kref callback for freeing of a profile (NOT NULL)
*/
void aa_free_profile_kref(struct kref *kref)
{
struct aa_profile *p = container_of(kref, struct aa_profile, count);
call_rcu(&p->rcu, aa_free_profile_rcu);
}
/** /**
* aa_alloc_profile - allocate, initialize and return a new profile * aa_alloc_profile - allocate, initialize and return a new profile
* @hname: name of the profile (NOT NULL) * @hname: name of the profile (NOT NULL)
* @gfp: allocation type
* *
* Returns: refcount profile or NULL on failure * Returns: refcount profile or NULL on failure
*/ */
struct aa_profile *aa_alloc_profile(const char *hname) struct aa_profile *aa_alloc_profile(const char *hname, struct aa_proxy *proxy,
gfp_t gfp)
{ {
struct aa_profile *profile; struct aa_profile *profile;
/* freed by free_profile - usually through aa_put_profile */ /* freed by free_profile - usually through aa_put_profile */
profile = kzalloc(sizeof(*profile), GFP_KERNEL); profile = kzalloc(sizeof(*profile) + sizeof (struct aa_profile *) * 2,
gfp);
if (!profile) if (!profile)
return NULL; return NULL;
profile->replacedby = kzalloc(sizeof(struct aa_replacedby), GFP_KERNEL); if (!aa_policy_init(&profile->base, NULL, hname, gfp))
if (!profile->replacedby) goto fail;
if (!aa_label_init(&profile->label, 1))
goto fail; goto fail;
kref_init(&profile->replacedby->count);
if (!policy_init(&profile->base, NULL, hname)) /* update being set needed by fs interface */
if (!proxy) {
proxy = aa_alloc_proxy(&profile->label, gfp);
if (!proxy)
goto fail; goto fail;
kref_init(&profile->count); } else
aa_get_proxy(proxy);
profile->label.proxy = proxy;
profile->label.hname = profile->base.hname;
profile->label.flags |= FLAG_PROFILE;
profile->label.vec[0] = profile;
/* refcount released by caller */ /* refcount released by caller */
return profile; return profile;
fail: fail:
kzfree(profile->replacedby); aa_free_profile(profile);
kzfree(profile);
return NULL; return NULL;
} }
/** /**
* aa_new_null_profile - create a new null-X learning profile * aa_null_profile - create or find a null-X learning profile
* @parent: profile that caused this profile to be created (NOT NULL) * @parent: profile that caused this profile to be created (NOT NULL)
* @hat: true if the null- learning profile is a hat * @hat: true if the null- learning profile is a hat
* @base: name to base the null profile off of
* @gfp: type of allocation
* *
* Create a null- complain mode profile used in learning mode. The name of * Find/Create a null- complain mode profile used in learning mode. The
* the profile is unique and follows the format of parent//null-<uniq>. * name of the profile is unique and follows the format of parent//null-XXX.
* where XXX is based on the @name or if that fails or is not supplied
* a unique number
* *
* null profiles are added to the profile list but the list does not * null profiles are added to the profile list but the list does not
* hold a count on them so that they are automatically released when * hold a count on them so that they are automatically released when
...@@ -685,43 +286,86 @@ struct aa_profile *aa_alloc_profile(const char *hname) ...@@ -685,43 +286,86 @@ struct aa_profile *aa_alloc_profile(const char *hname)
* *
* Returns: new refcounted profile else NULL on failure * Returns: new refcounted profile else NULL on failure
*/ */
struct aa_profile *aa_new_null_profile(struct aa_profile *parent, int hat) struct aa_profile *aa_null_profile(struct aa_profile *parent, bool hat,
const char *base, gfp_t gfp)
{ {
struct aa_profile *profile = NULL; struct aa_profile *profile;
char *name; char *name;
int uniq = atomic_inc_return(&parent->ns->uniq_null);
/* freed below */ AA_BUG(!parent);
name = kmalloc(strlen(parent->base.hname) + 2 + 7 + 8, GFP_KERNEL);
if (base) {
name = kmalloc(strlen(parent->base.hname) + 8 + strlen(base),
gfp);
if (name) {
sprintf(name, "%s//null-%s", parent->base.hname, base);
goto name;
}
/* fall through to try shorter uniq */
}
name = kmalloc(strlen(parent->base.hname) + 2 + 7 + 8, gfp);
if (!name) if (!name)
goto fail; return NULL;
sprintf(name, "%s//null-%x", parent->base.hname, uniq); sprintf(name, "%s//null-%x", parent->base.hname,
atomic_inc_return(&parent->ns->uniq_null));
profile = aa_alloc_profile(name); name:
kfree(name); /* lookup to see if this is a dup creation */
profile = aa_find_child(parent, basename(name));
if (profile)
goto out;
profile = aa_alloc_profile(name, NULL, gfp);
if (!profile) if (!profile)
goto fail; goto fail;
profile->mode = APPARMOR_COMPLAIN; profile->mode = APPARMOR_COMPLAIN;
profile->flags = PFLAG_NULL; profile->label.flags |= FLAG_NULL;
if (hat) if (hat)
profile->flags |= PFLAG_HAT; profile->label.flags |= FLAG_HAT;
/* released on free_profile */ /* released on free_profile */
rcu_assign_pointer(profile->parent, aa_get_profile(parent)); rcu_assign_pointer(profile->parent, aa_get_profile(parent));
profile->ns = aa_get_namespace(parent->ns); profile->ns = aa_get_ns(parent->ns);
profile->file.dfa = aa_get_dfa(nulldfa);
profile->policy.dfa = aa_get_dfa(nulldfa);
mutex_lock(&profile->ns->lock); mutex_lock(&profile->ns->lock);
__list_add_profile(&parent->base.profiles, profile); __add_profile(&parent->base.profiles, profile);
mutex_unlock(&profile->ns->lock); mutex_unlock(&profile->ns->lock);
/* refcount released by caller */ /* refcount released by caller */
out:
kfree(name);
return profile; return profile;
fail: fail:
aa_free_profile(profile);
return NULL; return NULL;
} }
/**
* aa_setup_default_label - create the initial default label
*/
struct aa_label *aa_setup_default_label(void)
{
struct aa_profile *profile = aa_alloc_profile("default", NULL,
GFP_KERNEL);
if (!profile)
return NULL;
/* the default profile pretends to be unconfined until it is replaced */
profile->label.flags |= FLAG_IX_ON_NAME_ERROR | FLAG_UNCONFINED;
profile->mode = APPARMOR_UNCONFINED;
profile->ns = aa_get_ns(root_ns);
__add_profile(&root_ns->base.profiles, profile);
return &profile->label;
}
/* TODO: profile accounting - setup in remove */ /* TODO: profile accounting - setup in remove */
/** /**
...@@ -766,7 +410,9 @@ struct aa_profile *aa_find_child(struct aa_profile *parent, const char *name) ...@@ -766,7 +410,9 @@ struct aa_profile *aa_find_child(struct aa_profile *parent, const char *name)
struct aa_profile *profile; struct aa_profile *profile;
rcu_read_lock(); rcu_read_lock();
profile = aa_get_profile(__find_child(&parent->base.profiles, name)); do {
profile = __find_child(&parent->base.profiles, name);
} while (profile && !aa_get_profile_not0(profile));
rcu_read_unlock(); rcu_read_unlock();
/* refcount released by caller */ /* refcount released by caller */
...@@ -786,8 +432,7 @@ struct aa_profile *aa_find_child(struct aa_profile *parent, const char *name) ...@@ -786,8 +432,7 @@ struct aa_profile *aa_find_child(struct aa_profile *parent, const char *name)
* *
* Returns: unrefcounted policy or NULL if not found * Returns: unrefcounted policy or NULL if not found
*/ */
static struct aa_policy *__lookup_parent(struct aa_namespace *ns, static struct aa_policy *__lookup_parent(struct aa_ns *ns, const char *hname)
const char *hname)
{ {
struct aa_policy *policy; struct aa_policy *policy;
struct aa_profile *profile = NULL; struct aa_profile *profile = NULL;
...@@ -810,9 +455,10 @@ static struct aa_policy *__lookup_parent(struct aa_namespace *ns, ...@@ -810,9 +455,10 @@ static struct aa_policy *__lookup_parent(struct aa_namespace *ns,
} }
/** /**
* __lookup_profile - lookup the profile matching @hname * __lookupn_profile - lookup the profile matching @hname
* @base: base list to start looking up profile name from (NOT NULL) * @base: base list to start looking up profile name from (NOT NULL)
* @hname: hierarchical profile name (NOT NULL) * @hname: hierarchical profile name (NOT NULL)
* @n: length of @hname
* *
* Requires: rcu_read_lock be held * Requires: rcu_read_lock be held
* *
...@@ -820,53 +466,95 @@ static struct aa_policy *__lookup_parent(struct aa_namespace *ns, ...@@ -820,53 +466,95 @@ static struct aa_policy *__lookup_parent(struct aa_namespace *ns,
* *
* Do a relative name lookup, recursing through profile tree. * Do a relative name lookup, recursing through profile tree.
*/ */
static struct aa_profile *__lookup_profile(struct aa_policy *base, static struct aa_profile *__lookupn_profile(struct aa_policy *base,
const char *hname) const char *hname, size_t n)
{ {
struct aa_profile *profile = NULL; struct aa_profile *profile = NULL;
char *split; const char *split;
for (split = strstr(hname, "//"); split;) { for (split = strnstr(hname, "//", n); split;
split = strnstr(hname, "//", n)) {
profile = __strn_find_child(&base->profiles, hname, profile = __strn_find_child(&base->profiles, hname,
split - hname); split - hname);
if (!profile) if (!profile)
return NULL; return NULL;
base = &profile->base; base = &profile->base;
n -= split + 2 - hname;
hname = split + 2; hname = split + 2;
split = strstr(hname, "//");
} }
profile = __find_child(&base->profiles, hname); if (n)
return __strn_find_child(&base->profiles, hname, n);
return NULL;
}
return profile; static struct aa_profile *__lookup_profile(struct aa_policy *base,
const char *hname)
{
return __lookupn_profile(base, hname, strlen(hname));
} }
/** /**
* aa_lookup_profile - find a profile by its full or partial name * aa_lookup_profile - find a profile by its full or partial name
* @ns: the namespace to start from (NOT NULL) * @ns: the namespace to start from (NOT NULL)
* @hname: name to do lookup on. Does not contain namespace prefix (NOT NULL) * @hname: name to do lookup on. Does not contain namespace prefix (NOT NULL)
* @n: size of @hname
* *
* Returns: refcounted profile or NULL if not found * Returns: refcounted profile or NULL if not found
*/ */
struct aa_profile *aa_lookup_profile(struct aa_namespace *ns, const char *hname) struct aa_profile *aa_lookupn_profile(struct aa_ns *ns, const char *hname,
size_t n)
{ {
struct aa_profile *profile; struct aa_profile *profile;
rcu_read_lock(); rcu_read_lock();
do { do {
profile = __lookup_profile(&ns->base, hname); profile = __lookupn_profile(&ns->base, hname, n);
} while (profile && !aa_get_profile_not0(profile)); } while (profile && !aa_get_profile_not0(profile));
rcu_read_unlock(); rcu_read_unlock();
/* the unconfined profile is not in the regular profile list */ /* the unconfined profile is not in the regular profile list */
if (!profile && strcmp(hname, "unconfined") == 0) if (!profile && strncmp(hname, "unconfined", n) == 0)
profile = aa_get_newest_profile(ns->unconfined); profile = aa_get_newest_profile(ns->unconfined);
/* refcount released by caller */ /* refcount released by caller */
return profile; return profile;
} }
struct aa_profile *aa_lookup_profile(struct aa_ns *ns, const char *hname)
{
return aa_lookupn_profile(ns, hname, strlen(hname));
}
struct aa_profile *aa_fqlookupn_profile(struct aa_label *base,
const char *fqname, size_t n)
{
struct aa_profile *profile;
struct aa_ns *ns;
const char *name, *ns_name;
size_t ns_len;
name = aa_splitn_fqname(fqname, n, &ns_name, &ns_len);
if (ns_name) {
ns = aa_findn_ns(labels_ns(base), ns_name, ns_len);
if (!ns)
return NULL;
} else
ns = aa_get_ns(labels_ns(base));
if (name)
profile = aa_lookupn_profile(ns, name, n - (name - fqname));
else if (ns)
/* default profile for ns, currently unconfined */
profile = aa_get_newest_profile(ns->unconfined);
else
profile = NULL;
aa_put_ns(ns);
return profile;
}
/** /**
* replacement_allowed - test to see if replacement is allowed * replacement_allowed - test to see if replacement is allowed
* @profile: profile to test if it can be replaced (MAYBE NULL) * @profile: profile to test if it can be replaced (MAYBE NULL)
...@@ -879,7 +567,7 @@ static int replacement_allowed(struct aa_profile *profile, int noreplace, ...@@ -879,7 +567,7 @@ static int replacement_allowed(struct aa_profile *profile, int noreplace,
const char **info) const char **info)
{ {
if (profile) { if (profile) {
if (profile->flags & PFLAG_IMMUTABLE) { if (profile->label.flags & FLAG_IMMUTIBLE) {
*info = "cannot replace immutible profile"; *info = "cannot replace immutible profile";
return -EPERM; return -EPERM;
} else if (noreplace) { } else if (noreplace) {
...@@ -890,58 +578,94 @@ static int replacement_allowed(struct aa_profile *profile, int noreplace, ...@@ -890,58 +578,94 @@ static int replacement_allowed(struct aa_profile *profile, int noreplace,
return 0; return 0;
} }
/* audit callback for net specific fields */
static void audit_cb(struct audit_buffer *ab, void *va)
{
struct common_audit_data *sa = va;
if (aad(sa)->iface.ns) {
audit_log_format(ab, " ns=");
audit_log_untrustedstring(ab, aad(sa)->iface.ns);
}
}
/** /**
* aa_audit_policy - Do auditing of policy changes * audit_policy - Do auditing of policy changes
* @label: label to check if it can manage policy
* @op: policy operation being performed * @op: policy operation being performed
* @gfp: memory allocation flags * @profile: name of profile being manipulated (NOT NULL)
* @name: name of profile being manipulated (NOT NULL)
* @info: any extra information to be audited (MAYBE NULL) * @info: any extra information to be audited (MAYBE NULL)
* @error: error code * @error: error code
* *
* Returns: the error to be returned after audit is done * Returns: the error to be returned after audit is done
*/ */
static int audit_policy(int op, gfp_t gfp, const char *name, const char *info, static int audit_policy(struct aa_label *label, const char *op,
int error) const char *ns_name, const char *name,
const char *info, int error)
{
DEFINE_AUDIT_DATA(sa, LSM_AUDIT_DATA_NONE, op);
// aad(&sa)->op = op;
aad(&sa)->iface.ns = ns_name;
aad(&sa)->name = name;
aad(&sa)->info = info;
aad(&sa)->error = error;
aad(&sa)->label = label;
aa_audit_msg(AUDIT_APPARMOR_STATUS, &sa, audit_cb);
return error;
}
bool policy_admin_capable(void)
{ {
struct common_audit_data sa; struct user_namespace *user_ns = current_user_ns();
struct apparmor_audit_data aad = {0,}; struct aa_ns *ns = aa_get_current_ns();
sa.type = LSM_AUDIT_DATA_NONE; bool response = false;
sa.aad = &aad;
aad.op = op; if (ns_capable(user_ns, CAP_MAC_ADMIN) &&
aad.name = name; (user_ns == &init_user_ns ||
aad.info = info; (user_ns->level == 1 && ns != root_ns)))
aad.error = error; response = true;
aa_put_ns(ns);
return aa_audit(AUDIT_APPARMOR_STATUS, __aa_current_profile(), gfp,
&sa, NULL); return response;
} }
/** /**
* aa_may_manage_policy - can the current task manage policy * aa_may_manage_policy - can the current task manage policy
* @label: label to check if it can manage policy
* @op: the policy manipulation operation being done * @op: the policy manipulation operation being done
* *
* Returns: true if the task is allowed to manipulate policy * Returns: 0 if the task is allowed to manipulate policy else error
*/ */
bool aa_may_manage_policy(int op) int aa_may_manage_policy(struct aa_label *label, u32 mask)
{ {
const char *op;
if (mask & AA_MAY_REMOVE_POLICY)
op = OP_PROF_RM;
else if (mask & AA_MAY_REPLACE_POLICY)
op = OP_PROF_REPL;
else
op = OP_PROF_LOAD;
/* check if loading policy is locked out */ /* check if loading policy is locked out */
if (aa_g_lock_policy) { if (aa_g_lock_policy)
audit_policy(op, GFP_KERNEL, NULL, "policy_locked", -EACCES); return audit_policy(label, op, NULL, NULL, "policy_locked",
return 0; -EACCES);
}
if (!capable(CAP_MAC_ADMIN)) { if (!policy_admin_capable())
audit_policy(op, GFP_KERNEL, NULL, "not policy admin", -EACCES); return audit_policy(label, op, NULL, NULL, "not policy admin",
return 0; -EACCES);
}
return 1; /* TODO: add fine grained mediation of policy loads */
return 0;
} }
static struct aa_profile *__list_lookup_parent(struct list_head *lh, static struct aa_profile *__list_lookup_parent(struct list_head *lh,
struct aa_profile *profile) struct aa_profile *profile)
{ {
const char *base = hname_tail(profile->base.hname); const char *base = basename(profile->base.hname);
long len = base - profile->base.hname; long len = base - profile->base.hname;
struct aa_load_ent *ent; struct aa_load_ent *ent;
...@@ -965,7 +689,6 @@ static struct aa_profile *__list_lookup_parent(struct list_head *lh, ...@@ -965,7 +689,6 @@ static struct aa_profile *__list_lookup_parent(struct list_head *lh,
* __replace_profile - replace @old with @new on a list * __replace_profile - replace @old with @new on a list
* @old: profile to be replaced (NOT NULL) * @old: profile to be replaced (NOT NULL)
* @new: profile to replace @old with (NOT NULL) * @new: profile to replace @old with (NOT NULL)
* @share_replacedby: transfer @old->replacedby to @new
* *
* Will duplicate and refcount elements that @new inherits from @old * Will duplicate and refcount elements that @new inherits from @old
* and will inherit @old children. * and will inherit @old children.
...@@ -974,8 +697,7 @@ static struct aa_profile *__list_lookup_parent(struct list_head *lh, ...@@ -974,8 +697,7 @@ static struct aa_profile *__list_lookup_parent(struct list_head *lh,
* *
* Requires: namespace list lock be held, or list not be shared * Requires: namespace list lock be held, or list not be shared
*/ */
static void __replace_profile(struct aa_profile *old, struct aa_profile *new, static void __replace_profile(struct aa_profile *old, struct aa_profile *new)
bool share_replacedby)
{ {
struct aa_profile *child, *tmp; struct aa_profile *child, *tmp;
...@@ -990,7 +712,7 @@ static void __replace_profile(struct aa_profile *old, struct aa_profile *new, ...@@ -990,7 +712,7 @@ static void __replace_profile(struct aa_profile *old, struct aa_profile *new,
p = __find_child(&new->base.profiles, child->base.name); p = __find_child(&new->base.profiles, child->base.name);
if (p) { if (p) {
/* @p replaces @child */ /* @p replaces @child */
__replace_profile(child, p, share_replacedby); __replace_profile(child, p);
continue; continue;
} }
...@@ -1008,14 +730,8 @@ static void __replace_profile(struct aa_profile *old, struct aa_profile *new, ...@@ -1008,14 +730,8 @@ static void __replace_profile(struct aa_profile *old, struct aa_profile *new,
struct aa_profile *parent = aa_deref_parent(old); struct aa_profile *parent = aa_deref_parent(old);
rcu_assign_pointer(new->parent, aa_get_profile(parent)); rcu_assign_pointer(new->parent, aa_get_profile(parent));
} }
__aa_update_replacedby(old, new); aa_label_replace(&old->label, &new->label);
if (share_replacedby) { /* migrate dents must come after label replacement b/c update */
aa_put_replacedby(new->replacedby);
new->replacedby = aa_get_replacedby(old->replacedby);
} else if (!rcu_access_pointer(new->replacedby->profile))
/* aafs interface uses replacedby */
rcu_assign_pointer(new->replacedby->profile,
aa_get_profile(new));
__aa_fs_profile_migrate_dents(old, new); __aa_fs_profile_migrate_dents(old, new);
if (list_empty(&new->base.list)) { if (list_empty(&new->base.list)) {
...@@ -1037,7 +753,7 @@ static void __replace_profile(struct aa_profile *old, struct aa_profile *new, ...@@ -1037,7 +753,7 @@ static void __replace_profile(struct aa_profile *old, struct aa_profile *new,
* *
* Returns: profile to replace (no ref) on success else ptr error * Returns: profile to replace (no ref) on success else ptr error
*/ */
static int __lookup_replace(struct aa_namespace *ns, const char *hname, static int __lookup_replace(struct aa_ns *ns, const char *hname,
bool noreplace, struct aa_profile **p, bool noreplace, struct aa_profile **p,
const char **info) const char **info)
{ {
...@@ -1053,25 +769,57 @@ static int __lookup_replace(struct aa_namespace *ns, const char *hname, ...@@ -1053,25 +769,57 @@ static int __lookup_replace(struct aa_namespace *ns, const char *hname,
return 0; return 0;
} }
static void share_name(struct aa_profile *old, struct aa_profile *new)
{
aa_put_str(new->base.hname);
aa_get_str(old->base.hname);
new->base.hname = old->base.hname;
new->base.name = old->base.name;
new->label.hname = old->label.hname;
}
/* Update to newest version of parent after previous replacements
* Returns: unrefcount newest version of parent
*/
static struct aa_profile *update_to_newest_parent(struct aa_profile *new)
{
struct aa_profile *parent, *newest;
parent = rcu_dereference_protected(new->parent,
mutex_is_locked(&new->ns->lock));
newest = aa_get_newest_profile(parent);
/* parent replaced in this atomic set? */
if (newest != parent) {
aa_put_profile(parent);
rcu_assign_pointer(new->parent, newest);
} else
aa_put_profile(newest);
return newest;
}
/** /**
* aa_replace_profiles - replace profile(s) on the profile list * aa_replace_profiles - replace profile(s) on the profile list
* @label: label that is attempting to load/replace policy
* @mask: permission mask
* @udata: serialized data stream (NOT NULL) * @udata: serialized data stream (NOT NULL)
* @size: size of the serialized data stream * @size: size of the serialized data stream
* @noreplace: true if only doing addition, no replacement allowed
* *
* unpack and replace a profile on the profile list and uses of that profile * unpack and replace a profile on the profile list and uses of that profile
* by any aa_task_cxt. If the profile does not exist on the profile list * by any aa_task_ctx. If the profile does not exist on the profile list
* it is added. * it is added.
* *
* Returns: size of data consumed else error code on failure. * Returns: size of data consumed else error code on failure.
*/ */
ssize_t aa_replace_profiles(void *udata, size_t size, bool noreplace) ssize_t aa_replace_profiles(struct aa_label *label, u32 mask, void *udata,
size_t size)
{ {
const char *ns_name, *name = NULL, *info = NULL; const char *ns_name, *name = NULL, *info = NULL;
struct aa_namespace *ns = NULL; struct aa_ns *ns = NULL;
struct aa_load_ent *ent, *tmp; struct aa_load_ent *ent, *tmp;
int op = OP_PROF_REPL; const char *op = mask & AA_MAY_REPLACE_POLICY ? OP_PROF_REPL : OP_PROF_LOAD;
ssize_t error; ssize_t count, error;
LIST_HEAD(lh); LIST_HEAD(lh);
/* released below */ /* released below */
...@@ -1079,14 +827,39 @@ ssize_t aa_replace_profiles(void *udata, size_t size, bool noreplace) ...@@ -1079,14 +827,39 @@ ssize_t aa_replace_profiles(void *udata, size_t size, bool noreplace)
if (error) if (error)
goto out; goto out;
/* released below */ /* ensure that profiles are all for the same ns
ns = aa_prepare_namespace(ns_name); * TODO: update locking to remove this constaint. All profiles in
* the load set must succeed as a set or the load will
* fail. Sort ent list and take ns locks in hierarchy order
*/
count = 0;
list_for_each_entry(ent, &lh, list) {
if (ns_name) {
if (ent->ns_name &&
strcmp(ent->ns_name, ns_name) != 0) {
info = "policy load has mixed namespaces";
error = -EACCES;
goto fail;
}
} else if (ent->ns_name) {
if (count) {
info = "policy load has mixed namespaces";
error = -EACCES;
goto fail;
}
ns_name = ent->ns_name;
} else
count++;
}
if (ns_name) {
ns = aa_prepare_ns(labels_ns(label), ns_name);
if (!ns) { if (!ns) {
info = "failed to prepare namespace"; info = "failed to prepare namespace";
error = -ENOMEM; error = -ENOMEM;
name = ns_name;
goto fail; goto fail;
} }
} else
ns = aa_get_ns(labels_ns(label));
mutex_lock(&ns->lock); mutex_lock(&ns->lock);
/* setup parent and ns info */ /* setup parent and ns info */
...@@ -1094,21 +867,22 @@ ssize_t aa_replace_profiles(void *udata, size_t size, bool noreplace) ...@@ -1094,21 +867,22 @@ ssize_t aa_replace_profiles(void *udata, size_t size, bool noreplace)
struct aa_policy *policy; struct aa_policy *policy;
name = ent->new->base.hname; name = ent->new->base.hname;
error = __lookup_replace(ns, ent->new->base.hname, noreplace, error = __lookup_replace(ns, ent->new->base.hname,
!(mask & AA_MAY_REPLACE_POLICY),
&ent->old, &info); &ent->old, &info);
if (error) if (error)
goto fail_lock; goto fail_lock;
if (ent->new->rename) { if (ent->new->rename) {
error = __lookup_replace(ns, ent->new->rename, error = __lookup_replace(ns, ent->new->rename,
noreplace, &ent->rename, !(mask & AA_MAY_REPLACE_POLICY),
&info); &ent->rename, &info);
if (error) if (error)
goto fail_lock; goto fail_lock;
} }
/* released when @new is freed */ /* released when @new is freed */
ent->new->ns = aa_get_namespace(ns); ent->new->ns = aa_get_ns(ns);
if (ent->old || ent->rename) if (ent->old || ent->rename)
continue; continue;
...@@ -1134,14 +908,7 @@ ssize_t aa_replace_profiles(void *udata, size_t size, bool noreplace) ...@@ -1134,14 +908,7 @@ ssize_t aa_replace_profiles(void *udata, size_t size, bool noreplace)
/* create new fs entries for introspection if needed */ /* create new fs entries for introspection if needed */
list_for_each_entry(ent, &lh, list) { list_for_each_entry(ent, &lh, list) {
if (ent->old) { if (!ent->old) {
/* inherit old interface files */
/* if (ent->rename)
TODO: support rename */
/* } else if (ent->rename) {
TODO: support rename */
} else {
struct dentry *parent; struct dentry *parent;
if (rcu_access_pointer(ent->new->parent)) { if (rcu_access_pointer(ent->new->parent)) {
struct aa_profile *p; struct aa_profile *p;
...@@ -1153,7 +920,7 @@ ssize_t aa_replace_profiles(void *udata, size_t size, bool noreplace) ...@@ -1153,7 +920,7 @@ ssize_t aa_replace_profiles(void *udata, size_t size, bool noreplace)
} }
if (error) { if (error) {
info = "failed to create "; info = "failed to create";
goto fail_lock; goto fail_lock;
} }
} }
...@@ -1163,50 +930,33 @@ ssize_t aa_replace_profiles(void *udata, size_t size, bool noreplace) ...@@ -1163,50 +930,33 @@ ssize_t aa_replace_profiles(void *udata, size_t size, bool noreplace)
list_del_init(&ent->list); list_del_init(&ent->list);
op = (!ent->old && !ent->rename) ? OP_PROF_LOAD : OP_PROF_REPL; op = (!ent->old && !ent->rename) ? OP_PROF_LOAD : OP_PROF_REPL;
audit_policy(op, GFP_ATOMIC, ent->new->base.name, NULL, error); audit_policy(label, op, ns_name, ent->new->base.hname, NULL, error);
if (ent->old) { if (ent->old) {
__replace_profile(ent->old, ent->new, 1); share_name(ent->old, ent->new);
if (ent->rename) { __replace_profile(ent->old, ent->new);
/* aafs interface uses replacedby */ if (ent->rename)
struct aa_replacedby *r = ent->new->replacedby; __replace_profile(ent->rename, ent->new);
rcu_assign_pointer(r->profile,
aa_get_profile(ent->new));
__replace_profile(ent->rename, ent->new, 0);
}
} else if (ent->rename) { } else if (ent->rename) {
/* aafs interface uses replacedby */ /* TODO: case not actually supported yet */
rcu_assign_pointer(ent->new->replacedby->profile, ;
aa_get_profile(ent->new));
__replace_profile(ent->rename, ent->new, 0);
} else if (ent->new->parent) {
struct aa_profile *parent, *newest;
parent = aa_deref_parent(ent->new);
newest = aa_get_newest_profile(parent);
/* parent replaced in this atomic set? */
if (newest != parent) {
aa_get_profile(newest);
aa_put_profile(parent);
rcu_assign_pointer(ent->new->parent, newest);
} else
aa_put_profile(newest);
/* aafs interface uses replacedby */
rcu_assign_pointer(ent->new->replacedby->profile,
aa_get_profile(ent->new));
__list_add_profile(&parent->base.profiles, ent->new);
} else { } else {
/* aafs interface uses replacedby */ struct list_head *lh;
rcu_assign_pointer(ent->new->replacedby->profile, if (rcu_access_pointer(ent->new->parent)) {
aa_get_profile(ent->new)); struct aa_profile *parent;
__list_add_profile(&ns->base.profiles, ent->new); parent = update_to_newest_parent(ent->new);
lh = &parent->base.profiles;
} else
lh = &ns->base.profiles;
__add_profile(lh, ent->new);
} }
aa_load_ent_free(ent); aa_load_ent_free(ent);
} }
__aa_labelset_update_subtree(ns);
mutex_unlock(&ns->lock); mutex_unlock(&ns->lock);
out: out:
aa_put_namespace(ns); aa_put_ns(ns);
if (error) if (error)
return error; return error;
...@@ -1215,7 +965,7 @@ ssize_t aa_replace_profiles(void *udata, size_t size, bool noreplace) ...@@ -1215,7 +965,7 @@ ssize_t aa_replace_profiles(void *udata, size_t size, bool noreplace)
fail_lock: fail_lock:
mutex_unlock(&ns->lock); mutex_unlock(&ns->lock);
fail: fail:
error = audit_policy(op, GFP_KERNEL, name, info, error); error = audit_policy(label, op, ns_name, name, info, error);
list_for_each_entry_safe(ent, tmp, &lh, list) { list_for_each_entry_safe(ent, tmp, &lh, list) {
list_del_init(&ent->list); list_del_init(&ent->list);
...@@ -1227,6 +977,7 @@ ssize_t aa_replace_profiles(void *udata, size_t size, bool noreplace) ...@@ -1227,6 +977,7 @@ ssize_t aa_replace_profiles(void *udata, size_t size, bool noreplace)
/** /**
* aa_remove_profiles - remove profile(s) from the system * aa_remove_profiles - remove profile(s) from the system
* @label: label attempting to remove policy
* @fqname: name of the profile or namespace to remove (NOT NULL) * @fqname: name of the profile or namespace to remove (NOT NULL)
* @size: size of the name * @size: size of the name
* *
...@@ -1237,11 +988,12 @@ ssize_t aa_replace_profiles(void *udata, size_t size, bool noreplace) ...@@ -1237,11 +988,12 @@ ssize_t aa_replace_profiles(void *udata, size_t size, bool noreplace)
* *
* Returns: size of data consume else error code if fails * Returns: size of data consume else error code if fails
*/ */
ssize_t aa_remove_profiles(char *fqname, size_t size) ssize_t aa_remove_profiles(struct aa_label *label, char *fqname, size_t size)
{ {
struct aa_namespace *root, *ns = NULL; struct aa_ns *root = NULL, *ns = NULL;
struct aa_profile *profile = NULL; struct aa_profile *profile = NULL;
const char *name = fqname, *info = NULL; const char *name = fqname, *info = NULL;
char *ns_name = NULL;
ssize_t error = 0; ssize_t error = 0;
if (*fqname == 0) { if (*fqname == 0) {
...@@ -1250,13 +1002,12 @@ ssize_t aa_remove_profiles(char *fqname, size_t size) ...@@ -1250,13 +1002,12 @@ ssize_t aa_remove_profiles(char *fqname, size_t size)
goto fail; goto fail;
} }
root = aa_current_profile()->ns; root = labels_ns(label);
if (fqname[0] == ':') { if (fqname[0] == ':') {
char *ns_name;
name = aa_split_fqname(fqname, &ns_name); name = aa_split_fqname(fqname, &ns_name);
/* released below */ /* released below */
ns = aa_find_namespace(root, ns_name); ns = aa_find_ns(root, ns_name);
if (!ns) { if (!ns) {
info = "namespace does not exist"; info = "namespace does not exist";
error = -ENOENT; error = -ENOENT;
...@@ -1264,12 +1015,12 @@ ssize_t aa_remove_profiles(char *fqname, size_t size) ...@@ -1264,12 +1015,12 @@ ssize_t aa_remove_profiles(char *fqname, size_t size)
} }
} else } else
/* released below */ /* released below */
ns = aa_get_namespace(root); ns = aa_get_ns(root);
if (!name) { if (!name) {
/* remove namespace - can only happen if fqname[0] == ':' */ /* remove namespace - can only happen if fqname[0] == ':' */
mutex_lock(&ns->parent->lock); mutex_lock(&ns->parent->lock);
__remove_namespace(ns); __aa_remove_ns(ns);
mutex_unlock(&ns->parent->lock); mutex_unlock(&ns->parent->lock);
} else { } else {
/* remove profile */ /* remove profile */
...@@ -1282,20 +1033,19 @@ ssize_t aa_remove_profiles(char *fqname, size_t size) ...@@ -1282,20 +1033,19 @@ ssize_t aa_remove_profiles(char *fqname, size_t size)
} }
name = profile->base.hname; name = profile->base.hname;
__remove_profile(profile); __remove_profile(profile);
__aa_labelset_update_subtree(ns);
mutex_unlock(&ns->lock); mutex_unlock(&ns->lock);
} }
/* don't fail removal if audit fails */ /* don't fail removal if audit fails */
(void) audit_policy(OP_PROF_RM, GFP_KERNEL, name, info, error); (void) audit_policy(label, OP_PROF_RM, ns_name, name, info, error);
aa_put_namespace(ns);
aa_put_profile(profile); aa_put_profile(profile);
return size; return size;
fail_ns_lock: fail_ns_lock:
mutex_unlock(&ns->lock); mutex_unlock(&ns->lock);
aa_put_namespace(ns);
fail: fail:
(void) audit_policy(OP_PROF_RM, GFP_KERNEL, name, info, error); (void) audit_policy(label, OP_PROF_RM, ns_name, name, info, error);
return error; return error;
} }
/*
* AppArmor security module
*
* This file contains AppArmor policy manipulation functions
*
* Copyright (C) 1998-2008 Novell/SUSE
* Copyright 2009-2015 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.
*
* 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/context.h"
#include "include/policy_ns.h"
#include "include/label.h"
#include "include/policy.h"
/* 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_na_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;
} else
return aa_hidden_ns_name;
}
/**
* 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(sizeof(*ns), GFP_KERNEL);
AA_DEBUG("%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);
mutex_init(&ns->lock);
/* released by free_namespace */
ns->unconfined = aa_alloc_profile("unconfined", NULL, GFP_KERNEL);
if (!ns->unconfined)
goto fail_unconfined;
ns->unconfined->label.flags |= FLAG_IX_ON_NAME_ERROR |
FLAG_IMMUTIBLE | FLAG_NS_COUNT | FLAG_UNCONFINED;
ns->unconfined->mode = APPARMOR_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:
kzfree(ns->base.hname);
fail_ns:
kzfree(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);
kzfree(ns);
}
/**
* __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 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);
}
/**
* aa_find_ns - look up a profile namespace on the namespace list
* @root: 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_findn_ns(struct aa_ns *root, const char *name, size_t n)
{
struct aa_ns *ns = NULL;
rcu_read_lock();
ns = aa_get_ns(__aa_findn_ns(&root->sub_ns, name, n));
rcu_read_unlock();
return ns;
}
/**
* aa_find_ns - look up a profile namespace on the namespace list
* @root: namespace to search in (NOT NULL)
* @name: name of namespace to find (NOT NULL)
*
* Returns: a refcounted namespace on the list, or NULL if no namespace
* called @name exists.
*
* refcount released by caller
*/
struct aa_ns *aa_find_ns(struct aa_ns *root, const char *name)
{
return aa_findn_ns(root, name, strlen(name));
}
/**
* aa_prepare_ns - find an existing or create a new namespace of @name
* @root: ns to treat as root
* @name: the namespace to find or add (NOT NULL)
*
* Returns: refcounted namespace or NULL if failed to create one
*/
struct aa_ns *aa_prepare_ns(struct aa_ns *root, const char *name)
{
struct aa_ns *ns;
mutex_lock(&root->lock);
/* try and find the specified ns and if it doesn't exist create it */
/* released by caller */
ns = aa_get_ns(__aa_findn_ns(&root->sub_ns, name, strlen(name)));
if (!ns) {
ns = alloc_ns(root->base.hname, name);
if (!ns)
goto out;
mutex_lock(&ns->lock);
if (__aa_fs_ns_mkdir(ns, ns_subns_dir(root), name)) {
AA_ERROR("Failed to create interface for ns %s\n",
ns->base.name);
mutex_unlock(&ns->lock);
aa_free_ns(ns);
ns = NULL;
goto out;
}
ns->parent = aa_get_ns(root);
ns->level = root->level + 1;
list_add_rcu(&ns->base.list, &root->sub_ns);
/* add list ref */
aa_get_ns(ns);
mutex_unlock(&ns->lock);
}
out:
mutex_unlock(&root->lock);
/* return ref */
return ns;
}
static void __ns_list_release(struct list_head *head);
/**
* destroy_namespace - 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(&ns->lock);
/* 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)
__aa_proxy_redirect(ns_unconfined(ns),
ns_unconfined(ns->parent));
__aa_fs_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)
{
/* released by aa_free_root_ns - used as list ref*/
root_ns = alloc_ns(NULL, "root");
if (!root_ns)
return -ENOMEM;
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;
destroy_ns(ns);
aa_put_ns(ns);
}
...@@ -20,15 +20,25 @@ ...@@ -20,15 +20,25 @@
#include <asm/unaligned.h> #include <asm/unaligned.h>
#include <linux/ctype.h> #include <linux/ctype.h>
#include <linux/errno.h> #include <linux/errno.h>
#include <linux/string.h>
#include "include/apparmor.h" #include "include/apparmor.h"
#include "include/audit.h" #include "include/audit.h"
#include "include/context.h" #include "include/context.h"
#include "include/crypto.h" #include "include/crypto.h"
#include "include/match.h" #include "include/match.h"
#include "include/path.h"
#include "include/policy.h" #include "include/policy.h"
#include "include/policy_unpack.h" #include "include/policy_unpack.h"
#define K_ABI_MASK 0x3ff
#define FORCE_COMPLAIN_FLAG 0x800
#define VERSION_CMP(OP, X, Y) (((X) & K_ABI_MASK) OP ((Y) & K_ABI_MASK))
#define v5 5 /* base version */
#define v6 6 /* per entry policydb mediation check */
#define v7 7 /* full network masking */
/* /*
* The AppArmor interface treats data as a type byte followed by the * The AppArmor interface treats data as a type byte followed by the
* actual data. The interface has the notion of a a named entry * actual data. The interface has the notion of a a named entry
...@@ -70,18 +80,23 @@ struct aa_ext { ...@@ -70,18 +80,23 @@ struct aa_ext {
static void audit_cb(struct audit_buffer *ab, void *va) static void audit_cb(struct audit_buffer *ab, void *va)
{ {
struct common_audit_data *sa = va; struct common_audit_data *sa = va;
if (sa->aad->iface.target) {
struct aa_profile *name = sa->aad->iface.target; if (aad(sa)->iface.ns) {
audit_log_format(ab, " ns=");
audit_log_untrustedstring(ab, aad(sa)->iface.ns);
}
if (aad(sa)->name) {
audit_log_format(ab, " name="); audit_log_format(ab, " name=");
audit_log_untrustedstring(ab, name->base.hname); audit_log_untrustedstring(ab, aad(sa)->name);
} }
if (sa->aad->iface.pos) if (aad(sa)->iface.pos)
audit_log_format(ab, " offset=%ld", sa->aad->iface.pos); audit_log_format(ab, " offset=%ld", aad(sa)->iface.pos);
} }
/** /**
* audit_iface - do audit message for policy unpacking/load/replace/remove * audit_iface - do audit message for policy unpacking/load/replace/remove
* @new: profile if it has been allocated (MAYBE NULL) * @new: profile if it has been allocated (MAYBE NULL)
* @ns_name: name of the ns the profile is to be loaded to (MAY BE NULL)
* @name: name of the profile being manipulated (MAYBE NULL) * @name: name of the profile being manipulated (MAYBE NULL)
* @info: any extra info about the failure (MAYBE NULL) * @info: any extra info about the failure (MAYBE NULL)
* @e: buffer position info * @e: buffer position info
...@@ -89,23 +104,24 @@ static void audit_cb(struct audit_buffer *ab, void *va) ...@@ -89,23 +104,24 @@ static void audit_cb(struct audit_buffer *ab, void *va)
* *
* Returns: %0 or error * Returns: %0 or error
*/ */
static int audit_iface(struct aa_profile *new, const char *name, static int audit_iface(struct aa_profile *new, const char *ns_name,
const char *info, struct aa_ext *e, int error) const char *name, const char *info, struct aa_ext *e,
int error)
{ {
struct aa_profile *profile = __aa_current_profile(); struct aa_profile *profile = labels_profile(aa_current_raw_label());
struct common_audit_data sa; DEFINE_AUDIT_DATA(sa, LSM_AUDIT_DATA_NONE, NULL);
struct apparmor_audit_data aad = {0,};
sa.type = LSM_AUDIT_DATA_NONE;
sa.aad = &aad;
if (e) if (e)
aad.iface.pos = e->pos - e->start; aad(&sa)->iface.pos = e->pos - e->start;
aad.iface.target = new;
aad.name = name; aad(&sa)->iface.ns = ns_name;
aad.info = info; if (new)
aad.error = error; aad(&sa)->name = new->base.hname;
else
return aa_audit(AUDIT_APPARMOR_STATUS, profile, GFP_KERNEL, &sa, aad(&sa)->name = name;
audit_cb); aad(&sa)->info = info;
aad(&sa)->error = error;
return aa_audit(AUDIT_APPARMOR_STATUS, profile, &sa, audit_cb);
} }
/* test if read will be in packed data bounds */ /* test if read will be in packed data bounds */
...@@ -193,6 +209,19 @@ static bool unpack_nameX(struct aa_ext *e, enum aa_code code, const char *name) ...@@ -193,6 +209,19 @@ static bool unpack_nameX(struct aa_ext *e, enum aa_code code, const char *name)
return 0; return 0;
} }
static bool unpack_u16(struct aa_ext *e, u16 *data, const char *name)
{
if (unpack_nameX(e, AA_U16, name)) {
if (!inbounds(e, sizeof(u16)))
return 0;
if (data)
*data = le16_to_cpu(get_unaligned((u16 *) e->pos));
e->pos += sizeof(u16);
return 1;
}
return 0;
}
static bool unpack_u32(struct aa_ext *e, u32 *data, const char *name) static bool unpack_u32(struct aa_ext *e, u32 *data, const char *name)
{ {
if (unpack_nameX(e, AA_U32, name)) { if (unpack_nameX(e, AA_U32, name)) {
...@@ -340,12 +369,7 @@ static struct aa_dfa *unpack_dfa(struct aa_ext *e) ...@@ -340,12 +369,7 @@ static struct aa_dfa *unpack_dfa(struct aa_ext *e)
((e->pos - e->start) & 7); ((e->pos - e->start) & 7);
size_t pad = ALIGN(sz, 8) - sz; size_t pad = ALIGN(sz, 8) - sz;
int flags = TO_ACCEPT1_FLAG(YYTD_DATA32) | int flags = TO_ACCEPT1_FLAG(YYTD_DATA32) |
TO_ACCEPT2_FLAG(YYTD_DATA32); TO_ACCEPT2_FLAG(YYTD_DATA32) | DFA_FLAG_VERIFY_STATES;
if (aa_g_paranoid_load)
flags |= DFA_FLAG_VERIFY_STATES;
dfa = aa_dfa_unpack(blob + pad, size - pad, flags); dfa = aa_dfa_unpack(blob + pad, size - pad, flags);
if (IS_ERR(dfa)) if (IS_ERR(dfa))
...@@ -389,7 +413,7 @@ static bool unpack_trans_table(struct aa_ext *e, struct aa_profile *profile) ...@@ -389,7 +413,7 @@ static bool unpack_trans_table(struct aa_ext *e, struct aa_profile *profile)
profile->file.trans.size = size; profile->file.trans.size = size;
for (i = 0; i < size; i++) { for (i = 0; i < size; i++) {
char *str; char *str;
int c, j, size2 = unpack_strdup(e, &str, NULL); int c, j, pos, size2 = unpack_strdup(e, &str, NULL);
/* unpack_strdup verifies that the last character is /* unpack_strdup verifies that the last character is
* null termination byte. * null termination byte.
*/ */
...@@ -401,19 +425,24 @@ static bool unpack_trans_table(struct aa_ext *e, struct aa_profile *profile) ...@@ -401,19 +425,24 @@ static bool unpack_trans_table(struct aa_ext *e, struct aa_profile *profile)
goto fail; goto fail;
/* count internal # of internal \0 */ /* count internal # of internal \0 */
for (c = j = 0; j < size2 - 2; j++) { for (c = j = 0; j < size2 - 1; j++) {
if (!str[j]) if (!str[j]) {
pos = j;
c++; c++;
} }
}
if (*str == ':') { if (*str == ':') {
/* first character after : must be valid */
if (!str[1])
goto fail;
/* beginning with : requires an embedded \0, /* beginning with : requires an embedded \0,
* verify that exactly 1 internal \0 exists * verify that exactly 1 internal \0 exists
* trailing \0 already verified by unpack_strdup * trailing \0 already verified by unpack_strdup
*/ */
if (c != 1) if (c == 1)
goto fail; /* convert \0 back to : for label_parse */
/* first character after : must be valid */ str[pos] = ':';
if (!str[1]) else if (c > 1)
goto fail; goto fail;
} else if (c) } else if (c)
/* fail - all other cases with embedded \0 */ /* fail - all other cases with embedded \0 */
...@@ -472,21 +501,35 @@ static bool unpack_rlimits(struct aa_ext *e, struct aa_profile *profile) ...@@ -472,21 +501,35 @@ static bool unpack_rlimits(struct aa_ext *e, struct aa_profile *profile)
* *
* NOTE: unpack profile sets audit struct if there is a failure * NOTE: unpack profile sets audit struct if there is a failure
*/ */
static struct aa_profile *unpack_profile(struct aa_ext *e) static struct aa_profile *unpack_profile(struct aa_ext *e, char **ns_name)
{ {
struct aa_profile *profile = NULL; struct aa_profile *profile = NULL;
const char *name = NULL; const char *tmpname, *tmpns = NULL, *name = NULL;
const char *info = "failed to unpack profile";
size_t size = 0, ns_len;
int i, error = -EPROTO; int i, error = -EPROTO;
kernel_cap_t tmpcap; kernel_cap_t tmpcap;
u32 tmp; u32 tmp;
*ns_name = NULL;
/* check that we have the right struct being passed */ /* check that we have the right struct being passed */
if (!unpack_nameX(e, AA_STRUCT, "profile")) if (!unpack_nameX(e, AA_STRUCT, "profile"))
goto fail; goto fail;
if (!unpack_str(e, &name, NULL)) if (!unpack_str(e, &name, NULL))
goto fail; goto fail;
if (*name == '\0')
goto fail;
tmpname = aa_splitn_fqname(name, strlen(name), &tmpns, &ns_len);
if (tmpns) {
*ns_name = kstrndup(tmpns, ns_len, GFP_KERNEL);
if (!*ns_name)
goto fail;
name = tmpname;
}
profile = aa_alloc_profile(name); profile = aa_alloc_profile(name, NULL, GFP_KERNEL);
if (!profile) if (!profile)
return ERR_PTR(-ENOMEM); return ERR_PTR(-ENOMEM);
...@@ -510,16 +553,19 @@ static struct aa_profile *unpack_profile(struct aa_ext *e) ...@@ -510,16 +553,19 @@ static struct aa_profile *unpack_profile(struct aa_ext *e)
profile->xmatch_len = tmp; profile->xmatch_len = tmp;
} }
/* disconnected attachment string is optional */
(void) unpack_str(e, &profile->disconnected, "disconnected");
/* per profile debug flags (complain, audit) */ /* per profile debug flags (complain, audit) */
if (!unpack_nameX(e, AA_STRUCT, "flags")) if (!unpack_nameX(e, AA_STRUCT, "flags"))
goto fail; goto fail;
if (!unpack_u32(e, &tmp, NULL)) if (!unpack_u32(e, &tmp, NULL))
goto fail; goto fail;
if (tmp & PACKED_FLAG_HAT) if (tmp & PACKED_FLAG_HAT)
profile->flags |= PFLAG_HAT; profile->label.flags |= FLAG_HAT;
if (!unpack_u32(e, &tmp, NULL)) if (!unpack_u32(e, &tmp, NULL))
goto fail; goto fail;
if (tmp == PACKED_MODE_COMPLAIN) if (tmp == PACKED_MODE_COMPLAIN || (e->version & FORCE_COMPLAIN_FLAG))
profile->mode = APPARMOR_COMPLAIN; profile->mode = APPARMOR_COMPLAIN;
else if (tmp == PACKED_MODE_KILL) else if (tmp == PACKED_MODE_KILL)
profile->mode = APPARMOR_KILL; profile->mode = APPARMOR_KILL;
...@@ -534,11 +580,9 @@ static struct aa_profile *unpack_profile(struct aa_ext *e) ...@@ -534,11 +580,9 @@ static struct aa_profile *unpack_profile(struct aa_ext *e)
goto fail; goto fail;
/* path_flags is optional */ /* path_flags is optional */
if (unpack_u32(e, &profile->path_flags, "path_flags")) if (!unpack_u32(e, &profile->path_flags, "path_flags"))
profile->path_flags |= profile->flags & PFLAG_MEDIATE_DELETED;
else
/* set a default value if path_flags field is not present */ /* set a default value if path_flags field is not present */
profile->path_flags = PFLAG_MEDIATE_DELETED; profile->path_flags = PATH_MEDIATE_DELETED;
if (!unpack_u32(e, &(profile->caps.allow.cap[0]), NULL)) if (!unpack_u32(e, &(profile->caps.allow.cap[0]), NULL))
goto fail; goto fail;
...@@ -576,6 +620,37 @@ static struct aa_profile *unpack_profile(struct aa_ext *e) ...@@ -576,6 +620,37 @@ static struct aa_profile *unpack_profile(struct aa_ext *e)
if (!unpack_rlimits(e, profile)) if (!unpack_rlimits(e, profile))
goto fail; goto fail;
size = unpack_array(e, "net_allowed_af");
if (size) {
for (i = 0; i < size; i++) {
/* discard extraneous rules that this kernel will
* never request
*/
if (i >= AF_MAX) {
u16 tmp;
if (!unpack_u16(e, &tmp, NULL) ||
!unpack_u16(e, &tmp, NULL) ||
!unpack_u16(e, &tmp, NULL))
goto fail;
continue;
}
if (!unpack_u16(e, &profile->net.allow[i], NULL))
goto fail;
if (!unpack_u16(e, &profile->net.audit[i], NULL))
goto fail;
if (!unpack_u16(e, &profile->net.quiet[i], NULL))
goto fail;
}
if (!unpack_nameX(e, AA_ARRAYEND, NULL))
goto fail;
}
if (VERSION_CMP(<, e->version, v7)) {
/* old policy always allowed these too */
profile->net.allow[AF_UNIX] = 0xffff;
profile->net.allow[AF_NETLINK] = 0xffff;
}
if (unpack_nameX(e, AA_STRUCT, "policydb")) { if (unpack_nameX(e, AA_STRUCT, "policydb")) {
/* generic policy dfa - optional and may be NULL */ /* generic policy dfa - optional and may be NULL */
profile->policy.dfa = unpack_dfa(e); profile->policy.dfa = unpack_dfa(e);
...@@ -596,7 +671,8 @@ static struct aa_profile *unpack_profile(struct aa_ext *e) ...@@ -596,7 +671,8 @@ static struct aa_profile *unpack_profile(struct aa_ext *e)
} }
if (!unpack_nameX(e, AA_STRUCTEND, NULL)) if (!unpack_nameX(e, AA_STRUCTEND, NULL))
goto fail; goto fail;
} } else
profile->policy.dfa = aa_get_dfa(nulldfa);
/* get file rules */ /* get file rules */
profile->file.dfa = unpack_dfa(e); profile->file.dfa = unpack_dfa(e);
...@@ -604,11 +680,16 @@ static struct aa_profile *unpack_profile(struct aa_ext *e) ...@@ -604,11 +680,16 @@ static struct aa_profile *unpack_profile(struct aa_ext *e)
error = PTR_ERR(profile->file.dfa); error = PTR_ERR(profile->file.dfa);
profile->file.dfa = NULL; profile->file.dfa = NULL;
goto fail; goto fail;
} } else if (profile->file.dfa) {
if (!unpack_u32(e, &profile->file.start, "dfa_start")) if (!unpack_u32(e, &profile->file.start, "dfa_start"))
/* default start state */ /* default start state */
profile->file.start = DFA_START; profile->file.start = DFA_START;
} else if (profile->policy.dfa &&
profile->policy.start[AA_CLASS_FILE]) {
profile->file.dfa = aa_get_dfa(profile->policy.dfa);
profile->file.start = profile->policy.start[AA_CLASS_FILE];
} else
profile->file.dfa = aa_get_dfa(nulldfa);
if (!unpack_trans_table(e, profile)) if (!unpack_trans_table(e, profile))
goto fail; goto fail;
...@@ -623,7 +704,7 @@ static struct aa_profile *unpack_profile(struct aa_ext *e) ...@@ -623,7 +704,7 @@ static struct aa_profile *unpack_profile(struct aa_ext *e)
name = NULL; name = NULL;
else if (!name) else if (!name)
name = "unknown"; name = "unknown";
audit_iface(profile, name, "failed to unpack profile", e, error); audit_iface(profile, NULL, name, info, e, error);
aa_free_profile(profile); aa_free_profile(profile);
return ERR_PTR(error); return ERR_PTR(error);
...@@ -646,24 +727,32 @@ static int verify_header(struct aa_ext *e, int required, const char **ns) ...@@ -646,24 +727,32 @@ static int verify_header(struct aa_ext *e, int required, const char **ns)
/* get the interface version */ /* get the interface version */
if (!unpack_u32(e, &e->version, "version")) { if (!unpack_u32(e, &e->version, "version")) {
if (required) { if (required) {
audit_iface(NULL, NULL, "invalid profile format", e, audit_iface(NULL, NULL, NULL, "invalid profile format",
error); e, error);
return error; return error;
} }
}
/* check that the interface version is currently supported */ /* Check that the interface version is currently supported.
if (e->version != 5) { * if not specified use previous version
audit_iface(NULL, NULL, "unsupported interface version", * Mask off everything that is not kernel abi version
*/
if (VERSION_CMP(<, e->version, v5) && VERSION_CMP(>, e->version, v7)) {
audit_iface(NULL, NULL, NULL, "unsupported interface version",
e, error); e, error);
return error; return error;
} }
}
/* read the namespace if present */ /* read the namespace if present */
if (unpack_str(e, &name, "namespace")) { if (unpack_str(e, &name, "namespace")) {
if (*name == '\0') {
audit_iface(NULL, NULL, NULL, "invalid namespace name",
e, error);
return error;
}
if (*ns && strcmp(*ns, name)) if (*ns && strcmp(*ns, name))
audit_iface(NULL, NULL, "invalid ns change", e, error); audit_iface(NULL, NULL, NULL, "invalid ns change", e,
error);
else if (!*ns) else if (!*ns)
*ns = name; *ns = name;
} }
...@@ -676,7 +765,7 @@ static bool verify_xindex(int xindex, int table_size) ...@@ -676,7 +765,7 @@ static bool verify_xindex(int xindex, int table_size)
int index, xtype; int index, xtype;
xtype = xindex & AA_X_TYPE_MASK; xtype = xindex & AA_X_TYPE_MASK;
index = xindex & AA_X_INDEX_MASK; index = xindex & AA_X_INDEX_MASK;
if (xtype == AA_X_TABLE && index > table_size) if (xtype == AA_X_TABLE && index >= table_size)
return 0; return 0;
return 1; return 1;
} }
...@@ -702,15 +791,13 @@ static bool verify_dfa_xindex(struct aa_dfa *dfa, int table_size) ...@@ -702,15 +791,13 @@ static bool verify_dfa_xindex(struct aa_dfa *dfa, int table_size)
*/ */
static int verify_profile(struct aa_profile *profile) static int verify_profile(struct aa_profile *profile)
{ {
if (aa_g_paranoid_load) {
if (profile->file.dfa && if (profile->file.dfa &&
!verify_dfa_xindex(profile->file.dfa, !verify_dfa_xindex(profile->file.dfa,
profile->file.trans.size)) { profile->file.trans.size)) {
audit_iface(profile, NULL, "Invalid named transition", audit_iface(profile, NULL, NULL,
NULL, -EPROTO); "Invalid named transition", NULL, -EPROTO);
return -EPROTO; return -EPROTO;
} }
}
return 0; return 0;
} }
...@@ -721,6 +808,7 @@ void aa_load_ent_free(struct aa_load_ent *ent) ...@@ -721,6 +808,7 @@ void aa_load_ent_free(struct aa_load_ent *ent)
aa_put_profile(ent->rename); aa_put_profile(ent->rename);
aa_put_profile(ent->old); aa_put_profile(ent->old);
aa_put_profile(ent->new); aa_put_profile(ent->new);
kfree(ent->ns_name);
kzfree(ent); kzfree(ent);
} }
} }
...@@ -759,13 +847,14 @@ int aa_unpack(void *udata, size_t size, struct list_head *lh, const char **ns) ...@@ -759,13 +847,14 @@ int aa_unpack(void *udata, size_t size, struct list_head *lh, const char **ns)
*ns = NULL; *ns = NULL;
while (e.pos < e.end) { while (e.pos < e.end) {
char *ns_name = NULL;
void *start; void *start;
error = verify_header(&e, e.pos == e.start, ns); error = verify_header(&e, e.pos == e.start, ns);
if (error) if (error)
goto fail; goto fail;
start = e.pos; start = e.pos;
profile = unpack_profile(&e); profile = unpack_profile(&e, &ns_name);
if (IS_ERR(profile)) { if (IS_ERR(profile)) {
error = PTR_ERR(profile); error = PTR_ERR(profile);
goto fail; goto fail;
...@@ -775,6 +864,7 @@ int aa_unpack(void *udata, size_t size, struct list_head *lh, const char **ns) ...@@ -775,6 +864,7 @@ int aa_unpack(void *udata, size_t size, struct list_head *lh, const char **ns)
if (error) if (error)
goto fail_profile; goto fail_profile;
if (aa_g_hash_policy)
error = aa_calc_profile_hash(profile, e.version, start, error = aa_calc_profile_hash(profile, e.version, start,
e.pos - start); e.pos - start);
if (error) if (error)
...@@ -787,6 +877,7 @@ int aa_unpack(void *udata, size_t size, struct list_head *lh, const char **ns) ...@@ -787,6 +877,7 @@ int aa_unpack(void *udata, size_t size, struct list_head *lh, const char **ns)
} }
ent->new = profile; ent->new = profile;
ent->ns_name = ns_name;
list_add_tail(&ent->list, lh); list_add_tail(&ent->list, lh);
} }
......
...@@ -33,50 +33,41 @@ ...@@ -33,50 +33,41 @@
* *
* Returns: size of string placed in @string else error code on failure * Returns: size of string placed in @string else error code on failure
*/ */
int aa_getprocattr(struct aa_profile *profile, char **string) int aa_getprocattr(struct aa_label *label, char **string)
{ {
char *str; struct aa_ns *ns = labels_ns(label);
int len = 0, mode_len = 0, ns_len = 0, name_len; struct aa_ns *current_ns = aa_get_current_ns();
const char *mode_str = aa_profile_mode_names[profile->mode]; int len;
const char *ns_name = NULL;
struct aa_namespace *ns = profile->ns;
struct aa_namespace *current_ns = __aa_current_profile()->ns;
char *s;
if (!aa_ns_visible(current_ns, ns))
return -EACCES;
ns_name = aa_ns_name(current_ns, ns);
ns_len = strlen(ns_name);
/* if the visible ns_name is > 0 increase size for : :// seperator */ if (!aa_ns_visible(current_ns, ns, true)) {
if (ns_len) aa_put_ns(current_ns);
ns_len += 4; return -EACCES;
}
/* unconfined profiles don't have a mode string appended */ len = aa_label_snxprint(NULL, 0, current_ns, label,
if (!unconfined(profile)) FLAG_SHOW_MODE | FLAG_VIEW_SUBNS |
mode_len = strlen(mode_str) + 3; /* + 3 for _() */ FLAG_HIDDEN_UNCONFINED);
AA_BUG(len < 0);
name_len = strlen(profile->base.hname); *string = kmalloc(len + 2, GFP_KERNEL);
len = mode_len + ns_len + name_len + 1; /* + 1 for \n */ if (!*string) {
s = str = kmalloc(len + 1, GFP_KERNEL); /* + 1 \0 */ aa_put_ns(current_ns);
if (!str)
return -ENOMEM; return -ENOMEM;
if (ns_len) {
/* skip over prefix current_ns->base.hname and separating // */
sprintf(s, ":%s://", ns_name);
s += ns_len;
} }
if (unconfined(profile))
/* mode string not being appended */ len = aa_label_snxprint(*string, len + 2, current_ns, label,
sprintf(s, "%s\n", profile->base.hname); FLAG_SHOW_MODE | FLAG_VIEW_SUBNS |
else FLAG_HIDDEN_UNCONFINED);
sprintf(s, "%s (%s)\n", profile->base.hname, mode_str); if (len < 0) {
*string = str; aa_put_ns(current_ns);
/* NOTE: len does not include \0 of string, not saved as part of file */
return len; return len;
}
(*string)[len] = '\n';
(*string)[len + 1] = 0;
aa_put_ns(current_ns);
return len + 1;
} }
/** /**
...@@ -87,13 +78,13 @@ int aa_getprocattr(struct aa_profile *profile, char **string) ...@@ -87,13 +78,13 @@ int aa_getprocattr(struct aa_profile *profile, char **string)
* *
* Returns: start position of name after token else NULL on failure * Returns: start position of name after token else NULL on failure
*/ */
static char *split_token_from_name(int op, char *args, u64 * token) static char *split_token_from_name(const char *op, char *args, u64 * token)
{ {
char *name; char *name;
*token = simple_strtoull(args, &name, 16); *token = simple_strtoull(args, &name, 16);
if ((name == args) || *name != '^') { if ((name == args) || *name != '^') {
AA_ERROR("%s: Invalid input '%s'", op_table[op], args); AA_ERROR("%s: Invalid input '%s'", op, args);
return ERR_PTR(-EINVAL); return ERR_PTR(-EINVAL);
} }
...@@ -138,28 +129,13 @@ int aa_setprocattr_changehat(char *args, size_t size, int test) ...@@ -138,28 +129,13 @@ int aa_setprocattr_changehat(char *args, size_t size, int test)
for (count = 0; (hat < end) && count < 16; ++count) { for (count = 0; (hat < end) && count < 16; ++count) {
char *next = hat + strlen(hat) + 1; char *next = hat + strlen(hat) + 1;
hats[count] = hat; hats[count] = hat;
AA_DEBUG("%s: (pid %d) Magic 0x%llx count %d hat '%s'\n"
, __func__, current->pid, token, count, hat);
hat = next; hat = next;
} }
} } else
AA_DEBUG("%s: (pid %d) Magic 0x%llx count %d Hat '%s'\n",
AA_DEBUG("%s: Magic 0x%llx Hat '%s'\n", __func__, current->pid, token, count, "<NULL>");
__func__, token, hat ? hat : NULL);
return aa_change_hat(hats, count, token, test); return aa_change_hat(hats, count, token, test);
} }
/**
* aa_setprocattr_changeprofile - handle procattr interface to changeprofile
* @fqname: args received from writting to /proc/<pid>/attr/current (NOT NULL)
* @onexec: true if change_profile should be delayed until exec
* @test: true if this is a test of change_profile permissions
*
* Returns: %0 or error code if change_profile fails
*/
int aa_setprocattr_changeprofile(char *fqname, bool onexec, int test)
{
char *name, *ns_name;
name = aa_split_fqname(fqname, &ns_name);
return aa_change_profile(ns_name, name, onexec, test);
}
...@@ -35,7 +35,7 @@ static void audit_cb(struct audit_buffer *ab, void *va) ...@@ -35,7 +35,7 @@ static void audit_cb(struct audit_buffer *ab, void *va)
struct common_audit_data *sa = va; struct common_audit_data *sa = va;
audit_log_format(ab, " rlimit=%s value=%lu", audit_log_format(ab, " rlimit=%s value=%lu",
rlim_names[sa->aad->rlim.rlim], sa->aad->rlim.max); rlim_names[aad(sa)->rlim.rlim], aad(sa)->rlim.max);
} }
/** /**
...@@ -50,17 +50,11 @@ static void audit_cb(struct audit_buffer *ab, void *va) ...@@ -50,17 +50,11 @@ static void audit_cb(struct audit_buffer *ab, void *va)
static int audit_resource(struct aa_profile *profile, unsigned int resource, static int audit_resource(struct aa_profile *profile, unsigned int resource,
unsigned long value, int error) unsigned long value, int error)
{ {
struct common_audit_data sa; DEFINE_AUDIT_DATA(sa, LSM_AUDIT_DATA_NONE, OP_SETRLIMIT);
struct apparmor_audit_data aad = {0,}; aad(&sa)->rlim.rlim = resource;
aad(&sa)->rlim.max = value;
sa.type = LSM_AUDIT_DATA_NONE; aad(&sa)->error = error;
sa.aad = &aad; return aa_audit(AUDIT_APPARMOR_AUTO, profile, &sa, audit_cb);
aad.op = OP_SETRLIMIT,
aad.rlim.rlim = resource;
aad.rlim.max = value;
aad.error = error;
return aa_audit(AUDIT_APPARMOR_AUTO, profile, GFP_KERNEL, &sa,
audit_cb);
} }
/** /**
...@@ -77,9 +71,20 @@ int aa_map_resource(int resource) ...@@ -77,9 +71,20 @@ int aa_map_resource(int resource)
return rlim_map[resource]; return rlim_map[resource];
} }
static int profile_setrlimit(struct aa_profile *profile, unsigned int resource,
struct rlimit *new_rlim)
{
int e = 0;
if (profile->rlimits.mask & (1 << resource) && new_rlim->rlim_max >
profile->rlimits.limits[resource].rlim_max)
e = -EACCES;
return audit_resource(profile, resource, new_rlim->rlim_max, e);
}
/** /**
* aa_task_setrlimit - test permission to set an rlimit * aa_task_setrlimit - test permission to set an rlimit
* @profile - profile confining the task (NOT NULL) * @label - label confining the task (NOT NULL)
* @task - task the resource is being set on * @task - task the resource is being set on
* @resource - the resource being set * @resource - the resource being set
* @new_rlim - the new resource limit (NOT NULL) * @new_rlim - the new resource limit (NOT NULL)
...@@ -88,67 +93,84 @@ int aa_map_resource(int resource) ...@@ -88,67 +93,84 @@ int aa_map_resource(int resource)
* *
* Returns: 0 or error code if setting resource failed * Returns: 0 or error code if setting resource failed
*/ */
int aa_task_setrlimit(struct aa_profile *profile, struct task_struct *task, int aa_task_setrlimit(struct aa_label *label, struct task_struct *task,
unsigned int resource, struct rlimit *new_rlim) unsigned int resource, struct rlimit *new_rlim)
{ {
struct aa_profile *task_profile; struct aa_profile *profile;
struct aa_label *peer;
int error = 0; int error = 0;
rcu_read_lock(); rcu_read_lock();
task_profile = aa_get_profile(aa_cred_profile(__task_cred(task))); peer = aa_get_newest_cred_label(__task_cred(task));
rcu_read_unlock(); rcu_read_unlock();
/* TODO: extend resource control to handle other (non current) /* TODO: extend resource control to handle other (non current)
* profiles. AppArmor rules currently have the implicit assumption * profiles. AppArmor rules currently have the implicit assumption
* that the task is setting the resource of a task confined with * that the task is setting the resource of a task confined with
* the same profile. * the same profile or that the task setting the resource of another
* task has CAP_SYS_RESOURCE.
*/ */
if (profile != task_profile ||
(profile->rlimits.mask & (1 << resource) &&
new_rlim->rlim_max > profile->rlimits.limits[resource].rlim_max))
error = -EACCES;
aa_put_profile(task_profile);
return audit_resource(profile, resource, new_rlim->rlim_max, error); if (label != peer &&
!aa_capable(label, CAP_SYS_RESOURCE, SECURITY_CAP_NOAUDIT))
error = fn_for_each(label, profile,
audit_resource(profile, resource,
new_rlim->rlim_max, EACCES));
else
error = fn_for_each_confined(label, profile,
profile_setrlimit(profile, resource, new_rlim));
aa_put_label(peer);
return error;
} }
/** /**
* __aa_transition_rlimits - apply new profile rlimits * __aa_transition_rlimits - apply new profile rlimits
* @old: old profile on task (NOT NULL) * @old_l: old label on task (NOT NULL)
* @new: new profile with rlimits to apply (NOT NULL) * @new_l: new label with rlimits to apply (NOT NULL)
*/ */
void __aa_transition_rlimits(struct aa_profile *old, struct aa_profile *new) void __aa_transition_rlimits(struct aa_label *old_l, struct aa_label *new_l)
{ {
unsigned int mask = 0; unsigned int mask = 0;
struct rlimit *rlim, *initrlim; struct rlimit *rlim, *initrlim;
int i; struct aa_profile *old, *new;
struct label_it i;
/* for any rlimits the profile controlled reset the soft limit old = labels_profile(old_l);
* to the less of the tasks hard limit and the init tasks soft limit 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) {
if (old->rlimits.mask) { if (old->rlimits.mask) {
for (i = 0, mask = 1; i < RLIM_NLIMITS; i++, mask <<= 1) { int j;
for (j = 0, mask = 1; j < RLIM_NLIMITS; j++,
mask <<= 1) {
if (old->rlimits.mask & mask) { if (old->rlimits.mask & mask) {
rlim = current->signal->rlim + i; rlim = current->signal->rlim + j;
initrlim = init_task.signal->rlim + i; initrlim = init_task.signal->rlim + j;
rlim->rlim_cur = min(rlim->rlim_max, rlim->rlim_cur = min(rlim->rlim_max,
initrlim->rlim_cur); initrlim->rlim_cur);
} }
} }
} }
}
/* set any new hard limits as dictated by the new profile */ /* set any new hard limits as dictated by the new profile */
label_for_each_confined(i, new_l, new) {
int j;
if (!new->rlimits.mask) if (!new->rlimits.mask)
return; continue;
for (i = 0, mask = 1; i < RLIM_NLIMITS; i++, mask <<= 1) { for (j = 0, mask = 1; j < RLIM_NLIMITS; j++, mask <<= 1) {
if (!(new->rlimits.mask & mask)) if (!(new->rlimits.mask & mask))
continue; continue;
rlim = current->signal->rlim + i; rlim = current->signal->rlim + j;
rlim->rlim_max = min(rlim->rlim_max, rlim->rlim_max = min(rlim->rlim_max,
new->rlimits.limits[i].rlim_max); new->rlimits.limits[j].rlim_max);
/* soft limit should not exceed hard limit */ /* soft limit should not exceed hard limit */
rlim->rlim_cur = min(rlim->rlim_cur, rlim->rlim_max); rlim->rlim_cur = min(rlim->rlim_cur, rlim->rlim_max);
} }
}
} }
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment