Updated AppArmor with a newer backported AppArmor release by UBports

This commit is contained in:
Nathan 2025-04-08 19:57:41 -05:00
parent fb417c8ab5
commit 3337d21439
45 changed files with 9343 additions and 1547 deletions

View File

@ -1,5 +1,6 @@
#
# Generated include files
#
net_names.h
capability_names.h
rlim_names.h

View File

@ -29,3 +29,63 @@ config SECURITY_APPARMOR_BOOTPARAM_VALUE
boot.
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
bool "enable introspection of sha1 hashes for loaded profiles"
depends on SECURITY_APPARMOR
depends on CRYPTO
select CRYPTO_SHA1
default y
help
This option selects whether introspection of loaded policy
is available to userspace via the apparmor 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.

View File

@ -4,10 +4,44 @@ obj-$(CONFIG_SECURITY_APPARMOR) += apparmor.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 \
resource.o sid.o file.o
resource.o sid.o file.o label.o mount.o net.o af_unix.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
# Transforms lines from
@ -18,7 +52,11 @@ quiet_cmd_make-caps = GEN $@
cmd_make-caps = echo "static const char *const capability_names[] = {" > $@ ;\
sed $< >>$@ -r -n -e '/CAP_FS_MASK/d' \
-e 's/^\#define[ \t]+CAP_([A-Z0-9_]+)[ \t]+([0-9]+)/[\2] = "\L\1",/p';\
echo "};" >> $@
echo "};" >> $@ ;\
echo -n '\#define AA_FS_CAPS_MASK "' >> $@ ;\
sed $< -r -n -e '/CAP_FS_MASK/d' \
-e 's/^\#define[ \t]+CAP_([A-Z0-9_]+)[ \t]+([0-9]+)/\L\1/p' | \
tr '\n' ' ' | sed -e 's/ $$/"\n/' >> $@
# Build a lower case string table of rlimit names.
@ -56,6 +94,7 @@ cmd_make-rlim = echo "static const char *const rlim_names[RLIM_NLIMITS] = {" \
tr '\n' ' ' | sed -e 's/ $$/"\n/' >> $@
$(obj)/capability.o : $(obj)/capability_names.h
$(obj)/net.o : $(obj)/net_names.h
$(obj)/resource.o : $(obj)/rlim_names.h
$(obj)/capability_names.h : $(srctree)/include/uapi/linux/capability.h \
$(src)/Makefile
@ -63,3 +102,8 @@ $(obj)/capability_names.h : $(srctree)/include/uapi/linux/capability.h \
$(obj)/rlim_names.h : $(srctree)/include/uapi/asm-generic/resource.h \
$(src)/Makefile
$(call cmd,make-rlim)
$(obj)/net_names.h : $(srctree)/include/linux/socket.h \
$(srctree)/include/linux/net.h \
$(src)/Makefile
$(call cmd,make-af)
$(call cmd,make-sock)

631
security/apparmor/af_unix.c Normal file
View File

@ -0,0 +1,631 @@
/*
* 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 int unix_fs_perm(int op, u32 mask, struct aa_label *label,
struct unix_sock *u, int flags)
{
AA_BUG(!label);
AA_BUG(!u);
AA_BUG(!UNIX_FS(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 file_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
*/
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,
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)->target = aa_peer_name(peer);
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;
AA_BUG(!profile);
AA_BUG(profile_unconfined(profile));
if ((state = PROFILE_MEDIATES_AF(profile, AF_UNIX))) {
DEFINE_AUDIT_UNIX(sa, OP_CREATE, NULL, type, protocol);
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, OP_CREATE, family, type, protocol,
NULL);
}
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, int op,
u32 request, struct sock *sk)
{
unsigned int state;
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) {
DEFINE_AUDIT_UNIX(sa, op, sk, sk->sk_type, sk->sk_protocol);
state = match_to_sk(profile, state, unix_sk(sk),
&aad(&sa)->info);
return do_perms(profile, state, request, &sa);
}
return aa_profile_af_perm(profile, op, sk->sk_family, sk->sk_type,
sk->sk_protocol, sk);
}
int aa_unix_label_sk_perm(struct aa_label *label, int 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, int 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(int op, u32 request, struct socket *sock)
{
return unix_label_sock_perm(aa_current_label(), op, request, sock);
}
static int profile_bind_perm(struct aa_profile *profile, struct sock *sk,
struct sockaddr *addr, int addrlen)
{
unsigned int state;
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 */
DEFINE_AUDIT_UNIX(sa, OP_BIND, sk, sk->sk_type,
sk->sk_protocol);
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_perm(profile, OP_BIND, sk->sk_family, sk->sk_type,
sk->sk_protocol, sk);
}
int aa_unix_bind_perm(struct socket *sock, struct sockaddr *address,
int addrlen)
{
struct aa_profile *profile;
struct aa_label *label = aa_current_label();
/* fs bind is handled by mknod */
if (unconfined(label) || unix_addr_fs(address, addrlen))
return 0;
return fn_for_each_confined(label, profile,
profile_bind_perm(profile, sock->sk, address, addrlen));
}
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;
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);
DEFINE_AUDIT_UNIX(sa, OP_LISTEN, sk, sk->sk_type,
sk->sk_protocol);
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_perm(profile, OP_LISTEN, sk->sk_family,
sk->sk_type, sk->sk_protocol, sk);
}
int aa_unix_listen_perm(struct socket *sock, int backlog)
{
struct aa_profile *profile;
struct aa_label *label = aa_current_label();
if (unconfined(label) || UNIX_FS(sock->sk))
return 0;
return fn_for_each_confined(label, profile,
profile_listen_perm(profile, sock->sk, backlog));
}
static inline int profile_accept_perm(struct aa_profile *profile,
struct sock *sk,
struct sock *newsk)
{
unsigned int state;
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) {
DEFINE_AUDIT_UNIX(sa, OP_ACCEPT, sk, sk->sk_type,
sk->sk_protocol);
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_perm(profile, OP_ACCEPT, sk->sk_family,
sk->sk_type, sk->sk_protocol, 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_current_label();
if (unconfined(label) || UNIX_FS(sock->sk))
return 0;
return fn_for_each_confined(label, profile,
profile_accept_perm(profile, sock->sk, newsock->sk));
}
/* 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(int op, u32 request, struct socket *sock,
struct msghdr *msg, int size)
{
return 0;
}
static int profile_opt_perm(struct aa_profile *profile, int op, u32 request,
struct sock *sk, int level, int optname)
{
unsigned int state;
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);
DEFINE_AUDIT_UNIX(sa, op, sk, sk->sk_type, sk->sk_protocol);
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_perm(profile, op, sk->sk_family,
sk->sk_type, sk->sk_protocol, sk);
}
int aa_unix_opt_perm(int op, u32 request, struct socket *sock, int level,
int optname)
{
struct aa_profile *profile;
struct aa_label *label = aa_current_label();
if (unconfined(label) || UNIX_FS(sock->sk))
return 0;
return fn_for_each_confined(label, profile,
profile_opt_perm(profile, op, request, sock->sk,
level, optname));
}
/* null peer_label is allowed, in which case the peer_sk label is used */
static int profile_peer_perm(struct aa_profile *profile, int 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_cxt *peer_cxt = SK_CXT(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_cxt->label;
return fn_for_each(peer_label, peerp,
match_label(profile, peerp, state, request,
sa));
}
return aa_profile_af_perm(profile, op, sk->sk_family, sk->sk_type,
sk->sk_protocol, sk);
}
/**
*
* Requires: lock held on both @sk and @peer_sk
*/
int aa_unix_peer_perm(struct aa_label *label, int 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(peeru))
return unix_fs_perm(op, request, label, peeru, 0);
else if (UNIX_FS(u))
return unix_fs_perm(op, request, label, u, 0);
else {
struct aa_profile *profile;
DEFINE_AUDIT_UNIX(sa, op, sk, sk->sk_type, sk->sk_protocol);
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, int 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_cxt *pcxt = SK_CXT(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(pcxt->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;
}

File diff suppressed because it is too large Load Diff

View File

@ -39,10 +39,16 @@ const char *const op_table[] = {
"getattr",
"open",
"file_receive",
"file_perm",
"file_lock",
"file_mmap",
"file_mprotect",
"file_inherit",
"pivotroot",
"mount",
"umount",
"create",
"post_create",
@ -59,6 +65,7 @@ const char *const op_table[] = {
"socket_shutdown",
"ptrace",
"signal",
"exec",
"change_hat",
@ -88,7 +95,7 @@ static const char *const aa_audit_type[] = {
"HINT",
"STATUS",
"ERROR",
"KILLED"
"KILLED",
"AUTO"
};
@ -111,50 +118,45 @@ static const char *const aa_audit_type[] = {
static void audit_pre(struct audit_buffer *ab, void *ca)
{
struct common_audit_data *sa = ca;
struct task_struct *tsk = sa->aad->tsk ? sa->aad->tsk : current;
if (aa_g_audit_header) {
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_string(ab, op_table[sa->aad->op]);
audit_log_string(ab, op_table[aad(sa)->op]);
}
if (sa->aad->info) {
if (aad(sa)->info) {
audit_log_format(ab, " info=");
audit_log_string(ab, sa->aad->info);
if (sa->aad->error)
audit_log_format(ab, " error=%d", sa->aad->error);
audit_log_string(ab, aad(sa)->info);
if (aad(sa)->error)
audit_log_format(ab, " error=%d", aad(sa)->error);
}
if (sa->aad->profile) {
struct aa_profile *profile = sa->aad->profile;
pid_t pid;
rcu_read_lock();
pid = rcu_dereference(tsk->real_parent)->pid;
rcu_read_unlock();
audit_log_format(ab, " parent=%d", pid);
if (profile->ns != root_ns) {
audit_log_format(ab, " namespace=");
audit_log_untrustedstring(ab, profile->ns->base.hname);
if (aad(sa)->label) {
struct aa_label *label = aad(sa)->label;
if (label_isprofile(label)) {
struct aa_profile *profile = labels_profile(label);
if (profile->ns != root_ns) {
audit_log_format(ab, " namespace=");
audit_log_untrustedstring(ab,
profile->ns->base.hname);
}
audit_log_format(ab, " profile=");
audit_log_untrustedstring(ab, profile->base.hname);
} else {
audit_log_format(ab, " label=");
aa_label_audit(ab, root_ns, label, false, GFP_ATOMIC);
}
audit_log_format(ab, " profile=");
audit_log_untrustedstring(ab, profile->base.hname);
}
if (sa->aad->name) {
if (aad(sa)->name) {
audit_log_format(ab, " name=");
audit_log_untrustedstring(ab, sa->aad->name);
audit_log_untrustedstring(ab, aad(sa)->name);
}
if (sa->aad->tsk) {
audit_log_format(ab, " pid=%d comm=", tsk->pid);
audit_log_untrustedstring(ab, tsk->comm);
}
}
/**
@ -165,7 +167,12 @@ static void audit_pre(struct audit_buffer *ab, void *ca)
void aa_audit_msg(int type, struct common_audit_data *sa,
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);
}
@ -173,7 +180,6 @@ void aa_audit_msg(int type, struct common_audit_data *sa,
* aa_audit - Log a profile based audit event to the audit subsystem
* @type: audit type for the message
* @profile: profile to check against (NOT NULL)
* @gfp: allocation flags to use
* @sa: audit event (NOT NULL)
* @cb: optional callback fn for type specific fields (MAYBE NULL)
*
@ -181,14 +187,13 @@ void aa_audit_msg(int type, struct common_audit_data *sa,
*
* Returns: error on failure
*/
int aa_audit(int type, struct aa_profile *profile, gfp_t gfp,
struct common_audit_data *sa,
int aa_audit(int type, struct aa_profile *profile, struct common_audit_data *sa,
void (*cb) (struct audit_buffer *, void *))
{
BUG_ON(!profile);
if (type == AUDIT_APPARMOR_AUTO) {
if (likely(!sa->aad->error)) {
if (likely(!aad(sa)->error)) {
if (AUDIT_MODE(profile) != AUDIT_ALL)
return 0;
type = AUDIT_APPARMOR_AUDIT;
@ -200,22 +205,22 @@ int aa_audit(int type, struct aa_profile *profile, gfp_t gfp,
if (AUDIT_MODE(profile) == AUDIT_QUIET ||
(type == AUDIT_APPARMOR_DENIED &&
AUDIT_MODE(profile) == AUDIT_QUIET))
return sa->aad->error;
return aad(sa)->error;
if (KILL_MODE(profile) && type == AUDIT_APPARMOR_DENIED)
type = AUDIT_APPARMOR_KILL;
if (!unconfined(profile))
sa->aad->profile = profile;
aad(sa)->label = &profile->label;
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,
sa->aad->tsk ? sa->aad->tsk : current);
sa->type == LSM_AUDIT_DATA_TASK && sa->u.tsk ?
sa->u.tsk : current);
if (sa->aad->type == AUDIT_APPARMOR_ALLOWED)
return complain_error(sa->aad->error);
if (aad(sa)->type == AUDIT_APPARMOR_ALLOWED)
return complain_error(aad(sa)->error);
return sa->aad->error;
return aad(sa)->error;
}

View File

@ -27,6 +27,11 @@
*/
#include "capability_names.h"
struct aa_fs_entry aa_fs_entry_caps[] = {
AA_FS_FILE_STRING("mask", AA_FS_CAPS_MASK),
{ }
};
struct audit_cache {
struct aa_profile *profile;
kernel_cap_t caps;
@ -48,8 +53,8 @@ static void audit_cb(struct audit_buffer *ab, void *va)
/**
* audit_caps - audit a capability
* @profile: profile confining task (NOT NULL)
* @task: task capability test was performed against (NOT NULL)
* @sa: audit data
* @profile: profile being tested for confinement (NOT NULL)
* @cap: capability tested
* @error: error code returned by test
*
@ -58,19 +63,12 @@ static void audit_cb(struct audit_buffer *ab, void *va)
*
* Returns: 0 or sa->error on success, error code on failure
*/
static int audit_caps(struct aa_profile *profile, struct task_struct *task,
static int audit_caps(struct common_audit_data *sa, struct aa_profile *profile,
int cap, int error)
{
struct audit_cache *ent;
int type = AUDIT_APPARMOR_AUTO;
struct common_audit_data sa;
struct apparmor_audit_data aad = {0,};
sa.type = LSM_AUDIT_DATA_CAP;
sa.aad = &aad;
sa.u.cap = cap;
sa.aad->tsk = task;
sa.aad->op = OP_CAPABLE;
sa.aad->error = error;
aad(sa)->error = error;
if (likely(!error)) {
/* test if auditing is being forced */
@ -102,25 +100,40 @@ static int audit_caps(struct aa_profile *profile, struct task_struct *task,
}
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: profile being enforced (NOT NULL, NOT unconfined)
* @cap: capability to test if allowed
* @sa: audit data (MAY BE NULL indicating no auditing)
*
* 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,
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 (!sa) {
if (COMPLAIN_MODE(profile))
return complain_error(error);
return error;
}
return audit_caps(sa, profile, cap, error);
}
/**
* aa_capable - test permission to use capability
* @task: task doing capability test against (NOT NULL)
* @profile: profile confining @task (NOT NULL)
* @label: label being tested for capability (NOT NULL)
* @cap: capability to be tested
* @audit: whether an audit record should be generated
*
@ -128,16 +141,15 @@ static int profile_capable(struct aa_profile *profile, int cap)
*
* Returns: 0 on success, or else an error code.
*/
int aa_capable(struct task_struct *task, 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) {
if (COMPLAIN_MODE(profile))
return complain_error(error);
return error;
}
error = fn_for_each_confined(label, profile,
profile_capable(profile, cap, audit ? &sa : NULL));
return audit_caps(profile, task, cap, error);
return error;
}

View File

@ -14,9 +14,9 @@
*
*
* AppArmor sets confinement on every task, via the the aa_task_cxt and
* the aa_task_cxt.profile, both of which are required and are not allowed
* the aa_task_cxt.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 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.
*
* TODO
@ -47,9 +47,9 @@ struct aa_task_cxt *aa_alloc_task_context(gfp_t flags)
void aa_free_task_context(struct aa_task_cxt *cxt)
{
if (cxt) {
aa_put_profile(cxt->profile);
aa_put_profile(cxt->previous);
aa_put_profile(cxt->onexec);
aa_put_label(cxt->label);
aa_put_label(cxt->previous);
aa_put_label(cxt->onexec);
kzfree(cxt);
}
@ -63,48 +63,57 @@ void aa_free_task_context(struct aa_task_cxt *cxt)
void aa_dup_task_context(struct aa_task_cxt *new, const struct aa_task_cxt *old)
{
*new = *old;
aa_get_profile(new->profile);
aa_get_profile(new->previous);
aa_get_profile(new->onexec);
aa_get_label(new->label);
aa_get_label(new->previous);
aa_get_label(new->onexec);
}
/**
* aa_replace_current_profile - replace the current tasks profiles
* @profile: new profile (NOT NULL)
* aa_get_task_label - Get another task's label
* @task: task to query (NOT NULL)
*
* Returns: counted reference to @task's label
*/
struct aa_label *aa_get_task_label(struct task_struct *task)
{
struct aa_label *p;
rcu_read_lock();
p = aa_get_newest_label(__aa_task_raw_label(task));
rcu_read_unlock();
return p;
}
/**
* aa_replace_current_label - replace the current tasks label
* @label: new label (NOT NULL)
*
* Returns: 0 or error on failure
*/
int aa_replace_current_profile(struct aa_profile *profile)
int aa_replace_current_label(struct aa_label *label)
{
struct aa_task_cxt *cxt = current_cred()->security;
struct aa_task_cxt *cxt = current_cxt();
struct cred *new;
BUG_ON(!profile);
BUG_ON(!label);
if (cxt->profile == profile)
if (cxt->label == label)
return 0;
new = prepare_creds();
if (!new)
return -ENOMEM;
cxt = new->security;
if (unconfined(profile) || (cxt->profile->ns != profile->ns)) {
/* if switching to unconfined or a different profile namespace
cxt = cred_cxt(new);
if (unconfined(label) || (labels_ns(cxt->label) != labels_ns(label)))
/* if switching to unconfined or a different label namespace
* clear out context state
*/
aa_put_profile(cxt->previous);
aa_put_profile(cxt->onexec);
cxt->previous = NULL;
cxt->onexec = NULL;
cxt->token = 0;
}
/* be careful switching cxt->profile, when racing replacement it
* is possible that cxt->profile->replacedby is the reference keeping
* @profile valid, so make sure to get its reference before dropping
* the reference on cxt->profile */
aa_get_profile(profile);
aa_put_profile(cxt->profile);
cxt->profile = profile;
aa_clear_task_cxt_trans(cxt);
aa_get_label(label);
aa_put_label(cxt->label);
cxt->label = label;
commit_creds(new);
return 0;
@ -112,21 +121,21 @@ int aa_replace_current_profile(struct aa_profile *profile)
/**
* 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)
*
* Returns: 0 or error on failure
*/
int aa_set_current_onexec(struct aa_profile *profile)
int aa_set_current_onexec(struct aa_label *label)
{
struct aa_task_cxt *cxt;
struct cred *new = prepare_creds();
if (!new)
return -ENOMEM;
cxt = new->security;
aa_get_profile(profile);
aa_put_profile(cxt->onexec);
cxt->onexec = profile;
cxt = cred_cxt(new);
aa_get_label(label);
aa_put_label(cxt->onexec);
cxt->onexec = label;
commit_creds(new);
return 0;
@ -134,7 +143,7 @@ int aa_set_current_onexec(struct aa_profile *profile)
/**
* 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
*
* Do switch of tasks hat. If the task is currently in a hat
@ -142,29 +151,29 @@ int aa_set_current_onexec(struct aa_profile *profile)
*
* 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 cred *new = prepare_creds();
if (!new)
return -ENOMEM;
BUG_ON(!profile);
BUG_ON(!label);
cxt = new->security;
cxt = cred_cxt(new);
if (!cxt->previous) {
/* transfer refcount */
cxt->previous = cxt->profile;
cxt->previous = cxt->label;
cxt->token = token;
} else if (cxt->token == token) {
aa_put_profile(cxt->profile);
aa_put_label(cxt->label);
} else {
/* previous_profile && cxt->token != token */
abort_creds(new);
return -EACCES;
}
cxt->profile = aa_get_profile(aa_newest_version(profile));
cxt->label = aa_get_newest_label(label);
/* clear exec on switching context */
aa_put_profile(cxt->onexec);
aa_put_label(cxt->onexec);
cxt->onexec = NULL;
commit_creds(new);
@ -172,44 +181,37 @@ int aa_set_current_hat(struct aa_profile *profile, u64 token)
}
/**
* 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
*
* 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.
*
* 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 cred *new = prepare_creds();
if (!new)
return -ENOMEM;
cxt = new->security;
cxt = cred_cxt(new);
if (cxt->token != token) {
abort_creds(new);
return -EACCES;
}
/* ignore restores when there is no saved profile */
/* ignore restores when there is no saved label */
if (!cxt->previous) {
abort_creds(new);
return 0;
}
aa_put_profile(cxt->profile);
cxt->profile = aa_newest_version(cxt->previous);
BUG_ON(!cxt->profile);
if (unlikely(cxt->profile != cxt->previous)) {
aa_get_profile(cxt->profile);
aa_put_profile(cxt->previous);
}
aa_put_label(cxt->label);
cxt->label = aa_get_newest_label(cxt->previous);
BUG_ON(!cxt->label);
/* clear exec && prev information when restoring to previous context */
cxt->previous = NULL;
cxt->token = 0;
aa_put_profile(cxt->onexec);
cxt->onexec = NULL;
aa_clear_task_cxt_trans(cxt);
commit_creds(new);
return 0;

View File

@ -0,0 +1,95 @@
/*
* AppArmor security module
*
* This file contains AppArmor policy loading interface function 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.
*
* Fns to provide a checksum of policy that has been loaded this can be
* compared to userspace policy compiles to check loaded policy is what
* it should be.
*/
#include <crypto/hash.h>
#include "include/apparmor.h"
#include "include/crypto.h"
static unsigned int apparmor_hash_size;
static struct crypto_shash *apparmor_tfm;
unsigned int aa_hash_size(void)
{
return apparmor_hash_size;
}
int aa_calc_profile_hash(struct aa_profile *profile, u32 version, void *start,
size_t len)
{
struct {
struct shash_desc shash;
char ctx[crypto_shash_descsize(apparmor_tfm)];
} desc;
int error = -ENOMEM;
u32 le32_version = cpu_to_le32(version);
if (!apparmor_tfm)
return 0;
profile->hash = kzalloc(apparmor_hash_size, GFP_KERNEL);
if (!profile->hash)
goto fail;
desc.shash.tfm = apparmor_tfm;
desc.shash.flags = 0;
error = crypto_shash_init(&desc.shash);
if (error)
goto fail;
error = crypto_shash_update(&desc.shash, (u8 *) &le32_version, 4);
if (error)
goto fail;
error = crypto_shash_update(&desc.shash, (u8 *) start, len);
if (error)
goto fail;
error = crypto_shash_final(&desc.shash, profile->hash);
if (error)
goto fail;
return 0;
fail:
kfree(profile->hash);
profile->hash = NULL;
return error;
}
static int __init init_profile_hash(void)
{
struct crypto_shash *tfm;
if (!apparmor_initialized)
return 0;
tfm = crypto_alloc_shash("sha1", 0, CRYPTO_ALG_ASYNC);
if (IS_ERR(tfm)) {
int error = PTR_ERR(tfm);
AA_ERROR("failed to setup profile sha1 hashing: %d\n", error);
return error;
}
apparmor_tfm = tfm;
apparmor_hash_size = crypto_shash_digestsize(apparmor_tfm);
aa_info_message("AppArmor sha1 policy hashing enabled");
return 0;
}
late_initcall(init_profile_hash);

View File

@ -50,41 +50,39 @@ void aa_free_domain_entries(struct aa_domain *domain)
/**
* may_change_ptraced_domain - check if can change profile on ptraced task
* @task: task we want to change profile of (NOT NULL)
* @to_profile: profile to change to (NOT NULL)
* @info: message if there is an error
*
* Check if the task 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
*
* Returns: %0 or error if change not allowed
*/
static int may_change_ptraced_domain(struct task_struct *task,
struct aa_profile *to_profile)
static int may_change_ptraced_domain(struct aa_profile *to_profile,
const char **info)
{
struct task_struct *tracer;
const struct cred *cred = NULL;
struct aa_profile *tracerp = NULL;
struct aa_label *tracerl = NULL;
int error = 0;
rcu_read_lock();
tracer = ptrace_parent(task);
if (tracer) {
tracer = ptrace_parent(current);
if (tracer)
/* released below */
cred = get_task_cred(tracer);
tracerp = aa_cred_profile(cred);
}
tracerl = aa_get_task_label(tracer);
/* not ptraced */
if (!tracer || unconfined(tracerp))
if (!tracer || unconfined(tracerl))
goto out;
error = aa_may_ptrace(tracer, tracerp, to_profile, PTRACE_MODE_ATTACH);
error = aa_may_ptrace(tracerl, &to_profile->label, PTRACE_MODE_ATTACH);
out:
rcu_read_unlock();
if (cred)
put_cred(cred);
aa_put_label(tracerl);
if (error)
*info = "ptrace prevents transition";
return error;
}
@ -107,7 +105,7 @@ static struct file_perms change_profile_perms(struct aa_profile *profile,
struct path_cond cond = { };
unsigned int state;
if (unconfined(profile)) {
if (profile_unconfined(profile)) {
perms.allow = AA_MAY_CHANGE_PROFILE | AA_MAY_ONEXEC;
perms.audit = perms.quiet = perms.kill = 0;
return perms;
@ -148,8 +146,8 @@ static struct aa_profile *__attach_match(const char *name,
int len = 0;
struct aa_profile *profile, *candidate = NULL;
list_for_each_entry(profile, head, base.list) {
if (profile->flags & PFLAG_NULL)
list_for_each_entry_rcu(profile, head, base.list) {
if (profile->label.flags & FLAG_NULL)
continue;
if (profile->xmatch && profile->xmatch_len > len) {
unsigned int state = aa_dfa_match(profile->xmatch,
@ -181,9 +179,9 @@ static struct aa_profile *find_attach(struct aa_namespace *ns,
{
struct aa_profile *profile;
read_lock(&ns->lock);
rcu_read_lock();
profile = aa_get_profile(__attach_match(name, list));
read_unlock(&ns->lock);
rcu_read_unlock();
return profile;
}
@ -242,7 +240,7 @@ static const char *next_name(int xtype, const char *name)
*
* Returns: refcounted profile, or NULL on failure (MAYBE NULL)
*/
static struct aa_profile *x_table_lookup(struct aa_profile *profile, u32 xindex)
struct aa_profile *x_table_lookup(struct aa_profile *profile, u32 xindex)
{
struct aa_profile *new_profile = NULL;
struct aa_namespace *ns = profile->ns;
@ -343,6 +341,7 @@ static struct aa_profile *x_to_profile(struct aa_profile *profile,
int apparmor_bprm_set_creds(struct linux_binprm *bprm)
{
struct aa_task_cxt *cxt;
struct aa_label *label;
struct aa_profile *profile, *new_profile = NULL;
struct aa_namespace *ns;
char *buffer = NULL;
@ -360,10 +359,11 @@ int apparmor_bprm_set_creds(struct linux_binprm *bprm)
if (bprm->cred_prepared)
return 0;
cxt = bprm->cred->security;
cxt = cred_cxt(bprm->cred);
BUG_ON(!cxt);
profile = aa_get_profile(aa_newest_version(cxt->profile));
label = aa_get_newest_label(cxt->label);
profile = labels_profile(label);
/*
* get the namespace from the replacement profile as replacement
* can change the namespace
@ -372,11 +372,12 @@ int apparmor_bprm_set_creds(struct linux_binprm *bprm)
state = profile->file.start;
/* buffer freed below, name is pointer into buffer */
error = aa_path_name(&bprm->file->f_path, profile->path_flags, &buffer,
&name, &info);
get_buffers(buffer);
error = aa_path_name(&bprm->file->f_path, profile->path_flags, buffer,
&name, &info, profile->disconnected);
if (error) {
if (profile->flags &
(PFLAG_IX_ON_NAME_ERROR | PFLAG_UNCONFINED))
if (profile_unconfined(profile) ||
(profile->label.flags & FLAG_IX_ON_NAME_ERROR))
error = 0;
name = bprm->filename;
goto audit;
@ -385,11 +386,11 @@ int apparmor_bprm_set_creds(struct linux_binprm *bprm)
/* Test for onexec first as onexec directives override other
* x transitions.
*/
if (unconfined(profile)) {
if (profile_unconfined(profile)) {
/* unconfined task */
if (cxt->onexec)
/* change_profile on exec already been granted */
new_profile = aa_get_profile(cxt->onexec);
new_profile = labels_profile(aa_get_label(cxt->onexec));
else
new_profile = find_attach(ns, &ns->base.profiles, name);
if (!new_profile)
@ -415,13 +416,13 @@ int apparmor_bprm_set_creds(struct linux_binprm *bprm)
* exec\0change_profile
*/
state = aa_dfa_null_transition(profile->file.dfa, state);
cp = change_profile_perms(profile, cxt->onexec->ns,
cxt->onexec->base.name,
cp = change_profile_perms(profile, labels_profile(cxt->onexec)->ns,
labels_profile(cxt->onexec)->base.name,
AA_MAY_ONEXEC, state);
if (!(cp.allow & AA_MAY_ONEXEC))
goto audit;
new_profile = aa_get_profile(aa_newest_version(cxt->onexec));
new_profile = labels_profile(aa_get_newest_label(cxt->onexec));
goto apply;
}
@ -438,15 +439,19 @@ int apparmor_bprm_set_creds(struct linux_binprm *bprm)
new_profile = aa_get_profile(profile);
goto x_clear;
} else if (perms.xindex & AA_X_UNCONFINED) {
new_profile = aa_get_profile(ns->unconfined);
new_profile = labels_profile(aa_get_newest_label(&ns->unconfined->label));
info = "ux fallback";
} else {
error = -ENOENT;
error = -EACCES;
info = "profile not found";
/* remove MAY_EXEC to audit as failure */
perms.allow &= ~MAY_EXEC;
}
}
} else if (COMPLAIN_MODE(profile)) {
/* no exec permission - are we in learning mode */
/* no exec permission - learning mode. break rcu lock */
put_buffers(buffer);
name = NULL;
new_profile = aa_new_null_profile(profile, 0);
if (!new_profile) {
error = -ENOMEM;
@ -456,6 +461,12 @@ int apparmor_bprm_set_creds(struct linux_binprm *bprm)
target = new_profile->base.hname;
}
perms.xindex |= AA_X_UNSAFE;
/* re-aquire buffer and rcu readlock and re-get name */
get_buffers(buffer);
if (!error)
error = aa_path_name(&bprm->file->f_path,
profile->path_flags, buffer,
&name, &info, profile->disconnected);
} else
/* fail exec */
error = -EACCES;
@ -479,7 +490,7 @@ int apparmor_bprm_set_creds(struct linux_binprm *bprm)
}
if (bprm->unsafe & (LSM_UNSAFE_PTRACE | LSM_UNSAFE_PTRACE_CAP)) {
error = may_change_ptraced_domain(current, new_profile);
error = may_change_ptraced_domain(new_profile, &info);
if (error) {
aa_put_profile(new_profile);
goto audit;
@ -509,24 +520,20 @@ apply:
bprm->per_clear |= PER_CLEAR_ON_SETID;
x_clear:
aa_put_profile(cxt->profile);
aa_put_label(cxt->label);
/* transfer new profile reference will be released when cxt is freed */
cxt->profile = new_profile;
cxt->label = &new_profile->label;
/* clear out all temporary/transitional state from the context */
aa_put_profile(cxt->previous);
aa_put_profile(cxt->onexec);
cxt->previous = NULL;
cxt->onexec = NULL;
cxt->token = 0;
aa_clear_task_cxt_trans(cxt);
audit:
error = aa_audit_file(profile, &perms, GFP_KERNEL, OP_EXEC, MAY_EXEC,
name, target, cond.uid, info, error);
error = aa_audit_file(profile, &perms, OP_EXEC, MAY_EXEC, name, target,
cond.uid, info, error);
cleanup:
aa_put_profile(profile);
kfree(buffer);
aa_put_label(label);
put_buffers(buffer);
return error;
}
@ -550,36 +557,6 @@ int apparmor_bprm_secureexec(struct linux_binprm *bprm)
return ret;
}
/**
* 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_profile *profile = __aa_current_profile();
struct aa_task_cxt *new_cxt = bprm->cred->security;
/* 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 */
__aa_transition_rlimits(profile, new_cxt->profile);
}
/**
* 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;
}
/*
* Functions for self directed profile change
*/
@ -617,7 +594,8 @@ int aa_change_hat(const char *hats[], int count, u64 token, bool permtest)
{
const struct cred *cred;
struct aa_task_cxt *cxt;
struct aa_profile *profile, *previous_profile, *hat = NULL;
struct aa_label *label, *previous;
struct aa_profile *profile, *hat = NULL;
char *name = NULL;
int i;
struct file_perms perms = {};
@ -634,11 +612,13 @@ int aa_change_hat(const char *hats[], int count, u64 token, bool permtest)
/* released below */
cred = get_current_cred();
cxt = cred->security;
profile = aa_cred_profile(cred);
previous_profile = cxt->previous;
cxt = cred_cxt(cred);
label = aa_get_newest_cred_label(cred);
previous = cxt->previous;
if (unconfined(profile)) {
profile = labels_profile(label);
if (unconfined(label)) {
info = "unconfined";
error = -EPERM;
goto audit;
@ -647,7 +627,10 @@ int aa_change_hat(const char *hats[], int count, u64 token, bool permtest)
if (count) {
/* attempting to change into a new hat or switch to a sibling */
struct aa_profile *root;
root = PROFILE_IS_HAT(profile) ? profile->parent : profile;
if (PROFILE_IS_HAT(profile))
root = aa_get_profile_rcu(&profile->parent);
else
root = aa_get_profile(labels_profile(label));
/* find first matching hat */
for (i = 0; i < count && !hat; i++)
@ -659,6 +642,7 @@ int aa_change_hat(const char *hats[], int count, u64 token, bool permtest)
error = -ECHILD;
else
error = -ENOENT;
aa_put_profile(root);
goto out;
}
@ -673,6 +657,7 @@ int aa_change_hat(const char *hats[], int count, u64 token, bool permtest)
/* 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);
@ -682,6 +667,7 @@ int aa_change_hat(const char *hats[], int count, u64 token, bool permtest)
goto audit;
}
} else {
aa_put_profile(root);
target = hat->base.hname;
if (!PROFILE_IS_HAT(hat)) {
info = "target not hat";
@ -690,15 +676,14 @@ int aa_change_hat(const char *hats[], int count, u64 token, bool permtest)
}
}
error = may_change_ptraced_domain(current, hat);
error = may_change_ptraced_domain(hat, &info);
if (error) {
info = "ptraced";
error = -EPERM;
goto audit;
}
if (!permtest) {
error = aa_set_current_hat(hat, token);
error = aa_set_current_hat(&hat->label, token);
if (error == -EACCES)
/* kill task in case of brute force attacks */
perms.kill = AA_MAY_CHANGEHAT;
@ -706,12 +691,12 @@ int aa_change_hat(const char *hats[], int count, u64 token, bool permtest)
/* reset error for learning of new hats */
error = -ENOENT;
}
} else if (previous_profile) {
/* Return to saved profile. Kill task if restore fails
} else if (previous) {
/* Return to saved label. Kill task if restore fails
* to avoid brute force attacks
*/
target = previous_profile->base.hname;
error = aa_restore_previous_profile(token);
target = previous->hname;
error = aa_restore_previous_label(token);
perms.kill = AA_MAY_CHANGEHAT;
} else
/* ignore restores when there is no saved profile */
@ -719,12 +704,13 @@ int aa_change_hat(const char *hats[], int count, u64 token, bool permtest)
audit:
if (!permtest)
error = aa_audit_file(profile, &perms, GFP_KERNEL,
OP_CHANGE_HAT, AA_MAY_CHANGEHAT, NULL,
target, GLOBAL_ROOT_UID, info, error);
error = aa_audit_file(profile, &perms, OP_CHANGE_HAT,
AA_MAY_CHANGEHAT, NULL, target,
GLOBAL_ROOT_UID, info, error);
out:
aa_put_profile(hat);
aa_put_label(label);
kfree(name);
put_cred(cred);
@ -750,7 +736,7 @@ int aa_change_profile(const char *ns_name, const char *hname, bool onexec,
bool permtest)
{
const struct cred *cred;
struct aa_task_cxt *cxt;
struct aa_label *label;
struct aa_profile *profile, *target = NULL;
struct aa_namespace *ns = NULL;
struct file_perms perms = {};
@ -770,8 +756,8 @@ int aa_change_profile(const char *ns_name, const char *hname, bool onexec,
}
cred = get_current_cred();
cxt = cred->security;
profile = aa_cred_profile(cred);
label = aa_get_newest_cred_label(cred);
profile = labels_profile(label);
/*
* Fail explicitly requested domain transitions if no_new_privs
@ -780,7 +766,8 @@ int aa_change_profile(const char *ns_name, const char *hname, bool onexec,
* no_new_privs is set because this aways results in a reduction
* of permissions.
*/
if (task_no_new_privs(current) && !unconfined(profile)) {
if (task_no_new_privs(current) && !unconfined(label)) {
aa_put_label(label);
put_cred(cred);
return -EPERM;
}
@ -801,7 +788,7 @@ int aa_change_profile(const char *ns_name, const char *hname, bool onexec,
/* if the name was not specified, use the name of the current profile */
if (!hname) {
if (unconfined(profile))
if (profile_unconfined(profile))
hname = ns->unconfined->base.hname;
else
hname = profile->base.hname;
@ -831,27 +818,26 @@ int aa_change_profile(const char *ns_name, const char *hname, bool onexec,
}
/* check if tracing task is allowed to trace target domain */
error = may_change_ptraced_domain(current, target);
if (error) {
info = "ptrace prevents transition";
error = may_change_ptraced_domain(target, &info);
if (error)
goto audit;
}
if (permtest)
goto audit;
if (onexec)
error = aa_set_current_onexec(target);
error = aa_set_current_onexec(&target->label);
else
error = aa_replace_current_profile(target);
error = aa_replace_current_label(&target->label);
audit:
if (!permtest)
error = aa_audit_file(profile, &perms, GFP_KERNEL, op, request,
name, hname, GLOBAL_ROOT_UID, info, error);
error = aa_audit_file(profile, &perms, op, request, name,
hname, GLOBAL_ROOT_UID, info, error);
aa_put_namespace(ns);
aa_put_profile(target);
aa_put_label(label);
put_cred(cred);
return error;

View File

@ -12,8 +12,14 @@
* License.
*/
#include <linux/tty.h>
#include <linux/fdtable.h>
#include <linux/file.h>
#include "include/af_unix.h"
#include "include/apparmor.h"
#include "include/audit.h"
#include "include/context.h"
#include "include/file.h"
#include "include/match.h"
#include "include/path.h"
@ -22,6 +28,17 @@
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
* @buffer: buffer to write string to (NOT NULL)
@ -31,29 +48,7 @@ static void audit_file_mask(struct audit_buffer *ab, u32 mask)
{
char str[10];
char *m = str;
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';
aa_perm_mask_to_str(str, aa_file_perm_chrs, map_mask_to_chr_mask(mask));
audit_log_string(ab, str);
}
@ -67,24 +62,24 @@ static void file_audit_cb(struct audit_buffer *ab, void *va)
struct common_audit_data *sa = va;
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_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_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",
from_kuid(&init_user_ns, fsuid));
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)->target) {
audit_log_format(ab, " target=");
audit_log_untrustedstring(ab, sa->aad->fs.target);
audit_log_untrustedstring(ab, aad(sa)->target);
}
}
@ -92,7 +87,6 @@ static void file_audit_cb(struct audit_buffer *ab, void *va)
* aa_audit_file - handle the auditing of file operations
* @profile: the profile being enforced (NOT NULL)
* @perms: the permissions computed for the request (NOT NULL)
* @gfp: allocation flags
* @op: operation being mediated
* @request: permissions requested
* @name: name of object being mediated (MAYBE NULL)
@ -104,53 +98,85 @@ static void file_audit_cb(struct audit_buffer *ab, void *va)
* Returns: %0 or error on failure
*/
int aa_audit_file(struct aa_profile *profile, struct file_perms *perms,
gfp_t gfp, int op, u32 request, const char *name,
const char *target, kuid_t ouid, const char *info, int error)
int op, u32 request, const char *name, const char *target,
kuid_t ouid, const char *info, int error)
{
int type = AUDIT_APPARMOR_AUTO;
struct common_audit_data sa;
struct apparmor_audit_data aad = {0,};
sa.type = LSM_AUDIT_DATA_NONE;
sa.aad = &aad;
aad.op = op,
aad.fs.request = request;
aad.name = name;
aad.fs.target = target;
aad.fs.ouid = ouid;
aad.info = info;
aad.error = error;
if (likely(!sa.aad->error)) {
DEFINE_AUDIT_DATA(sa, LSM_AUDIT_DATA_NONE, op);
aad(&sa)->request = request;
aad(&sa)->name = name;
aad(&sa)->target = target;
aad(&sa)->fs.ouid = ouid;
aad(&sa)->info = info;
aad(&sa)->error = error;
sa.u.tsk = NULL;
if (likely(!aad(&sa)->error)) {
u32 mask = perms->audit;
if (unlikely(AUDIT_MODE(profile) == AUDIT_ALL))
mask = 0xffff;
/* 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;
type = AUDIT_APPARMOR_AUDIT;
} else {
/* only report permissions that were denied */
sa.aad->fs.request = sa.aad->fs.request & ~perms->allow;
aad(&sa)->request = aad(&sa)->request & ~perms->allow;
if (sa.aad->fs.request & perms->kill)
if (aad(&sa)->request & perms->kill)
type = AUDIT_APPARMOR_KILL;
/* 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_ALL)
sa.aad->fs.request &= ~perms->quiet;
aad(&sa)->request &= ~perms->quiet;
if (!sa.aad->fs.request)
return COMPLAIN_MODE(profile) ? 0 : sa.aad->error;
if (!aad(&sa)->request)
return COMPLAIN_MODE(profile) ? 0 : aad(&sa)->error;
}
sa.aad->fs.denied = sa.aad->fs.request & ~perms->allow;
return aa_audit(type, profile, gfp, &sa, file_audit_cb);
aad(&sa)->denied = aad(&sa)->request & ~perms->allow;
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) && dentry->d_inode->i_nlink == 0)
return 1;
return 0;
}
static int path_name(int 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, cond->uid, info, error));
return error;
}
return 0;
}
/**
@ -163,10 +189,11 @@ static u32 map_old_perms(u32 old)
{
u32 new = old & 0xf;
if (old & MAY_READ)
new |= AA_MAY_META_READ;
new |= AA_MAY_GETATTR | AA_MAY_OPEN;
if (old & MAY_WRITE)
new |= AA_MAY_META_WRITE | AA_MAY_CREATE | AA_MAY_DELETE |
AA_MAY_CHMOD | AA_MAY_CHOWN;
new |= AA_MAY_SETATTR | AA_MAY_CREATE | AA_MAY_DELETE |
AA_MAY_CHMOD | AA_MAY_CHOWN | AA_MAY_OPEN |
AA_MAY_DELETE;
if (old & 0x10)
new |= AA_MAY_LINK;
/* the old mapping lock and link_subset flags where overlaid
@ -214,7 +241,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.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 */
if (ACCEPT_TABLE(dfa)[state] & 0x80000000)
@ -251,23 +278,25 @@ unsigned int aa_str_perms(struct aa_dfa *dfa, unsigned int start,
return state;
}
/**
* 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)
int __aa_path_perm(int op, struct aa_profile *profile, const char *name,
u32 request, struct path_cond *cond, int flags,
struct file_perms *perms)
{
if (d_unlinked(dentry) && dentry->d_inode->i_nlink == 0)
return 1;
return 0;
int e = 0;
if (profile_unconfined(profile) ||
((flags & PATH_SOCK_COND) && !PROFILE_MEDIATES_AF(profile, AF_UNIX)))
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,
cond->uid, NULL, e);
}
/**
* aa_path_perm - do permissions check & audit for @path
* @op: operation being checked
* @profile: profile being enforced (NOT NULL)
* @label: profile being enforced (NOT NULL)
* @path: path to check permissions of (NOT NULL)
* @flags: any additional path flags beyond what the profile specifies
* @request: requested permissions
@ -275,35 +304,28 @@ static inline bool is_deleted(struct dentry *dentry)
*
* 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(int op, struct aa_label *label, struct path *path,
int flags, u32 request, struct path_cond *cond)
{
char *buffer = NULL;
struct file_perms perms = {};
const char *name, *info = NULL;
char *buffer = NULL;
const char *name;
struct aa_profile *profile;
int error;
flags |= profile->path_flags | (S_ISDIR(cond->mode) ? PATH_IS_DIR : 0);
error = aa_path_name(path, flags, &buffer, &name, &info);
if (error) {
if (error == -ENOENT && is_deleted(path->dentry)) {
/* 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);
/* TODO: fix path lookup flags */
flags |= labels_profile(label)->path_flags |
(S_ISDIR(cond->mode) ? PATH_IS_DIR : 0);
get_buffers(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;
}
@ -327,65 +349,25 @@ static inline bool xindex_is_subset(u32 link, u32 target)
return 1;
}
/**
* aa_path_link - Handle hard link permission check
* @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)
static int profile_path_link(struct aa_profile *profile, const char *lname,
const char *tname, struct path_cond *cond)
{
struct path link = { new_dir->mnt, new_dentry };
struct path target = { new_dir->mnt, old_dentry };
struct path_cond cond = {
old_dentry->d_inode->i_uid,
old_dentry->d_inode->i_mode
};
char *buffer = NULL, *buffer2 = NULL;
const char *lname, *tname = NULL, *info = NULL;
struct file_perms lperms, perms;
const char *info = NULL;
u32 request = AA_MAY_LINK;
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 */
state = aa_str_perms(profile->file.dfa, profile->file.start, lname,
&cond, &lperms);
cond, &lperms);
if (!(lperms.allow & AA_MAY_LINK))
goto audit;
/* test to see if target can be paired with link */
state = aa_dfa_null_transition(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
* in the link pair.
@ -403,10 +385,10 @@ int aa_path_link(struct aa_profile *profile, struct dentry *old_dentry,
if (!(perms.allow & AA_LINK_SUBSET))
goto done_tests;
/* Do link perm subset test requiring allowed permission on link are a
* subset of the allowed permissions on target.
/* Do link perm subset test requiring allowed permission on link are
* 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);
/* AA_MAY_LINK is not considered in the subset test */
@ -425,13 +407,175 @@ int aa_path_link(struct aa_profile *profile, struct dentry *old_dentry,
}
done_tests:
error = 0;
e = 0;
audit:
error = aa_audit_file(profile, &lperms, GFP_KERNEL, OP_LINK, request,
lname, tname, cond.uid, info, error);
kfree(buffer);
kfree(buffer2);
return aa_audit_file(profile, &lperms, OP_LINK, request, lname, tname,
cond->uid, info, e);
}
/**
* 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 = {
old_dentry->d_inode->i_uid,
old_dentry->d_inode->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_cxt(struct aa_file_cxt *fcxt, struct aa_label *label,
u32 request)
{
struct aa_label *l, *old;
/* update caching of label on file_cxt */
spin_lock(&fcxt->lock);
old = rcu_dereference_protected(fcxt->label,
spin_is_locked(&fcxt->lock));
l = aa_label_merge(old, label, GFP_ATOMIC);
if (l) {
if (l != old) {
rcu_assign_pointer(fcxt->label, l);
aa_put_label(old);
} else
aa_put_label(l);
fcxt->allow |= request;
}
spin_unlock(&fcxt->lock);
}
static int __file_path_perm(int op, struct aa_label *label,
struct aa_label *flabel, struct file *file,
u32 request, u32 denied)
{
struct aa_profile *profile;
struct file_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_cxt(file_cxt(file), label, request);
out:
put_buffers(buffer);
return error;
}
static int __file_sock_perm(int 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_cxt(file_cxt(file), label, request);
return error;
}
@ -439,20 +583,117 @@ audit:
/**
* aa_file_perm - do permission revalidation check & audit for @file
* @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)
* @request: requested permissions
*
* Returns: %0 if access allowed else error
*/
int aa_file_perm(int op, struct aa_profile *profile, struct file *file,
int aa_file_perm(int op, struct aa_label *label, struct file *file,
u32 request)
{
struct path_cond cond = {
.uid = file_inode(file)->i_uid,
.mode = file_inode(file)->i_mode
};
struct aa_file_cxt *fcxt;
struct aa_label *flabel;
u32 denied;
int error = 0;
return aa_path_perm(op, profile, &file->f_path, PATH_DELEGATE_DELETED,
request, &cond);
AA_BUG(!label);
AA_BUG(!file);
fcxt = file_cxt(file);
rcu_read_lock();
flabel = rcu_dereference(fcxt->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 & ~fcxt->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);
}

View File

@ -0,0 +1,121 @@
/*
* 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_cxt && sock_cxt->label && sock_cxt->label->hname ? sock_cxt->label->hname : "<null>"); print_sk(sock); \
printk(" other %s:", other_cxt && other_cxt->label && other_cxt->label->hname ? other_cxt->label->hname : "<null>"); print_sk(other); \
printk(" new %s", new_cxt && new_cxt->label && new_cxt->label->hname ? new_cxt->label->hname : "<null>"); print_sk(newsk); \
} while (0)
#define DEFINE_AUDIT_UNIX(NAME, OP, SK, T, P) \
struct lsm_network_audit NAME ## _net = { .sk = (SK), \
.family = (AF_UNIX)}; \
DEFINE_AUDIT_DATA(NAME, LSM_AUDIT_DATA_NONE, OP); \
NAME.u.net = &(NAME ## _net); \
aad(&NAME)->net.type = (T); \
aad(&NAME)->net.protocol = (P)
int aa_unix_peer_perm(struct aa_label *label, int 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, int op, u32 request,
struct sock *sk);
int aa_unix_sock_perm(int 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(int op, u32 request, struct socket *sock,
struct msghdr *msg, int size);
int aa_unix_opt_perm(int op, u32 request, struct socket *sock, int level,
int optname);
int aa_unix_file_perm(struct aa_label *label, int op, u32 request,
struct socket *sock);
#endif /* __AA_AF_UNIX_H */

View File

@ -4,7 +4,7 @@
* This file contains AppArmor basic global and lib definitions
*
* 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
* modify it under the terms of the GNU General Public License as
@ -15,10 +15,22 @@
#ifndef __APPARMOR_H
#define __APPARMOR_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 */
/*
* Class of mediation types in the AppArmor policy db
*/
@ -29,17 +41,23 @@
#define AA_CLASS_NET 4
#define AA_CLASS_RLIMITS 5
#define AA_CLASS_DOMAIN 6
#define AA_CLASS_MOUNT 7
#define AA_CLASS_PTRACE 9
#define AA_CLASS_SIGNAL 10
#define AA_CLASS_LABEL 16
#define AA_CLASS_LAST AA_CLASS_DOMAIN
#define AA_CLASS_LAST AA_CLASS_LABEL
/* Control parameters settable through module/boot flags */
extern enum audit_mode aa_g_audit;
extern bool aa_g_audit_header;
extern bool aa_g_debug;
extern bool aa_g_hash_policy;
extern bool aa_g_lock_policy;
extern bool aa_g_logsyscall;
extern bool aa_g_paranoid_load;
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
@ -52,6 +70,12 @@ extern unsigned int aa_g_path_max;
printk(KERN_DEBUG "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()) \
@ -63,10 +87,25 @@ extern int apparmor_initialized __initdata;
/* fn's in lib */
char *aa_split_fqname(char *args, char **ns_name);
char *aa_splitn_fqname(char *fqname, size_t n, char **ns_name, size_t *ns_len);
void aa_info_message(const char *str);
void *kvmalloc(size_t size);
void kvfree(void *buffer);
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
@ -97,9 +136,40 @@ static inline unsigned int aa_dfa_null_transition(struct aa_dfa *dfa,
return aa_dfa_next(dfa, start, 0);
}
static inline bool mediated_filesystem(struct inode *inode)
static inline bool path_mediated_fs(struct dentry *dentry)
{
return !(inode->i_sb->s_flags & MS_NOUSER);
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);
#endif /* __APPARMOR_H */

View File

@ -15,6 +15,8 @@
#ifndef __AA_APPARMORFS_H
#define __AA_APPARMORFS_H
extern struct path aa_null;
enum aa_fs_type {
AA_FS_TYPE_BOOLEAN,
AA_FS_TYPE_STRING,
@ -61,4 +63,44 @@ extern const struct file_operations aa_fs_seq_file_ops;
extern void __init aa_destroy_aafs(void);
struct aa_profile;
struct aa_namespace;
enum aafs_ns_type {
AAFS_NS_DIR,
AAFS_NS_PROFS,
AAFS_NS_NS,
AAFS_NS_COUNT,
AAFS_NS_MAX_COUNT,
AAFS_NS_SIZE,
AAFS_NS_MAX_SIZE,
AAFS_NS_OWNER,
AAFS_NS_SIZEOF,
};
enum aafs_prof_type {
AAFS_PROF_DIR,
AAFS_PROF_PROFS,
AAFS_PROF_NAME,
AAFS_PROF_MODE,
AAFS_PROF_ATTACH,
AAFS_PROF_HASH,
AAFS_PROF_SIZEOF,
};
#define ns_dir(X) ((X)->dents[AAFS_NS_DIR])
#define ns_subns_dir(X) ((X)->dents[AAFS_NS_NS])
#define ns_subprofs_dir(X) ((X)->dents[AAFS_NS_PROFS])
#define prof_dir(X) ((X)->dents[AAFS_PROF_DIR])
#define prof_child_dir(X) ((X)->dents[AAFS_PROF_PROFS])
void __aa_fs_profile_rmdir(struct aa_profile *profile);
void __aa_fs_profile_migrate_dents(struct aa_profile *old,
struct aa_profile *new);
int __aa_fs_profile_mkdir(struct aa_profile *profile, struct dentry *parent);
void __aa_fs_namespace_rmdir(struct aa_namespace *ns);
int __aa_fs_namespace_mkdir(struct aa_namespace *ns, struct dentry *parent,
const char *name);
#endif /* __AA_APPARMORFS_H */

View File

@ -22,12 +22,10 @@
#include <linux/slab.h>
#include "file.h"
struct aa_profile;
#include "label.h"
extern const char *const audit_mode_names[];
#define AUDIT_MAX_INDEX 5
enum audit_mode {
AUDIT_NORMAL, /* follow normal auditing of accesses */
AUDIT_QUIET_DENIED, /* quiet all denied access messages */
@ -68,10 +66,16 @@ enum aa_ops {
OP_GETATTR,
OP_OPEN,
OP_FRECEIVE,
OP_FPERM,
OP_FLOCK,
OP_FMMAP,
OP_FMPROT,
OP_INHERIT,
OP_PIVOTROOT,
OP_MOUNT,
OP_UMOUNT,
OP_CREATE,
OP_POST_CREATE,
@ -85,9 +89,10 @@ enum aa_ops {
OP_GETPEERNAME,
OP_GETSOCKOPT,
OP_SETSOCKOPT,
OP_SOCK_SHUTDOWN,
OP_SHUTDOWN,
OP_PTRACE,
OP_SIGNAL,
OP_EXEC,
OP_CHANGE_HAT,
@ -107,38 +112,69 @@ struct apparmor_audit_data {
int error;
int op;
int type;
void *profile;
struct aa_label *label;
const char *name;
const char *info;
struct task_struct *tsk;
u32 request;
u32 denied;
union {
void *target;
struct {
long pos;
void *target;
} iface;
const void *target;
union {
struct {
long pos;
} iface;
struct {
kuid_t ouid;
} fs;
struct {
int type, protocol;
struct sock *peer_sk;
void *addr;
int addrlen;
} net;
int signal;
};
};
struct {
int rlim;
unsigned long max;
} rlim;
struct {
const char *target;
u32 request;
u32 denied;
kuid_t ouid;
} fs;
const char *src_name;
const char *type;
const char *trans;
const char *data;
unsigned long flags;
} mnt;
};
};
/* define a short hand for apparmor_audit_data structure */
#define aad apparmor_audit_data
/* macros for dealing with apparmor_audit_data structure */
#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 (*cb) (struct audit_buffer *, void *));
int aa_audit(int type, struct aa_profile *profile, gfp_t gfp,
struct common_audit_data *sa,
int aa_audit(int type, struct aa_profile *profile, struct common_audit_data *sa,
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)
{
if (error == -EPERM || error == -EACCES)

View File

@ -4,7 +4,7 @@
* This file contains AppArmor capability mediation definitions.
*
* 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
* modify it under the terms of the GNU General Public License as
@ -17,11 +17,14 @@
#include <linux/sched.h>
struct aa_profile;
#include "apparmorfs.h"
struct aa_label;
/* aa_caps - confinement data for capabilities
* @allowed: capabilities mask
* @audit: caps that are to be audited
* @denied: caps that are explicitly denied
* @quiet: caps that should not be audited
* @kill: caps that when requested will result in the task being killed
* @extended: caps that are subject finer grained mediation
@ -29,13 +32,15 @@ struct aa_profile;
struct aa_caps {
kernel_cap_t allow;
kernel_cap_t audit;
kernel_cap_t denied;
kernel_cap_t quiet;
kernel_cap_t kill;
kernel_cap_t extended;
};
int aa_capable(struct task_struct *task, struct aa_profile *profile, int cap,
int audit);
extern struct aa_fs_entry aa_fs_entry_caps[];
int aa_capable(struct aa_label *label, int cap, int audit);
static inline void aa_free_cap_rules(struct aa_caps *caps)
{

View File

@ -19,56 +19,29 @@
#include <linux/slab.h>
#include <linux/sched.h>
#include "label.h"
#include "policy.h"
/* 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);
}
#define cred_cxt(X) (X)->security
#define current_cxt() cred_cxt(current_cred())
#define current_ns() labels_ns(aa_current_raw_label())
/**
* struct aa_task_cxt - primary label for confined tasks
* @profile: the current profile (NOT NULL)
* @exec: profile to transition to on next exec (MAYBE NULL)
* @previous: profile the task may return to (MAYBE NULL)
* @token: magic value the task must know for returning to @previous_profile
* @label: the current label (NOT NULL)
* @exec: label to transition to on next exec (MAYBE NULL)
* @previous: label the task may return to (MAYBE NULL)
* @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.
*
* TODO: make so a task can be confined by a stack of contexts
*/
struct aa_task_cxt {
struct aa_profile *profile;
struct aa_profile *onexec;
struct aa_profile *previous;
struct aa_label *label;
struct aa_label *onexec;
struct aa_label *previous;
u64 token;
};
@ -76,10 +49,51 @@ struct aa_task_cxt *aa_alloc_task_context(gfp_t flags);
void aa_free_task_context(struct aa_task_cxt *cxt);
void aa_dup_task_context(struct aa_task_cxt *new,
const struct aa_task_cxt *old);
int aa_replace_current_profile(struct aa_profile *profile);
int aa_set_current_onexec(struct aa_profile *profile);
int aa_set_current_hat(struct aa_profile *profile, u64 token);
int aa_restore_previous_profile(u64 cookie);
int aa_replace_current_label(struct aa_label *label);
int aa_set_current_onexec(struct aa_label *label);
int aa_set_current_hat(struct aa_label *label, u64 token);
int aa_restore_previous_label(u64 cookie);
struct aa_label *aa_get_task_label(struct task_struct *task);
/**
* aa_cred_raw_label - obtain cred's label
* @cred: cred to obtain label from (NOT NULL)
*
* Returns: confining label
*
* does NOT increment reference count
*/
static inline struct aa_label *aa_cred_raw_label(const struct cred *cred)
{
struct aa_task_cxt *cxt = cred_cxt(cred);
BUG_ON(!cxt || !cxt->label);
return cxt->label;
}
/**
* 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)
*
* Returns: @task's label without incrementing its ref count
*
* If @task != current needs to be called in RCU safe critical section
*/
static inline struct aa_label *__aa_task_raw_label(struct task_struct *task)
{
return aa_cred_raw_label(__task_cred(task));
}
/**
* __aa_task_is_confined - determine if @task has any confinement
@ -89,66 +103,106 @@ int aa_restore_previous_profile(u64 cookie);
*/
static inline bool __aa_task_is_confined(struct task_struct *task)
{
struct aa_task_cxt *cxt = __task_cred(task)->security;
BUG_ON(!cxt || !cxt->profile);
if (unconfined(aa_newest_version(cxt->profile)))
return 0;
return 1;
return !unconfined(__aa_task_raw_label(task));
}
/**
* aa_cred_profile - obtain cred's profiles
* @cred: cred to obtain profiles from (NOT NULL)
* aa_current_raw_label - find the current tasks confining label
*
* Returns: confining profile
*
* does NOT increment reference count
*/
static inline struct aa_profile *aa_cred_profile(const struct cred *cred)
{
struct aa_task_cxt *cxt = cred->security;
BUG_ON(!cxt || !cxt->profile);
return aa_newest_version(cxt->profile);
}
/**
* __aa_current_profile - find the current tasks confining profile
*
* 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
* 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: up to date confining profile or the ns unconfined profile (NOT NULL)
* Returns: newest version of confining label (NOT NULL)
*
* This fn will update the tasks cred structure if the profile has been
* This fn will not update the tasks cred, so it is safe inside of locks
*
* The returned reference must be put with aa_put_label()
*/
static inline struct aa_label *aa_get_current_label(void)
{
struct aa_label *l = aa_current_raw_label();
if (label_invalid(l))
return aa_get_newest_label(l);
return aa_get_label(l);
}
/**
* aa_begin_current_label - find newest version of the current tasks label
*
* Returns: newest version of confining label (NOT NULL)
*
* This fn will not update the tasks cred, so it is safe inside of locks
*
* The returned reference must be put with aa_end_current_label()
*/
static inline struct aa_label *aa_begin_current_label(void)
{
struct aa_label *l = aa_current_raw_label();
if (label_invalid(l))
l = aa_get_newest_label(l);
return l;
}
/**
* 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_current_label - find the current tasks confining label and update it
*
* Returns: up to date confining label or the ns unconfined label (NOT NULL)
*
* This fn will update the tasks cred structure if the label has been
* replaced. Not safe to call inside locks
*/
static inline struct aa_profile *aa_current_profile(void)
static inline struct aa_label *aa_current_label(void)
{
const struct aa_task_cxt *cxt = current_cred()->security;
struct aa_profile *profile;
BUG_ON(!cxt || !cxt->profile);
const struct aa_task_cxt *cxt = current_cxt();
struct aa_label *label;
BUG_ON(!cxt || !cxt->label);
profile = aa_newest_version(cxt->profile);
/*
* Whether or not replacement succeeds, use newest profile so
* there is no need to update it after replacement.
*/
if (unlikely((cxt->profile != profile)))
aa_replace_current_profile(profile);
if (label_invalid(cxt->label)) {
label = aa_get_newest_label(cxt->label);
aa_replace_current_label(label);
aa_put_label(label);
cxt = current_cxt();
}
return profile;
return cxt->label;
}
/**
* aa_clear_task_cxt_trans - clear transition tracking info from the cxt
* @cxt: task context to clear (NOT NULL)
*/
static inline void aa_clear_task_cxt_trans(struct aa_task_cxt *cxt)
{
aa_put_label(cxt->previous);
aa_put_label(cxt->onexec);
cxt->previous = NULL;
cxt->onexec = NULL;
cxt->token = 0;
}
#endif /* __AA_CONTEXT_H */

View File

@ -0,0 +1,36 @@
/*
* AppArmor security module
*
* This file contains AppArmor policy loading interface function 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 __APPARMOR_CRYPTO_H
#define __APPARMOR_CRYPTO_H
#include "policy.h"
#ifdef CONFIG_SECURITY_APPARMOR_HASH
unsigned int aa_hash_size(void);
int aa_calc_profile_hash(struct aa_profile *profile, u32 version, void *start,
size_t len);
#else
static inline int aa_calc_profile_hash(struct aa_profile *profile, u32 version,
void *start, size_t len)
{
return 0;
}
static inline unsigned int aa_hash_size(void)
{
return 0;
}
#endif
#endif /* __APPARMOR_CRYPTO_H */

View File

@ -23,6 +23,8 @@ struct aa_domain {
char **table;
};
struct aa_profile *x_table_lookup(struct aa_profile *profile, u32 xindex);
int apparmor_bprm_set_creds(struct linux_binprm *bprm);
int apparmor_bprm_secureexec(struct linux_binprm *bprm);
void apparmor_bprm_committing_creds(struct linux_binprm *bprm);

View File

@ -15,38 +15,75 @@
#ifndef __AA_FILE_H
#define __AA_FILE_H
#include <linux/spinlock.h>
#include "domain.h"
#include "match.h"
#include "label.h"
#include "perms.h"
struct aa_profile;
struct path;
/*
* 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 mask_mode_t(X) (X & (MAY_EXEC | MAY_WRITE | MAY_READ | MAY_APPEND))
#define AA_AUDIT_FILE_MASK (MAY_READ | MAY_WRITE | MAY_EXEC | MAY_APPEND |\
AA_MAY_CREATE | AA_MAY_DELETE | \
AA_MAY_META_READ | AA_MAY_META_WRITE | \
AA_MAY_GETATTR | AA_MAY_SETATTR | \
AA_MAY_CHMOD | AA_MAY_CHOWN | AA_MAY_LOCK | \
AA_EXEC_MMAP | AA_MAY_LINK)
#define file_cxt(X) ((struct aa_file_cxt *)(X)->f_security)
/* struct aa_file_cxt - the AppArmor context the file was opened in
* @lock: lock to update the cxt
* @label: label currently cached on the cxt
* @perms: the permission the file was opened with
*/
struct aa_file_cxt {
spinlock_t lock;
struct aa_label __rcu *label;
u32 allow;
};
/**
* aa_alloc_file_cxt - allocate file_cxt
* @label: initial label of task creating the file
* @gfp: gfp flags for allocation
*
* Returns: file_cxt or NULL on failure
*/
static inline struct aa_file_cxt *aa_alloc_file_cxt(struct aa_label *label, gfp_t gfp)
{
struct aa_file_cxt *cxt;
cxt = kzalloc(sizeof(struct aa_file_cxt), gfp);
if (cxt) {
spin_lock_init(&cxt->lock);
rcu_assign_pointer(cxt->label, aa_get_label(label));
}
return cxt;
}
/**
* aa_free_file_cxt - free a file_cxt
* @cxt: file_cxt to free (MAYBE_NULL)
*/
static inline void aa_free_file_cxt(struct aa_file_cxt *cxt)
{
if (cxt) {
aa_put_label(rcu_access_pointer(cxt->label));
kzfree(cxt);
}
}
static inline struct aa_label *aa_get_file_label(struct aa_file_cxt *cxt)
{
return aa_get_label_rcu(&cxt->label);
}
#define inode_cxt(X) (X)->i_security
/*
* The xindex is broken into 3 parts
* - index - an index into either the exec name table or the variable table
@ -145,8 +182,8 @@ static inline u16 dfa_map_xindex(u16 mask)
dfa_map_xindex((ACCEPT_TABLE(dfa)[state] >> 14) & 0x3fff)
int aa_audit_file(struct aa_profile *profile, struct file_perms *perms,
gfp_t gfp, int op, u32 request, const char *name,
const char *target, kuid_t ouid, const char *info, int error);
int op, u32 request, const char *name, const char *target,
kuid_t ouid, const char *info, int error);
/**
* struct aa_file_rules - components used for file rule permissions
@ -171,26 +208,26 @@ unsigned int aa_str_perms(struct aa_dfa *dfa, unsigned int start,
const char *name, struct path_cond *cond,
struct file_perms *perms);
int aa_path_perm(int op, struct aa_profile *profile, struct path *path,
int __aa_path_perm(int op, struct aa_profile *profile, const char *name,
u32 request, struct path_cond *cond, int flags,
struct file_perms *perms);
int aa_path_perm(int op, struct aa_label *label, struct path *path,
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);
int aa_file_perm(int op, struct aa_profile *profile, struct file *file,
int aa_file_perm(int op, struct aa_label *label, struct file *file,
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)
{
aa_put_dfa(rules->dfa);
aa_free_domain_entries(&rules->trans);
}
#define ACC_FMODE(x) (("\000\004\002\006"[(x)&O_ACCMODE]) | (((x) << 1) & 0x40))
/* from namei.c */
#define MAP_OPEN_FLAGS(x) ((((x) + 1) & O_ACCMODE) ? (x) + 1 : (x))
/**
* aa_map_file_perms - map file flags to AppArmor permissions
* @file: open file to map flags to AppArmor permissions
@ -199,8 +236,13 @@ static inline void aa_free_file_rules(struct aa_file_rules *rules)
*/
static inline u32 aa_map_file_to_perms(struct file *file)
{
int flags = MAP_OPEN_FLAGS(file->f_flags);
u32 perms = ACC_FMODE(file->f_mode);
int flags = file->f_flags;
u32 perms = 0;
if (file->f_mode & FMODE_WRITE)
perms |= MAY_WRITE;
if (file->f_mode & FMODE_READ)
perms |= MAY_READ;
if ((flags & O_APPEND) && (perms & MAY_WRITE))
perms = (perms & ~MAY_WRITE) | MAY_APPEND;

View File

@ -4,7 +4,7 @@
* This file contains AppArmor ipc mediation function definitions.
*
* 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
* modify it under the terms of the GNU General Public License as
@ -19,10 +19,22 @@
struct aa_profile;
int aa_may_ptrace(struct task_struct *tracer_task, struct aa_profile *tracer,
struct aa_profile *tracee, unsigned int mode);
#define AA_PTRACE_TRACE MAY_WRITE
#define AA_PTRACE_READ MAY_READ
#define AA_MAY_BE_TRACED AA_MAY_APPEND
#define AA_MAY_BE_READ AA_MAY_CREATE
#define PTRACE_PERM_SHIFT 2
int aa_ptrace(struct task_struct *tracer, struct task_struct *tracee,
unsigned int mode);
#define AA_PTRACE_PERM_MASK (AA_PTRACE_READ | AA_PTRACE_TRACE | \
AA_MAY_BE_READ | AA_MAY_BE_TRACED)
#define AA_SIGNAL_PERM_MASK (MAY_READ | MAY_WRITE)
#define AA_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 */

View File

@ -0,0 +1,419 @@
/*
* 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"
struct aa_namespace;
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 invalid; /* outstanding invalid */
};
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 */
FALG_EXPLICIT = 0x400, /* explict static label */
FLAG_INVALID = 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_replacedby {
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
* @replacedby: is set to the label that replaced this label
* @hname: text representation of the label (MAYBE_NULL)
* @flags: invalid 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_replacedby *replacedby;
__counted char *hname;
long flags;
u32 sid;
int size;
struct aa_profile *ent[2];
};
#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_invalid(X) ((X)->flags & FLAG_INVALID)
#define __label_invalidate(X) do { \
labelsetstats_inc(labels_set(X), invalid); \
((X)->flags |= FLAG_INVALID); \
} while (0)
#define labels_last(X) ((X)->ent[(X)->size - 1])
#define labels_ns(X) (labels_last(X)->ns)
#define labels_set(X) (&labels_ns(X)->labels)
#define labels_profile(X) ({ \
AA_BUG(!label_isprofile(X)); \
container_of((X), struct aa_profile, label); \
})
int aa_label_next_confined(struct aa_label *l, int i);
/* for each profile in a label */
#define label_for_each(I, L, P) \
for ((I).i = 0; ((P) = (L)->ent[(I).i]); ++((I).i))
#define label_for_each_at(I, L, P) \
for (; ((P) = (L)->ent[(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)->ent[(I).i]) && ((P2) = (L2)->ent[(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)->ent[(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 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_all(struct aa_namespace *ns);
void aa_label_destroy(struct aa_label *label);
void aa_label_free(struct aa_label *label);
void aa_label_kref(struct kref *kref);
bool aa_label_init(struct aa_label *label, int size);
struct aa_label *aa_label_alloc(int size, 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_labelset *ls, struct aa_label *label);
struct aa_label *aa_label_insert(struct aa_labelset *ls, struct aa_label *l);
struct aa_label *aa_label_remove_and_insert(struct aa_labelset *ls,
struct aa_label *remove,
struct aa_label *insert);
bool aa_label_replace(struct aa_labelset *ls, 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_labelset *ls, struct aa_label *l);
struct aa_label *aa_label_vec_find(struct aa_labelset *ls,
struct aa_profile **vec,
int n);
struct aa_label *aa_label_vec_merge(struct aa_profile **vec, int len,
gfp_t gfp);
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_namespace *ns, struct aa_label *label,
gfp_t gfp);
int aa_profile_snprint(char *str, size_t size, struct aa_namespace *ns,
struct aa_profile *profile, bool mode);
int aa_label_snprint(char *str, size_t size, struct aa_namespace *ns,
struct aa_label *label, bool mode);
int aa_label_asprint(char **strp, struct aa_namespace *ns,
struct aa_label *label, bool mode, gfp_t gfp);
int aa_label_acntsprint(char __counted **strp, struct aa_namespace *ns,
struct aa_label *label, bool mode, gfp_t gfp);
void aa_label_audit(struct audit_buffer *ab, struct aa_namespace *ns,
struct aa_label *label, bool mode, gfp_t gfp);
void aa_label_seq_print(struct seq_file *f, struct aa_namespace *ns,
struct aa_label *label, bool mode, gfp_t gfp);
void aa_label_printk(struct aa_namespace *ns, struct aa_label *label,
bool mode, gfp_t gfp);
struct aa_label *aa_label_parse(struct aa_label *base, char *str,
gfp_t gfp, bool create);
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_invalid(l))
return aa_get_label_rcu(&l->replacedby->label);
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_replacedby *aa_alloc_replacedby(struct aa_label *l);
void aa_free_replacedby_kref(struct kref *kref);
static inline struct aa_replacedby *aa_get_replacedby(struct aa_replacedby *r)
{
if (r)
kref_get(&(r->count));
return r;
}
static inline void aa_put_replacedby(struct aa_replacedby *r)
{
if (r)
kref_put(&r->count, aa_free_replacedby_kref);
}
void __aa_update_replacedby(struct aa_label *orig, struct aa_label *new);
#endif /* __AA_LABEL_H */

View File

@ -4,7 +4,7 @@
* This file contains AppArmor policy dfa matching engine definitions.
*
* Copyright (C) 1998-2008 Novell/SUSE
* Copyright 2009-2010 Canonical Ltd.
* 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
@ -16,25 +16,30 @@
#define __AA_MATCH_H
#include <linux/kref.h>
#include <linux/workqueue.h>
#define DFA_NOMATCH 0
#define DFA_START 1
#define DFA_VALID_PERM_MASK 0xffffffff
#define DFA_VALID_PERM2_MASK 0xffffffff
/**
* The format used for transition tables is based on the GNU flex table
* file format (--tables-file option; see Table File Format in the flex
* info pages and the flex sources for documentation). The magic number
* used in the header is 0x1B5E783D instead of 0xF13C57B1 though, because
* the YY_ID_CHK (check) and YY_ID_DEF (default) tables are used
* slightly differently (see the apparmor-parser package).
* new tables have been defined and others YY_ID_CHK (check) and YY_ID_DEF
* (default) tables are used slightly differently (see the apparmor-parser
* package).
*
*
* The data in the packed dfa is stored in network byte order, and the tables
* are arranged for flexibility. We convert the table data to host native
* byte order.
*
* The dfa begins with a table set header, and is followed by the actual
* tables.
*/
#define YYTH_MAGIC 0x1B5E783D
#define YYTH_DEF_RECURSE 0x1 /* DEF Table is recursive */
struct table_set_header {
u32 th_magic; /* YYTH_MAGIC */
@ -63,7 +68,7 @@ struct table_set_header {
#define YYTD_DATA32 4
#define YYTD_DATA64 8
/* Each ACCEPT2 table gets 6 dedicated flags, YYTD_DATAX define the
/* ACCEPT & ACCEPT2 tables gets 6 dedicated flags, YYTD_DATAX define the
* first flags
*/
#define ACCEPT1_FLAGS(X) ((X) & 0x3f)
@ -121,6 +126,21 @@ unsigned int aa_dfa_next(struct aa_dfa *dfa, unsigned int state,
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
* @dfa: dfa to put refcount (MAYBE NULL)

View File

@ -0,0 +1,54 @@
/*
* 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 */

View File

@ -0,0 +1,110 @@
/*
* 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 "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_cxt {
struct aa_label *label;
struct aa_label *peer;
};
#define SK_CXT(X) (X)->sk_security
#define SOCK_CXT(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) ? LSM_AUDIT_DATA_NET : LSM_AUDIT_DATA_NONE,\
OP); \
NAME.u.net = &(NAME ## _net); \
aad(&NAME)->net.type = (T); \
aad(&NAME)->net.protocol = (P)
/* 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, int op, u16 family,
int type, int protocol, struct sock *sk);
int aa_af_perm(struct aa_label *label, int op, u32 request, u16 family,
int type, int protocol, struct sock *sk);
int aa_sock_perm(int 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(int op, u32 request, struct socket *sock,
struct msghdr *msg, int size);
int aa_sock_opt_perm(int op, u32 request, struct socket *sock, int level,
int optname);
int aa_sock_file_perm(struct aa_label *label, int op, u32 request,
struct socket *sock);
static inline void aa_free_net_rules(struct aa_net *new)
{
/* NOP */
}
#endif /* __AA_NET_H */

View File

@ -18,15 +18,72 @@
enum path_flags {
PATH_IS_DIR = 0x1, /* path is a directory */
PATH_SOCK_COND = 0x2,
PATH_CONNECT_PATH = 0x4, /* connect disconnected paths to / */
PATH_CHROOT_REL = 0x8, /* do path lookup relative to chroot */
PATH_CHROOT_NSCONNECT = 0x10, /* connect paths that are at ns root */
PATH_DELEGATE_DELETED = 0x08000, /* delegate deleted files */
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,
const char **name, const char **info);
int aa_path_name(struct path *path, int flags, char *buffer,
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 */

View File

@ -0,0 +1,174 @@
/*
* 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 * /
*/
};
#define ALL_PERMS_MASK 0xffffffff
#define aa_perms_clear(X) memset((X), 0, sizeof(*(X)));
#define aa_perms_all(X) \
do { \
aa_perms_clear(X); \
(X)->allow = ALL_PERMS_MASK; \
/* the following are only used for denials */ \
(X)->quiet = ALL_PERMS_MASK; \
(X)->hide = ALL_PERMS_MASK; \
} while (0)
#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, const char *label,
int type, 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 *));
const char *aa_peer_name(struct aa_profile *peer);
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 */

View File

@ -27,20 +27,27 @@
#include "capability.h"
#include "domain.h"
#include "file.h"
#include "label.h"
#include "net.h"
#include "resource.h"
extern const char *const profile_mode_names[];
#define APPARMOR_NAMES_MAX_INDEX 3
extern const char *aa_hidden_ns_name;
extern const char *const aa_profile_mode_names[];
#define APPARMOR_MODE_NAMES_MAX_INDEX 4
#define COMPLAIN_MODE(_profile) \
((aa_g_profile_mode == APPARMOR_COMPLAIN) || \
((_profile)->mode == APPARMOR_COMPLAIN))
#define PROFILE_MODE(_profile, _mode) \
((aa_g_profile_mode == (_mode)) || \
((_profile)->mode == (_mode)))
#define KILL_MODE(_profile) \
((aa_g_profile_mode == APPARMOR_KILL) || \
((_profile)->mode == APPARMOR_KILL))
#define COMPLAIN_MODE(_profile) PROFILE_MODE((_profile), APPARMOR_COMPLAIN)
#define PROFILE_IS_HAT(_profile) ((_profile)->flags & PFLAG_HAT)
#define KILL_MODE(_profile) PROFILE_MODE((_profile), APPARMOR_KILL)
#define PROFILE_IS_HAT(_profile) ((_profile)->label.flags & FLAG_HAT)
#define PROFILE_INVALID(_profile) ((_profile)->label.flags & FLAG_INVALID)
#define on_list_rcu(X) (!list_empty(X) && (X)->prev != LIST_POISON2)
/*
* FIXME: currently need a clean way to replace and remove profiles as a
@ -52,35 +59,21 @@ enum profile_mode {
APPARMOR_ENFORCE, /* enforce access rules */
APPARMOR_COMPLAIN, /* allow and log access violations */
APPARMOR_KILL, /* kill task on access violation */
APPARMOR_UNCONFINED, /* profile set to unconfined */
};
enum profile_flags {
PFLAG_HAT = 1, /* profile is a hat */
PFLAG_UNCONFINED = 2, /* profile is an unconfined profile */
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 */
/* 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
* @count: reference count of the obj
* @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 {
char *name;
char *hname;
struct kref count;
const char *name;
__counted char *hname;
struct list_head list;
struct list_head profiles;
};
@ -105,6 +98,9 @@ struct aa_ns_acct {
* @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
@ -123,10 +119,16 @@ struct aa_ns_acct {
struct aa_namespace {
struct aa_policy base;
struct aa_namespace *parent;
rwlock_t lock;
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];
};
/* struct aa_policydb - match engine for a policy
@ -142,30 +144,33 @@ struct aa_policydb {
/* struct aa_profile - basic confinement data
* @base - base components of the profile (name, refcount, lists, lock ...)
* @label - label this profile is an extension of
* @parent: parent of profile
* @ns: namespace the profile is in
* @replacedby: is set to the profile that replaced this profile
* @rename: optional profile name that this profile renamed
* @attach: human readable attachment string
* @xmatch: optional extended matching for unconfined executables names
* @xmatch_len: xmatch prefix len, used to determine xmatch priority
* @sid: the unique security id number of this profile
* @audit: the auditing mode of the profile
* @mode: the enforcement mode of the profile
* @flags: flags controlling profile 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
* @policy: general match rules governing policy
* @file: The set of rules governing basic file access and domain transitions
* @caps: capabilities for the profile
* @net: network controls for the profile
* @rlimits: rlimits for the profile
*
* @dents: dentries for the profiles file entries in apparmorfs
* @dirname: name of the profile dir in apparmorfs
*
* The AppArmor profile contains the basic confinement data. Each profile
* has a name, and exists in a namespace. The @name and @exec_match are
* used to determine profile attachment against unconfined tasks. All other
* attachments are determined by profile X transition rules.
*
* The @replacedby field is write protected by the profile lock. Reads
* are assumed to be atomic, and are done without locking.
* The @replacedby struct is write protected by the profile lock.
*
* Profiles have a hierarchy where hats and children profiles keep
* a reference to their parent.
@ -176,49 +181,172 @@ struct aa_policydb {
*/
struct aa_profile {
struct aa_policy base;
struct aa_profile *parent;
struct aa_label label;
struct aa_profile __rcu *parent;
struct aa_namespace *ns;
struct aa_profile *replacedby;
const char *rename;
const char *attach;
struct aa_dfa *xmatch;
int xmatch_len;
u32 sid;
enum audit_mode audit;
enum profile_mode mode;
u32 flags;
long mode;
u32 path_flags;
const char *disconnected;
int size;
struct aa_policydb policy;
struct aa_file_rules file;
struct aa_caps caps;
struct aa_net net;
struct aa_rlimit rlimits;
unsigned char *hash;
char *dirname;
struct dentry *dents[AAFS_PROF_SIZEOF];
};
extern struct aa_namespace *root_ns;
extern enum profile_mode aa_g_profile_mode;
#define profiles_ns(P) ((P)->ns)
void aa_add_profile(struct aa_policy *common, struct aa_profile *profile);
bool aa_ns_visible(struct aa_namespace *curr, struct aa_namespace *view);
const char *aa_ns_name(struct aa_namespace *parent, struct aa_namespace *child);
void aa_free_namespace(struct aa_namespace *ns);
int aa_alloc_root_ns(void);
void aa_free_root_ns(void);
void aa_free_namespace_kref(struct kref *kref);
struct aa_namespace *aa_find_namespace(struct aa_namespace *root,
const char *name);
struct aa_namespace *aa_findn_namespace(struct aa_namespace *root,
const char *name, size_t n);
static inline struct aa_policy *aa_get_common(struct aa_policy *c)
struct aa_label *aa_setup_default_label(void);
struct aa_profile *aa_alloc_profile(const char *name);
struct aa_profile *aa_new_null_profile(struct aa_profile *parent, int hat);
void aa_free_profile(struct aa_profile *profile);
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_lookupn_profile(struct aa_namespace *ns,
const char *hname, size_t n);
struct aa_profile *aa_lookup_profile(struct aa_namespace *ns, const char *name);
struct aa_profile *aa_fqlookupn_profile(struct aa_label *base, char *fqname,
size_t n);
struct aa_profile *aa_match_profile(struct aa_namespace *ns, const char *name);
ssize_t aa_replace_profiles(void *udata, size_t size, bool noreplace);
ssize_t aa_remove_profiles(char *name, size_t size);
#define PROF_ADD 1
#define PROF_REPLACE 0
#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)
{
if (c)
kref_get(&c->count);
return labels_profile(aa_get_newest_label(&p->label));
}
#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)
{
if (profile->policy.dfa)
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);
}
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_profile - increment refcount on profile @p
* @p: profile (MAYBE NULL)
*
* Returns: pointer to @p if @p is NULL will return NULL
* Requires: @p must be held with valid refcount when called
*/
static inline struct aa_profile *aa_get_profile(struct aa_profile *p)
{
if (p)
kref_get(&(p->label.count));
return p;
}
/**
* aa_get_profile_not0 - increment refcount on profile @p found via lookup
* @p: profile (MAYBE NULL)
*
* Returns: pointer to @p if @p is NULL will return NULL
* Requires: @p must be held with valid refcount when called
*/
static inline struct aa_profile *aa_get_profile_not0(struct aa_profile *p)
{
if (p && kref_get_not0(&p->label.count))
return p;
return NULL;
}
/**
* aa_get_profile_rcu - increment a refcount profile that can be replaced
* @p: pointer to profile that can be replaced (NOT NULL)
*
* Returns: pointer to a refcounted profile.
* else NULL if no profile
*/
static inline struct aa_profile *aa_get_profile_rcu(struct aa_profile __rcu **p)
{
struct aa_profile *c;
rcu_read_lock();
do {
c = rcu_dereference(*p);
} while (c && !kref_get_not0(&c->label.count));
rcu_read_unlock();
return c;
}
/**
* aa_put_profile - decrement refcount on profile @p
* @p: profile (MAYBE NULL)
*/
static inline void aa_put_profile(struct aa_profile *p)
{
if (p)
kref_put(&p->label.count, aa_label_kref);
}
/**
* aa_get_namespace - increment references count on @ns
* @ns: namespace to increment reference count of (MAYBE NULL)
@ -229,7 +357,7 @@ static inline struct aa_policy *aa_get_common(struct aa_policy *c)
static inline struct aa_namespace *aa_get_namespace(struct aa_namespace *ns)
{
if (ns)
kref_get(&(ns->base.count));
aa_get_profile(ns->unconfined);
return ns;
}
@ -243,66 +371,7 @@ static inline struct aa_namespace *aa_get_namespace(struct aa_namespace *ns)
static inline void aa_put_namespace(struct aa_namespace *ns)
{
if (ns)
kref_put(&ns->base.count, aa_free_namespace_kref);
}
struct aa_profile *aa_alloc_profile(const char *name);
struct aa_profile *aa_new_null_profile(struct aa_profile *parent, int hat);
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_lookup_profile(struct aa_namespace *ns, const char *name);
struct aa_profile *aa_match_profile(struct aa_namespace *ns, const char *name);
ssize_t aa_replace_profiles(void *udata, size_t size, bool noreplace);
ssize_t aa_remove_profiles(char *name, size_t size);
#define PROF_ADD 1
#define PROF_REPLACE 0
#define unconfined(X) ((X)->flags & PFLAG_UNCONFINED)
/**
* aa_newest_version - find the newest version of @profile
* @profile: the profile to check for newer versions of (NOT NULL)
*
* Returns: newest version of @profile, if @profile is the newest version
* return @profile.
*
* NOTE: the profile returned is not refcounted, The refcount on @profile
* must be held until the caller decides what to do with the returned newest
* version.
*/
static inline struct aa_profile *aa_newest_version(struct aa_profile *profile)
{
while (profile->replacedby)
profile = profile->replacedby;
return profile;
}
/**
* aa_get_profile - increment refcount on profile @p
* @p: profile (MAYBE NULL)
*
* Returns: pointer to @p if @p is NULL will return NULL
* Requires: @p must be held with valid refcount when called
*/
static inline struct aa_profile *aa_get_profile(struct aa_profile *p)
{
if (p)
kref_get(&(p->base.count));
return p;
}
/**
* aa_put_profile - decrement refcount on profile @p
* @p: profile (MAYBE NULL)
*/
static inline void aa_put_profile(struct aa_profile *p)
{
if (p)
kref_put(&p->base.count, aa_free_profile_kref);
aa_put_profile(ns->unconfined);
}
static inline int AUDIT_MODE(struct aa_profile *profile)
@ -315,4 +384,30 @@ static inline int AUDIT_MODE(struct aa_profile *profile)
bool aa_may_manage_policy(int op);
#define LOCAL_VEC_ENTRIES 8
#define DEFINE_PROFILE_VEC(V, T) \
struct aa_profile *(T)[LOCAL_VEC_ENTRIES]; \
struct aa_profile **(V)
#define aa_setup_profile_vec(V, T, L) \
({ \
if ((L) > LOCAL_VEC_ENTRIES) \
(V) = kmalloc(sizeof(struct aa_profile *) * (L), GFP_KERNEL);\
else \
(V) = (T); \
(V) ? 0 : -ENOMEM; \
})
static inline void aa_cleanup_profile_vec(struct aa_profile **vec, \
struct aa_profile **tmp, int len) \
{ \
int i; \
for (i = 0; i < len; i++) \
aa_put_profile(vec[i]); \
if (vec != tmp) \
kfree(vec); \
}
#endif /* __AA_POLICY_H */

View File

@ -15,6 +15,25 @@
#ifndef __POLICY_INTERFACE_H
#define __POLICY_INTERFACE_H
struct aa_profile *aa_unpack(void *udata, size_t size, const char **ns);
#include <linux/list.h>
struct aa_load_ent {
struct list_head list;
struct aa_profile *new;
struct aa_profile *old;
struct aa_profile *rename;
};
void aa_load_ent_free(struct aa_load_ent *ent);
struct aa_load_ent *aa_load_ent_alloc(void);
#define PACKED_FLAG_HAT 1
#define PACKED_MODE_ENFORCE 0
#define PACKED_MODE_COMPLAIN 1
#define PACKED_MODE_KILL 2
#define PACKED_MODE_UNCONFINED 3
int aa_unpack(void *udata, size_t size, struct list_head *lh, const char **ns);
#endif /* __POLICY_INTERFACE_H */

View File

@ -18,9 +18,8 @@
#define AA_DO_TEST 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_changeprofile(char *fqname, bool onexec, int test);
int aa_setprocattr_permipc(char *fqname);
#endif /* __AA_PROCATTR_H */

View File

@ -37,10 +37,10 @@ struct aa_rlimit {
extern struct aa_fs_entry aa_fs_entry_rlimit[];
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);
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)
{

View File

@ -16,7 +16,9 @@
#include <linux/types.h>
struct aa_profile;
/* sid value that will not be allocated */
#define AA_SID_INVALID 0
#define AA_SID_ALLOC AA_SID_INVALID
u32 aa_alloc_sid(void);
void aa_free_sid(u32 sid);

View File

@ -0,0 +1,95 @@
#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 */
};

View File

@ -4,7 +4,7 @@
* This file contains AppArmor ipc mediation
*
* 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
* modify it under the terms of the GNU General Public License as
@ -20,98 +20,201 @@
#include "include/context.h"
#include "include/policy.h"
#include "include/ipc.h"
/* call back to audit ptrace fields */
static void audit_cb(struct audit_buffer *ab, void *va)
{
struct common_audit_data *sa = va;
audit_log_format(ab, " target=");
audit_log_untrustedstring(ab, sa->aad->target);
}
#include "include/sig_names.h"
/**
* aa_audit_ptrace - do auditing for ptrace
* @profile: profile being enforced (NOT NULL)
* @target: profile being traced (NOT NULL)
* @error: error condition
*
* Returns: %0 or error code
* audit_ptrace_mask - convert mask to permission string
* @buffer: buffer to write string to (NOT NULL)
* @mask: permission mask to convert
*/
static int aa_audit_ptrace(struct aa_profile *profile,
struct aa_profile *target, int error)
static void audit_ptrace_mask(struct audit_buffer *ab, u32 mask)
{
struct common_audit_data sa;
struct apparmor_audit_data aad = {0,};
sa.type = LSM_AUDIT_DATA_NONE;
sa.aad = &aad;
aad.op = OP_PTRACE;
aad.target = target;
aad.error = error;
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;
}
}
return aa_audit(AUDIT_APPARMOR_AUTO, profile, GFP_ATOMIC, &sa,
audit_cb);
/* call back to audit ptrace fields */
static void audit_ptrace_cb(struct audit_buffer *ab, void *va)
{
struct common_audit_data *sa = va;
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=");
audit_log_untrustedstring(ab, aad(sa)->target);
}
/* TODO: conditionals */
static int profile_ptrace_perm(struct aa_profile *profile,
struct aa_profile *peer, u32 request,
struct common_audit_data *sa)
{
struct aa_perms perms;
/* need because of peer in cross check */
if (profile_unconfined(profile) ||
!PROFILE_MEDIATES(profile, AA_CLASS_PTRACE))
return 0;
aad(sa)->target = peer->base.hname;
aa_profile_match_label(profile, aa_peer_name(peer), AA_CLASS_PTRACE,
&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)
{
if (PROFILE_MEDIATES(tracer, AA_CLASS_PTRACE))
return xcheck(profile_ptrace_perm(tracer, tracee, request, sa),
profile_ptrace_perm(tracee, tracer,
request << PTRACE_PERM_SHIFT,
sa));
/* policy uses the old style capability check for ptrace */
if (profile_unconfined(tracer) || tracer == tracee)
return 0;
aad(sa)->label = &tracer->label;
aad(sa)->target = tracee->base.hname;
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
* @tracer_task: task who will do the tracing (NOT NULL)
* @tracer: profile of the task doing the tracing (NOT NULL)
* @tracee: task to be traced
* @mode: whether PTRACE_MODE_READ || PTRACE_MODE_ATTACH
* @tracer: label of the task doing the tracing (NOT NULL)
* @tracee: task label to be traced
* @request: permission request
*
* Returns: %0 else error code if permission denied or error
*/
int aa_may_ptrace(struct task_struct *tracer_task, struct aa_profile *tracer,
struct aa_profile *tracee, unsigned int mode)
int aa_may_ptrace(struct aa_label *tracer, struct aa_label *tracee,
u32 request)
{
/* TODO: currently only based on capability, not extended ptrace
* rules,
* Test mode for PTRACE_MODE_READ || PTRACE_MODE_ATTACH
*/
DEFINE_AUDIT_DATA(sa, LSM_AUDIT_DATA_NONE, OP_PTRACE);
if (unconfined(tracer) || tracer == tracee)
return 0;
/* log this capability request */
return aa_capable(tracer_task, tracer, CAP_SYS_PTRACE, 1);
return xcheck_labels_profiles(tracer, tracee, cross_ptrace_perm,
request, &sa);
}
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
* @tracer: task doing the tracing (NOT NULL)
* @tracee: task being traced (NOT NULL)
* @mode: ptrace mode either PTRACE_MODE_READ || PTRACE_MODE_ATTACH
*
* Returns: %0 else error code if permission denied or error
* audit_file_mask - convert mask to permission string
* @buffer: buffer to write string to (NOT NULL)
* @mask: permission mask to convert
*/
int aa_ptrace(struct task_struct *tracer, struct task_struct *tracee,
unsigned int mode)
static void audit_signal_mask(struct audit_buffer *ab, u32 mask)
{
/*
* tracer can ptrace tracee when
* - tracer is unconfined ||
* - tracer is in complain mode
* - tracer has rules allowing it to trace tracee currently this is:
* - confined by the same profile ||
* - tracer profile has CAP_SYS_PTRACE
*/
struct aa_profile *tracer_p;
/* cred released below */
const struct cred *cred = get_task_cred(tracer);
int error = 0;
tracer_p = aa_cred_profile(cred);
if (!unconfined(tracer_p)) {
/* lcred released below */
const struct cred *lcred = get_task_cred(tracee);
struct aa_profile *tracee_p = aa_cred_profile(lcred);
error = aa_may_ptrace(tracer, tracer_p, tracee_p, mode);
error = aa_audit_ptrace(tracer_p, tracee_p, error);
put_cred(lcred);
}
put_cred(cred);
return error;
if (mask & MAY_READ)
audit_log_string(ab, "receive");
if (mask & MAY_WRITE)
audit_log_string(ab, "send");
}
/**
* 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=");
audit_log_untrustedstring(ab, aad(sa)->target);
}
/* 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;
if (profile->policy.dfa) {
/* 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);
} else
memset(perms, 0, sizeof(*perms));
}
static int profile_signal_perm(struct aa_profile *profile,
struct aa_profile *peer, u32 request,
struct common_audit_data *sa)
{
struct aa_perms perms;
if (profile_unconfined(profile) ||
!PROFILE_MEDIATES(profile, AA_CLASS_SIGNAL))
return 0;
aad(sa)->target = peer->base.hname;
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);
}
static int aa_signal_cross_perm(struct aa_profile *sender,
struct aa_profile *target,
struct common_audit_data *sa)
{
return xcheck(profile_signal_perm(sender, target, MAY_WRITE, sa),
profile_signal_perm(target, sender, MAY_READ, sa));
}
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);
}

1844
security/apparmor/label.c Normal file

File diff suppressed because it is too large Load Diff

View File

@ -4,7 +4,7 @@
* This file contains basic common functions used in AppArmor
*
* 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
* modify it under the terms of the GNU General Public License as
@ -12,14 +12,17 @@
* License.
*/
#include <linux/ctype.h>
#include <linux/mm.h>
#include <linux/slab.h>
#include <linux/string.h>
#include <linux/vmalloc.h>
#include "include/audit.h"
#include "include/apparmor.h"
#include "include/audit.h"
#include "include/label.h"
#include "include/perms.h"
#include "include/policy.h"
/**
* aa_split_fqname - split a fqname into a profile and namespace name
@ -45,8 +48,10 @@ char *aa_split_fqname(char *fqname, char **ns_name)
*ns_name = skip_spaces(&name[1]);
if (split) {
/* overwrite ':' with \0 */
*split = 0;
name = skip_spaces(split + 1);
*split++ = 0;
if (strncmp(split, "//", 2) == 0)
split += 2;
name = skip_spaces(split);
} else
/* a ns name without a following profile is allowed */
name = NULL;
@ -57,6 +62,56 @@ char *aa_split_fqname(char *fqname, char **ns_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 char *skipn_spaces(const char *str, size_t n)
{
for (;n && isspace(*str); --n)
++str;
if (n)
return (char *)str;
return NULL;
}
char *aa_splitn_fqname(char *fqname, size_t n, char **ns_name, size_t *ns_len)
{
char *end = fqname + n;
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 - 1;
if (*ns_len == 0)
*ns_name = NULL;
split++;
if (end - split > 1 && strncmp(split, "//", 2) == 0)
split += 2;
name = skipn_spaces(split, end - split);
} else {
/* a ns name without a following profile is allowed */
name = NULL;
*ns_len = end - *ns_name;
}
}
if (name && *name == 0)
name = NULL;
return name;
}
/**
* aa_info_message - log a none profile related status message
* @str: message to log
@ -64,26 +119,24 @@ char *aa_split_fqname(char *fqname, char **ns_name)
void aa_info_message(const char *str)
{
if (audit_enabled) {
struct common_audit_data sa;
struct apparmor_audit_data aad = {0,};
sa.type = LSM_AUDIT_DATA_NONE;
sa.aad = &aad;
aad.info = str;
DEFINE_AUDIT_DATA(sa, LSM_AUDIT_DATA_NONE, 0);
aad(&sa)->info = str;
aa_audit_msg(AUDIT_APPARMOR_STATUS, &sa, NULL);
}
printk(KERN_INFO "AppArmor: %s\n", str);
}
/**
* kvmalloc - do allocation preferring kmalloc but falling back to vmalloc
* @size: size of allocation
* __aa_kvmalloc - do allocation preferring kmalloc but falling back to vmalloc
* @size: how many bytes of memory are required
* @flags: the type of memory to allocate (see kmalloc).
*
* Return: allocated buffer or NULL if failed
*
* It is possible that policy being loaded from the user is larger than
* what can be allocated by kmalloc, in those cases fall back to vmalloc.
*/
void *kvmalloc(size_t size)
void *__aa_kvmalloc(size_t size, gfp_t flags)
{
void *buffer = NULL;
@ -92,46 +145,379 @@ void *kvmalloc(size_t size)
/* do not attempt kmalloc if we need more than 16 pages at once */
if (size <= (16*PAGE_SIZE))
buffer = kmalloc(size, GFP_NOIO | __GFP_NOWARN);
buffer = kmalloc(size, flags | GFP_NOIO | __GFP_NOWARN);
if (!buffer) {
/* see kvfree for why size must be at least work_struct size
* when allocated via vmalloc
*/
if (size < sizeof(struct work_struct))
size = sizeof(struct work_struct);
buffer = vmalloc(size);
if (flags & __GFP_ZERO)
buffer = vzalloc(size);
else
buffer = vmalloc(size);
}
return buffer;
}
/**
* do_vfree - workqueue routine for freeing vmalloced memory
* @work: data to be freed
*
* The work_struct is overlaid to the data being freed, as at the point
* the work is scheduled the data is no longer valid, be its freeing
* needs to be delayed until safe.
*/
static void do_vfree(struct work_struct *work)
__counted char *aa_str_alloc(int size, gfp_t gfp)
{
vfree(work);
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, "\"");
}
/**
* kvfree - free an allocation do by kvmalloc
* @buffer: buffer to free (MAYBE_NULL)
*
* Free a buffer allocated by kvmalloc
* aa_audit_perms_cb - generic callback fn for auditing perms
* @ab: audit buffer (NOT NULL)
* @va: audit struct to audit values of (NOT NULL)
*/
void kvfree(void *buffer)
static void aa_audit_perms_cb(struct audit_buffer *ab, void *va)
{
if (is_vmalloc_addr(buffer)) {
/* Data is no longer valid so just use the allocated space
* as the work_struct
*/
struct work_struct *work = (struct work_struct *) buffer;
INIT_WORK(work, do_vfree);
schedule_work(work);
} else
kfree(buffer);
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, " target=");
audit_log_untrustedstring(ab, aad(sa)->target);
}
void map_old_policy_perms(struct aa_dfa *dfa, unsigned int state,
struct aa_perms *perms)
{
}
/**
* 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, const char *label,
int type, struct aa_perms *perms)
{
/* TODO: doesn't yet handle extended types */
unsigned int state;
if (profile->policy.dfa) {
state = aa_dfa_next(profile->policy.dfa,
profile->policy.start[AA_CLASS_LABEL],
type);
state = aa_dfa_match(profile->policy.dfa, state, label);
aa_compute_perms(profile->policy.dfa, state, perms);
} else
memset(perms, 0, sizeof(*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)
{
struct aa_perms perms;
aad(sa)->label = &profile->label;
aad(sa)->target = target;
aad(sa)->request = request;
aa_profile_match_label(profile, target->base.hname, type, &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 (type != AUDIT_APPARMOR_ALLOWED && (!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";
}
const char *aa_peer_name(struct aa_profile *peer)
{
if (profile_unconfined(peer))
return "unconfined";
return peer->base.hname;
}

File diff suppressed because it is too large Load Diff

View File

@ -4,7 +4,7 @@
* This file contains AppArmor dfa based regular expression matching engine
*
* Copyright (C) 1998-2008 Novell/SUSE
* Copyright 2009-2010 Canonical Ltd.
* 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
@ -23,6 +23,8 @@
#include "include/apparmor.h"
#include "include/match.h"
#define base_idx(X) ((X) & 0xffffff)
/**
* unpack_table - unpack a dfa table (one of accept, default, base, next check)
* @blob: data to unpack (NOT NULL)
@ -30,7 +32,7 @@
*
* Returns: pointer to table else NULL on failure
*
* NOTE: must be freed by kvfree (not kmalloc)
* NOTE: must be freed by kvfree (not kfree)
*/
static struct table_header *unpack_table(char *blob, size_t bsize)
{
@ -57,7 +59,7 @@ static struct table_header *unpack_table(char *blob, size_t bsize)
if (bsize < tsize)
goto out;
table = kvmalloc(tsize);
table = kvzalloc(tsize);
if (table) {
*table = th;
if (th.td_flags == YYTD_DATA8)
@ -137,8 +139,7 @@ static int verify_dfa(struct aa_dfa *dfa, int flags)
for (i = 0; i < state_count; i++) {
if (DEFAULT_TABLE(dfa)[i] >= state_count)
goto out;
/* TODO: do check that DEF state recursion terminates */
if (BASE_TABLE(dfa)[i] + 255 >= trans_count) {
if (base_idx(BASE_TABLE(dfa)[i]) + 255 >= trans_count) {
printk(KERN_ERR "AppArmor DFA next/check upper "
"bounds error\n");
goto out;
@ -314,7 +315,7 @@ unsigned int aa_dfa_match_len(struct aa_dfa *dfa, unsigned int start,
u8 *equiv = EQUIV_TABLE(dfa);
/* default is direct to next state */
for (; len; len--) {
pos = base[state] + equiv[(u8) *str++];
pos = base_idx(base[state]) + equiv[(u8) *str++];
if (check[pos] == state)
state = next[pos];
else
@ -323,7 +324,7 @@ unsigned int aa_dfa_match_len(struct aa_dfa *dfa, unsigned int start,
} else {
/* default is direct to next state */
for (; len; len--) {
pos = base[state] + (u8) *str++;
pos = base_idx(base[state]) + (u8) *str++;
if (check[pos] == state)
state = next[pos];
else
@ -364,7 +365,7 @@ unsigned int aa_dfa_match(struct aa_dfa *dfa, unsigned int start,
u8 *equiv = EQUIV_TABLE(dfa);
/* default is direct to next state */
while (*str) {
pos = base[state] + equiv[(u8) *str++];
pos = base_idx(base[state]) + equiv[(u8) *str++];
if (check[pos] == state)
state = next[pos];
else
@ -373,7 +374,7 @@ unsigned int aa_dfa_match(struct aa_dfa *dfa, unsigned int start,
} else {
/* default is direct to next state */
while (*str) {
pos = base[state] + (u8) *str++;
pos = base_idx(base[state]) + (u8) *str++;
if (check[pos] == state)
state = next[pos];
else
@ -409,14 +410,14 @@ unsigned int aa_dfa_next(struct aa_dfa *dfa, unsigned int state,
u8 *equiv = EQUIV_TABLE(dfa);
/* default is direct to next state */
pos = base[state] + equiv[(u8) c];
pos = base_idx(base[state]) + equiv[(u8) c];
if (check[pos] == state)
state = next[pos];
else
state = def[state];
} else {
/* default is direct to next state */
pos = base[state] + (u8) c;
pos = base_idx(base[state]) + (u8) c;
if (check[pos] == state)
state = next[pos];
else

703
security/apparmor/mount.c Normal file
View File

@ -0,0 +1,703 @@
/*
* 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 || aad(sa)->op == OP_MOUNT) {
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, int op, const char *name,
const char *src_name, const char *type,
const char *trans, unsigned long flags,
const void *data, u32 request, struct file_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 COMPLAIN_MODE(profile) ?
complain_error(error) : 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 file_perms compute_mnt_perms(struct aa_dfa *dfa,
unsigned int state)
{
struct file_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 file_perms *perms)
{
unsigned int state;
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 file_perms perms = { };
const char *info = NULL;
int pos, error = -EACCES;
if (!profile->policy.dfa)
goto audit;
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)
{
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;
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;
if (!dev_name || !*dev_name)
return -EINVAL;
flags &= MS_REC | MS_BIND;
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 = kern_path(dev_name, LOOKUP_FOLLOW|LOOKUP_AUTOMOUNT, &old_path);
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;
/* 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;
if (!orig_name || !*orig_name)
return -EINVAL;
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 = kern_path(orig_name, LOOKUP_FOLLOW, &old_path);
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;
dev_name = orig_dev_name;
get_buffers(buffer, dev_buffer);
if (type) {
int requires_dev;
struct file_system_type *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) {
struct path dev_path;
if (!dev_name || !*dev_name) {
error = -ENOENT;
goto out;
}
error = kern_path(dev_name, LOOKUP_FOLLOW, &dev_path);
if (error)
goto error;
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);
out:
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 file_perms perms = { };
const char *info = NULL;
int e = 0;
if (profile->policy.dfa) {
unsigned int state;
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;
} else
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 };
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_profile **trans)
{
struct aa_profile *target = NULL;
struct file_perms perms = { };
const char *info = NULL;
int error = -EACCES;
/* TODO: actual domain transition computation for multiple
* profiles
*/
if (profile->policy.dfa) {
unsigned int state;
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);
if (!target)
error = -ENOENT;
else
*trans = target;
} else
error = 0;
}
}
error = audit_mount(profile, OP_PIVOTROOT, new_name, old_name,
NULL, target ? target->base.name : NULL,
0, NULL, AA_MAY_PIVOTROOT, &perms, info,
error);
if (!*trans)
aa_put_profile(target);
return error;
}
int aa_pivotroot(struct aa_label *label, struct path *old_path,
struct path *new_path)
{
struct aa_profile *profile, *target = NULL;
char *old_buffer = NULL, *new_buffer = NULL;
const char *old_name, *new_name = NULL, *info = NULL;
int error;
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->label);
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;
}

397
security/apparmor/net.c Normal file
View File

@ -0,0 +1,397 @@
/*
* 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)->target) {
audit_log_format(ab, " peer=");
audit_log_untrustedstring(ab, aad(sa)->target);
}
}
/**
* audit_net - audit network access
* @profile: profile being enforced (NOT NULL)
* @op: operation being checked
* @family: network family
* @type: network type
* @protocol: network protocol
* @sk: socket auditing is being applied to
* @error: error code for failure else 0
*
* Returns: %0 or sa->error else other errorcode on failure
*/
static int audit_net(struct aa_profile *profile, int op, u16 family, int type,
int protocol, struct sock *sk, int error)
{
int audit_type = AUDIT_APPARMOR_AUTO;
DEFINE_AUDIT_NET(sa, op, sk, family, type, protocol);
aad(&sa)->error = error;
if (likely(!aad(&sa)->error)) {
u16 audit_mask = profile->net.audit[sa.u.net->family];
if (likely((AUDIT_MODE(profile) != AUDIT_ALL) &&
!(1 << aad(&sa)->net.type & audit_mask)))
return 0;
audit_type = AUDIT_APPARMOR_AUDIT;
} else {
u16 quiet_mask = profile->net.quiet[sa.u.net->family];
u16 kill_mask = 0;
u16 denied = (1 << aad(&sa)->net.type);
if (denied & kill_mask)
audit_type = AUDIT_APPARMOR_KILL;
if ((denied & quiet_mask) &&
AUDIT_MODE(profile) != AUDIT_NOQUIET &&
AUDIT_MODE(profile) != AUDIT_ALL)
return COMPLAIN_MODE(profile) ? 0 : aad(&sa)->error;
}
return aa_audit(audit_type, profile, &sa, audit_net_cb);
}
static inline int aa_af_mask_perm(struct aa_profile *profile, u16 family,
int type)
{
u16 family_mask;
AA_BUG(family >= AF_MAX);
AA_BUG(type < 0 && type >= SOCK_MAX);
if (profile_unconfined(profile))
return 0;
family_mask = profile->net.allow[family];
return (family_mask & (1 << type)) ? 0 : -EACCES;
}
/* Generic af perm */
int aa_profile_af_perm(struct aa_profile *profile, int op, u16 family,
int type, int protocol, struct sock *sk)
{
int error = aa_af_mask_perm(profile, family, type);
return audit_net(profile, op, family, type, protocol, sk, error);
}
int aa_af_perm(struct aa_label *label, int op, u32 request, u16 family,
int type, int protocol, struct sock *sk)
{
struct aa_profile *profile;
return fn_for_each_confined(label, profile,
aa_profile_af_perm(profile, op, family, type, protocol,
sk));
}
static int aa_label_sk_perm(struct aa_label *label, int op, u32 request,
struct sock *sk)
{
struct aa_profile *profile;
AA_BUG(!label);
AA_BUG(!sk);
if (unconfined(label))
return 0;
return fn_for_each_confined(label, profile,
aa_profile_af_perm(profile, op, sk->sk_family,
sk->sk_type, sk->sk_protocol,
sk));
}
static int aa_sk_perm(int op, u32 request, struct sock *sk)
{
struct aa_label *label;
AA_BUG(!sk);
AA_BUG(in_interrupt());
/* TODO: switch to begin_current_label ???? */
label = aa_current_label();
return aa_label_sk_perm(label, op, request, sk);
}
#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(int 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, NULL));
}
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(int 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(int 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, int 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));
}

View File

@ -25,7 +25,6 @@
#include "include/path.h"
#include "include/policy.h"
/* modified from dcache.c */
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)
/* 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
* @path: path to lookup (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)
* @flags: flags controlling path lookup
* @disconnected: string to prefix to disconnected paths
*
* Handle path name lookup.
*
@ -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
* to a position in @buf
*/
static int d_namespace_path(struct path *path, char *buf, int buflen,
char **name, int flags)
static int d_namespace_path(struct path *path, char *buf, char **name,
int flags, const char *disconnected)
{
char *res;
int error = 0;
int connected = 1;
int isdir = (flags & PATH_IS_DIR) ? 1 : 0;
int buflen = aa_g_path_max - isdir;
if (path->mnt->mnt_flags & MNT_INTERNAL) {
/* it's not mounted anywhere */
@ -73,9 +111,12 @@ static int d_namespace_path(struct path *path, char *buf, int buflen,
/* TODO: convert over to using a per namespace
* control instead of hard coded /proc
*/
return prepend(name, *name - buf, "/proc", 5);
}
return 0;
error = prepend(name, *name - buf, "/proc", 5);
goto out;
} else
error = disconnect(path, buf, name, flags,
disconnected);
goto out;
}
/* resolve paths relative to chroot?*/
@ -94,8 +135,11 @@ static int d_namespace_path(struct path *path, char *buf, int buflen,
* be returned.
*/
if (!res || IS_ERR(res)) {
if (PTR_ERR(res) == -ENAMETOOLONG)
return -ENAMETOOLONG;
if (PTR_ERR(res) == -ENAMETOOLONG) {
error = -ENAMETOOLONG;
*name = buf;
goto out;
}
connected = 0;
res = dentry_path_raw(path->dentry, buf, buflen);
if (IS_ERR(res)) {
@ -114,84 +158,34 @@ static int d_namespace_path(struct path *path, char *buf, int buflen,
* security_path hooks as a deleted dentry except without an inode
* allocated.
*/
if (d_unlinked(path->dentry) && path->dentry->d_inode &&
if (d_unlinked(path->dentry) && d_is_positive(path->dentry) &&
!(flags & PATH_MEDIATE_DELETED)) {
error = -ENOENT;
goto out;
}
/* 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.
*/
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;
}
}
if (!connected)
error = disconnect(path, buf, name, flags, disconnected);
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
* case; it already ends in slash.
*/
strcpy(&buffer[size - 2], "/");
if (info && error) {
if (error == -ENOENT)
*info = "Failed name lookup - deleted entry";
else if (error == -ESTALE)
*info = "Failed name lookup - disconnected path";
else if (error == -ENAMETOOLONG)
*info = "Failed name lookup - name too long";
else
*info = "Failed name lookup";
}
/*
* Append "/" to the pathname. The root directory is a special
* case; it already ends in slash.
*/
if (!error && isdir && ((*name)[1] != '\0' || (*name)[0] != '/'))
strcpy(&buf[aa_g_path_max - 2], "/");
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)
* @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)
* @info: Returns - information on why the path lookup failed (MAYBE NULL)
* @disconnected: string to prepend to disconnected paths
*
* @name is a pointer to the beginning of the pathname (which usually differs
* from the beginning of the buffer), or NULL. If there is an error @name
@ -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
*/
int aa_path_name(struct path *path, int flags, char **buffer, const char **name,
const char **info)
int aa_path_name(struct path *path, int flags, char *buffer, const char **name,
const char **info, const char *disconnected)
{
char *buf, *str = NULL;
int size = 256;
int error;
char *str = NULL;
int error = d_namespace_path(path, buffer, &str, flags, disconnected);
*name = NULL;
*buffer = NULL;
for (;;) {
/* freed by caller */
buf = kmalloc(size, GFP_KERNEL);
if (!buf)
return -ENOMEM;
error = get_name_to_buffer(path, flags, buf, size, &str, info);
if (error != -ENAMETOOLONG)
break;
kfree(buf);
size <<= 1;
if (size > aa_g_path_max)
return -ENAMETOOLONG;
*info = NULL;
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";
}
*buffer = buf;
*name = str;
*name = str;
return error;
}

File diff suppressed because it is too large Load Diff

View File

@ -24,10 +24,19 @@
#include "include/apparmor.h"
#include "include/audit.h"
#include "include/context.h"
#include "include/crypto.h"
#include "include/match.h"
#include "include/path.h"
#include "include/policy.h"
#include "include/policy_unpack.h"
#include "include/sid.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
@ -70,13 +79,13 @@ struct aa_ext {
static void audit_cb(struct audit_buffer *ab, void *va)
{
struct common_audit_data *sa = va;
if (sa->aad->iface.target) {
struct aa_profile *name = sa->aad->iface.target;
if (aad(sa)->target) {
const struct aa_profile *name = aad(sa)->target;
audit_log_format(ab, " name=");
audit_log_untrustedstring(ab, name->base.hname);
}
if (sa->aad->iface.pos)
audit_log_format(ab, " offset=%ld", sa->aad->iface.pos);
if (aad(sa)->iface.pos)
audit_log_format(ab, " offset=%ld", aad(sa)->iface.pos);
}
/**
@ -92,20 +101,16 @@ static void audit_cb(struct audit_buffer *ab, void *va)
static int audit_iface(struct aa_profile *new, const char *name,
const char *info, struct aa_ext *e, int error)
{
struct aa_profile *profile = __aa_current_profile();
struct common_audit_data sa;
struct apparmor_audit_data aad = {0,};
sa.type = LSM_AUDIT_DATA_NONE;
sa.aad = &aad;
struct aa_profile *profile = labels_profile(aa_current_raw_label());
DEFINE_AUDIT_DATA(sa, LSM_AUDIT_DATA_NONE, 0);
if (e)
aad.iface.pos = e->pos - e->start;
aad.iface.target = new;
aad.name = name;
aad.info = info;
aad.error = error;
aad(&sa)->iface.pos = e->pos - e->start;
aad(&sa)->target = new;
aad(&sa)->name = name;
aad(&sa)->info = info;
aad(&sa)->error = error;
return aa_audit(AUDIT_APPARMOR_STATUS, profile, GFP_KERNEL, &sa,
audit_cb);
return aa_audit(AUDIT_APPARMOR_STATUS, profile, &sa, audit_cb);
}
/* test if read will be in packed data bounds */
@ -193,6 +198,19 @@ fail:
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)
{
if (unpack_nameX(e, AA_U32, name)) {
@ -290,6 +308,9 @@ static int unpack_strdup(struct aa_ext *e, char **string, const char *name)
return res;
}
#define DFA_VALID_PERM_MASK 0xffffffff
#define DFA_VALID_PERM2_MASK 0xffffffff
/**
* verify_accept - verify the accept tables of a dfa
* @dfa: dfa to verify accept tables of (NOT NULL)
@ -331,8 +352,10 @@ static struct aa_dfa *unpack_dfa(struct aa_ext *e)
/*
* The dfa is aligned with in the blob to 8 bytes
* from the beginning of the stream.
* alignment adjust needed by dfa unpack
*/
size_t sz = blob - (char *)e->start;
size_t sz = blob - (char *) e->start -
((e->pos - e->start) & 7);
size_t pad = ALIGN(sz, 8) - sz;
int flags = TO_ACCEPT1_FLAG(YYTD_DATA32) |
TO_ACCEPT2_FLAG(YYTD_DATA32);
@ -471,6 +494,7 @@ static struct aa_profile *unpack_profile(struct aa_ext *e)
{
struct aa_profile *profile = NULL;
const char *name = NULL;
size_t size = 0;
int i, error = -EPROTO;
kernel_cap_t tmpcap;
u32 tmp;
@ -488,6 +512,9 @@ static struct aa_profile *unpack_profile(struct aa_ext *e)
/* profile renaming is optional */
(void) unpack_str(e, &profile->rename, "rename");
/* attachment string is optional */
(void) unpack_str(e, &profile->attach, "attach");
/* xmatch is optional and may be NULL */
profile->xmatch = unpack_dfa(e);
if (IS_ERR(profile->xmatch)) {
@ -502,17 +529,24 @@ static struct aa_profile *unpack_profile(struct aa_ext *e)
profile->xmatch_len = tmp;
}
/* disconnected attachment string is optional */
(void) unpack_str(e, &profile->disconnected, "disconnected");
/* per profile debug flags (complain, audit) */
if (!unpack_nameX(e, AA_STRUCT, "flags"))
goto fail;
if (!unpack_u32(e, &tmp, NULL))
goto fail;
if (tmp)
profile->flags |= PFLAG_HAT;
if (tmp & PACKED_FLAG_HAT)
profile->label.flags |= FLAG_HAT;
if (!unpack_u32(e, &tmp, NULL))
goto fail;
if (tmp)
if (tmp == PACKED_MODE_COMPLAIN || (e->version & FORCE_COMPLAIN_FLAG))
profile->mode = APPARMOR_COMPLAIN;
else if (tmp == PACKED_MODE_KILL)
profile->mode = APPARMOR_KILL;
else if (tmp == PACKED_MODE_UNCONFINED)
profile->mode = APPARMOR_UNCONFINED;
if (!unpack_u32(e, &tmp, NULL))
goto fail;
if (tmp)
@ -522,11 +556,9 @@ static struct aa_profile *unpack_profile(struct aa_ext *e)
goto fail;
/* path_flags is optional */
if (unpack_u32(e, &profile->path_flags, "path_flags"))
profile->path_flags |= profile->flags & PFLAG_MEDIATE_DELETED;
else
if (!unpack_u32(e, &profile->path_flags, "path_flags"))
/* 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))
goto fail;
@ -564,6 +596,37 @@ static struct aa_profile *unpack_profile(struct aa_ext *e)
if (!unpack_rlimits(e, profile))
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")) {
/* generic policy dfa - optional and may be NULL */
profile->policy.dfa = unpack_dfa(e);
@ -592,12 +655,16 @@ static struct aa_profile *unpack_profile(struct aa_ext *e)
error = PTR_ERR(profile->file.dfa);
profile->file.dfa = NULL;
goto fail;
} else if (profile->file.dfa) {
if (!unpack_u32(e, &profile->file.start, "dfa_start"))
/* default start state */
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];
}
if (!unpack_u32(e, &profile->file.start, "dfa_start"))
/* default start state */
profile->file.start = DFA_START;
if (!unpack_trans_table(e, profile))
goto fail;
@ -612,7 +679,7 @@ fail:
else if (!name)
name = "unknown";
audit_iface(profile, name, "failed to unpack profile", e, error);
aa_put_profile(profile);
aa_free_profile(profile);
return ERR_PTR(error);
}
@ -620,29 +687,43 @@ fail:
/**
* verify_head - unpack serialized stream header
* @e: serialized data read head (NOT NULL)
* @required: whether the header is required or optional
* @ns: Returns - namespace if one is specified else NULL (NOT NULL)
*
* Returns: error or 0 if header is good
*/
static int verify_header(struct aa_ext *e, const char **ns)
static int verify_header(struct aa_ext *e, int required, const char **ns)
{
int error = -EPROTONOSUPPORT;
const char *name = NULL;
*ns = NULL;
/* get the interface version */
if (!unpack_u32(e, &e->version, "version")) {
audit_iface(NULL, NULL, "invalid profile format", e, error);
return error;
if (required) {
audit_iface(NULL, NULL, "invalid profile format", e,
error);
return error;
}
}
/* check that the interface version is currently supported */
if (e->version != 5) {
audit_iface(NULL, NULL, "unsupported interface version", e,
error);
/* Check that the interface version is currently supported.
* if not specified use previous 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, "unsupported interface version",
e, error);
return error;
}
/* read the namespace if present */
if (!unpack_str(e, ns, "namespace"))
*ns = NULL;
if (unpack_str(e, &name, "namespace")) {
if (*ns && strcmp(*ns, name))
audit_iface(NULL, NULL, "invalid ns change", e, error);
else if (!*ns)
*ns = name;
}
return 0;
}
@ -691,18 +772,40 @@ static int verify_profile(struct aa_profile *profile)
return 0;
}
void aa_load_ent_free(struct aa_load_ent *ent)
{
if (ent) {
aa_put_profile(ent->rename);
aa_put_profile(ent->old);
aa_put_profile(ent->new);
kzfree(ent);
}
}
struct aa_load_ent *aa_load_ent_alloc(void)
{
struct aa_load_ent *ent = kzalloc(sizeof(*ent), GFP_KERNEL);
if (ent)
INIT_LIST_HEAD(&ent->list);
return ent;
}
/**
* aa_unpack - unpack packed binary profile data loaded from user space
* aa_unpack - unpack packed binary profile(s) data loaded from user space
* @udata: user data copied to kmem (NOT NULL)
* @size: the size of the user data
* @lh: list to place unpacked profiles in a aa_repl_ws
* @ns: Returns namespace profile is in if specified else NULL (NOT NULL)
*
* Unpack user data and return refcounted allocated profile or ERR_PTR
* Unpack user data and return refcounted allocated profile(s) stored in
* @lh in order of discovery, with the list chain stored in base.list
* or error
*
* Returns: profile else error pointer if fails to unpack
* Returns: profile(s) on @lh else error pointer if fails to unpack
*/
struct aa_profile *aa_unpack(void *udata, size_t size, const char **ns)
int aa_unpack(void *udata, size_t size, struct list_head *lh, const char **ns)
{
struct aa_load_ent *tmp, *ent;
struct aa_profile *profile = NULL;
int error;
struct aa_ext e = {
@ -711,20 +814,50 @@ struct aa_profile *aa_unpack(void *udata, size_t size, const char **ns)
.pos = udata,
};
error = verify_header(&e, ns);
if (error)
return ERR_PTR(error);
*ns = NULL;
while (e.pos < e.end) {
void *start;
error = verify_header(&e, e.pos == e.start, ns);
if (error)
goto fail;
profile = unpack_profile(&e);
if (IS_ERR(profile))
return profile;
start = e.pos;
profile = unpack_profile(&e);
if (IS_ERR(profile)) {
error = PTR_ERR(profile);
goto fail;
}
error = verify_profile(profile);
if (error) {
aa_put_profile(profile);
profile = ERR_PTR(error);
error = verify_profile(profile);
if (error)
goto fail_profile;
if (aa_g_hash_policy)
error = aa_calc_profile_hash(profile, e.version, start,
e.pos - start);
if (error)
goto fail_profile;
ent = aa_load_ent_alloc();
if (!ent) {
error = -ENOMEM;
goto fail_profile;
}
ent->new = profile;
list_add_tail(&ent->list, lh);
}
/* return refcount */
return profile;
return 0;
fail_profile:
aa_put_profile(profile);
fail:
list_for_each_entry_safe(ent, tmp, lh, list) {
list_del_init(&ent->list);
aa_load_ent_free(ent);
}
return error;
}

View File

@ -33,50 +33,29 @@
*
* 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;
int len = 0, mode_len = 0, ns_len = 0, name_len;
const char *mode_str = profile_mode_names[profile->mode];
const char *ns_name = NULL;
struct aa_namespace *ns = profile->ns;
struct aa_namespace *current_ns = __aa_current_profile()->ns;
char *s;
struct aa_namespace *ns = labels_ns(label);
struct aa_namespace *current_ns = labels_ns(aa_current_label());
int len;
if (!aa_ns_visible(current_ns, ns))
return -EACCES;
ns_name = aa_ns_name(current_ns, ns);
ns_len = strlen(ns_name);
len = aa_label_snprint(NULL, 0, current_ns, label, true);
AA_BUG(len < 0);
/* if the visible ns_name is > 0 increase size for : :// seperator */
if (ns_len)
ns_len += 4;
/* unconfined profiles don't have a mode string appended */
if (!unconfined(profile))
mode_len = strlen(mode_str) + 3; /* + 3 for _() */
name_len = strlen(profile->base.hname);
len = mode_len + ns_len + name_len + 1; /* + 1 for \n */
s = str = kmalloc(len + 1, GFP_KERNEL); /* + 1 \0 */
if (!str)
*string = kmalloc(len + 2, GFP_KERNEL);
if (!*string)
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 */
sprintf(s, "%s\n", profile->base.hname);
else
sprintf(s, "%s (%s)\n", profile->base.hname, mode_str);
*string = str;
len = aa_label_snprint(*string, len + 2, current_ns, label, true);
if (len < 0)
return len;
(*string)[len] = '\n';
(*string)[len + 1] = 0;
/* NOTE: len does not include \0 of string, not saved as part of file */
return len;
return len + 1;
}
/**
@ -138,12 +117,13 @@ int aa_setprocattr_changehat(char *args, size_t size, int test)
for (count = 0; (hat < end) && count < 16; ++count) {
char *next = hat + strlen(hat) + 1;
hats[count] = hat;
AA_DEBUG("%s: (pid %d) Magic 0x%llx count %d hat '%s'\n"
, __func__, current->pid, token, count, hat);
hat = next;
}
}
AA_DEBUG("%s: Magic 0x%llx Hat '%s'\n",
__func__, token, hat ? hat : NULL);
} else
AA_DEBUG("%s: (pid %d) Magic 0x%llx count %d Hat '%s'\n",
__func__, current->pid, token, count, "<NULL>");
return aa_change_hat(hats, count, token, test);
}
@ -163,9 +143,3 @@ int aa_setprocattr_changeprofile(char *fqname, bool onexec, int test)
name = aa_split_fqname(fqname, &ns_name);
return aa_change_profile(ns_name, name, onexec, test);
}
int aa_setprocattr_permipc(char *fqname)
{
/* TODO: add ipc permission querying */
return -ENOTSUPP;
}

View File

@ -15,6 +15,7 @@
#include <linux/audit.h>
#include "include/audit.h"
#include "include/context.h"
#include "include/resource.h"
#include "include/policy.h"
@ -34,7 +35,7 @@ static void audit_cb(struct audit_buffer *ab, void *va)
struct common_audit_data *sa = va;
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);
}
/**
@ -49,17 +50,11 @@ static void audit_cb(struct audit_buffer *ab, void *va)
static int audit_resource(struct aa_profile *profile, unsigned int resource,
unsigned long value, int error)
{
struct common_audit_data sa;
struct apparmor_audit_data aad = {0,};
sa.type = LSM_AUDIT_DATA_NONE;
sa.aad = &aad;
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);
DEFINE_AUDIT_DATA(sa, LSM_AUDIT_DATA_NONE, OP_SETRLIMIT);
aad(&sa)->rlim.rlim = resource;
aad(&sa)->rlim.max = value;
aad(&sa)->error = error;
return aa_audit(AUDIT_APPARMOR_AUTO, profile, &sa, audit_cb);
}
/**
@ -76,9 +71,19 @@ int aa_map_resource(int 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
* @profile - profile confining the task (NOT NULL)
* @label - label confining the task (NOT NULL)
* @task - task the resource is being set on
* @resource - the resource being set
* @new_rlim - the new resource limit (NOT NULL)
@ -87,59 +92,81 @@ int aa_map_resource(int resource)
*
* 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)
{
struct aa_profile *profile;
struct aa_label *task_label;
int error = 0;
/* TODO: extend resource control to handle other (non current)
* processes. AppArmor rules currently have the implicit assumption
* that the task is setting the resource of the current process
*/
if ((task != current->group_leader) ||
(profile->rlimits.mask & (1 << resource) &&
new_rlim->rlim_max > profile->rlimits.limits[resource].rlim_max))
error = -EACCES;
rcu_read_lock();
task_label = aa_get_newest_cred_label(__task_cred(task));
rcu_read_unlock();
return audit_resource(profile, resource, new_rlim->rlim_max, error);
/* TODO: extend resource control to handle other (non current)
* profiles. AppArmor rules currently have the implicit assumption
* that the task is setting the resource of a task confined with
* the same profile.
*/
if (label != task_label)
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(task_label);
return error;
}
/**
* __aa_transition_rlimits - apply new profile rlimits
* @old: old profile on task (NOT NULL)
* @new: new profile with rlimits to apply (NOT NULL)
* @old_l: old label on task (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;
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
* to the less of the tasks hard limit and the init tasks soft limit
old = labels_profile(old_l);
new = labels_profile(new_l);
/* for any rlimits the profile controlled, reset the soft limit
* to the lesser of the tasks hard limit and the init tasks soft limit
*/
if (old->rlimits.mask) {
for (i = 0, mask = 1; i < RLIM_NLIMITS; i++, mask <<= 1) {
if (old->rlimits.mask & mask) {
rlim = current->signal->rlim + i;
initrlim = init_task.signal->rlim + i;
rlim->rlim_cur = min(rlim->rlim_max,
initrlim->rlim_cur);
label_for_each_confined(i, old_l, old) {
if (old->rlimits.mask) {
int j;
for (j = 0, mask = 1; j < RLIM_NLIMITS; j++,
mask <<= 1) {
if (old->rlimits.mask & mask) {
rlim = current->signal->rlim + j;
initrlim = init_task.signal->rlim + j;
rlim->rlim_cur = min(rlim->rlim_max,
initrlim->rlim_cur);
}
}
}
}
/* set any new hard limits as dictated by the new profile */
if (!new->rlimits.mask)
return;
for (i = 0, mask = 1; i < RLIM_NLIMITS; i++, mask <<= 1) {
if (!(new->rlimits.mask & mask))
label_for_each_confined(i, new_l, new) {
int j;
if (!new->rlimits.mask)
continue;
for (j = 0, mask = 1; j < RLIM_NLIMITS; j++, mask <<= 1) {
if (!(new->rlimits.mask & mask))
continue;
rlim = current->signal->rlim + i;
rlim->rlim_max = min(rlim->rlim_max,
new->rlimits.limits[i].rlim_max);
/* soft limit should not exceed hard limit */
rlim->rlim_cur = min(rlim->rlim_cur, rlim->rlim_max);
rlim = current->signal->rlim + j;
rlim->rlim_max = min(rlim->rlim_max,
new->rlimits.limits[j].rlim_max);
/* soft limit should not exceed hard limit */
rlim->rlim_cur = min(rlim->rlim_cur, rlim->rlim_max);
}
}
}