Add Apparmor patches
This commit is contained in:
parent
7b86427e4c
commit
e112aa84d8
52 changed files with 11580 additions and 2687 deletions
|
@ -1595,6 +1595,19 @@ static inline void security_audit_rule_free(void *lsmrule)
|
||||||
|
|
||||||
#ifdef CONFIG_SECURITYFS
|
#ifdef CONFIG_SECURITYFS
|
||||||
|
|
||||||
|
|
||||||
|
extern int securityfs_pin_fs(void);
|
||||||
|
|
||||||
|
extern int __securityfs_setup_d_inode(struct inode *dir, struct dentry *dentry,
|
||||||
|
umode_t mode, void *data,
|
||||||
|
const struct file_operations *fops,
|
||||||
|
const struct inode_operations *iops);
|
||||||
|
|
||||||
|
extern struct dentry *securityfs_create_dentry(const char *name, umode_t mode,
|
||||||
|
struct dentry *parent, void *data,
|
||||||
|
const struct file_operations *fops,
|
||||||
|
const struct inode_operations *iops);
|
||||||
|
|
||||||
extern struct dentry *securityfs_create_file(const char *name, umode_t mode,
|
extern struct dentry *securityfs_create_file(const char *name, umode_t mode,
|
||||||
struct dentry *parent, void *data,
|
struct dentry *parent, void *data,
|
||||||
const struct file_operations *fops);
|
const struct file_operations *fops);
|
||||||
|
|
1
security/apparmor/.gitignore
vendored
1
security/apparmor/.gitignore
vendored
|
@ -1,5 +1,6 @@
|
||||||
#
|
#
|
||||||
# Generated include files
|
# Generated include files
|
||||||
#
|
#
|
||||||
|
net_names.h
|
||||||
capability_names.h
|
capability_names.h
|
||||||
rlim_names.h
|
rlim_names.h
|
||||||
|
|
|
@ -30,14 +30,62 @@ config SECURITY_APPARMOR_BOOTPARAM_VALUE
|
||||||
|
|
||||||
If you are unsure how to answer this question, answer 1.
|
If you are unsure how to answer this question, answer 1.
|
||||||
|
|
||||||
|
config SECURITY_APPARMOR_STATS
|
||||||
|
bool "enable debug statistics"
|
||||||
|
depends on SECURITY_APPARMOR
|
||||||
|
select APPARMOR_LABEL_STATS
|
||||||
|
default n
|
||||||
|
help
|
||||||
|
This enables keeping statistics on various internal structures
|
||||||
|
and functions in apparmor.
|
||||||
|
|
||||||
|
If you are unsure how to answer this question, answer N.
|
||||||
|
|
||||||
|
config SECURITY_APPARMOR_UNCONFINED_INIT
|
||||||
|
bool "Set init to unconfined on boot"
|
||||||
|
depends on SECURITY_APPARMOR
|
||||||
|
default y
|
||||||
|
help
|
||||||
|
This option determines policy behavior during early boot by
|
||||||
|
placing the init process in the unconfined state, or the
|
||||||
|
'default' profile.
|
||||||
|
|
||||||
|
This option determines policy behavior during early boot by
|
||||||
|
placing the init process in the unconfined state, or the
|
||||||
|
'default' profile.
|
||||||
|
|
||||||
|
'Y' means init and its children are not confined, unless the
|
||||||
|
init process is re-execed after a policy load; loaded policy
|
||||||
|
will only apply to processes started after the load.
|
||||||
|
|
||||||
|
'N' means init and its children are confined in a profile
|
||||||
|
named 'default', which can be replaced later and thus
|
||||||
|
provide for confinement for processes started early at boot,
|
||||||
|
though not confined during early boot.
|
||||||
|
|
||||||
|
If you are unsure how to answer this question, answer Y.
|
||||||
|
|
||||||
config SECURITY_APPARMOR_HASH
|
config SECURITY_APPARMOR_HASH
|
||||||
bool "SHA1 hash of loaded profiles"
|
bool "Enable introspection of sha1 hashes for loaded profiles"
|
||||||
depends on SECURITY_APPARMOR
|
depends on SECURITY_APPARMOR
|
||||||
select CRYPTO
|
select CRYPTO
|
||||||
select CRYPTO_SHA1
|
select CRYPTO_SHA1
|
||||||
default y
|
default y
|
||||||
|
|
||||||
help
|
help
|
||||||
This option selects whether sha1 hashing is done against loaded
|
This option selects whether introspection of loaded policy
|
||||||
profiles and exported for inspection to user space via the apparmor
|
is available to userspace via the apparmor filesystem.
|
||||||
filesystem.
|
|
||||||
|
config SECURITY_APPARMOR_HASH_DEFAULT
|
||||||
|
bool "Enable policy hash introspection by default"
|
||||||
|
depends on SECURITY_APPARMOR_HASH
|
||||||
|
default y
|
||||||
|
|
||||||
|
help
|
||||||
|
This option selects whether sha1 hashing of loaded policy
|
||||||
|
is enabled by default. The generation of sha1 hashes for
|
||||||
|
loaded policy provide system administrators a quick way
|
||||||
|
to verify that policy in the kernel matches what is expected,
|
||||||
|
however it can slow down policy load on some devices. In
|
||||||
|
these cases policy hashing can be disabled by default and
|
||||||
|
enabled only if needed.
|
||||||
|
|
|
@ -4,11 +4,45 @@ obj-$(CONFIG_SECURITY_APPARMOR) += apparmor.o
|
||||||
|
|
||||||
apparmor-y := apparmorfs.o audit.o capability.o context.o ipc.o lib.o match.o \
|
apparmor-y := apparmorfs.o audit.o capability.o context.o ipc.o lib.o match.o \
|
||||||
path.o domain.o policy.o policy_unpack.o procattr.o lsm.o \
|
path.o domain.o policy.o policy_unpack.o procattr.o lsm.o \
|
||||||
resource.o sid.o file.o
|
resource.o sid.o file.o label.o mount.o net.o af_unix.o \
|
||||||
|
policy_ns.o backport.o
|
||||||
apparmor-$(CONFIG_SECURITY_APPARMOR_HASH) += crypto.o
|
apparmor-$(CONFIG_SECURITY_APPARMOR_HASH) += crypto.o
|
||||||
|
|
||||||
clean-files := capability_names.h rlim_names.h
|
clean-files := capability_names.h rlim_names.h net_names.h
|
||||||
|
|
||||||
|
# Build a lower case string table of address family names
|
||||||
|
# Transform lines from
|
||||||
|
# define AF_LOCAL 1 /* POSIX name for AF_UNIX */
|
||||||
|
# #define AF_INET 2 /* Internet IP Protocol */
|
||||||
|
# to
|
||||||
|
# [1] = "local",
|
||||||
|
# [2] = "inet",
|
||||||
|
#
|
||||||
|
# and build the securityfs entries for the mapping.
|
||||||
|
# Transforms lines from
|
||||||
|
# #define AF_INET 2 /* Internet IP Protocol */
|
||||||
|
# to
|
||||||
|
# #define AA_FS_AF_MASK "local inet"
|
||||||
|
quiet_cmd_make-af = GEN $@
|
||||||
|
cmd_make-af = echo "static const char *address_family_names[] = {" > $@ ;\
|
||||||
|
sed $< >>$@ -r -n -e "/AF_MAX/d" -e "/AF_LOCAL/d" -e "/AF_ROUTE/d" -e \
|
||||||
|
's/^\#define[ \t]+AF_([A-Z0-9_]+)[ \t]+([0-9]+)(.*)/[\2] = "\L\1",/p';\
|
||||||
|
echo "};" >> $@ ;\
|
||||||
|
echo -n '\#define AA_FS_AF_MASK "' >> $@ ;\
|
||||||
|
sed -r -n -e "/AF_MAX/d" -e "/AF_LOCAL/d" -e "/AF_ROUTE/d" -e \
|
||||||
|
's/^\#define[ \t]+AF_([A-Z0-9_]+)[ \t]+([0-9]+)(.*)/\L\1/p'\
|
||||||
|
$< | tr '\n' ' ' | sed -e 's/ $$/"\n/' >> $@
|
||||||
|
|
||||||
|
# Build a lower case string table of sock type names
|
||||||
|
# Transform lines from
|
||||||
|
# SOCK_STREAM = 1,
|
||||||
|
# to
|
||||||
|
# [1] = "stream",
|
||||||
|
quiet_cmd_make-sock = GEN $@
|
||||||
|
cmd_make-sock = echo "static const char *sock_type_names[] = {" >> $@ ;\
|
||||||
|
sed $^ >>$@ -r -n \
|
||||||
|
-e 's/^\tSOCK_([A-Z0-9_]+)[\t]+=[ \t]+([0-9]+)(.*)/[\2] = "\L\1",/p';\
|
||||||
|
echo "};" >> $@
|
||||||
|
|
||||||
# Build a lower case string table of capability names
|
# Build a lower case string table of capability names
|
||||||
# Transforms lines from
|
# Transforms lines from
|
||||||
|
@ -61,6 +95,7 @@ cmd_make-rlim = echo "static const char *const rlim_names[RLIM_NLIMITS] = {" \
|
||||||
tr '\n' ' ' | sed -e 's/ $$/"\n/' >> $@
|
tr '\n' ' ' | sed -e 's/ $$/"\n/' >> $@
|
||||||
|
|
||||||
$(obj)/capability.o : $(obj)/capability_names.h
|
$(obj)/capability.o : $(obj)/capability_names.h
|
||||||
|
$(obj)/net.o : $(obj)/net_names.h
|
||||||
$(obj)/resource.o : $(obj)/rlim_names.h
|
$(obj)/resource.o : $(obj)/rlim_names.h
|
||||||
$(obj)/capability_names.h : $(srctree)/include/uapi/linux/capability.h \
|
$(obj)/capability_names.h : $(srctree)/include/uapi/linux/capability.h \
|
||||||
$(src)/Makefile
|
$(src)/Makefile
|
||||||
|
@ -68,3 +103,8 @@ $(obj)/capability_names.h : $(srctree)/include/uapi/linux/capability.h \
|
||||||
$(obj)/rlim_names.h : $(srctree)/include/uapi/asm-generic/resource.h \
|
$(obj)/rlim_names.h : $(srctree)/include/uapi/asm-generic/resource.h \
|
||||||
$(src)/Makefile
|
$(src)/Makefile
|
||||||
$(call cmd,make-rlim)
|
$(call cmd,make-rlim)
|
||||||
|
$(obj)/net_names.h : $(srctree)/include/linux/socket.h \
|
||||||
|
$(srctree)/include/linux/net.h \
|
||||||
|
$(src)/Makefile
|
||||||
|
$(call cmd,make-af)
|
||||||
|
$(call cmd,make-sock)
|
||||||
|
|
643
security/apparmor/af_unix.c
Normal file
643
security/apparmor/af_unix.c
Normal file
|
@ -0,0 +1,643 @@
|
||||||
|
/*
|
||||||
|
* AppArmor security module
|
||||||
|
*
|
||||||
|
* This file contains AppArmor af_unix fine grained mediation
|
||||||
|
*
|
||||||
|
* Copyright 2014 Canonical Ltd.
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU General Public License as
|
||||||
|
* published by the Free Software Foundation, version 2 of the
|
||||||
|
* License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <net/tcp_states.h>
|
||||||
|
|
||||||
|
#include "include/af_unix.h"
|
||||||
|
#include "include/apparmor.h"
|
||||||
|
#include "include/context.h"
|
||||||
|
#include "include/file.h"
|
||||||
|
#include "include/label.h"
|
||||||
|
#include "include/path.h"
|
||||||
|
#include "include/policy.h"
|
||||||
|
|
||||||
|
static inline struct sock *aa_sock(struct unix_sock *u)
|
||||||
|
{
|
||||||
|
return &u->sk;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int unix_fs_perm(const char *op, u32 mask, struct aa_label *label,
|
||||||
|
struct unix_sock *u, int flags)
|
||||||
|
{
|
||||||
|
AA_BUG(!label);
|
||||||
|
AA_BUG(!u);
|
||||||
|
AA_BUG(!UNIX_FS(aa_sock(u)));
|
||||||
|
|
||||||
|
if (unconfined(label) || !LABEL_MEDIATES(label, AA_CLASS_FILE))
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
mask &= NET_FS_PERMS;
|
||||||
|
if (!u->path.dentry) {
|
||||||
|
struct path_cond cond = { };
|
||||||
|
struct aa_perms perms = { };
|
||||||
|
struct aa_profile *profile;
|
||||||
|
|
||||||
|
/* socket path has been cleared because it is being shutdown
|
||||||
|
* can only fall back to original sun_path request
|
||||||
|
*/
|
||||||
|
struct aa_sk_ctx *ctx = SK_CTX(&u->sk);
|
||||||
|
if (ctx->path.dentry)
|
||||||
|
return aa_path_perm(op, label, &ctx->path, flags, mask,
|
||||||
|
&cond);
|
||||||
|
return fn_for_each_confined(label, profile,
|
||||||
|
((flags | profile->path_flags) & PATH_MEDIATE_DELETED) ?
|
||||||
|
__aa_path_perm(op, profile,
|
||||||
|
u->addr->name->sun_path, mask,
|
||||||
|
&cond, flags, &perms) :
|
||||||
|
aa_audit_file(profile, &nullperms, op, mask,
|
||||||
|
u->addr->name->sun_path, NULL,
|
||||||
|
NULL, cond.uid,
|
||||||
|
"Failed name lookup - "
|
||||||
|
"deleted entry", -EACCES));
|
||||||
|
} else {
|
||||||
|
/* the sunpath may not be valid for this ns so use the path */
|
||||||
|
struct path_cond cond = { u->path.dentry->d_inode->i_uid,
|
||||||
|
u->path.dentry->d_inode->i_mode
|
||||||
|
};
|
||||||
|
|
||||||
|
return aa_path_perm(op, label, &u->path, flags, mask, &cond);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* passing in state returned by PROFILE_MEDIATES_AF */
|
||||||
|
static unsigned int match_to_prot(struct aa_profile *profile,
|
||||||
|
unsigned int state, int type, int protocol,
|
||||||
|
const char **info)
|
||||||
|
{
|
||||||
|
u16 buffer[2];
|
||||||
|
buffer[0] = cpu_to_be16(type);
|
||||||
|
buffer[1] = cpu_to_be16(protocol);
|
||||||
|
state = aa_dfa_match_len(profile->policy.dfa, state, (char *) &buffer,
|
||||||
|
4);
|
||||||
|
if (!state)
|
||||||
|
*info = "failed type and protocol match";
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
static unsigned int match_addr(struct aa_profile *profile, unsigned int state,
|
||||||
|
struct sockaddr_un *addr, int addrlen)
|
||||||
|
{
|
||||||
|
if (addr)
|
||||||
|
/* include leading \0 */
|
||||||
|
state = aa_dfa_match_len(profile->policy.dfa, state,
|
||||||
|
addr->sun_path,
|
||||||
|
unix_addr_len(addrlen));
|
||||||
|
else
|
||||||
|
/* anonymous end point */
|
||||||
|
state = aa_dfa_match_len(profile->policy.dfa, state, "\x01",
|
||||||
|
1);
|
||||||
|
/* todo change to out of band */
|
||||||
|
state = aa_dfa_null_transition(profile->policy.dfa, state);
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
static unsigned int match_to_local(struct aa_profile *profile,
|
||||||
|
unsigned int state, int type, int protocol,
|
||||||
|
struct sockaddr_un *addr, int addrlen,
|
||||||
|
const char **info)
|
||||||
|
{
|
||||||
|
state = match_to_prot(profile, state, type, protocol, info);
|
||||||
|
if (state) {
|
||||||
|
state = match_addr(profile, state, addr, addrlen);
|
||||||
|
if (state) {
|
||||||
|
/* todo: local label matching */
|
||||||
|
state = aa_dfa_null_transition(profile->policy.dfa,
|
||||||
|
state);
|
||||||
|
if (!state)
|
||||||
|
*info = "failed local label match";
|
||||||
|
} else
|
||||||
|
*info = "failed local address match";
|
||||||
|
}
|
||||||
|
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
static unsigned int match_to_sk(struct aa_profile *profile,
|
||||||
|
unsigned int state, struct unix_sock *u,
|
||||||
|
const char **info)
|
||||||
|
{
|
||||||
|
struct sockaddr_un *addr = NULL;
|
||||||
|
int addrlen = 0;
|
||||||
|
|
||||||
|
if (u->addr) {
|
||||||
|
addr = u->addr->name;
|
||||||
|
addrlen = u->addr->len;
|
||||||
|
}
|
||||||
|
|
||||||
|
return match_to_local(profile, state, u->sk.sk_type, u->sk.sk_protocol,
|
||||||
|
addr, addrlen, info);
|
||||||
|
}
|
||||||
|
|
||||||
|
#define CMD_ADDR 1
|
||||||
|
#define CMD_LISTEN 2
|
||||||
|
#define CMD_OPT 4
|
||||||
|
|
||||||
|
static inline unsigned int match_to_cmd(struct aa_profile *profile,
|
||||||
|
unsigned int state, struct unix_sock *u,
|
||||||
|
char cmd, const char **info)
|
||||||
|
{
|
||||||
|
state = match_to_sk(profile, state, u, info);
|
||||||
|
if (state) {
|
||||||
|
state = aa_dfa_match_len(profile->policy.dfa, state, &cmd, 1);
|
||||||
|
if (!state)
|
||||||
|
*info = "failed cmd selection match";
|
||||||
|
}
|
||||||
|
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline unsigned int match_to_peer(struct aa_profile *profile,
|
||||||
|
unsigned int state,
|
||||||
|
struct unix_sock *u,
|
||||||
|
struct sockaddr_un *peer_addr,
|
||||||
|
int peer_addrlen,
|
||||||
|
const char **info)
|
||||||
|
{
|
||||||
|
state = match_to_cmd(profile, state, u, CMD_ADDR, info);
|
||||||
|
if (state) {
|
||||||
|
state = match_addr(profile, state, peer_addr, peer_addrlen);
|
||||||
|
if (!state)
|
||||||
|
*info = "failed peer address match";
|
||||||
|
}
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int do_perms(struct aa_profile *profile, unsigned int state, u32 request,
|
||||||
|
struct common_audit_data *sa)
|
||||||
|
{
|
||||||
|
struct aa_perms perms;
|
||||||
|
|
||||||
|
AA_BUG(!profile);
|
||||||
|
|
||||||
|
aa_compute_perms(profile->policy.dfa, state, &perms);
|
||||||
|
aa_apply_modes_to_perms(profile, &perms);
|
||||||
|
return aa_check_perms(profile, &perms, request, sa,
|
||||||
|
audit_net_cb);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int match_label(struct aa_profile *profile, struct aa_profile *peer,
|
||||||
|
unsigned int state, u32 request,
|
||||||
|
struct common_audit_data *sa)
|
||||||
|
{
|
||||||
|
AA_BUG(!profile);
|
||||||
|
AA_BUG(!peer);
|
||||||
|
|
||||||
|
aad(sa)->peer = &peer->label;
|
||||||
|
|
||||||
|
if (state) {
|
||||||
|
state = aa_dfa_match(profile->policy.dfa, state, aa_peer_name(peer));
|
||||||
|
if (!state)
|
||||||
|
aad(sa)->info = "failed peer label match";
|
||||||
|
}
|
||||||
|
return do_perms(profile, state, request, sa);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* unix sock creation comes before we know if the socket will be an fs
|
||||||
|
* socket
|
||||||
|
* v6 - semantics are handled by mapping in profile load
|
||||||
|
* v7 - semantics require sock create for tasks creating an fs socket.
|
||||||
|
*/
|
||||||
|
static int profile_create_perm(struct aa_profile *profile, int family,
|
||||||
|
int type, int protocol)
|
||||||
|
{
|
||||||
|
unsigned int state;
|
||||||
|
DEFINE_AUDIT_NET(sa, OP_CREATE, NULL, family, type, protocol);
|
||||||
|
|
||||||
|
AA_BUG(!profile);
|
||||||
|
AA_BUG(profile_unconfined(profile));
|
||||||
|
|
||||||
|
if ((state = PROFILE_MEDIATES_AF(profile, AF_UNIX))) {
|
||||||
|
state = match_to_prot(profile, state, type, protocol,
|
||||||
|
&aad(&sa)->info);
|
||||||
|
return do_perms(profile, state, AA_MAY_CREATE, &sa);
|
||||||
|
}
|
||||||
|
|
||||||
|
return aa_profile_af_perm(profile, &sa, AA_MAY_CREATE, family, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
int aa_unix_create_perm(struct aa_label *label, int family, int type,
|
||||||
|
int protocol)
|
||||||
|
{
|
||||||
|
struct aa_profile *profile;
|
||||||
|
|
||||||
|
if (unconfined(label))
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
return fn_for_each_confined(label, profile,
|
||||||
|
profile_create_perm(profile, family, type, protocol));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static inline int profile_sk_perm(struct aa_profile *profile, const char *op,
|
||||||
|
u32 request, struct sock *sk)
|
||||||
|
{
|
||||||
|
unsigned int state;
|
||||||
|
DEFINE_AUDIT_SK(sa, op, sk);
|
||||||
|
|
||||||
|
AA_BUG(!profile);
|
||||||
|
AA_BUG(!sk);
|
||||||
|
AA_BUG(UNIX_FS(sk));
|
||||||
|
AA_BUG(profile_unconfined(profile));
|
||||||
|
|
||||||
|
state = PROFILE_MEDIATES_AF(profile, AF_UNIX);
|
||||||
|
if (state) {
|
||||||
|
state = match_to_sk(profile, state, unix_sk(sk),
|
||||||
|
&aad(&sa)->info);
|
||||||
|
return do_perms(profile, state, request, &sa);
|
||||||
|
}
|
||||||
|
|
||||||
|
return aa_profile_af_sk_perm(profile, &sa, request, sk);
|
||||||
|
}
|
||||||
|
|
||||||
|
int aa_unix_label_sk_perm(struct aa_label *label, const char *op, u32 request,
|
||||||
|
struct sock *sk)
|
||||||
|
{
|
||||||
|
struct aa_profile *profile;
|
||||||
|
|
||||||
|
return fn_for_each_confined(label, profile,
|
||||||
|
profile_sk_perm(profile, op, request, sk));
|
||||||
|
}
|
||||||
|
|
||||||
|
static int unix_label_sock_perm(struct aa_label *label, const char *op, u32 request,
|
||||||
|
struct socket *sock)
|
||||||
|
{
|
||||||
|
if (unconfined(label))
|
||||||
|
return 0;
|
||||||
|
if (UNIX_FS(sock->sk))
|
||||||
|
return unix_fs_perm(op, request, label, unix_sk(sock->sk), 0);
|
||||||
|
|
||||||
|
return aa_unix_label_sk_perm(label, op, request, sock->sk);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* revaliation, get/set attr */
|
||||||
|
int aa_unix_sock_perm(const char *op, u32 request, struct socket *sock)
|
||||||
|
{
|
||||||
|
struct aa_label *label = aa_begin_current_label(DO_UPDATE);
|
||||||
|
int error = unix_label_sock_perm(label, op, request, sock);
|
||||||
|
aa_end_current_label(label);
|
||||||
|
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int profile_bind_perm(struct aa_profile *profile, struct sock *sk,
|
||||||
|
struct sockaddr *addr, int addrlen)
|
||||||
|
{
|
||||||
|
unsigned int state;
|
||||||
|
DEFINE_AUDIT_SK(sa, OP_BIND, sk);
|
||||||
|
|
||||||
|
AA_BUG(!profile);
|
||||||
|
AA_BUG(!sk);
|
||||||
|
AA_BUG(addr->sa_family != AF_UNIX);
|
||||||
|
AA_BUG(profile_unconfined(profile));
|
||||||
|
AA_BUG(unix_addr_fs(addr, addrlen));
|
||||||
|
|
||||||
|
state = PROFILE_MEDIATES_AF(profile, AF_UNIX);
|
||||||
|
if (state) {
|
||||||
|
/* bind for abstract socket */
|
||||||
|
aad(&sa)->net.addr = unix_addr(addr);
|
||||||
|
aad(&sa)->net.addrlen = addrlen;
|
||||||
|
|
||||||
|
state = match_to_local(profile, state,
|
||||||
|
sk->sk_type, sk->sk_protocol,
|
||||||
|
unix_addr(addr), addrlen,
|
||||||
|
&aad(&sa)->info);
|
||||||
|
return do_perms(profile, state, AA_MAY_BIND, &sa);
|
||||||
|
}
|
||||||
|
|
||||||
|
return aa_profile_af_sk_perm(profile, &sa, AA_MAY_BIND, sk);
|
||||||
|
}
|
||||||
|
|
||||||
|
int aa_unix_bind_perm(struct socket *sock, struct sockaddr *address,
|
||||||
|
int addrlen)
|
||||||
|
{
|
||||||
|
struct aa_profile *profile;
|
||||||
|
struct aa_label *label = aa_begin_current_label(DO_UPDATE);
|
||||||
|
int error = 0;
|
||||||
|
|
||||||
|
/* fs bind is handled by mknod */
|
||||||
|
if (!(unconfined(label) || unix_addr_fs(address, addrlen)))
|
||||||
|
error = fn_for_each_confined(label, profile,
|
||||||
|
profile_bind_perm(profile, sock->sk, address,
|
||||||
|
addrlen));
|
||||||
|
aa_end_current_label(label);
|
||||||
|
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
|
int aa_unix_connect_perm(struct socket *sock, struct sockaddr *address,
|
||||||
|
int addrlen)
|
||||||
|
{
|
||||||
|
/* unix connections are covered by the
|
||||||
|
* - unix_stream_connect (stream) and unix_may_send hooks (dgram)
|
||||||
|
* - fs connect is handled by open
|
||||||
|
*/
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int profile_listen_perm(struct aa_profile *profile, struct sock *sk,
|
||||||
|
int backlog)
|
||||||
|
{
|
||||||
|
unsigned int state;
|
||||||
|
DEFINE_AUDIT_SK(sa, OP_LISTEN, sk);
|
||||||
|
|
||||||
|
AA_BUG(!profile);
|
||||||
|
AA_BUG(!sk);
|
||||||
|
AA_BUG(UNIX_FS(sk));
|
||||||
|
AA_BUG(profile_unconfined(profile));
|
||||||
|
|
||||||
|
state = PROFILE_MEDIATES_AF(profile, AF_UNIX);
|
||||||
|
if (state) {
|
||||||
|
u16 b = cpu_to_be16(backlog);
|
||||||
|
|
||||||
|
state = match_to_cmd(profile, state, unix_sk(sk), CMD_LISTEN,
|
||||||
|
&aad(&sa)->info);
|
||||||
|
if (state) {
|
||||||
|
state = aa_dfa_match_len(profile->policy.dfa, state,
|
||||||
|
(char *) &b, 2);
|
||||||
|
if (!state)
|
||||||
|
aad(&sa)->info = "failed listen backlog match";
|
||||||
|
}
|
||||||
|
return do_perms(profile, state, AA_MAY_LISTEN, &sa);
|
||||||
|
}
|
||||||
|
|
||||||
|
return aa_profile_af_sk_perm(profile, &sa, AA_MAY_LISTEN, sk);
|
||||||
|
}
|
||||||
|
|
||||||
|
int aa_unix_listen_perm(struct socket *sock, int backlog)
|
||||||
|
{
|
||||||
|
struct aa_profile *profile;
|
||||||
|
struct aa_label *label = aa_begin_current_label(DO_UPDATE);
|
||||||
|
int error = 0;
|
||||||
|
|
||||||
|
if (!(unconfined(label) || UNIX_FS(sock->sk)))
|
||||||
|
error = fn_for_each_confined(label, profile,
|
||||||
|
profile_listen_perm(profile, sock->sk,
|
||||||
|
backlog));
|
||||||
|
aa_end_current_label(label);
|
||||||
|
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static inline int profile_accept_perm(struct aa_profile *profile,
|
||||||
|
struct sock *sk,
|
||||||
|
struct sock *newsk)
|
||||||
|
{
|
||||||
|
unsigned int state;
|
||||||
|
DEFINE_AUDIT_SK(sa, OP_ACCEPT, sk);
|
||||||
|
|
||||||
|
AA_BUG(!profile);
|
||||||
|
AA_BUG(!sk);
|
||||||
|
AA_BUG(UNIX_FS(sk));
|
||||||
|
AA_BUG(profile_unconfined(profile));
|
||||||
|
|
||||||
|
state = PROFILE_MEDIATES_AF(profile, AF_UNIX);
|
||||||
|
if (state) {
|
||||||
|
state = match_to_sk(profile, state, unix_sk(sk),
|
||||||
|
&aad(&sa)->info);
|
||||||
|
return do_perms(profile, state, AA_MAY_ACCEPT, &sa);
|
||||||
|
}
|
||||||
|
|
||||||
|
return aa_profile_af_sk_perm(profile, &sa, AA_MAY_ACCEPT, sk);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ability of sock to connect, not peer address binding */
|
||||||
|
int aa_unix_accept_perm(struct socket *sock, struct socket *newsock)
|
||||||
|
{
|
||||||
|
struct aa_profile *profile;
|
||||||
|
struct aa_label *label = aa_begin_current_label(DO_UPDATE);
|
||||||
|
int error = 0;
|
||||||
|
|
||||||
|
if (!(unconfined(label) || UNIX_FS(sock->sk)))
|
||||||
|
error = fn_for_each_confined(label, profile,
|
||||||
|
profile_accept_perm(profile, sock->sk,
|
||||||
|
newsock->sk));
|
||||||
|
aa_end_current_label(label);
|
||||||
|
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* dgram handled by unix_may_sendmsg, right to send on stream done at connect
|
||||||
|
* could do per msg unix_stream here
|
||||||
|
*/
|
||||||
|
/* sendmsg, recvmsg */
|
||||||
|
int aa_unix_msg_perm(const char *op, u32 request, struct socket *sock,
|
||||||
|
struct msghdr *msg, int size)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static int profile_opt_perm(struct aa_profile *profile, const char *op, u32 request,
|
||||||
|
struct sock *sk, int level, int optname)
|
||||||
|
{
|
||||||
|
unsigned int state;
|
||||||
|
DEFINE_AUDIT_SK(sa, op, sk);
|
||||||
|
|
||||||
|
AA_BUG(!profile);
|
||||||
|
AA_BUG(!sk);
|
||||||
|
AA_BUG(UNIX_FS(sk));
|
||||||
|
AA_BUG(profile_unconfined(profile));
|
||||||
|
|
||||||
|
state = PROFILE_MEDIATES_AF(profile, AF_UNIX);
|
||||||
|
if (state) {
|
||||||
|
u16 b = cpu_to_be16(optname);
|
||||||
|
|
||||||
|
state = match_to_cmd(profile, state, unix_sk(sk), CMD_OPT,
|
||||||
|
&aad(&sa)->info);
|
||||||
|
if (state) {
|
||||||
|
state = aa_dfa_match_len(profile->policy.dfa, state,
|
||||||
|
(char *) &b, 2);
|
||||||
|
if (!state)
|
||||||
|
aad(&sa)->info = "failed sockopt match";
|
||||||
|
}
|
||||||
|
return do_perms(profile, state, request, &sa);
|
||||||
|
}
|
||||||
|
|
||||||
|
return aa_profile_af_sk_perm(profile, &sa, request, sk);
|
||||||
|
}
|
||||||
|
|
||||||
|
int aa_unix_opt_perm(const char *op, u32 request, struct socket *sock, int level,
|
||||||
|
int optname)
|
||||||
|
{
|
||||||
|
struct aa_profile *profile;
|
||||||
|
struct aa_label *label = aa_begin_current_label(DO_UPDATE);
|
||||||
|
int error = 0;
|
||||||
|
|
||||||
|
if (!(unconfined(label) || UNIX_FS(sock->sk)))
|
||||||
|
error = fn_for_each_confined(label, profile,
|
||||||
|
profile_opt_perm(profile, op, request,
|
||||||
|
sock->sk, level, optname));
|
||||||
|
aa_end_current_label(label);
|
||||||
|
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* null peer_label is allowed, in which case the peer_sk label is used */
|
||||||
|
static int profile_peer_perm(struct aa_profile *profile, const char *op, u32 request,
|
||||||
|
struct sock *sk, struct sock *peer_sk,
|
||||||
|
struct aa_label *peer_label,
|
||||||
|
struct common_audit_data *sa)
|
||||||
|
{
|
||||||
|
unsigned int state;
|
||||||
|
|
||||||
|
AA_BUG(!profile);
|
||||||
|
AA_BUG(profile_unconfined(profile));
|
||||||
|
AA_BUG(!sk);
|
||||||
|
AA_BUG(!peer_sk);
|
||||||
|
AA_BUG(UNIX_FS(peer_sk));
|
||||||
|
|
||||||
|
state = PROFILE_MEDIATES_AF(profile, AF_UNIX);
|
||||||
|
if (state) {
|
||||||
|
struct aa_sk_ctx *peer_ctx = SK_CTX(peer_sk);
|
||||||
|
struct aa_profile *peerp;
|
||||||
|
struct sockaddr_un *addr = NULL;
|
||||||
|
int len = 0;
|
||||||
|
if (unix_sk(peer_sk)->addr) {
|
||||||
|
addr = unix_sk(peer_sk)->addr->name;
|
||||||
|
len = unix_sk(peer_sk)->addr->len;
|
||||||
|
}
|
||||||
|
state = match_to_peer(profile, state, unix_sk(sk),
|
||||||
|
addr, len, &aad(sa)->info);
|
||||||
|
if (!peer_label)
|
||||||
|
peer_label = peer_ctx->label;
|
||||||
|
return fn_for_each_in_ns(peer_label, peerp,
|
||||||
|
match_label(profile, peerp, state, request,
|
||||||
|
sa));
|
||||||
|
}
|
||||||
|
|
||||||
|
return aa_profile_af_sk_perm(profile, sa, request, sk);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Requires: lock held on both @sk and @peer_sk
|
||||||
|
*/
|
||||||
|
int aa_unix_peer_perm(struct aa_label *label, const char *op, u32 request,
|
||||||
|
struct sock *sk, struct sock *peer_sk,
|
||||||
|
struct aa_label *peer_label)
|
||||||
|
{
|
||||||
|
struct unix_sock *peeru = unix_sk(peer_sk);
|
||||||
|
struct unix_sock *u = unix_sk(sk);
|
||||||
|
|
||||||
|
AA_BUG(!label);
|
||||||
|
AA_BUG(!sk);
|
||||||
|
AA_BUG(!peer_sk);
|
||||||
|
|
||||||
|
if (UNIX_FS(aa_sock(peeru)))
|
||||||
|
return unix_fs_perm(op, request, label, peeru, 0);
|
||||||
|
else if (UNIX_FS(aa_sock(u)))
|
||||||
|
return unix_fs_perm(op, request, label, u, 0);
|
||||||
|
else {
|
||||||
|
struct aa_profile *profile;
|
||||||
|
DEFINE_AUDIT_SK(sa, op, sk);
|
||||||
|
aad(&sa)->net.peer_sk = peer_sk;
|
||||||
|
|
||||||
|
/* TODO: ns!!! */
|
||||||
|
if (!net_eq(sock_net(sk), sock_net(peer_sk))) {
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (unconfined(label))
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
return fn_for_each_confined(label, profile,
|
||||||
|
profile_peer_perm(profile, op, request, sk,
|
||||||
|
peer_sk, peer_label, &sa));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* from net/unix/af_unix.c */
|
||||||
|
static void unix_state_double_lock(struct sock *sk1, struct sock *sk2)
|
||||||
|
{
|
||||||
|
if (unlikely(sk1 == sk2) || !sk2) {
|
||||||
|
unix_state_lock(sk1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (sk1 < sk2) {
|
||||||
|
unix_state_lock(sk1);
|
||||||
|
unix_state_lock_nested(sk2);
|
||||||
|
} else {
|
||||||
|
unix_state_lock(sk2);
|
||||||
|
unix_state_lock_nested(sk1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void unix_state_double_unlock(struct sock *sk1, struct sock *sk2)
|
||||||
|
{
|
||||||
|
if (unlikely(sk1 == sk2) || !sk2) {
|
||||||
|
unix_state_unlock(sk1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
unix_state_unlock(sk1);
|
||||||
|
unix_state_unlock(sk2);
|
||||||
|
}
|
||||||
|
|
||||||
|
int aa_unix_file_perm(struct aa_label *label, const char *op, u32 request,
|
||||||
|
struct socket *sock)
|
||||||
|
{
|
||||||
|
struct sock *peer_sk = NULL;
|
||||||
|
u32 sk_req = request & ~NET_PEER_MASK;
|
||||||
|
int error = 0;
|
||||||
|
|
||||||
|
AA_BUG(!label);
|
||||||
|
AA_BUG(!sock);
|
||||||
|
AA_BUG(!sock->sk);
|
||||||
|
AA_BUG(sock->sk->sk_family != AF_UNIX);
|
||||||
|
|
||||||
|
/* TODO: update sock label with new task label */
|
||||||
|
unix_state_lock(sock->sk);
|
||||||
|
peer_sk = unix_peer(sock->sk);
|
||||||
|
if (peer_sk)
|
||||||
|
sock_hold(peer_sk);
|
||||||
|
if (!unix_connected(sock) && sk_req) {
|
||||||
|
error = unix_label_sock_perm(label, op, sk_req, sock);
|
||||||
|
if (!error) {
|
||||||
|
// update label
|
||||||
|
}
|
||||||
|
}
|
||||||
|
unix_state_unlock(sock->sk);
|
||||||
|
if (!peer_sk)
|
||||||
|
return error;
|
||||||
|
|
||||||
|
unix_state_double_lock(sock->sk, peer_sk);
|
||||||
|
if (UNIX_FS(sock->sk)) {
|
||||||
|
error = unix_fs_perm(op, request, label, unix_sk(sock->sk),
|
||||||
|
PATH_SOCK_COND);
|
||||||
|
} else if (UNIX_FS(peer_sk)) {
|
||||||
|
error = unix_fs_perm(op, request, label, unix_sk(peer_sk),
|
||||||
|
PATH_SOCK_COND);
|
||||||
|
} else {
|
||||||
|
struct aa_sk_ctx *pctx = SK_CTX(peer_sk);
|
||||||
|
if (sk_req)
|
||||||
|
error = aa_unix_label_sk_perm(label, op, sk_req,
|
||||||
|
sock->sk);
|
||||||
|
last_error(error,
|
||||||
|
xcheck(aa_unix_peer_perm(label, op,
|
||||||
|
MAY_READ | MAY_WRITE,
|
||||||
|
sock->sk, peer_sk, NULL),
|
||||||
|
aa_unix_peer_perm(pctx->label, op,
|
||||||
|
MAY_READ | MAY_WRITE,
|
||||||
|
peer_sk, sock->sk, label)));
|
||||||
|
}
|
||||||
|
|
||||||
|
unix_state_double_unlock(sock->sk, peer_sk);
|
||||||
|
sock_put(peer_sk);
|
||||||
|
|
||||||
|
return error;
|
||||||
|
}
|
File diff suppressed because it is too large
Load diff
|
@ -18,60 +18,8 @@
|
||||||
#include "include/apparmor.h"
|
#include "include/apparmor.h"
|
||||||
#include "include/audit.h"
|
#include "include/audit.h"
|
||||||
#include "include/policy.h"
|
#include "include/policy.h"
|
||||||
|
#include "include/policy_ns.h"
|
||||||
|
|
||||||
const char *const op_table[] = {
|
|
||||||
"null",
|
|
||||||
|
|
||||||
"sysctl",
|
|
||||||
"capable",
|
|
||||||
|
|
||||||
"unlink",
|
|
||||||
"mkdir",
|
|
||||||
"rmdir",
|
|
||||||
"mknod",
|
|
||||||
"truncate",
|
|
||||||
"link",
|
|
||||||
"symlink",
|
|
||||||
"rename_src",
|
|
||||||
"rename_dest",
|
|
||||||
"chmod",
|
|
||||||
"chown",
|
|
||||||
"getattr",
|
|
||||||
"open",
|
|
||||||
|
|
||||||
"file_perm",
|
|
||||||
"file_lock",
|
|
||||||
"file_mmap",
|
|
||||||
"file_mprotect",
|
|
||||||
|
|
||||||
"create",
|
|
||||||
"post_create",
|
|
||||||
"bind",
|
|
||||||
"connect",
|
|
||||||
"listen",
|
|
||||||
"accept",
|
|
||||||
"sendmsg",
|
|
||||||
"recvmsg",
|
|
||||||
"getsockname",
|
|
||||||
"getpeername",
|
|
||||||
"getsockopt",
|
|
||||||
"setsockopt",
|
|
||||||
"socket_shutdown",
|
|
||||||
|
|
||||||
"ptrace",
|
|
||||||
|
|
||||||
"exec",
|
|
||||||
"change_hat",
|
|
||||||
"change_profile",
|
|
||||||
"change_onexec",
|
|
||||||
|
|
||||||
"setprocattr",
|
|
||||||
"setrlimit",
|
|
||||||
|
|
||||||
"profile_replace",
|
|
||||||
"profile_load",
|
|
||||||
"profile_remove"
|
|
||||||
};
|
|
||||||
|
|
||||||
const char *const audit_mode_names[] = {
|
const char *const audit_mode_names[] = {
|
||||||
"normal",
|
"normal",
|
||||||
|
@ -114,34 +62,42 @@ static void audit_pre(struct audit_buffer *ab, void *ca)
|
||||||
|
|
||||||
if (aa_g_audit_header) {
|
if (aa_g_audit_header) {
|
||||||
audit_log_format(ab, "apparmor=");
|
audit_log_format(ab, "apparmor=");
|
||||||
audit_log_string(ab, aa_audit_type[sa->aad->type]);
|
audit_log_string(ab, aa_audit_type[aad(sa)->type]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sa->aad->op) {
|
if (aad(sa)->op) {
|
||||||
audit_log_format(ab, " operation=");
|
audit_log_format(ab, " operation=");
|
||||||
audit_log_string(ab, op_table[sa->aad->op]);
|
audit_log_string(ab, aad(sa)->op);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sa->aad->info) {
|
if (aad(sa)->info) {
|
||||||
audit_log_format(ab, " info=");
|
audit_log_format(ab, " info=");
|
||||||
audit_log_string(ab, sa->aad->info);
|
audit_log_string(ab, aad(sa)->info);
|
||||||
if (sa->aad->error)
|
if (aad(sa)->error)
|
||||||
audit_log_format(ab, " error=%d", sa->aad->error);
|
audit_log_format(ab, " error=%d", aad(sa)->error);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sa->aad->profile) {
|
if (aad(sa)->label) {
|
||||||
struct aa_profile *profile = sa->aad->profile;
|
struct aa_label *label = aad(sa)->label;
|
||||||
if (profile->ns != root_ns) {
|
if (label_isprofile(label)) {
|
||||||
audit_log_format(ab, " namespace=");
|
struct aa_profile *profile = labels_profile(label);
|
||||||
audit_log_untrustedstring(ab, profile->ns->base.hname);
|
if (profile->ns != root_ns) {
|
||||||
|
audit_log_format(ab, " namespace=");
|
||||||
|
audit_log_untrustedstring(ab,
|
||||||
|
profile->ns->base.hname);
|
||||||
|
}
|
||||||
|
audit_log_format(ab, " profile=");
|
||||||
|
audit_log_untrustedstring(ab, profile->base.hname);
|
||||||
|
} else {
|
||||||
|
audit_log_format(ab, " label=");
|
||||||
|
aa_label_xaudit(ab, root_ns, label, FLAG_VIEW_SUBNS,
|
||||||
|
GFP_ATOMIC);
|
||||||
}
|
}
|
||||||
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_format(ab, " name=");
|
||||||
audit_log_untrustedstring(ab, sa->aad->name);
|
audit_log_untrustedstring(ab, aad(sa)->name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -153,7 +109,12 @@ static void audit_pre(struct audit_buffer *ab, void *ca)
|
||||||
void aa_audit_msg(int type, struct common_audit_data *sa,
|
void aa_audit_msg(int type, struct common_audit_data *sa,
|
||||||
void (*cb) (struct audit_buffer *, void *))
|
void (*cb) (struct audit_buffer *, void *))
|
||||||
{
|
{
|
||||||
sa->aad->type = type;
|
/* TODO: redirect messages for profile to the correct ns
|
||||||
|
* rejects from subns should goto the audit associated
|
||||||
|
* with it, and audits from parent ns should got ns
|
||||||
|
* associated with it
|
||||||
|
*/
|
||||||
|
aad(sa)->type = type;
|
||||||
common_lsm_audit(sa, audit_pre, cb);
|
common_lsm_audit(sa, audit_pre, cb);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -161,7 +122,6 @@ void aa_audit_msg(int type, struct common_audit_data *sa,
|
||||||
* aa_audit - Log a profile based audit event to the audit subsystem
|
* aa_audit - Log a profile based audit event to the audit subsystem
|
||||||
* @type: audit type for the message
|
* @type: audit type for the message
|
||||||
* @profile: profile to check against (NOT NULL)
|
* @profile: profile to check against (NOT NULL)
|
||||||
* @gfp: allocation flags to use
|
|
||||||
* @sa: audit event (NOT NULL)
|
* @sa: audit event (NOT NULL)
|
||||||
* @cb: optional callback fn for type specific fields (MAYBE NULL)
|
* @cb: optional callback fn for type specific fields (MAYBE NULL)
|
||||||
*
|
*
|
||||||
|
@ -169,14 +129,13 @@ void aa_audit_msg(int type, struct common_audit_data *sa,
|
||||||
*
|
*
|
||||||
* Returns: error on failure
|
* Returns: error on failure
|
||||||
*/
|
*/
|
||||||
int aa_audit(int type, struct aa_profile *profile, gfp_t gfp,
|
int aa_audit(int type, struct aa_profile *profile, struct common_audit_data *sa,
|
||||||
struct common_audit_data *sa,
|
|
||||||
void (*cb) (struct audit_buffer *, void *))
|
void (*cb) (struct audit_buffer *, void *))
|
||||||
{
|
{
|
||||||
BUG_ON(!profile);
|
BUG_ON(!profile);
|
||||||
|
|
||||||
if (type == AUDIT_APPARMOR_AUTO) {
|
if (type == AUDIT_APPARMOR_AUTO) {
|
||||||
if (likely(!sa->aad->error)) {
|
if (likely(!aad(sa)->error)) {
|
||||||
if (AUDIT_MODE(profile) != AUDIT_ALL)
|
if (AUDIT_MODE(profile) != AUDIT_ALL)
|
||||||
return 0;
|
return 0;
|
||||||
type = AUDIT_APPARMOR_AUDIT;
|
type = AUDIT_APPARMOR_AUDIT;
|
||||||
|
@ -188,22 +147,22 @@ int aa_audit(int type, struct aa_profile *profile, gfp_t gfp,
|
||||||
if (AUDIT_MODE(profile) == AUDIT_QUIET ||
|
if (AUDIT_MODE(profile) == AUDIT_QUIET ||
|
||||||
(type == AUDIT_APPARMOR_DENIED &&
|
(type == AUDIT_APPARMOR_DENIED &&
|
||||||
AUDIT_MODE(profile) == AUDIT_QUIET))
|
AUDIT_MODE(profile) == AUDIT_QUIET))
|
||||||
return sa->aad->error;
|
return aad(sa)->error;
|
||||||
|
|
||||||
if (KILL_MODE(profile) && type == AUDIT_APPARMOR_DENIED)
|
if (KILL_MODE(profile) && type == AUDIT_APPARMOR_DENIED)
|
||||||
type = AUDIT_APPARMOR_KILL;
|
type = AUDIT_APPARMOR_KILL;
|
||||||
|
|
||||||
if (!unconfined(profile))
|
aad(sa)->label = &profile->label;
|
||||||
sa->aad->profile = profile;
|
|
||||||
|
|
||||||
aa_audit_msg(type, sa, cb);
|
aa_audit_msg(type, sa, cb);
|
||||||
|
|
||||||
if (sa->aad->type == AUDIT_APPARMOR_KILL)
|
if (aad(sa)->type == AUDIT_APPARMOR_KILL)
|
||||||
(void)send_sig_info(SIGKILL, NULL,
|
(void)send_sig_info(SIGKILL, NULL,
|
||||||
sa->u.tsk ? sa->u.tsk : current);
|
sa->type == LSM_AUDIT_DATA_TASK && sa->u.tsk ?
|
||||||
|
sa->u.tsk : current);
|
||||||
|
|
||||||
if (sa->aad->type == AUDIT_APPARMOR_ALLOWED)
|
if (aad(sa)->type == AUDIT_APPARMOR_ALLOWED)
|
||||||
return complain_error(sa->aad->error);
|
return complain_error(aad(sa)->error);
|
||||||
|
|
||||||
return sa->aad->error;
|
return aad(sa)->error;
|
||||||
}
|
}
|
||||||
|
|
19
security/apparmor/backport.c
Normal file
19
security/apparmor/backport.c
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
/*
|
||||||
|
* AppArmor security module
|
||||||
|
*
|
||||||
|
* This file contains AppArmor file mediation function definitions.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* This is a file of helper fns backported from newer kernels to support
|
||||||
|
* backporting of apparmor to older kernels. Fns prefixed with code they
|
||||||
|
* are copied of modified from
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "include/backport.h"
|
|
@ -53,6 +53,7 @@ static void audit_cb(struct audit_buffer *ab, void *va)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* audit_caps - audit a capability
|
* audit_caps - audit a capability
|
||||||
|
* @sa: audit data
|
||||||
* @profile: profile being tested for confinement (NOT NULL)
|
* @profile: profile being tested for confinement (NOT NULL)
|
||||||
* @cap: capability tested
|
* @cap: capability tested
|
||||||
* @error: error code returned by test
|
* @error: error code returned by test
|
||||||
|
@ -62,17 +63,12 @@ static void audit_cb(struct audit_buffer *ab, void *va)
|
||||||
*
|
*
|
||||||
* Returns: 0 or sa->error on success, error code on failure
|
* Returns: 0 or sa->error on success, error code on failure
|
||||||
*/
|
*/
|
||||||
static int audit_caps(struct aa_profile *profile, int cap, int error)
|
static int audit_caps(struct common_audit_data *sa, struct aa_profile *profile,
|
||||||
|
int cap, int error)
|
||||||
{
|
{
|
||||||
struct audit_cache *ent;
|
struct audit_cache *ent;
|
||||||
int type = AUDIT_APPARMOR_AUTO;
|
int type = AUDIT_APPARMOR_AUTO;
|
||||||
struct common_audit_data sa;
|
aad(sa)->error = error;
|
||||||
struct apparmor_audit_data aad = {0,};
|
|
||||||
sa.type = LSM_AUDIT_DATA_CAP;
|
|
||||||
sa.aad = &aad;
|
|
||||||
sa.u.cap = cap;
|
|
||||||
sa.aad->op = OP_CAPABLE;
|
|
||||||
sa.aad->error = error;
|
|
||||||
|
|
||||||
if (likely(!error)) {
|
if (likely(!error)) {
|
||||||
/* test if auditing is being forced */
|
/* test if auditing is being forced */
|
||||||
|
@ -104,24 +100,44 @@ static int audit_caps(struct aa_profile *profile, int cap, int error)
|
||||||
}
|
}
|
||||||
put_cpu_var(audit_cache);
|
put_cpu_var(audit_cache);
|
||||||
|
|
||||||
return aa_audit(type, profile, GFP_ATOMIC, &sa, audit_cb);
|
return aa_audit(type, profile, sa, audit_cb);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* profile_capable - test if profile allows use of capability @cap
|
* profile_capable - test if profile allows use of capability @cap
|
||||||
* @profile: profile being enforced (NOT NULL, NOT unconfined)
|
* @profile: profile being enforced (NOT NULL, NOT unconfined)
|
||||||
* @cap: capability to test if allowed
|
* @cap: capability to test if allowed
|
||||||
|
* @audit: whether an audit record should be generated
|
||||||
|
* @sa: audit data (MAY BE NULL indicating no auditing)
|
||||||
*
|
*
|
||||||
* Returns: 0 if allowed else -EPERM
|
* Returns: 0 if allowed else -EPERM
|
||||||
*/
|
*/
|
||||||
static int profile_capable(struct aa_profile *profile, int cap)
|
static int profile_capable(struct aa_profile *profile, int cap, int audit,
|
||||||
|
struct common_audit_data *sa)
|
||||||
{
|
{
|
||||||
return cap_raised(profile->caps.allow, cap) ? 0 : -EPERM;
|
int error;
|
||||||
|
|
||||||
|
if (cap_raised(profile->caps.allow, cap) &&
|
||||||
|
!cap_raised(profile->caps.denied, cap))
|
||||||
|
error = 0;
|
||||||
|
else
|
||||||
|
error = -EPERM;
|
||||||
|
|
||||||
|
if (audit == SECURITY_CAP_NOAUDIT) {
|
||||||
|
if (!COMPLAIN_MODE(profile))
|
||||||
|
return error;
|
||||||
|
/* audit the cap request in complain mode but note that it
|
||||||
|
* should be optional.
|
||||||
|
*/
|
||||||
|
aad(sa)->info = "optional: no audit";
|
||||||
|
}
|
||||||
|
|
||||||
|
return audit_caps(sa, profile, cap, error);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* aa_capable - test permission to use capability
|
* aa_capable - test permission to use capability
|
||||||
* @profile: profile being tested against (NOT NULL)
|
* @label: label being tested for capability (NOT NULL)
|
||||||
* @cap: capability to be tested
|
* @cap: capability to be tested
|
||||||
* @audit: whether an audit record should be generated
|
* @audit: whether an audit record should be generated
|
||||||
*
|
*
|
||||||
|
@ -129,15 +145,15 @@ static int profile_capable(struct aa_profile *profile, int cap)
|
||||||
*
|
*
|
||||||
* Returns: 0 on success, or else an error code.
|
* Returns: 0 on success, or else an error code.
|
||||||
*/
|
*/
|
||||||
int aa_capable(struct aa_profile *profile, int cap, int audit)
|
int aa_capable(struct aa_label *label, int cap, int audit)
|
||||||
{
|
{
|
||||||
int error = profile_capable(profile, cap);
|
struct aa_profile *profile;
|
||||||
|
int error = 0;
|
||||||
|
DEFINE_AUDIT_DATA(sa, LSM_AUDIT_DATA_CAP, OP_CAPABLE);
|
||||||
|
sa.u.cap = cap;
|
||||||
|
|
||||||
if (!audit) {
|
error = fn_for_each_confined(label, profile,
|
||||||
if (COMPLAIN_MODE(profile))
|
profile_capable(profile, cap, audit, &sa));
|
||||||
return complain_error(error);
|
|
||||||
return error;
|
|
||||||
}
|
|
||||||
|
|
||||||
return audit_caps(profile, cap, error);
|
return error;
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,11 +13,11 @@
|
||||||
* License.
|
* License.
|
||||||
*
|
*
|
||||||
*
|
*
|
||||||
* AppArmor sets confinement on every task, via the the aa_task_cxt and
|
* AppArmor sets confinement on every task, via the the aa_task_ctx and
|
||||||
* the aa_task_cxt.profile, both of which are required and are not allowed
|
* the aa_task_ctx.label, both of which are required and are not allowed
|
||||||
* to be NULL. The aa_task_cxt is not reference counted and is unique
|
* to be NULL. The aa_task_ctx is not reference counted and is unique
|
||||||
* to each cred (which is reference count). The profile pointed to by
|
* to each cred (which is reference count). The label pointed to by
|
||||||
* the task_cxt is reference counted.
|
* the task_ctx is reference counted.
|
||||||
*
|
*
|
||||||
* TODO
|
* TODO
|
||||||
* If a task uses change_hat it currently does not return to the old
|
* If a task uses change_hat it currently does not return to the old
|
||||||
|
@ -30,28 +30,28 @@
|
||||||
#include "include/policy.h"
|
#include "include/policy.h"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* aa_alloc_task_context - allocate a new task_cxt
|
* aa_alloc_task_context - allocate a new task_ctx
|
||||||
* @flags: gfp flags for allocation
|
* @flags: gfp flags for allocation
|
||||||
*
|
*
|
||||||
* Returns: allocated buffer or NULL on failure
|
* Returns: allocated buffer or NULL on failure
|
||||||
*/
|
*/
|
||||||
struct aa_task_cxt *aa_alloc_task_context(gfp_t flags)
|
struct aa_task_ctx *aa_alloc_task_context(gfp_t flags)
|
||||||
{
|
{
|
||||||
return kzalloc(sizeof(struct aa_task_cxt), flags);
|
return kzalloc(sizeof(struct aa_task_ctx), flags);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* aa_free_task_context - free a task_cxt
|
* aa_free_task_context - free a task_ctx
|
||||||
* @cxt: task_cxt to free (MAYBE NULL)
|
* @ctx: task_ctx to free (MAYBE NULL)
|
||||||
*/
|
*/
|
||||||
void aa_free_task_context(struct aa_task_cxt *cxt)
|
void aa_free_task_context(struct aa_task_ctx *ctx)
|
||||||
{
|
{
|
||||||
if (cxt) {
|
if (ctx) {
|
||||||
aa_put_profile(cxt->profile);
|
aa_put_label(ctx->label);
|
||||||
aa_put_profile(cxt->previous);
|
aa_put_label(ctx->previous);
|
||||||
aa_put_profile(cxt->onexec);
|
aa_put_label(ctx->onexec);
|
||||||
|
|
||||||
kzfree(cxt);
|
kzfree(ctx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -60,64 +60,63 @@ void aa_free_task_context(struct aa_task_cxt *cxt)
|
||||||
* @new: a blank task context (NOT NULL)
|
* @new: a blank task context (NOT NULL)
|
||||||
* @old: the task context to copy (NOT NULL)
|
* @old: the task context to copy (NOT NULL)
|
||||||
*/
|
*/
|
||||||
void aa_dup_task_context(struct aa_task_cxt *new, const struct aa_task_cxt *old)
|
void aa_dup_task_context(struct aa_task_ctx *new, const struct aa_task_ctx *old)
|
||||||
{
|
{
|
||||||
*new = *old;
|
*new = *old;
|
||||||
aa_get_profile(new->profile);
|
aa_get_label(new->label);
|
||||||
aa_get_profile(new->previous);
|
aa_get_label(new->previous);
|
||||||
aa_get_profile(new->onexec);
|
aa_get_label(new->onexec);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* aa_get_task_profile - Get another task's profile
|
* aa_get_task_label - Get another task's label
|
||||||
* @task: task to query (NOT NULL)
|
* @task: task to query (NOT NULL)
|
||||||
*
|
*
|
||||||
* Returns: counted reference to @task's profile
|
* Returns: counted reference to @task's label
|
||||||
*/
|
*/
|
||||||
struct aa_profile *aa_get_task_profile(struct task_struct *task)
|
struct aa_label *aa_get_task_label(struct task_struct *task)
|
||||||
{
|
{
|
||||||
struct aa_profile *p;
|
struct aa_label *p;
|
||||||
|
|
||||||
rcu_read_lock();
|
rcu_read_lock();
|
||||||
p = aa_get_profile(__aa_task_profile(task));
|
p = aa_get_newest_label(__aa_task_raw_label(task));
|
||||||
rcu_read_unlock();
|
rcu_read_unlock();
|
||||||
|
|
||||||
return p;
|
return p;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* aa_replace_current_profile - replace the current tasks profiles
|
* aa_replace_current_label - replace the current tasks label
|
||||||
* @profile: new profile (NOT NULL)
|
* @label: new label (NOT NULL)
|
||||||
*
|
*
|
||||||
* Returns: 0 or error on failure
|
* Returns: 0 or error on failure
|
||||||
*/
|
*/
|
||||||
int aa_replace_current_profile(struct aa_profile *profile)
|
int aa_replace_current_label(struct aa_label *label)
|
||||||
{
|
{
|
||||||
struct aa_task_cxt *cxt = current_cxt();
|
struct aa_task_ctx *ctx = current_ctx();
|
||||||
struct cred *new;
|
struct cred *new;
|
||||||
BUG_ON(!profile);
|
BUG_ON(!label);
|
||||||
|
|
||||||
if (cxt->profile == profile)
|
if (ctx->label == label)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
|
if (current_cred() != current_real_cred())
|
||||||
|
return -EBUSY;
|
||||||
|
|
||||||
new = prepare_creds();
|
new = prepare_creds();
|
||||||
if (!new)
|
if (!new)
|
||||||
return -ENOMEM;
|
return -ENOMEM;
|
||||||
|
|
||||||
cxt = cred_cxt(new);
|
ctx = cred_ctx(new);
|
||||||
if (unconfined(profile) || (cxt->profile->ns != profile->ns))
|
if (unconfined(label) || (labels_ns(ctx->label) != labels_ns(label)))
|
||||||
/* if switching to unconfined or a different profile namespace
|
/* if switching to unconfined or a different label namespace
|
||||||
* clear out context state
|
* clear out context state
|
||||||
*/
|
*/
|
||||||
aa_clear_task_cxt_trans(cxt);
|
aa_clear_task_ctx_trans(ctx);
|
||||||
|
|
||||||
/* be careful switching cxt->profile, when racing replacement it
|
aa_get_label(label);
|
||||||
* is possible that cxt->profile->replacedby->profile is the reference
|
aa_put_label(ctx->label);
|
||||||
* keeping @profile valid, so make sure to get its reference before
|
ctx->label = label;
|
||||||
* dropping the reference on cxt->profile */
|
|
||||||
aa_get_profile(profile);
|
|
||||||
aa_put_profile(cxt->profile);
|
|
||||||
cxt->profile = profile;
|
|
||||||
|
|
||||||
commit_creds(new);
|
commit_creds(new);
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -125,21 +124,22 @@ int aa_replace_current_profile(struct aa_profile *profile)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* aa_set_current_onexec - set the tasks change_profile to happen onexec
|
* aa_set_current_onexec - set the tasks change_profile to happen onexec
|
||||||
* @profile: system profile to set at exec (MAYBE NULL to clear value)
|
* @label: system label to set at exec (MAYBE NULL to clear value)
|
||||||
*
|
* @stack: whether stacking should be done
|
||||||
* Returns: 0 or error on failure
|
* Returns: 0 or error on failure
|
||||||
*/
|
*/
|
||||||
int aa_set_current_onexec(struct aa_profile *profile)
|
int aa_set_current_onexec(struct aa_label *label, bool stack)
|
||||||
{
|
{
|
||||||
struct aa_task_cxt *cxt;
|
struct aa_task_ctx *ctx;
|
||||||
struct cred *new = prepare_creds();
|
struct cred *new = prepare_creds();
|
||||||
if (!new)
|
if (!new)
|
||||||
return -ENOMEM;
|
return -ENOMEM;
|
||||||
|
|
||||||
cxt = cred_cxt(new);
|
ctx = cred_ctx(new);
|
||||||
aa_get_profile(profile);
|
aa_get_label(label);
|
||||||
aa_put_profile(cxt->onexec);
|
aa_clear_task_ctx_trans(ctx);
|
||||||
cxt->onexec = profile;
|
ctx->onexec = label;
|
||||||
|
ctx->token = stack;
|
||||||
|
|
||||||
commit_creds(new);
|
commit_creds(new);
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -147,7 +147,7 @@ int aa_set_current_onexec(struct aa_profile *profile)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* aa_set_current_hat - set the current tasks hat
|
* aa_set_current_hat - set the current tasks hat
|
||||||
* @profile: profile to set as the current hat (NOT NULL)
|
* @label: label to set as the current hat (NOT NULL)
|
||||||
* @token: token value that must be specified to change from the hat
|
* @token: token value that must be specified to change from the hat
|
||||||
*
|
*
|
||||||
* Do switch of tasks hat. If the task is currently in a hat
|
* Do switch of tasks hat. If the task is currently in a hat
|
||||||
|
@ -155,67 +155,67 @@ int aa_set_current_onexec(struct aa_profile *profile)
|
||||||
*
|
*
|
||||||
* Returns: 0 or error on failure
|
* Returns: 0 or error on failure
|
||||||
*/
|
*/
|
||||||
int aa_set_current_hat(struct aa_profile *profile, u64 token)
|
int aa_set_current_hat(struct aa_label *label, u64 token)
|
||||||
{
|
{
|
||||||
struct aa_task_cxt *cxt;
|
struct aa_task_ctx *ctx;
|
||||||
struct cred *new = prepare_creds();
|
struct cred *new = prepare_creds();
|
||||||
if (!new)
|
if (!new)
|
||||||
return -ENOMEM;
|
return -ENOMEM;
|
||||||
BUG_ON(!profile);
|
BUG_ON(!label);
|
||||||
|
|
||||||
cxt = cred_cxt(new);
|
ctx = cred_ctx(new);
|
||||||
if (!cxt->previous) {
|
if (!ctx->previous) {
|
||||||
/* transfer refcount */
|
/* transfer refcount */
|
||||||
cxt->previous = cxt->profile;
|
ctx->previous = ctx->label;
|
||||||
cxt->token = token;
|
ctx->token = token;
|
||||||
} else if (cxt->token == token) {
|
} else if (ctx->token == token) {
|
||||||
aa_put_profile(cxt->profile);
|
aa_put_label(ctx->label);
|
||||||
} else {
|
} else {
|
||||||
/* previous_profile && cxt->token != token */
|
/* previous_profile && ctx->token != token */
|
||||||
abort_creds(new);
|
abort_creds(new);
|
||||||
return -EACCES;
|
return -EACCES;
|
||||||
}
|
}
|
||||||
cxt->profile = aa_get_newest_profile(profile);
|
ctx->label = aa_get_newest_label(label);
|
||||||
/* clear exec on switching context */
|
/* clear exec on switching context */
|
||||||
aa_put_profile(cxt->onexec);
|
aa_put_label(ctx->onexec);
|
||||||
cxt->onexec = NULL;
|
ctx->onexec = NULL;
|
||||||
|
|
||||||
commit_creds(new);
|
commit_creds(new);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* aa_restore_previous_profile - exit from hat context restoring the profile
|
* aa_restore_previous_label - exit from hat context restoring previous label
|
||||||
* @token: the token that must be matched to exit hat context
|
* @token: the token that must be matched to exit hat context
|
||||||
*
|
*
|
||||||
* Attempt to return out of a hat to the previous profile. The token
|
* Attempt to return out of a hat to the previous label. The token
|
||||||
* must match the stored token value.
|
* must match the stored token value.
|
||||||
*
|
*
|
||||||
* Returns: 0 or error of failure
|
* Returns: 0 or error of failure
|
||||||
*/
|
*/
|
||||||
int aa_restore_previous_profile(u64 token)
|
int aa_restore_previous_label(u64 token)
|
||||||
{
|
{
|
||||||
struct aa_task_cxt *cxt;
|
struct aa_task_ctx *ctx;
|
||||||
struct cred *new = prepare_creds();
|
struct cred *new = prepare_creds();
|
||||||
if (!new)
|
if (!new)
|
||||||
return -ENOMEM;
|
return -ENOMEM;
|
||||||
|
|
||||||
cxt = cred_cxt(new);
|
ctx = cred_ctx(new);
|
||||||
if (cxt->token != token) {
|
if (ctx->token != token) {
|
||||||
abort_creds(new);
|
abort_creds(new);
|
||||||
return -EACCES;
|
return -EACCES;
|
||||||
}
|
}
|
||||||
/* ignore restores when there is no saved profile */
|
/* ignore restores when there is no saved label */
|
||||||
if (!cxt->previous) {
|
if (!ctx->previous) {
|
||||||
abort_creds(new);
|
abort_creds(new);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
aa_put_profile(cxt->profile);
|
aa_put_label(ctx->label);
|
||||||
cxt->profile = aa_get_newest_profile(cxt->previous);
|
ctx->label = aa_get_newest_label(ctx->previous);
|
||||||
BUG_ON(!cxt->profile);
|
BUG_ON(!ctx->label);
|
||||||
/* clear exec && prev information when restoring to previous context */
|
/* clear exec && prev information when restoring to previous context */
|
||||||
aa_clear_task_cxt_trans(cxt);
|
aa_clear_task_ctx_trans(ctx);
|
||||||
|
|
||||||
commit_creds(new);
|
commit_creds(new);
|
||||||
return 0;
|
return 0;
|
||||||
|
|
|
@ -29,16 +29,50 @@ unsigned int aa_hash_size(void)
|
||||||
return apparmor_hash_size;
|
return apparmor_hash_size;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
char *aa_calc_hash(void *data, size_t len)
|
||||||
|
{
|
||||||
|
SHASH_DESC_ON_STACK(desc, apparmor_tfm);
|
||||||
|
char *hash = NULL;
|
||||||
|
int error = -ENOMEM;
|
||||||
|
|
||||||
|
if (!apparmor_tfm)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
hash = kzalloc(apparmor_hash_size, GFP_KERNEL);
|
||||||
|
if (!hash)
|
||||||
|
goto fail;
|
||||||
|
|
||||||
|
desc->tfm = apparmor_tfm;
|
||||||
|
desc->flags = 0;
|
||||||
|
|
||||||
|
error = crypto_shash_init(desc);
|
||||||
|
if (error)
|
||||||
|
goto fail;
|
||||||
|
error = crypto_shash_update(desc, (u8 *) data, len);
|
||||||
|
if (error)
|
||||||
|
goto fail;
|
||||||
|
error = crypto_shash_final(desc, hash);
|
||||||
|
if (error)
|
||||||
|
goto fail;
|
||||||
|
|
||||||
|
return hash;
|
||||||
|
|
||||||
|
fail:
|
||||||
|
kfree(hash);
|
||||||
|
|
||||||
|
return ERR_PTR(error);
|
||||||
|
}
|
||||||
|
|
||||||
int aa_calc_profile_hash(struct aa_profile *profile, u32 version, void *start,
|
int aa_calc_profile_hash(struct aa_profile *profile, u32 version, void *start,
|
||||||
size_t len)
|
size_t len)
|
||||||
{
|
{
|
||||||
struct {
|
SHASH_DESC_ON_STACK(desc, apparmor_tfm);
|
||||||
struct shash_desc shash;
|
|
||||||
char ctx[crypto_shash_descsize(apparmor_tfm)];
|
|
||||||
} desc;
|
|
||||||
int error = -ENOMEM;
|
int error = -ENOMEM;
|
||||||
u32 le32_version = cpu_to_le32(version);
|
u32 le32_version = cpu_to_le32(version);
|
||||||
|
|
||||||
|
if (!aa_g_hash_policy)
|
||||||
|
return 0;
|
||||||
|
|
||||||
if (!apparmor_tfm)
|
if (!apparmor_tfm)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
|
@ -46,19 +80,19 @@ int aa_calc_profile_hash(struct aa_profile *profile, u32 version, void *start,
|
||||||
if (!profile->hash)
|
if (!profile->hash)
|
||||||
goto fail;
|
goto fail;
|
||||||
|
|
||||||
desc.shash.tfm = apparmor_tfm;
|
desc->tfm = apparmor_tfm;
|
||||||
desc.shash.flags = 0;
|
desc->flags = 0;
|
||||||
|
|
||||||
error = crypto_shash_init(&desc.shash);
|
error = crypto_shash_init(desc);
|
||||||
if (error)
|
if (error)
|
||||||
goto fail;
|
goto fail;
|
||||||
error = crypto_shash_update(&desc.shash, (u8 *) &le32_version, 4);
|
error = crypto_shash_update(desc, (u8 *) &le32_version, 4);
|
||||||
if (error)
|
if (error)
|
||||||
goto fail;
|
goto fail;
|
||||||
error = crypto_shash_update(&desc.shash, (u8 *) start, len);
|
error = crypto_shash_update(desc, (u8 *) start, len);
|
||||||
if (error)
|
if (error)
|
||||||
goto fail;
|
goto fail;
|
||||||
error = crypto_shash_final(&desc.shash, profile->hash);
|
error = crypto_shash_final(desc, profile->hash);
|
||||||
if (error)
|
if (error)
|
||||||
goto fail;
|
goto fail;
|
||||||
|
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -12,15 +12,30 @@
|
||||||
* License.
|
* License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include <linux/tty.h>
|
||||||
|
#include <linux/fdtable.h>
|
||||||
|
#include <linux/file.h>
|
||||||
|
|
||||||
|
#include "include/af_unix.h"
|
||||||
#include "include/apparmor.h"
|
#include "include/apparmor.h"
|
||||||
#include "include/audit.h"
|
#include "include/audit.h"
|
||||||
|
#include "include/context.h"
|
||||||
#include "include/file.h"
|
#include "include/file.h"
|
||||||
#include "include/match.h"
|
#include "include/match.h"
|
||||||
#include "include/path.h"
|
#include "include/path.h"
|
||||||
#include "include/policy.h"
|
#include "include/policy.h"
|
||||||
|
#include "include/label.h"
|
||||||
|
|
||||||
struct file_perms nullperms;
|
static u32 map_mask_to_chr_mask(u32 mask)
|
||||||
|
{
|
||||||
|
u32 m = mask & PERMS_CHRS_MASK;
|
||||||
|
if (mask & AA_MAY_GETATTR)
|
||||||
|
m |= MAY_READ;
|
||||||
|
if (mask & (AA_MAY_SETATTR | AA_MAY_CHMOD | AA_MAY_CHOWN))
|
||||||
|
m |= MAY_WRITE;
|
||||||
|
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* audit_file_mask - convert mask to permission string
|
* audit_file_mask - convert mask to permission string
|
||||||
|
@ -31,29 +46,7 @@ static void audit_file_mask(struct audit_buffer *ab, u32 mask)
|
||||||
{
|
{
|
||||||
char str[10];
|
char str[10];
|
||||||
|
|
||||||
char *m = str;
|
aa_perm_mask_to_str(str, aa_file_perm_chrs, map_mask_to_chr_mask(mask));
|
||||||
|
|
||||||
if (mask & AA_EXEC_MMAP)
|
|
||||||
*m++ = 'm';
|
|
||||||
if (mask & (MAY_READ | AA_MAY_META_READ))
|
|
||||||
*m++ = 'r';
|
|
||||||
if (mask & (MAY_WRITE | AA_MAY_META_WRITE | AA_MAY_CHMOD |
|
|
||||||
AA_MAY_CHOWN))
|
|
||||||
*m++ = 'w';
|
|
||||||
else if (mask & MAY_APPEND)
|
|
||||||
*m++ = 'a';
|
|
||||||
if (mask & AA_MAY_CREATE)
|
|
||||||
*m++ = 'c';
|
|
||||||
if (mask & AA_MAY_DELETE)
|
|
||||||
*m++ = 'd';
|
|
||||||
if (mask & AA_MAY_LINK)
|
|
||||||
*m++ = 'l';
|
|
||||||
if (mask & AA_MAY_LOCK)
|
|
||||||
*m++ = 'k';
|
|
||||||
if (mask & MAY_EXEC)
|
|
||||||
*m++ = 'x';
|
|
||||||
*m = '\0';
|
|
||||||
|
|
||||||
audit_log_string(ab, str);
|
audit_log_string(ab, str);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -67,24 +60,28 @@ static void file_audit_cb(struct audit_buffer *ab, void *va)
|
||||||
struct common_audit_data *sa = va;
|
struct common_audit_data *sa = va;
|
||||||
kuid_t fsuid = current_fsuid();
|
kuid_t fsuid = current_fsuid();
|
||||||
|
|
||||||
if (sa->aad->fs.request & AA_AUDIT_FILE_MASK) {
|
if (aad(sa)->request & AA_AUDIT_FILE_MASK) {
|
||||||
audit_log_format(ab, " requested_mask=");
|
audit_log_format(ab, " requested_mask=");
|
||||||
audit_file_mask(ab, sa->aad->fs.request);
|
audit_file_mask(ab, aad(sa)->request);
|
||||||
}
|
}
|
||||||
if (sa->aad->fs.denied & AA_AUDIT_FILE_MASK) {
|
if (aad(sa)->denied & AA_AUDIT_FILE_MASK) {
|
||||||
audit_log_format(ab, " denied_mask=");
|
audit_log_format(ab, " denied_mask=");
|
||||||
audit_file_mask(ab, sa->aad->fs.denied);
|
audit_file_mask(ab, aad(sa)->denied);
|
||||||
}
|
}
|
||||||
if (sa->aad->fs.request & AA_AUDIT_FILE_MASK) {
|
if (aad(sa)->request & AA_AUDIT_FILE_MASK) {
|
||||||
audit_log_format(ab, " fsuid=%d",
|
audit_log_format(ab, " fsuid=%d",
|
||||||
from_kuid(&init_user_ns, fsuid));
|
from_kuid(&init_user_ns, fsuid));
|
||||||
audit_log_format(ab, " ouid=%d",
|
audit_log_format(ab, " ouid=%d",
|
||||||
from_kuid(&init_user_ns, sa->aad->fs.ouid));
|
from_kuid(&init_user_ns, aad(sa)->fs.ouid));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sa->aad->fs.target) {
|
if (aad(sa)->peer) {
|
||||||
audit_log_format(ab, " target=");
|
audit_log_format(ab, " target=");
|
||||||
audit_log_untrustedstring(ab, sa->aad->fs.target);
|
aa_label_xaudit(ab, labels_ns(aad(sa)->label), aad(sa)->peer,
|
||||||
|
FLAG_VIEW_SUBNS, GFP_ATOMIC);
|
||||||
|
} else if (aad(sa)->fs.target) {
|
||||||
|
audit_log_format(ab, " target=");
|
||||||
|
audit_log_untrustedstring(ab, aad(sa)->fs.target);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -92,65 +89,98 @@ static void file_audit_cb(struct audit_buffer *ab, void *va)
|
||||||
* aa_audit_file - handle the auditing of file operations
|
* aa_audit_file - handle the auditing of file operations
|
||||||
* @profile: the profile being enforced (NOT NULL)
|
* @profile: the profile being enforced (NOT NULL)
|
||||||
* @perms: the permissions computed for the request (NOT NULL)
|
* @perms: the permissions computed for the request (NOT NULL)
|
||||||
* @gfp: allocation flags
|
|
||||||
* @op: operation being mediated
|
* @op: operation being mediated
|
||||||
* @request: permissions requested
|
* @request: permissions requested
|
||||||
* @name: name of object being mediated (MAYBE NULL)
|
* @name: name of object being mediated (MAYBE NULL)
|
||||||
* @target: name of target (MAYBE NULL)
|
* @target: name of target (MAYBE NULL)
|
||||||
|
* @tlabel: target label (MAY BE NULL)
|
||||||
* @ouid: object uid
|
* @ouid: object uid
|
||||||
* @info: extra information message (MAYBE NULL)
|
* @info: extra information message (MAYBE NULL)
|
||||||
* @error: 0 if operation allowed else failure error code
|
* @error: 0 if operation allowed else failure error code
|
||||||
*
|
*
|
||||||
* Returns: %0 or error on failure
|
* Returns: %0 or error on failure
|
||||||
*/
|
*/
|
||||||
int aa_audit_file(struct aa_profile *profile, struct file_perms *perms,
|
int aa_audit_file(struct aa_profile *profile, struct aa_perms *perms,
|
||||||
gfp_t gfp, int op, u32 request, const char *name,
|
const char *op, u32 request, const char *name,
|
||||||
const char *target, kuid_t ouid, const char *info, int error)
|
const char *target, struct aa_label *tlabel,
|
||||||
|
kuid_t ouid, const char *info, int error)
|
||||||
{
|
{
|
||||||
int type = AUDIT_APPARMOR_AUTO;
|
int type = AUDIT_APPARMOR_AUTO;
|
||||||
struct common_audit_data sa;
|
|
||||||
struct apparmor_audit_data aad = {0,};
|
|
||||||
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_TASK, op);
|
||||||
|
sa.u.tsk = NULL;
|
||||||
|
aad(&sa)->request = request;
|
||||||
|
aad(&sa)->name = name;
|
||||||
|
aad(&sa)->fs.target = target;
|
||||||
|
aad(&sa)->peer = tlabel;
|
||||||
|
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;
|
u32 mask = perms->audit;
|
||||||
|
|
||||||
if (unlikely(AUDIT_MODE(profile) == AUDIT_ALL))
|
if (unlikely(AUDIT_MODE(profile) == AUDIT_ALL))
|
||||||
mask = 0xffff;
|
mask = 0xffff;
|
||||||
|
|
||||||
/* mask off perms that are not being force audited */
|
/* mask off perms that are not being force audited */
|
||||||
sa.aad->fs.request &= mask;
|
aad(&sa)->request &= mask;
|
||||||
|
|
||||||
if (likely(!sa.aad->fs.request))
|
if (likely(!aad(&sa)->request))
|
||||||
return 0;
|
return 0;
|
||||||
type = AUDIT_APPARMOR_AUDIT;
|
type = AUDIT_APPARMOR_AUDIT;
|
||||||
} else {
|
} else {
|
||||||
/* only report permissions that were denied */
|
/* only report permissions that were denied */
|
||||||
sa.aad->fs.request = sa.aad->fs.request & ~perms->allow;
|
aad(&sa)->request = aad(&sa)->request & ~perms->allow;
|
||||||
|
AA_BUG(!aad(&sa)->request);
|
||||||
|
|
||||||
if (sa.aad->fs.request & perms->kill)
|
if (aad(&sa)->request & perms->kill)
|
||||||
type = AUDIT_APPARMOR_KILL;
|
type = AUDIT_APPARMOR_KILL;
|
||||||
|
|
||||||
/* quiet known rejects, assumes quiet and kill do not overlap */
|
/* quiet known rejects, assumes quiet and kill do not overlap */
|
||||||
if ((sa.aad->fs.request & perms->quiet) &&
|
if ((aad(&sa)->request & perms->quiet) &&
|
||||||
AUDIT_MODE(profile) != AUDIT_NOQUIET &&
|
AUDIT_MODE(profile) != AUDIT_NOQUIET &&
|
||||||
AUDIT_MODE(profile) != AUDIT_ALL)
|
AUDIT_MODE(profile) != AUDIT_ALL)
|
||||||
sa.aad->fs.request &= ~perms->quiet;
|
aad(&sa)->request &= ~perms->quiet;
|
||||||
|
|
||||||
if (!sa.aad->fs.request)
|
if (!aad(&sa)->request)
|
||||||
return COMPLAIN_MODE(profile) ? 0 : sa.aad->error;
|
return aad(&sa)->error;
|
||||||
}
|
}
|
||||||
|
|
||||||
sa.aad->fs.denied = sa.aad->fs.request & ~perms->allow;
|
aad(&sa)->denied = aad(&sa)->request & ~perms->allow;
|
||||||
return aa_audit(type, profile, gfp, &sa, file_audit_cb);
|
return aa_audit(type, profile, &sa, file_audit_cb);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* is_deleted - test if a file has been completely unlinked
|
||||||
|
* @dentry: dentry of file to test for deletion (NOT NULL)
|
||||||
|
*
|
||||||
|
* Returns: %1 if deleted else %0
|
||||||
|
*/
|
||||||
|
static inline bool is_deleted(struct dentry *dentry)
|
||||||
|
{
|
||||||
|
if (d_unlinked(dentry) && d_backing_inode(dentry)->i_nlink == 0)
|
||||||
|
return 1;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int path_name(const char *op, struct aa_label *label,
|
||||||
|
const struct path *path, int flags, char *buffer,
|
||||||
|
const char**name, struct path_cond *cond, u32 request)
|
||||||
|
{
|
||||||
|
struct aa_profile *profile;
|
||||||
|
const char *info = NULL;
|
||||||
|
int error = aa_path_name(path, flags, buffer, name, &info,
|
||||||
|
labels_profile(label)->disconnected);
|
||||||
|
if (error) {
|
||||||
|
fn_for_each_confined(label, profile,
|
||||||
|
aa_audit_file(profile, &nullperms, op, request, *name,
|
||||||
|
NULL, NULL, cond->uid, info, error));
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -163,10 +193,11 @@ static u32 map_old_perms(u32 old)
|
||||||
{
|
{
|
||||||
u32 new = old & 0xf;
|
u32 new = old & 0xf;
|
||||||
if (old & MAY_READ)
|
if (old & MAY_READ)
|
||||||
new |= AA_MAY_META_READ;
|
new |= AA_MAY_GETATTR | AA_MAY_OPEN;
|
||||||
if (old & MAY_WRITE)
|
if (old & MAY_WRITE)
|
||||||
new |= AA_MAY_META_WRITE | AA_MAY_CREATE | AA_MAY_DELETE |
|
new |= AA_MAY_SETATTR | AA_MAY_CREATE | AA_MAY_DELETE |
|
||||||
AA_MAY_CHMOD | AA_MAY_CHOWN;
|
AA_MAY_CHMOD | AA_MAY_CHOWN | AA_MAY_OPEN |
|
||||||
|
AA_MAY_DELETE;
|
||||||
if (old & 0x10)
|
if (old & 0x10)
|
||||||
new |= AA_MAY_LINK;
|
new |= AA_MAY_LINK;
|
||||||
/* the old mapping lock and link_subset flags where overlaid
|
/* the old mapping lock and link_subset flags where overlaid
|
||||||
|
@ -181,7 +212,7 @@ static u32 map_old_perms(u32 old)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* compute_perms - convert dfa compressed perms to internal perms
|
* aa_compute_fperms - convert dfa compressed perms to internal perms
|
||||||
* @dfa: dfa to compute perms for (NOT NULL)
|
* @dfa: dfa to compute perms for (NOT NULL)
|
||||||
* @state: state in dfa
|
* @state: state in dfa
|
||||||
* @cond: conditions to consider (NOT NULL)
|
* @cond: conditions to consider (NOT NULL)
|
||||||
|
@ -191,17 +222,21 @@ static u32 map_old_perms(u32 old)
|
||||||
*
|
*
|
||||||
* Returns: computed permission set
|
* Returns: computed permission set
|
||||||
*/
|
*/
|
||||||
static struct file_perms compute_perms(struct aa_dfa *dfa, unsigned int state,
|
struct aa_perms aa_compute_fperms(struct aa_dfa *dfa, unsigned int state,
|
||||||
struct path_cond *cond)
|
struct path_cond *cond)
|
||||||
{
|
{
|
||||||
struct file_perms perms;
|
struct aa_perms perms;
|
||||||
|
|
||||||
/* FIXME: change over to new dfa format
|
/* FIXME: change over to new dfa format
|
||||||
* currently file perms are encoded in the dfa, new format
|
* currently file perms are encoded in the dfa, new format
|
||||||
* splits the permissions from the dfa. This mapping can be
|
* splits the permissions from the dfa. This mapping can be
|
||||||
* done at profile load
|
* done at profile load
|
||||||
*/
|
*/
|
||||||
perms.kill = 0;
|
perms.deny = 0;
|
||||||
|
perms.kill = perms.stop = 0;
|
||||||
|
perms.complain = perms.cond = 0;
|
||||||
|
perms.hide = 0;
|
||||||
|
perms.prompt = 0;
|
||||||
|
|
||||||
if (uid_eq(current_fsuid(), cond->uid)) {
|
if (uid_eq(current_fsuid(), cond->uid)) {
|
||||||
perms.allow = map_old_perms(dfa_user_allow(dfa, state));
|
perms.allow = map_old_perms(dfa_user_allow(dfa, state));
|
||||||
|
@ -214,7 +249,7 @@ static struct file_perms compute_perms(struct aa_dfa *dfa, unsigned int state,
|
||||||
perms.quiet = map_old_perms(dfa_other_quiet(dfa, state));
|
perms.quiet = map_old_perms(dfa_other_quiet(dfa, state));
|
||||||
perms.xindex = dfa_other_xindex(dfa, state);
|
perms.xindex = dfa_other_xindex(dfa, state);
|
||||||
}
|
}
|
||||||
perms.allow |= AA_MAY_META_READ;
|
perms.allow |= AA_MAY_GETATTR;
|
||||||
|
|
||||||
/* change_profile wasn't determined by ownership in old mapping */
|
/* change_profile wasn't determined by ownership in old mapping */
|
||||||
if (ACCEPT_TABLE(dfa)[state] & 0x80000000)
|
if (ACCEPT_TABLE(dfa)[state] & 0x80000000)
|
||||||
|
@ -237,37 +272,56 @@ static struct file_perms compute_perms(struct aa_dfa *dfa, unsigned int state,
|
||||||
*/
|
*/
|
||||||
unsigned int aa_str_perms(struct aa_dfa *dfa, unsigned int start,
|
unsigned int aa_str_perms(struct aa_dfa *dfa, unsigned int start,
|
||||||
const char *name, struct path_cond *cond,
|
const char *name, struct path_cond *cond,
|
||||||
struct file_perms *perms)
|
struct aa_perms *perms)
|
||||||
{
|
{
|
||||||
unsigned int state;
|
unsigned int state;
|
||||||
if (!dfa) {
|
|
||||||
*perms = nullperms;
|
|
||||||
return DFA_NOMATCH;
|
|
||||||
}
|
|
||||||
|
|
||||||
state = aa_dfa_match(dfa, start, name);
|
state = aa_dfa_match(dfa, start, name);
|
||||||
*perms = compute_perms(dfa, state, cond);
|
*perms = aa_compute_fperms(dfa, state, cond);
|
||||||
|
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
int __aa_path_perm(const char *op, struct aa_profile *profile, const char *name,
|
||||||
* is_deleted - test if a file has been completely unlinked
|
u32 request, struct path_cond *cond, int flags,
|
||||||
* @dentry: dentry of file to test for deletion (NOT NULL)
|
struct aa_perms *perms)
|
||||||
*
|
|
||||||
* Returns: %1 if deleted else %0
|
|
||||||
*/
|
|
||||||
static inline bool is_deleted(struct dentry *dentry)
|
|
||||||
{
|
{
|
||||||
if (d_unlinked(dentry) && d_backing_inode(dentry)->i_nlink == 0)
|
int e = 0;
|
||||||
return 1;
|
|
||||||
return 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, NULL,
|
||||||
|
cond->uid, NULL, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static int profile_path_perm(const char *op, struct aa_profile *profile,
|
||||||
|
const struct path *path, char *buffer, u32 request,
|
||||||
|
struct path_cond *cond, int flags,
|
||||||
|
struct aa_perms *perms)
|
||||||
|
{
|
||||||
|
const char *name;
|
||||||
|
int error;
|
||||||
|
|
||||||
|
if (profile_unconfined(profile))
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
error = path_name(op, &profile->label, path,
|
||||||
|
flags | profile->path_flags, buffer, &name, cond,
|
||||||
|
request);
|
||||||
|
if (error)
|
||||||
|
return error;
|
||||||
|
return __aa_path_perm(op, profile, name, request, cond, flags,
|
||||||
|
perms);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* aa_path_perm - do permissions check & audit for @path
|
* aa_path_perm - do permissions check & audit for @path
|
||||||
* @op: operation being checked
|
* @op: operation being checked
|
||||||
* @profile: profile being enforced (NOT NULL)
|
* @label: profile being enforced (NOT NULL)
|
||||||
* @path: path to check permissions of (NOT NULL)
|
* @path: path to check permissions of (NOT NULL)
|
||||||
* @flags: any additional path flags beyond what the profile specifies
|
* @flags: any additional path flags beyond what the profile specifies
|
||||||
* @request: requested permissions
|
* @request: requested permissions
|
||||||
|
@ -275,35 +329,22 @@ static inline bool is_deleted(struct dentry *dentry)
|
||||||
*
|
*
|
||||||
* Returns: %0 else error if access denied or other error
|
* Returns: %0 else error if access denied or other error
|
||||||
*/
|
*/
|
||||||
int aa_path_perm(int op, struct aa_profile *profile, struct path *path,
|
int aa_path_perm(const char *op, struct aa_label *label,
|
||||||
int flags, u32 request, struct path_cond *cond)
|
const struct path *path, int flags, u32 request,
|
||||||
|
struct path_cond *cond)
|
||||||
{
|
{
|
||||||
|
struct aa_perms perms = {};
|
||||||
|
struct aa_profile *profile;
|
||||||
char *buffer = NULL;
|
char *buffer = NULL;
|
||||||
struct file_perms perms = {};
|
|
||||||
const char *name, *info = NULL;
|
|
||||||
int error;
|
int error;
|
||||||
|
|
||||||
flags |= profile->path_flags | (S_ISDIR(cond->mode) ? PATH_IS_DIR : 0);
|
flags |= PATH_DELEGATE_DELETED | (S_ISDIR(cond->mode) ? PATH_IS_DIR : 0);
|
||||||
error = aa_path_name(path, flags, &buffer, &name, &info);
|
get_buffers(buffer);
|
||||||
if (error) {
|
error = fn_for_each_confined(label, profile,
|
||||||
if (error == -ENOENT && is_deleted(path->dentry)) {
|
profile_path_perm(op, profile, path, buffer, request,
|
||||||
/* Access to open files that are deleted are
|
cond, flags, &perms));
|
||||||
* 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);
|
|
||||||
|
|
||||||
|
put_buffers(buffer);
|
||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -327,65 +368,40 @@ static inline bool xindex_is_subset(u32 link, u32 target)
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
static int profile_path_link(struct aa_profile *profile,
|
||||||
* aa_path_link - Handle hard link permission check
|
const struct path *link, char *buffer,
|
||||||
* @profile: the profile being enforced (NOT NULL)
|
const struct path *target, char *buffer2,
|
||||||
* @old_dentry: the target dentry (NOT NULL)
|
struct path_cond *cond)
|
||||||
* @new_dir: directory the new link will be created in (NOT NULL)
|
|
||||||
* @new_dentry: the link being created (NOT NULL)
|
|
||||||
*
|
|
||||||
* Handle the permission test for a link & target pair. Permission
|
|
||||||
* is encoded as a pair where the link permission is determined
|
|
||||||
* first, and if allowed, the target is tested. The target test
|
|
||||||
* is done from the point of the link match (not start of DFA)
|
|
||||||
* making the target permission dependent on the link permission match.
|
|
||||||
*
|
|
||||||
* The subset test if required forces that permissions granted
|
|
||||||
* on link are a subset of the permission granted to target.
|
|
||||||
*
|
|
||||||
* Returns: %0 if allowed else error
|
|
||||||
*/
|
|
||||||
int aa_path_link(struct aa_profile *profile, struct dentry *old_dentry,
|
|
||||||
struct path *new_dir, struct dentry *new_dentry)
|
|
||||||
{
|
{
|
||||||
struct path link = { new_dir->mnt, new_dentry };
|
const char *lname, *tname = NULL;
|
||||||
struct path target = { new_dir->mnt, old_dentry };
|
struct aa_perms lperms = {}, perms;
|
||||||
struct path_cond cond = {
|
const char *info = NULL;
|
||||||
d_backing_inode(old_dentry)->i_uid,
|
|
||||||
d_backing_inode(old_dentry)->i_mode
|
|
||||||
};
|
|
||||||
char *buffer = NULL, *buffer2 = NULL;
|
|
||||||
const char *lname, *tname = NULL, *info = NULL;
|
|
||||||
struct file_perms lperms, perms;
|
|
||||||
u32 request = AA_MAY_LINK;
|
u32 request = AA_MAY_LINK;
|
||||||
unsigned int state;
|
unsigned int state;
|
||||||
int error;
|
int error;
|
||||||
|
|
||||||
lperms = nullperms;
|
error = path_name(OP_LINK, &profile->label, link, profile->path_flags,
|
||||||
|
buffer, &lname, cond, AA_MAY_LINK);
|
||||||
/* buffer freed below, lname is pointer in buffer */
|
|
||||||
error = aa_path_name(&link, profile->path_flags, &buffer, &lname,
|
|
||||||
&info);
|
|
||||||
if (error)
|
if (error)
|
||||||
goto audit;
|
goto audit;
|
||||||
|
|
||||||
/* buffer2 freed below, tname is pointer in buffer2 */
|
/* buffer2 freed below, tname is pointer in buffer2 */
|
||||||
error = aa_path_name(&target, profile->path_flags, &buffer2, &tname,
|
error = path_name(OP_LINK, &profile->label, target, profile->path_flags,
|
||||||
&info);
|
buffer2, &tname, cond, AA_MAY_LINK);
|
||||||
if (error)
|
if (error)
|
||||||
goto audit;
|
goto audit;
|
||||||
|
|
||||||
error = -EACCES;
|
error = -EACCES;
|
||||||
/* aa_str_perms - handles the case of the dfa being NULL */
|
/* aa_str_perms - handles the case of the dfa being NULL */
|
||||||
state = aa_str_perms(profile->file.dfa, profile->file.start, lname,
|
state = aa_str_perms(profile->file.dfa, profile->file.start, lname,
|
||||||
&cond, &lperms);
|
cond, &lperms);
|
||||||
|
|
||||||
if (!(lperms.allow & AA_MAY_LINK))
|
if (!(lperms.allow & AA_MAY_LINK))
|
||||||
goto audit;
|
goto audit;
|
||||||
|
|
||||||
/* test to see if target can be paired with link */
|
/* test to see if target can be paired with link */
|
||||||
state = aa_dfa_null_transition(profile->file.dfa, state);
|
state = aa_dfa_null_transition(profile->file.dfa, state);
|
||||||
aa_str_perms(profile->file.dfa, state, tname, &cond, &perms);
|
aa_str_perms(profile->file.dfa, state, tname, cond, &perms);
|
||||||
|
|
||||||
/* force audit/quiet masks for link are stored in the second entry
|
/* force audit/quiet masks for link are stored in the second entry
|
||||||
* in the link pair.
|
* in the link pair.
|
||||||
|
@ -396,6 +412,7 @@ int aa_path_link(struct aa_profile *profile, struct dentry *old_dentry,
|
||||||
|
|
||||||
if (!(perms.allow & AA_MAY_LINK)) {
|
if (!(perms.allow & AA_MAY_LINK)) {
|
||||||
info = "target restricted";
|
info = "target restricted";
|
||||||
|
lperms = perms;
|
||||||
goto audit;
|
goto audit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -403,10 +420,10 @@ int aa_path_link(struct aa_profile *profile, struct dentry *old_dentry,
|
||||||
if (!(perms.allow & AA_LINK_SUBSET))
|
if (!(perms.allow & AA_LINK_SUBSET))
|
||||||
goto done_tests;
|
goto done_tests;
|
||||||
|
|
||||||
/* Do link perm subset test requiring allowed permission on link are a
|
/* Do link perm subset test requiring allowed permission on link are
|
||||||
* subset of the allowed permissions on target.
|
* a subset of the allowed permissions on target.
|
||||||
*/
|
*/
|
||||||
aa_str_perms(profile->file.dfa, profile->file.start, tname, &cond,
|
aa_str_perms(profile->file.dfa, profile->file.start, tname, cond,
|
||||||
&perms);
|
&perms);
|
||||||
|
|
||||||
/* AA_MAY_LINK is not considered in the subset test */
|
/* AA_MAY_LINK is not considered in the subset test */
|
||||||
|
@ -428,10 +445,140 @@ done_tests:
|
||||||
error = 0;
|
error = 0;
|
||||||
|
|
||||||
audit:
|
audit:
|
||||||
error = aa_audit_file(profile, &lperms, GFP_KERNEL, OP_LINK, request,
|
return aa_audit_file(profile, &lperms, OP_LINK, request, lname, tname,
|
||||||
lname, tname, cond.uid, info, error);
|
NULL, cond->uid, info, error);
|
||||||
kfree(buffer);
|
}
|
||||||
kfree(buffer2);
|
|
||||||
|
/**
|
||||||
|
* aa_path_link - Handle hard link permission check
|
||||||
|
* @label: the label being enforced (NOT NULL)
|
||||||
|
* @old_dentry: the target dentry (NOT NULL)
|
||||||
|
* @new_dir: directory the new link will be created in (NOT NULL)
|
||||||
|
* @new_dentry: the link being created (NOT NULL)
|
||||||
|
*
|
||||||
|
* Handle the permission test for a link & target pair. Permission
|
||||||
|
* is encoded as a pair where the link permission is determined
|
||||||
|
* first, and if allowed, the target is tested. The target test
|
||||||
|
* is done from the point of the link match (not start of DFA)
|
||||||
|
* making the target permission dependent on the link permission match.
|
||||||
|
*
|
||||||
|
* The subset test if required forces that permissions granted
|
||||||
|
* on link are a subset of the permission granted to target.
|
||||||
|
*
|
||||||
|
* Returns: %0 if allowed else error
|
||||||
|
*/
|
||||||
|
int aa_path_link(struct aa_label *label, struct dentry *old_dentry,
|
||||||
|
const struct path *new_dir, struct dentry *new_dentry)
|
||||||
|
{
|
||||||
|
struct path link = { new_dir->mnt, new_dentry };
|
||||||
|
struct path target = { new_dir->mnt, old_dentry };
|
||||||
|
struct path_cond cond = {
|
||||||
|
d_backing_inode(old_dentry)->i_uid,
|
||||||
|
d_backing_inode(old_dentry)->i_mode
|
||||||
|
};
|
||||||
|
char *buffer = NULL, *buffer2 = NULL;
|
||||||
|
struct aa_profile *profile;
|
||||||
|
int error;
|
||||||
|
|
||||||
|
/* buffer freed below, lname is pointer in buffer */
|
||||||
|
get_buffers(buffer, buffer2);
|
||||||
|
error = fn_for_each_confined(label, profile,
|
||||||
|
profile_path_link(profile, &link, buffer, &target,
|
||||||
|
buffer2, &cond));
|
||||||
|
put_buffers(buffer, buffer2);
|
||||||
|
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void update_file_ctx(struct aa_file_ctx *fctx, struct aa_label *label,
|
||||||
|
u32 request)
|
||||||
|
{
|
||||||
|
struct aa_label *l, *old;
|
||||||
|
|
||||||
|
/* update caching of label on file_ctx */
|
||||||
|
spin_lock(&fctx->lock);
|
||||||
|
old = rcu_dereference_protected(fctx->label,
|
||||||
|
spin_is_locked(&fctx->lock));
|
||||||
|
l = aa_label_merge(old, label, GFP_ATOMIC);
|
||||||
|
if (l) {
|
||||||
|
if (l != old) {
|
||||||
|
rcu_assign_pointer(fctx->label, l);
|
||||||
|
aa_put_label(old);
|
||||||
|
} else
|
||||||
|
aa_put_label(l);
|
||||||
|
fctx->allow |= request;
|
||||||
|
}
|
||||||
|
spin_unlock(&fctx->lock);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int __file_path_perm(const char *op, struct aa_label *label,
|
||||||
|
struct aa_label *flabel, struct file *file,
|
||||||
|
u32 request, u32 denied)
|
||||||
|
{
|
||||||
|
struct aa_profile *profile;
|
||||||
|
struct aa_perms perms = {};
|
||||||
|
struct path_cond cond = {
|
||||||
|
.uid = file_inode(file)->i_uid,
|
||||||
|
.mode = file_inode(file)->i_mode
|
||||||
|
};
|
||||||
|
char *buffer;
|
||||||
|
int flags, error;
|
||||||
|
|
||||||
|
/* revalidation due to label out of date. No revocation at this time */
|
||||||
|
if (!denied && aa_label_is_subset(flabel, label))
|
||||||
|
/* TODO: check for revocation on stale profiles */
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
flags = PATH_DELEGATE_DELETED | (S_ISDIR(cond.mode) ? PATH_IS_DIR : 0);
|
||||||
|
get_buffers(buffer);
|
||||||
|
|
||||||
|
/* check every profile in task label not in current cache */
|
||||||
|
error = fn_for_each_not_in_set(flabel, label, profile,
|
||||||
|
profile_path_perm(op, profile, &file->f_path, buffer,
|
||||||
|
request, &cond, flags, &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,
|
||||||
|
profile_path_perm(op, profile, &file->f_path,
|
||||||
|
buffer, request, &cond, flags,
|
||||||
|
&perms)));
|
||||||
|
}
|
||||||
|
if (!error)
|
||||||
|
update_file_ctx(file_ctx(file), label, request);
|
||||||
|
|
||||||
|
put_buffers(buffer);
|
||||||
|
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int __file_sock_perm(const char *op, struct aa_label *label,
|
||||||
|
struct aa_label *flabel, struct file *file,
|
||||||
|
u32 request, u32 denied)
|
||||||
|
{
|
||||||
|
struct socket *sock = (struct socket *) file->private_data;
|
||||||
|
int error;
|
||||||
|
|
||||||
|
AA_BUG(!sock);
|
||||||
|
|
||||||
|
/* revalidation due to label out of date. No revocation at this time */
|
||||||
|
if (!denied && aa_label_is_subset(flabel, label))
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
/* TODO: improve to skip profiles cached in flabel */
|
||||||
|
error = aa_sock_file_perm(label, op, request, sock);
|
||||||
|
if (denied) {
|
||||||
|
/* TODO: improve to skip profiles checked above */
|
||||||
|
/* check every profile in file label to is cached */
|
||||||
|
last_error(error, aa_sock_file_perm(flabel, op, request, sock));
|
||||||
|
}
|
||||||
|
if (!error)
|
||||||
|
update_file_ctx(file_ctx(file), label, request);
|
||||||
|
|
||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
|
@ -439,20 +586,117 @@ audit:
|
||||||
/**
|
/**
|
||||||
* aa_file_perm - do permission revalidation check & audit for @file
|
* aa_file_perm - do permission revalidation check & audit for @file
|
||||||
* @op: operation being checked
|
* @op: operation being checked
|
||||||
* @profile: profile being enforced (NOT NULL)
|
* @label: label being enforced (NOT NULL)
|
||||||
* @file: file to revalidate access permissions on (NOT NULL)
|
* @file: file to revalidate access permissions on (NOT NULL)
|
||||||
* @request: requested permissions
|
* @request: requested permissions
|
||||||
*
|
*
|
||||||
* Returns: %0 if access allowed else error
|
* Returns: %0 if access allowed else error
|
||||||
*/
|
*/
|
||||||
int aa_file_perm(int op, struct aa_profile *profile, struct file *file,
|
int aa_file_perm(const char *op, struct aa_label *label, struct file *file,
|
||||||
u32 request)
|
u32 request)
|
||||||
{
|
{
|
||||||
struct path_cond cond = {
|
struct aa_file_ctx *fctx;
|
||||||
.uid = file_inode(file)->i_uid,
|
struct aa_label *flabel;
|
||||||
.mode = file_inode(file)->i_mode
|
u32 denied;
|
||||||
};
|
int error = 0;
|
||||||
|
|
||||||
return aa_path_perm(op, profile, &file->f_path, PATH_DELEGATE_DELETED,
|
AA_BUG(!label);
|
||||||
request, &cond);
|
AA_BUG(!file);
|
||||||
|
|
||||||
|
fctx = file_ctx(file);
|
||||||
|
|
||||||
|
rcu_read_lock();
|
||||||
|
flabel = rcu_dereference(fctx->label);
|
||||||
|
AA_BUG(!flabel);
|
||||||
|
|
||||||
|
/* revalidate access, if task is unconfined, or the cached cred
|
||||||
|
* doesn't match or if the request is for more permissions than
|
||||||
|
* was granted.
|
||||||
|
*
|
||||||
|
* Note: the test for !unconfined(flabel) is to handle file
|
||||||
|
* delegation from unconfined tasks
|
||||||
|
*/
|
||||||
|
denied = request & ~fctx->allow;
|
||||||
|
if (unconfined(label) || 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);
|
||||||
}
|
}
|
||||||
|
|
114
security/apparmor/include/af_unix.h
Normal file
114
security/apparmor/include/af_unix.h
Normal file
|
@ -0,0 +1,114 @@
|
||||||
|
/*
|
||||||
|
* AppArmor security module
|
||||||
|
*
|
||||||
|
* This file contains AppArmor af_unix fine grained mediation
|
||||||
|
*
|
||||||
|
* Copyright 2014 Canonical Ltd.
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU General Public License as
|
||||||
|
* published by the Free Software Foundation, version 2 of the
|
||||||
|
* License.
|
||||||
|
*/
|
||||||
|
#ifndef __AA_AF_UNIX_H
|
||||||
|
|
||||||
|
#include <net/af_unix.h>
|
||||||
|
|
||||||
|
#include "label.h"
|
||||||
|
//#include "include/net.h"
|
||||||
|
|
||||||
|
#define unix_addr_len(L) ((L) - sizeof(sa_family_t))
|
||||||
|
#define unix_abstract_name_len(L) (unix_addr_len(L) - 1)
|
||||||
|
#define unix_abstract_len(U) (unix_abstract_name_len((U)->addr->len))
|
||||||
|
#define addr_unix_abstract_name(B) ((B)[0] == 0)
|
||||||
|
#define addr_unix_anonymous(U) (addr_unix_len(U) <= 0)
|
||||||
|
#define addr_unix_abstract(U) (!addr_unix_anonymous(U) && addr_unix_abstract_name((U)->addr))
|
||||||
|
//#define unix_addr_fs(U) (!unix_addr_anonymous(U) && !unix_addr_abstract_name((U)->addr))
|
||||||
|
|
||||||
|
#define unix_addr(A) ((struct sockaddr_un *)(A))
|
||||||
|
#define unix_addr_anon(A, L) ((A) && unix_addr_len(L) <= 0)
|
||||||
|
#define unix_addr_fs(A, L) (!unix_addr_anon(A, L) && !addr_unix_abstract_name(unix_addr(A)->sun_path))
|
||||||
|
|
||||||
|
#define UNIX_ANONYMOUS(U) (!unix_sk(U)->addr)
|
||||||
|
/* from net/unix/af_unix.c */
|
||||||
|
#define UNIX_ABSTRACT(U) (!UNIX_ANONYMOUS(U) && \
|
||||||
|
unix_sk(U)->addr->hash < UNIX_HASH_SIZE)
|
||||||
|
#define UNIX_FS(U) (!UNIX_ANONYMOUS(U) && unix_sk(U)->addr->name->sun_path[0])
|
||||||
|
#define unix_peer(sk) (unix_sk(sk)->peer)
|
||||||
|
#define unix_connected(S) ((S)->state == SS_CONNECTED)
|
||||||
|
|
||||||
|
static inline void print_unix_addr(struct sockaddr_un *A, int L)
|
||||||
|
{
|
||||||
|
char *buf = (A) ? (char *) &(A)->sun_path : NULL;
|
||||||
|
int len = unix_addr_len(L);
|
||||||
|
if (!buf || len <= 0)
|
||||||
|
printk(" <anonymous>");
|
||||||
|
else if (buf[0])
|
||||||
|
printk(" %s", buf);
|
||||||
|
else
|
||||||
|
/* abstract name len includes leading \0 */
|
||||||
|
printk(" %d @%.*s", len - 1, len - 1, buf+1);
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
printk("%s: %s: f %d, t %d, p %d", __FUNCTION__, \
|
||||||
|
#SK , \
|
||||||
|
*/
|
||||||
|
#define print_unix_sk(SK) \
|
||||||
|
do { \
|
||||||
|
struct unix_sock *u = unix_sk(SK); \
|
||||||
|
printk("%s: f %d, t %d, p %d", #SK , \
|
||||||
|
(SK)->sk_family, (SK)->sk_type, (SK)->sk_protocol); \
|
||||||
|
if (u->addr) \
|
||||||
|
print_unix_addr(u->addr->name, u->addr->len); \
|
||||||
|
else \
|
||||||
|
print_unix_addr(NULL, sizeof(sa_family_t)); \
|
||||||
|
/* printk("\n");*/ \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
#define print_sk(SK) \
|
||||||
|
do { \
|
||||||
|
if (!(SK)) { \
|
||||||
|
printk("%s: %s is null\n", __FUNCTION__, #SK); \
|
||||||
|
} else if ((SK)->sk_family == PF_UNIX) { \
|
||||||
|
print_unix_sk(SK); \
|
||||||
|
printk("\n"); \
|
||||||
|
} else { \
|
||||||
|
printk("%s: %s: family %d\n", __FUNCTION__, #SK , \
|
||||||
|
(SK)->sk_family); \
|
||||||
|
} \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
#define print_sock_addr(U) \
|
||||||
|
do { \
|
||||||
|
printk("%s:\n", __FUNCTION__); \
|
||||||
|
printk(" sock %s:", sock_ctx && sock_ctx->label ? aa_label_printk(sock_ctx->label, GFP_ATOMIC); : "<null>"); print_sk(sock); \
|
||||||
|
printk(" other %s:", other_ctx && other_ctx->label ? aa_label_printk(other_ctx->label, GFP_ATOMIC); : "<null>"); print_sk(other); \
|
||||||
|
printk(" new %s", new_ctx && new_ctx->label ? aa_label_printk(new_ctx->label, GFP_ATOMIC); : "<null>"); print_sk(newsk); \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
int aa_unix_peer_perm(struct aa_label *label, const char *op, u32 request,
|
||||||
|
struct sock *sk, struct sock *peer_sk,
|
||||||
|
struct aa_label *peer_label);
|
||||||
|
int aa_unix_label_sk_perm(struct aa_label *label, const char *op, u32 request,
|
||||||
|
struct sock *sk);
|
||||||
|
int aa_unix_sock_perm(const char *op, u32 request, struct socket *sock);
|
||||||
|
int aa_unix_create_perm(struct aa_label *label, int family, int type,
|
||||||
|
int protocol);
|
||||||
|
int aa_unix_bind_perm(struct socket *sock, struct sockaddr *address,
|
||||||
|
int addrlen);
|
||||||
|
int aa_unix_connect_perm(struct socket *sock, struct sockaddr *address,
|
||||||
|
int addrlen);
|
||||||
|
int aa_unix_listen_perm(struct socket *sock, int backlog);
|
||||||
|
int aa_unix_accept_perm(struct socket *sock, struct socket *newsock);
|
||||||
|
int aa_unix_msg_perm(const char *op, u32 request, struct socket *sock,
|
||||||
|
struct msghdr *msg, int size);
|
||||||
|
int aa_unix_opt_perm(const char *op, u32 request, struct socket *sock, int level,
|
||||||
|
int optname);
|
||||||
|
int aa_unix_file_perm(struct aa_label *label, const char *op, u32 request,
|
||||||
|
struct socket *sock);
|
||||||
|
|
||||||
|
#endif /* __AA_AF_UNIX_H */
|
|
@ -1,10 +1,10 @@
|
||||||
/*
|
/*
|
||||||
* AppArmor security module
|
* AppArmor security module
|
||||||
*
|
*
|
||||||
* This file contains AppArmor basic global and lib definitions
|
* This file contains AppArmor basic global
|
||||||
*
|
*
|
||||||
* Copyright (C) 1998-2008 Novell/SUSE
|
* Copyright (C) 1998-2008 Novell/SUSE
|
||||||
* Copyright 2009-2010 Canonical Ltd.
|
* Copyright 2009-2016 Canonical Ltd.
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or
|
* This program is free software; you can redistribute it and/or
|
||||||
* modify it under the terms of the GNU General Public License as
|
* modify it under the terms of the GNU General Public License as
|
||||||
|
@ -15,10 +15,9 @@
|
||||||
#ifndef __APPARMOR_H
|
#ifndef __APPARMOR_H
|
||||||
#define __APPARMOR_H
|
#define __APPARMOR_H
|
||||||
|
|
||||||
#include <linux/slab.h>
|
#include <linux/types.h>
|
||||||
#include <linux/fs.h>
|
|
||||||
|
|
||||||
#include "match.h"
|
#include "backport.h"
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Class of mediation types in the AppArmor policy db
|
* Class of mediation types in the AppArmor policy db
|
||||||
|
@ -30,91 +29,22 @@
|
||||||
#define AA_CLASS_NET 4
|
#define AA_CLASS_NET 4
|
||||||
#define AA_CLASS_RLIMITS 5
|
#define AA_CLASS_RLIMITS 5
|
||||||
#define AA_CLASS_DOMAIN 6
|
#define AA_CLASS_DOMAIN 6
|
||||||
|
#define AA_CLASS_MOUNT 7
|
||||||
|
#define AA_CLASS_PTRACE 9
|
||||||
|
#define AA_CLASS_SIGNAL 10
|
||||||
|
#define AA_CLASS_LABEL 16
|
||||||
|
|
||||||
#define AA_CLASS_LAST AA_CLASS_DOMAIN
|
#define AA_CLASS_LAST AA_CLASS_LABEL
|
||||||
|
|
||||||
/* Control parameters settable through module/boot flags */
|
/* Control parameters settable through module/boot flags */
|
||||||
extern enum audit_mode aa_g_audit;
|
extern enum audit_mode aa_g_audit;
|
||||||
extern bool aa_g_audit_header;
|
extern bool aa_g_audit_header;
|
||||||
extern bool aa_g_debug;
|
extern bool aa_g_debug;
|
||||||
|
extern bool aa_g_hash_policy;
|
||||||
extern bool aa_g_lock_policy;
|
extern bool aa_g_lock_policy;
|
||||||
extern bool aa_g_logsyscall;
|
extern bool aa_g_logsyscall;
|
||||||
extern bool aa_g_paranoid_load;
|
extern bool aa_g_paranoid_load;
|
||||||
extern unsigned int aa_g_path_max;
|
extern unsigned int aa_g_path_max;
|
||||||
|
extern bool aa_g_unconfined_init;
|
||||||
/*
|
|
||||||
* DEBUG remains global (no per profile flag) since it is mostly used in sysctl
|
|
||||||
* which is not related to profile accesses.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#define AA_DEBUG(fmt, args...) \
|
|
||||||
do { \
|
|
||||||
if (aa_g_debug && printk_ratelimit()) \
|
|
||||||
printk(KERN_DEBUG "AppArmor: " fmt, ##args); \
|
|
||||||
} while (0)
|
|
||||||
|
|
||||||
#define AA_ERROR(fmt, args...) \
|
|
||||||
do { \
|
|
||||||
if (printk_ratelimit()) \
|
|
||||||
printk(KERN_ERR "AppArmor: " fmt, ##args); \
|
|
||||||
} while (0)
|
|
||||||
|
|
||||||
/* Flag indicating whether initialization completed */
|
|
||||||
extern int apparmor_initialized __initdata;
|
|
||||||
|
|
||||||
/* fn's in lib */
|
|
||||||
char *aa_split_fqname(char *args, char **ns_name);
|
|
||||||
void aa_info_message(const char *str);
|
|
||||||
void *__aa_kvmalloc(size_t size, gfp_t flags);
|
|
||||||
|
|
||||||
static inline void *kvmalloc(size_t size)
|
|
||||||
{
|
|
||||||
return __aa_kvmalloc(size, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline void *kvzalloc(size_t size)
|
|
||||||
{
|
|
||||||
return __aa_kvmalloc(size, __GFP_ZERO);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* returns 0 if kref not incremented */
|
|
||||||
static inline int kref_get_not0(struct kref *kref)
|
|
||||||
{
|
|
||||||
return atomic_inc_not_zero(&kref->refcount);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* aa_strneq - compare null terminated @str to a non null terminated substring
|
|
||||||
* @str: a null terminated string
|
|
||||||
* @sub: a substring, not necessarily null terminated
|
|
||||||
* @len: length of @sub to compare
|
|
||||||
*
|
|
||||||
* The @str string must be full consumed for this to be considered a match
|
|
||||||
*/
|
|
||||||
static inline bool aa_strneq(const char *str, const char *sub, int len)
|
|
||||||
{
|
|
||||||
return !strncmp(str, sub, len) && !str[len];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* aa_dfa_null_transition - step to next state after null character
|
|
||||||
* @dfa: the dfa to match against
|
|
||||||
* @start: the state of the dfa to start matching in
|
|
||||||
*
|
|
||||||
* aa_dfa_null_transition transitions to the next state after a null
|
|
||||||
* character which is not used in standard matching and is only
|
|
||||||
* used to separate pairs.
|
|
||||||
*/
|
|
||||||
static inline unsigned int aa_dfa_null_transition(struct aa_dfa *dfa,
|
|
||||||
unsigned int start)
|
|
||||||
{
|
|
||||||
/* the null transition only needs the string's null terminator byte */
|
|
||||||
return aa_dfa_next(dfa, start, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline bool mediated_filesystem(struct dentry *dentry)
|
|
||||||
{
|
|
||||||
return !(dentry->d_sb->s_flags & MS_NOUSER);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif /* __APPARMOR_H */
|
#endif /* __APPARMOR_H */
|
||||||
|
|
|
@ -15,6 +15,8 @@
|
||||||
#ifndef __AA_APPARMORFS_H
|
#ifndef __AA_APPARMORFS_H
|
||||||
#define __AA_APPARMORFS_H
|
#define __AA_APPARMORFS_H
|
||||||
|
|
||||||
|
extern struct path aa_null;
|
||||||
|
|
||||||
enum aa_fs_type {
|
enum aa_fs_type {
|
||||||
AA_FS_TYPE_BOOLEAN,
|
AA_FS_TYPE_BOOLEAN,
|
||||||
AA_FS_TYPE_STRING,
|
AA_FS_TYPE_STRING,
|
||||||
|
@ -62,12 +64,16 @@ extern const struct file_operations aa_fs_seq_file_ops;
|
||||||
extern void __init aa_destroy_aafs(void);
|
extern void __init aa_destroy_aafs(void);
|
||||||
|
|
||||||
struct aa_profile;
|
struct aa_profile;
|
||||||
struct aa_namespace;
|
struct aa_ns;
|
||||||
|
|
||||||
enum aafs_ns_type {
|
enum aafs_ns_type {
|
||||||
AAFS_NS_DIR,
|
AAFS_NS_DIR,
|
||||||
AAFS_NS_PROFS,
|
AAFS_NS_PROFS,
|
||||||
AAFS_NS_NS,
|
AAFS_NS_NS,
|
||||||
|
AAFS_NS_RAW_DATA,
|
||||||
|
AAFS_NS_LOAD,
|
||||||
|
AAFS_NS_REPLACE,
|
||||||
|
AAFS_NS_REMOVE,
|
||||||
AAFS_NS_COUNT,
|
AAFS_NS_COUNT,
|
||||||
AAFS_NS_MAX_COUNT,
|
AAFS_NS_MAX_COUNT,
|
||||||
AAFS_NS_SIZE,
|
AAFS_NS_SIZE,
|
||||||
|
@ -83,12 +89,19 @@ enum aafs_prof_type {
|
||||||
AAFS_PROF_MODE,
|
AAFS_PROF_MODE,
|
||||||
AAFS_PROF_ATTACH,
|
AAFS_PROF_ATTACH,
|
||||||
AAFS_PROF_HASH,
|
AAFS_PROF_HASH,
|
||||||
|
AAFS_PROF_RAW_DATA,
|
||||||
|
AAFS_PROF_RAW_HASH,
|
||||||
|
AAFS_PROF_RAW_ABI,
|
||||||
AAFS_PROF_SIZEOF,
|
AAFS_PROF_SIZEOF,
|
||||||
};
|
};
|
||||||
|
|
||||||
#define ns_dir(X) ((X)->dents[AAFS_NS_DIR])
|
#define ns_dir(X) ((X)->dents[AAFS_NS_DIR])
|
||||||
#define ns_subns_dir(X) ((X)->dents[AAFS_NS_NS])
|
#define ns_subns_dir(X) ((X)->dents[AAFS_NS_NS])
|
||||||
#define ns_subprofs_dir(X) ((X)->dents[AAFS_NS_PROFS])
|
#define ns_subprofs_dir(X) ((X)->dents[AAFS_NS_PROFS])
|
||||||
|
#define ns_subdata_dir(X) ((X)->dents[AAFS_NS_RAW_DATA])
|
||||||
|
#define ns_subload(X) ((X)->dents[AAFS_NS_LOAD])
|
||||||
|
#define ns_subreplace(X) ((X)->dents[AAFS_NS_REPLACE])
|
||||||
|
#define ns_subremove(X) ((X)->dents[AAFS_NS_REMOVE])
|
||||||
|
|
||||||
#define prof_dir(X) ((X)->dents[AAFS_PROF_DIR])
|
#define prof_dir(X) ((X)->dents[AAFS_PROF_DIR])
|
||||||
#define prof_child_dir(X) ((X)->dents[AAFS_PROF_PROFS])
|
#define prof_child_dir(X) ((X)->dents[AAFS_PROF_PROFS])
|
||||||
|
@ -97,8 +110,8 @@ void __aa_fs_profile_rmdir(struct aa_profile *profile);
|
||||||
void __aa_fs_profile_migrate_dents(struct aa_profile *old,
|
void __aa_fs_profile_migrate_dents(struct aa_profile *old,
|
||||||
struct aa_profile *new);
|
struct aa_profile *new);
|
||||||
int __aa_fs_profile_mkdir(struct aa_profile *profile, struct dentry *parent);
|
int __aa_fs_profile_mkdir(struct aa_profile *profile, struct dentry *parent);
|
||||||
void __aa_fs_namespace_rmdir(struct aa_namespace *ns);
|
void __aa_fs_ns_rmdir(struct aa_ns *ns);
|
||||||
int __aa_fs_namespace_mkdir(struct aa_namespace *ns, struct dentry *parent,
|
int __aa_fs_ns_mkdir(struct aa_ns *ns, struct dentry *parent, const char *name,
|
||||||
const char *name);
|
struct dentry *dent);
|
||||||
|
|
||||||
#endif /* __AA_APPARMORFS_H */
|
#endif /* __AA_APPARMORFS_H */
|
||||||
|
|
|
@ -22,8 +22,7 @@
|
||||||
#include <linux/slab.h>
|
#include <linux/slab.h>
|
||||||
|
|
||||||
#include "file.h"
|
#include "file.h"
|
||||||
|
#include "label.h"
|
||||||
struct aa_profile;
|
|
||||||
|
|
||||||
extern const char *const audit_mode_names[];
|
extern const char *const audit_mode_names[];
|
||||||
#define AUDIT_MAX_INDEX 5
|
#define AUDIT_MAX_INDEX 5
|
||||||
|
@ -46,97 +45,140 @@ enum audit_type {
|
||||||
AUDIT_APPARMOR_AUTO
|
AUDIT_APPARMOR_AUTO
|
||||||
};
|
};
|
||||||
|
|
||||||
extern const char *const op_table[];
|
#define OP_NULL NULL
|
||||||
enum aa_ops {
|
|
||||||
OP_NULL,
|
|
||||||
|
|
||||||
OP_SYSCTL,
|
#define OP_SYSCTL "sysctl"
|
||||||
OP_CAPABLE,
|
#define OP_CAPABLE "capable"
|
||||||
|
|
||||||
OP_UNLINK,
|
#define OP_UNLINK "unlink"
|
||||||
OP_MKDIR,
|
#define OP_MKDIR "mkdir"
|
||||||
OP_RMDIR,
|
#define OP_RMDIR "rmdir"
|
||||||
OP_MKNOD,
|
#define OP_MKNOD "mknod"
|
||||||
OP_TRUNC,
|
#define OP_TRUNC "truncate"
|
||||||
OP_LINK,
|
#define OP_LINK "link"
|
||||||
OP_SYMLINK,
|
#define OP_SYMLINK "symlink"
|
||||||
OP_RENAME_SRC,
|
#define OP_RENAME_SRC "rename_src"
|
||||||
OP_RENAME_DEST,
|
#define OP_RENAME_DEST "rename_dest"
|
||||||
OP_CHMOD,
|
#define OP_CHMOD "chmod"
|
||||||
OP_CHOWN,
|
#define OP_CHOWN "chown"
|
||||||
OP_GETATTR,
|
#define OP_GETATTR "getattr"
|
||||||
OP_OPEN,
|
#define OP_OPEN "open"
|
||||||
|
|
||||||
OP_FPERM,
|
#define OP_FRECEIVE "file_receive"
|
||||||
OP_FLOCK,
|
#define OP_FPERM "file_perm"
|
||||||
OP_FMMAP,
|
#define OP_FLOCK "file_lock"
|
||||||
OP_FMPROT,
|
#define OP_FMMAP "file_mmap"
|
||||||
|
#define OP_FMPROT "file_mprotect"
|
||||||
|
#define OP_INHERIT "file_inherit"
|
||||||
|
|
||||||
OP_CREATE,
|
#define OP_PIVOTROOT "pivotroot"
|
||||||
OP_POST_CREATE,
|
#define OP_MOUNT "mount"
|
||||||
OP_BIND,
|
#define OP_UMOUNT "umount"
|
||||||
OP_CONNECT,
|
|
||||||
OP_LISTEN,
|
|
||||||
OP_ACCEPT,
|
|
||||||
OP_SENDMSG,
|
|
||||||
OP_RECVMSG,
|
|
||||||
OP_GETSOCKNAME,
|
|
||||||
OP_GETPEERNAME,
|
|
||||||
OP_GETSOCKOPT,
|
|
||||||
OP_SETSOCKOPT,
|
|
||||||
OP_SOCK_SHUTDOWN,
|
|
||||||
|
|
||||||
OP_PTRACE,
|
#define OP_CREATE "create"
|
||||||
|
#define OP_POST_CREATE "post_create"
|
||||||
|
#define OP_BIND "bind"
|
||||||
|
#define OP_CONNECT "connect"
|
||||||
|
#define OP_LISTEN "listen"
|
||||||
|
#define OP_ACCEPT "accept"
|
||||||
|
#define OP_SENDMSG "sendmsg"
|
||||||
|
#define OP_RECVMSG "recvmsg"
|
||||||
|
#define OP_GETSOCKNAME "getsockname"
|
||||||
|
#define OP_GETPEERNAME "getpeername"
|
||||||
|
#define OP_GETSOCKOPT "getsockopt"
|
||||||
|
#define OP_SETSOCKOPT "setsockopt"
|
||||||
|
#define OP_SHUTDOWN "socket_shutdown"
|
||||||
|
|
||||||
OP_EXEC,
|
#define OP_PTRACE "ptrace"
|
||||||
OP_CHANGE_HAT,
|
#define OP_SIGNAL "signal"
|
||||||
OP_CHANGE_PROFILE,
|
|
||||||
OP_CHANGE_ONEXEC,
|
|
||||||
|
|
||||||
OP_SETPROCATTR,
|
#define OP_EXEC "exec"
|
||||||
OP_SETRLIMIT,
|
|
||||||
|
|
||||||
OP_PROF_REPL,
|
#define OP_CHANGE_HAT "change_hat"
|
||||||
OP_PROF_LOAD,
|
#define OP_CHANGE_PROFILE "change_profile"
|
||||||
OP_PROF_RM,
|
#define OP_CHANGE_ONEXEC "change_onexec"
|
||||||
};
|
#define OP_STACK "stack"
|
||||||
|
#define OP_STACK_ONEXEC "stack_onexec"
|
||||||
|
|
||||||
|
#define OP_SETPROCATTR "setprocattr"
|
||||||
|
#define OP_SETRLIMIT "setrlimit"
|
||||||
|
|
||||||
|
#define OP_PROF_REPL "profile_replace"
|
||||||
|
#define OP_PROF_LOAD "profile_load"
|
||||||
|
#define OP_PROF_RM "profile_remove"
|
||||||
|
|
||||||
|
|
||||||
struct apparmor_audit_data {
|
struct apparmor_audit_data {
|
||||||
int error;
|
int error;
|
||||||
int op;
|
|
||||||
int type;
|
int type;
|
||||||
void *profile;
|
const char *op;
|
||||||
|
struct aa_label *label;
|
||||||
const char *name;
|
const char *name;
|
||||||
const char *info;
|
const char *info;
|
||||||
|
u32 request;
|
||||||
|
u32 denied;
|
||||||
union {
|
union {
|
||||||
void *target;
|
/* these entries require a custom callback fn */
|
||||||
struct {
|
struct {
|
||||||
|
struct aa_label *peer;
|
||||||
|
union {
|
||||||
|
struct {
|
||||||
|
kuid_t ouid;
|
||||||
|
const char *target;
|
||||||
|
} fs;
|
||||||
|
struct {
|
||||||
|
int type, protocol;
|
||||||
|
struct sock *peer_sk;
|
||||||
|
void *addr;
|
||||||
|
int addrlen;
|
||||||
|
} net;
|
||||||
|
int signal;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
struct {
|
||||||
|
struct aa_profile *profile;
|
||||||
|
const char *ns;
|
||||||
long pos;
|
long pos;
|
||||||
void *target;
|
|
||||||
} iface;
|
} iface;
|
||||||
struct {
|
struct {
|
||||||
int rlim;
|
int rlim;
|
||||||
unsigned long max;
|
unsigned long max;
|
||||||
} rlim;
|
} rlim;
|
||||||
struct {
|
struct {
|
||||||
const char *target;
|
const char *src_name;
|
||||||
u32 request;
|
const char *type;
|
||||||
u32 denied;
|
const char *trans;
|
||||||
kuid_t ouid;
|
const char *data;
|
||||||
} fs;
|
unsigned long flags;
|
||||||
|
} mnt;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
/* define a short hand for apparmor_audit_data structure */
|
/* macros for dealing with apparmor_audit_data structure */
|
||||||
#define aad apparmor_audit_data
|
#define aad(SA) (SA)->apparmor_audit_data
|
||||||
|
#define DEFINE_AUDIT_DATA(NAME, T, X) \
|
||||||
|
/* TODO: cleanup audit init so we don't need _aad = {0,} */ \
|
||||||
|
struct apparmor_audit_data NAME ## _aad = { .op = (X), }; \
|
||||||
|
struct common_audit_data NAME = \
|
||||||
|
{ \
|
||||||
|
.type = (T), \
|
||||||
|
.u.tsk = NULL, \
|
||||||
|
}; \
|
||||||
|
NAME.apparmor_audit_data = &(NAME ## _aad)
|
||||||
|
|
||||||
void aa_audit_msg(int type, struct common_audit_data *sa,
|
void aa_audit_msg(int type, struct common_audit_data *sa,
|
||||||
void (*cb) (struct audit_buffer *, void *));
|
void (*cb) (struct audit_buffer *, void *));
|
||||||
int aa_audit(int type, struct aa_profile *profile, gfp_t gfp,
|
int aa_audit(int type, struct aa_profile *profile, struct common_audit_data *sa,
|
||||||
struct common_audit_data *sa,
|
|
||||||
void (*cb) (struct audit_buffer *, void *));
|
void (*cb) (struct audit_buffer *, void *));
|
||||||
|
|
||||||
|
#define aa_audit_error(ERROR, SA, CB) \
|
||||||
|
({ \
|
||||||
|
aad((SA))->error = (ERROR); \
|
||||||
|
aa_audit_msg(AUDIT_APPARMOR_ERROR, (SA), (CB)); \
|
||||||
|
aad((SA))->error; \
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
static inline int complain_error(int error)
|
static inline int complain_error(int error)
|
||||||
{
|
{
|
||||||
if (error == -EPERM || error == -EACCES)
|
if (error == -EPERM || error == -EACCES)
|
||||||
|
|
20
security/apparmor/include/backport.h
Normal file
20
security/apparmor/include/backport.h
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
/*
|
||||||
|
* AppArmor security module
|
||||||
|
*
|
||||||
|
* This file contains AppArmor file mediation function definitions.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* This is a file of helper macros, defines for backporting newer versions
|
||||||
|
* of apparmor to older kernels
|
||||||
|
*/
|
||||||
|
#ifndef __AA_BACKPORT_H
|
||||||
|
#define __AA_BACKPORT_H
|
||||||
|
|
||||||
|
#endif /* __AA_BACKPORT_H */
|
|
@ -19,11 +19,12 @@
|
||||||
|
|
||||||
#include "apparmorfs.h"
|
#include "apparmorfs.h"
|
||||||
|
|
||||||
struct aa_profile;
|
struct aa_label;
|
||||||
|
|
||||||
/* aa_caps - confinement data for capabilities
|
/* aa_caps - confinement data for capabilities
|
||||||
* @allowed: capabilities mask
|
* @allowed: capabilities mask
|
||||||
* @audit: caps that are to be audited
|
* @audit: caps that are to be audited
|
||||||
|
* @denied: caps that are explicitly denied
|
||||||
* @quiet: caps that should not be audited
|
* @quiet: caps that should not be audited
|
||||||
* @kill: caps that when requested will result in the task being killed
|
* @kill: caps that when requested will result in the task being killed
|
||||||
* @extended: caps that are subject finer grained mediation
|
* @extended: caps that are subject finer grained mediation
|
||||||
|
@ -31,6 +32,7 @@ struct aa_profile;
|
||||||
struct aa_caps {
|
struct aa_caps {
|
||||||
kernel_cap_t allow;
|
kernel_cap_t allow;
|
||||||
kernel_cap_t audit;
|
kernel_cap_t audit;
|
||||||
|
kernel_cap_t denied;
|
||||||
kernel_cap_t quiet;
|
kernel_cap_t quiet;
|
||||||
kernel_cap_t kill;
|
kernel_cap_t kill;
|
||||||
kernel_cap_t extended;
|
kernel_cap_t extended;
|
||||||
|
@ -38,7 +40,7 @@ struct aa_caps {
|
||||||
|
|
||||||
extern struct aa_fs_entry aa_fs_entry_caps[];
|
extern struct aa_fs_entry aa_fs_entry_caps[];
|
||||||
|
|
||||||
int aa_capable(struct aa_profile *profile, int cap, int audit);
|
int aa_capable(struct aa_label *label, int cap, int audit);
|
||||||
|
|
||||||
static inline void aa_free_cap_rules(struct aa_caps *caps)
|
static inline void aa_free_cap_rules(struct aa_caps *caps)
|
||||||
{
|
{
|
||||||
|
|
|
@ -19,99 +19,79 @@
|
||||||
#include <linux/slab.h>
|
#include <linux/slab.h>
|
||||||
#include <linux/sched.h>
|
#include <linux/sched.h>
|
||||||
|
|
||||||
#include "policy.h"
|
#include "label.h"
|
||||||
|
#include "policy_ns.h"
|
||||||
|
|
||||||
#define cred_cxt(X) (X)->security
|
#define cred_ctx(X) (X)->security
|
||||||
#define current_cxt() cred_cxt(current_cred())
|
#define current_ctx() cred_ctx(current_cred())
|
||||||
|
|
||||||
/* struct aa_file_cxt - the AppArmor context the file was opened in
|
|
||||||
* @perms: the permission the file was opened with
|
|
||||||
*
|
|
||||||
* The file_cxt could currently be directly stored in file->f_security
|
|
||||||
* as the profile reference is now stored in the f_cred. However the
|
|
||||||
* cxt struct will expand in the future so we keep the struct.
|
|
||||||
*/
|
|
||||||
struct aa_file_cxt {
|
|
||||||
u16 allow;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* aa_alloc_file_context - allocate file_cxt
|
* struct aa_task_ctx - primary label for confined tasks
|
||||||
* @gfp: gfp flags for allocation
|
* @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
|
||||||
*
|
*
|
||||||
* Returns: file_cxt or NULL on failure
|
* Contains the task's current label (which could change due to
|
||||||
*/
|
|
||||||
static inline struct aa_file_cxt *aa_alloc_file_context(gfp_t gfp)
|
|
||||||
{
|
|
||||||
return kzalloc(sizeof(struct aa_file_cxt), gfp);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* aa_free_file_context - free a file_cxt
|
|
||||||
* @cxt: file_cxt to free (MAYBE_NULL)
|
|
||||||
*/
|
|
||||||
static inline void aa_free_file_context(struct aa_file_cxt *cxt)
|
|
||||||
{
|
|
||||||
if (cxt)
|
|
||||||
kzfree(cxt);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* struct aa_task_cxt - primary label for confined tasks
|
|
||||||
* @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
|
|
||||||
*
|
|
||||||
* Contains the task's current profile (which could change due to
|
|
||||||
* change_hat). Plus the hat_magic needed during change_hat.
|
* change_hat). Plus the hat_magic needed during change_hat.
|
||||||
*
|
*
|
||||||
* TODO: make so a task can be confined by a stack of contexts
|
* TODO: make so a task can be confined by a stack of contexts
|
||||||
*/
|
*/
|
||||||
struct aa_task_cxt {
|
struct aa_task_ctx {
|
||||||
struct aa_profile *profile;
|
struct aa_label *label;
|
||||||
struct aa_profile *onexec;
|
struct aa_label *onexec;
|
||||||
struct aa_profile *previous;
|
struct aa_label *previous;
|
||||||
u64 token;
|
u64 token;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct aa_task_cxt *aa_alloc_task_context(gfp_t flags);
|
struct aa_task_ctx *aa_alloc_task_context(gfp_t flags);
|
||||||
void aa_free_task_context(struct aa_task_cxt *cxt);
|
void aa_free_task_context(struct aa_task_ctx *ctx);
|
||||||
void aa_dup_task_context(struct aa_task_cxt *new,
|
void aa_dup_task_context(struct aa_task_ctx *new,
|
||||||
const struct aa_task_cxt *old);
|
const struct aa_task_ctx *old);
|
||||||
int aa_replace_current_profile(struct aa_profile *profile);
|
int aa_replace_current_label(struct aa_label *label);
|
||||||
int aa_set_current_onexec(struct aa_profile *profile);
|
int aa_set_current_onexec(struct aa_label *label, bool stack);
|
||||||
int aa_set_current_hat(struct aa_profile *profile, u64 token);
|
int aa_set_current_hat(struct aa_label *label, u64 token);
|
||||||
int aa_restore_previous_profile(u64 cookie);
|
int aa_restore_previous_label(u64 cookie);
|
||||||
struct aa_profile *aa_get_task_profile(struct task_struct *task);
|
struct aa_label *aa_get_task_label(struct task_struct *task);
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* aa_cred_profile - obtain cred's profiles
|
* aa_cred_raw_label - obtain cred's label
|
||||||
* @cred: cred to obtain profiles from (NOT NULL)
|
* @cred: cred to obtain label from (NOT NULL)
|
||||||
*
|
*
|
||||||
* Returns: confining profile
|
* Returns: confining label
|
||||||
*
|
*
|
||||||
* does NOT increment reference count
|
* does NOT increment reference count
|
||||||
*/
|
*/
|
||||||
static inline struct aa_profile *aa_cred_profile(const struct cred *cred)
|
static inline struct aa_label *aa_cred_raw_label(const struct cred *cred)
|
||||||
{
|
{
|
||||||
struct aa_task_cxt *cxt = cred_cxt(cred);
|
struct aa_task_ctx *ctx = cred_ctx(cred);
|
||||||
BUG_ON(!cxt || !cxt->profile);
|
BUG_ON(!ctx || !ctx->label);
|
||||||
return cxt->profile;
|
return ctx->label;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* __aa_task_profile - retrieve another task's profile
|
* aa_get_newest_cred_label - obtain the newest version of the label on a cred
|
||||||
|
* @cred: cred to obtain label from (NOT NULL)
|
||||||
|
*
|
||||||
|
* Returns: newest version of confining label
|
||||||
|
*/
|
||||||
|
static inline struct aa_label *aa_get_newest_cred_label(const struct cred *cred)
|
||||||
|
{
|
||||||
|
return aa_get_newest_label(aa_cred_raw_label(cred));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* __aa_task_raw_label - retrieve another task's label
|
||||||
* @task: task to query (NOT NULL)
|
* @task: task to query (NOT NULL)
|
||||||
*
|
*
|
||||||
* Returns: @task's profile without incrementing its ref count
|
* Returns: @task's label without incrementing its ref count
|
||||||
*
|
*
|
||||||
* If @task != current needs to be called in RCU safe critical section
|
* If @task != current needs to be called in RCU safe critical section
|
||||||
*/
|
*/
|
||||||
static inline struct aa_profile *__aa_task_profile(struct task_struct *task)
|
static inline struct aa_label *__aa_task_raw_label(struct task_struct *task)
|
||||||
{
|
{
|
||||||
return aa_cred_profile(__task_cred(task));
|
return aa_cred_raw_label(__task_cred(task));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -122,57 +102,105 @@ static inline struct aa_profile *__aa_task_profile(struct task_struct *task)
|
||||||
*/
|
*/
|
||||||
static inline bool __aa_task_is_confined(struct task_struct *task)
|
static inline bool __aa_task_is_confined(struct task_struct *task)
|
||||||
{
|
{
|
||||||
return !unconfined(__aa_task_profile(task));
|
return !unconfined(__aa_task_raw_label(task));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* __aa_current_profile - find the current tasks confining profile
|
* aa_current_raw_label - find the current tasks confining label
|
||||||
*
|
*
|
||||||
* Returns: up to date confining profile or the ns unconfined profile (NOT NULL)
|
* Returns: up to date confining label or the ns unconfined label (NOT NULL)
|
||||||
*
|
*
|
||||||
* This fn will not update the tasks cred to the most up to date version
|
* This fn will not update the tasks cred to the most up to date version
|
||||||
* of the profile so it is safe to call when inside of locks.
|
* of the label so it is safe to call when inside of locks.
|
||||||
*/
|
*/
|
||||||
static inline struct aa_profile *__aa_current_profile(void)
|
static inline struct aa_label *aa_current_raw_label(void)
|
||||||
{
|
{
|
||||||
return aa_cred_profile(current_cred());
|
return aa_cred_raw_label(current_cred());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* aa_current_profile - find the current tasks confining profile and do updates
|
* aa_get_current_label - get the newest version of the current tasks label
|
||||||
*
|
*
|
||||||
* Returns: 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
|
||||||
* replaced. Not safe to call inside locks
|
*
|
||||||
|
* The returned reference must be put with aa_put_label()
|
||||||
*/
|
*/
|
||||||
static inline struct aa_profile *aa_current_profile(void)
|
static inline struct aa_label *aa_get_current_label(void)
|
||||||
{
|
{
|
||||||
const struct aa_task_cxt *cxt = current_cxt();
|
struct aa_label *l = aa_current_raw_label();
|
||||||
struct aa_profile *profile;
|
|
||||||
BUG_ON(!cxt || !cxt->profile);
|
|
||||||
|
|
||||||
if (PROFILE_INVALID(cxt->profile)) {
|
if (label_is_stale(l))
|
||||||
profile = aa_get_newest_profile(cxt->profile);
|
return aa_get_newest_label(l);
|
||||||
aa_replace_current_profile(profile);
|
return aa_get_label(l);
|
||||||
aa_put_profile(profile);
|
}
|
||||||
cxt = current_cxt();
|
|
||||||
|
/**
|
||||||
|
* aa_end_current_label - put a reference found with aa_begin_current_label
|
||||||
|
* @label: label reference to put
|
||||||
|
*
|
||||||
|
* Should only be used with a reference obtained with aa_begin_current_label
|
||||||
|
* and never used in situations where the task cred may be updated
|
||||||
|
*/
|
||||||
|
static inline void aa_end_current_label(struct aa_label *label)
|
||||||
|
{
|
||||||
|
if (label != aa_current_raw_label())
|
||||||
|
aa_put_label(label);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* aa_begin_current_label - find the current tasks confining label and update it
|
||||||
|
* @update: whether the current label can be updated
|
||||||
|
*
|
||||||
|
* Returns: up to date confining label or the ns unconfined label (NOT NULL)
|
||||||
|
*
|
||||||
|
* If @update is true this fn will update the tasks cred structure if the
|
||||||
|
* label has been replaced. Not safe to call inside locks
|
||||||
|
* else
|
||||||
|
* just return the up to date label
|
||||||
|
*
|
||||||
|
* The returned reference must be put with aa_end_current_label()
|
||||||
|
* This must NOT be used if the task cred could be updated within the
|
||||||
|
* critical section between aa_begin_current_label() .. aa_end_current_label()
|
||||||
|
*/
|
||||||
|
static inline struct aa_label *aa_begin_current_label(bool update)
|
||||||
|
{
|
||||||
|
struct aa_label *label = aa_current_raw_label();
|
||||||
|
|
||||||
|
if (label_is_stale(label)) {
|
||||||
|
label = aa_get_newest_label(label);
|
||||||
|
if (update && aa_replace_current_label(label) == 0)
|
||||||
|
/* task cred will keep the reference */
|
||||||
|
aa_put_label(label);
|
||||||
}
|
}
|
||||||
|
|
||||||
return cxt->profile;
|
return label;
|
||||||
|
}
|
||||||
|
|
||||||
|
#define NO_UPDATE false
|
||||||
|
#define DO_UPDATE true
|
||||||
|
|
||||||
|
static inline struct aa_ns *aa_get_current_ns(void)
|
||||||
|
{
|
||||||
|
struct aa_label *label = aa_begin_current_label(NO_UPDATE);
|
||||||
|
struct aa_ns *ns = aa_get_ns(labels_ns(label));
|
||||||
|
aa_end_current_label(label);
|
||||||
|
|
||||||
|
return ns;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* aa_clear_task_cxt_trans - clear transition tracking info from the cxt
|
* aa_clear_task_ctx_trans - clear transition tracking info from the ctx
|
||||||
* @cxt: task context to clear (NOT NULL)
|
* @ctx: task context to clear (NOT NULL)
|
||||||
*/
|
*/
|
||||||
static inline void aa_clear_task_cxt_trans(struct aa_task_cxt *cxt)
|
static inline void aa_clear_task_ctx_trans(struct aa_task_ctx *ctx)
|
||||||
{
|
{
|
||||||
aa_put_profile(cxt->previous);
|
aa_put_label(ctx->previous);
|
||||||
aa_put_profile(cxt->onexec);
|
aa_put_label(ctx->onexec);
|
||||||
cxt->previous = NULL;
|
ctx->previous = NULL;
|
||||||
cxt->onexec = NULL;
|
ctx->onexec = NULL;
|
||||||
cxt->token = 0;
|
ctx->token = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif /* __AA_CONTEXT_H */
|
#endif /* __AA_CONTEXT_H */
|
||||||
|
|
|
@ -18,9 +18,14 @@
|
||||||
|
|
||||||
#ifdef CONFIG_SECURITY_APPARMOR_HASH
|
#ifdef CONFIG_SECURITY_APPARMOR_HASH
|
||||||
unsigned int aa_hash_size(void);
|
unsigned int aa_hash_size(void);
|
||||||
|
char *aa_calc_hash(void *data, size_t len);
|
||||||
int aa_calc_profile_hash(struct aa_profile *profile, u32 version, void *start,
|
int aa_calc_profile_hash(struct aa_profile *profile, u32 version, void *start,
|
||||||
size_t len);
|
size_t len);
|
||||||
#else
|
#else
|
||||||
|
static inline char *aa_calc_hash(void *data, size_t len)
|
||||||
|
{
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
static inline int aa_calc_profile_hash(struct aa_profile *profile, u32 version,
|
static inline int aa_calc_profile_hash(struct aa_profile *profile, u32 version,
|
||||||
void *start, size_t len)
|
void *start, size_t len)
|
||||||
{
|
{
|
||||||
|
|
|
@ -15,6 +15,8 @@
|
||||||
#include <linux/binfmts.h>
|
#include <linux/binfmts.h>
|
||||||
#include <linux/types.h>
|
#include <linux/types.h>
|
||||||
|
|
||||||
|
#include "label.h"
|
||||||
|
|
||||||
#ifndef __AA_DOMAIN_H
|
#ifndef __AA_DOMAIN_H
|
||||||
#define __AA_DOMAIN_H
|
#define __AA_DOMAIN_H
|
||||||
|
|
||||||
|
@ -23,6 +25,9 @@ struct aa_domain {
|
||||||
char **table;
|
char **table;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct aa_label *x_table_lookup(struct aa_profile *profile, u32 xindex,
|
||||||
|
const char **name);
|
||||||
|
|
||||||
int apparmor_bprm_set_creds(struct linux_binprm *bprm);
|
int apparmor_bprm_set_creds(struct linux_binprm *bprm);
|
||||||
int apparmor_bprm_secureexec(struct linux_binprm *bprm);
|
int apparmor_bprm_secureexec(struct linux_binprm *bprm);
|
||||||
void apparmor_bprm_committing_creds(struct linux_binprm *bprm);
|
void apparmor_bprm_committing_creds(struct linux_binprm *bprm);
|
||||||
|
@ -30,7 +35,7 @@ void apparmor_bprm_committed_creds(struct linux_binprm *bprm);
|
||||||
|
|
||||||
void aa_free_domain_entries(struct aa_domain *domain);
|
void aa_free_domain_entries(struct aa_domain *domain);
|
||||||
int aa_change_hat(const char *hats[], int count, u64 token, bool permtest);
|
int aa_change_hat(const char *hats[], int count, u64 token, bool permtest);
|
||||||
int aa_change_profile(const char *ns_name, const char *name, bool onexec,
|
int aa_change_profile(const char *fqname, bool onexec, bool permtest,
|
||||||
bool permtest);
|
bool stack);
|
||||||
|
|
||||||
#endif /* __AA_DOMAIN_H */
|
#endif /* __AA_DOMAIN_H */
|
||||||
|
|
|
@ -15,38 +15,75 @@
|
||||||
#ifndef __AA_FILE_H
|
#ifndef __AA_FILE_H
|
||||||
#define __AA_FILE_H
|
#define __AA_FILE_H
|
||||||
|
|
||||||
|
#include <linux/spinlock.h>
|
||||||
|
|
||||||
#include "domain.h"
|
#include "domain.h"
|
||||||
#include "match.h"
|
#include "match.h"
|
||||||
|
#include "label.h"
|
||||||
|
#include "perms.h"
|
||||||
|
|
||||||
struct aa_profile;
|
struct aa_profile;
|
||||||
struct path;
|
struct path;
|
||||||
|
|
||||||
/*
|
#define mask_mode_t(X) (X & (MAY_EXEC | MAY_WRITE | MAY_READ | MAY_APPEND))
|
||||||
* We use MAY_EXEC, MAY_WRITE, MAY_READ, MAY_APPEND and the following flags
|
|
||||||
* for profile permissions
|
|
||||||
*/
|
|
||||||
#define AA_MAY_CREATE 0x0010
|
|
||||||
#define AA_MAY_DELETE 0x0020
|
|
||||||
#define AA_MAY_META_WRITE 0x0040
|
|
||||||
#define AA_MAY_META_READ 0x0080
|
|
||||||
|
|
||||||
#define AA_MAY_CHMOD 0x0100
|
|
||||||
#define AA_MAY_CHOWN 0x0200
|
|
||||||
#define AA_MAY_LOCK 0x0400
|
|
||||||
#define AA_EXEC_MMAP 0x0800
|
|
||||||
|
|
||||||
#define AA_MAY_LINK 0x1000
|
|
||||||
#define AA_LINK_SUBSET AA_MAY_LOCK /* overlaid */
|
|
||||||
#define AA_MAY_ONEXEC 0x40000000 /* exec allows onexec */
|
|
||||||
#define AA_MAY_CHANGE_PROFILE 0x80000000
|
|
||||||
#define AA_MAY_CHANGEHAT 0x80000000 /* ctrl auditing only */
|
|
||||||
|
|
||||||
#define AA_AUDIT_FILE_MASK (MAY_READ | MAY_WRITE | MAY_EXEC | MAY_APPEND |\
|
#define AA_AUDIT_FILE_MASK (MAY_READ | MAY_WRITE | MAY_EXEC | MAY_APPEND |\
|
||||||
AA_MAY_CREATE | AA_MAY_DELETE | \
|
AA_MAY_CREATE | AA_MAY_DELETE | \
|
||||||
AA_MAY_META_READ | AA_MAY_META_WRITE | \
|
AA_MAY_GETATTR | AA_MAY_SETATTR | \
|
||||||
AA_MAY_CHMOD | AA_MAY_CHOWN | AA_MAY_LOCK | \
|
AA_MAY_CHMOD | AA_MAY_CHOWN | AA_MAY_LOCK | \
|
||||||
AA_EXEC_MMAP | AA_MAY_LINK)
|
AA_EXEC_MMAP | AA_MAY_LINK)
|
||||||
|
|
||||||
|
#define file_ctx(X) ((struct aa_file_ctx *)(X)->f_security)
|
||||||
|
|
||||||
|
/* struct aa_file_ctx - the AppArmor context the file was opened in
|
||||||
|
* @lock: lock to update the ctx
|
||||||
|
* @label: label currently cached on the ctx
|
||||||
|
* @perms: the permission the file was opened with
|
||||||
|
*/
|
||||||
|
struct aa_file_ctx {
|
||||||
|
spinlock_t lock;
|
||||||
|
struct aa_label __rcu *label;
|
||||||
|
u32 allow;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* aa_alloc_file_ctx - allocate file_ctx
|
||||||
|
* @label: initial label of task creating the file
|
||||||
|
* @gfp: gfp flags for allocation
|
||||||
|
*
|
||||||
|
* Returns: file_ctx or NULL on failure
|
||||||
|
*/
|
||||||
|
static inline struct aa_file_ctx *aa_alloc_file_ctx(struct aa_label *label, gfp_t gfp)
|
||||||
|
{
|
||||||
|
struct aa_file_ctx *ctx;
|
||||||
|
|
||||||
|
ctx = kzalloc(sizeof(struct aa_file_ctx), gfp);
|
||||||
|
if (ctx) {
|
||||||
|
spin_lock_init(&ctx->lock);
|
||||||
|
rcu_assign_pointer(ctx->label, aa_get_label(label));
|
||||||
|
}
|
||||||
|
return ctx;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* aa_free_file_ctx - free a file_ctx
|
||||||
|
* @ctx: file_ctx to free (MAYBE_NULL)
|
||||||
|
*/
|
||||||
|
static inline void aa_free_file_ctx(struct aa_file_ctx *ctx)
|
||||||
|
{
|
||||||
|
if (ctx) {
|
||||||
|
aa_put_label(rcu_access_pointer(ctx->label));
|
||||||
|
kzfree(ctx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline struct aa_label *aa_get_file_label(struct aa_file_ctx *ctx)
|
||||||
|
{
|
||||||
|
return aa_get_label_rcu(&ctx->label);
|
||||||
|
}
|
||||||
|
|
||||||
|
#define inode_ctx(X) (X)->i_security
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* The xindex is broken into 3 parts
|
* The xindex is broken into 3 parts
|
||||||
* - index - an index into either the exec name table or the variable table
|
* - index - an index into either the exec name table or the variable table
|
||||||
|
@ -75,25 +112,6 @@ struct path_cond {
|
||||||
umode_t mode;
|
umode_t mode;
|
||||||
};
|
};
|
||||||
|
|
||||||
/* struct file_perms - file permission
|
|
||||||
* @allow: mask of permissions that are allowed
|
|
||||||
* @audit: mask of permissions to force an audit message for
|
|
||||||
* @quiet: mask of permissions to quiet audit messages for
|
|
||||||
* @kill: mask of permissions that when matched will kill the task
|
|
||||||
* @xindex: exec transition index if @allow contains MAY_EXEC
|
|
||||||
*
|
|
||||||
* The @audit and @queit mask should be mutually exclusive.
|
|
||||||
*/
|
|
||||||
struct file_perms {
|
|
||||||
u32 allow;
|
|
||||||
u32 audit;
|
|
||||||
u32 quiet;
|
|
||||||
u32 kill;
|
|
||||||
u16 xindex;
|
|
||||||
};
|
|
||||||
|
|
||||||
extern struct file_perms nullperms;
|
|
||||||
|
|
||||||
#define COMBINED_PERM_MASK(X) ((X).allow | (X).audit | (X).quiet | (X).kill)
|
#define COMBINED_PERM_MASK(X) ((X).allow | (X).audit | (X).quiet | (X).kill)
|
||||||
|
|
||||||
/* FIXME: split perms from dfa and match this to description
|
/* FIXME: split perms from dfa and match this to description
|
||||||
|
@ -144,9 +162,9 @@ static inline u16 dfa_map_xindex(u16 mask)
|
||||||
#define dfa_other_xindex(dfa, state) \
|
#define dfa_other_xindex(dfa, state) \
|
||||||
dfa_map_xindex((ACCEPT_TABLE(dfa)[state] >> 14) & 0x3fff)
|
dfa_map_xindex((ACCEPT_TABLE(dfa)[state] >> 14) & 0x3fff)
|
||||||
|
|
||||||
int aa_audit_file(struct aa_profile *profile, struct file_perms *perms,
|
int aa_audit_file(struct aa_profile *profile, struct aa_perms *perms,
|
||||||
gfp_t gfp, int op, u32 request, const char *name,
|
const char *op, u32 request, const char *name, const char *target, struct aa_label *tlabel,
|
||||||
const char *target, kuid_t ouid, const char *info, int error);
|
kuid_t ouid, const char *info, int error);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* struct aa_file_rules - components used for file rule permissions
|
* struct aa_file_rules - components used for file rule permissions
|
||||||
|
@ -167,19 +185,27 @@ struct aa_file_rules {
|
||||||
/* TODO: add delegate table */
|
/* TODO: add delegate table */
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct aa_perms aa_compute_fperms(struct aa_dfa *dfa, unsigned int state,
|
||||||
|
struct path_cond *cond);
|
||||||
unsigned int aa_str_perms(struct aa_dfa *dfa, unsigned int start,
|
unsigned int aa_str_perms(struct aa_dfa *dfa, unsigned int start,
|
||||||
const char *name, struct path_cond *cond,
|
const char *name, struct path_cond *cond,
|
||||||
struct file_perms *perms);
|
struct aa_perms *perms);
|
||||||
|
|
||||||
int aa_path_perm(int op, struct aa_profile *profile, struct path *path,
|
int __aa_path_perm(const char *op, struct aa_profile *profile,
|
||||||
int flags, u32 request, struct path_cond *cond);
|
const char *name, u32 request, struct path_cond *cond,
|
||||||
|
int flags, struct aa_perms *perms);
|
||||||
|
int aa_path_perm(const char *op, struct aa_label *label,
|
||||||
|
const 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);
|
const struct path *new_dir, struct dentry *new_dentry);
|
||||||
|
|
||||||
int aa_file_perm(int op, struct aa_profile *profile, struct file *file,
|
int aa_file_perm(const char *op, struct aa_label *label, struct file *file,
|
||||||
u32 request);
|
u32 request);
|
||||||
|
|
||||||
|
void aa_inherit_files(const struct cred *cred, struct files_struct *files);
|
||||||
|
|
||||||
static inline void aa_free_file_rules(struct aa_file_rules *rules)
|
static inline void aa_free_file_rules(struct aa_file_rules *rules)
|
||||||
{
|
{
|
||||||
aa_put_dfa(rules->dfa);
|
aa_put_dfa(rules->dfa);
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
* This file contains AppArmor ipc mediation function definitions.
|
* This file contains AppArmor ipc mediation function definitions.
|
||||||
*
|
*
|
||||||
* Copyright (C) 1998-2008 Novell/SUSE
|
* Copyright (C) 1998-2008 Novell/SUSE
|
||||||
* Copyright 2009-2010 Canonical Ltd.
|
* Copyright 2009-2013 Canonical Ltd.
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or
|
* This program is free software; you can redistribute it and/or
|
||||||
* modify it under the terms of the GNU General Public License as
|
* modify it under the terms of the GNU General Public License as
|
||||||
|
@ -19,10 +19,22 @@
|
||||||
|
|
||||||
struct aa_profile;
|
struct aa_profile;
|
||||||
|
|
||||||
int aa_may_ptrace(struct aa_profile *tracer, struct aa_profile *tracee,
|
#define AA_PTRACE_TRACE MAY_WRITE
|
||||||
unsigned int mode);
|
#define AA_PTRACE_READ MAY_READ
|
||||||
|
#define AA_MAY_BE_TRACED AA_MAY_APPEND
|
||||||
|
#define AA_MAY_BE_READ AA_MAY_CREATE
|
||||||
|
#define PTRACE_PERM_SHIFT 2
|
||||||
|
|
||||||
int aa_ptrace(struct task_struct *tracer, struct task_struct *tracee,
|
#define AA_PTRACE_PERM_MASK (AA_PTRACE_READ | AA_PTRACE_TRACE | \
|
||||||
unsigned int mode);
|
AA_MAY_BE_READ | AA_MAY_BE_TRACED)
|
||||||
|
#define AA_SIGNAL_PERM_MASK (MAY_READ | MAY_WRITE)
|
||||||
|
|
||||||
|
#define AA_FS_SIG_MASK "hup int quit ill trap abrt bus fpe kill usr1 " \
|
||||||
|
"segv usr2 pipe alrm term stkflt chld cont stop stp ttin ttou urg " \
|
||||||
|
"xcpu xfsz vtalrm prof winch io pwr sys emt lost"
|
||||||
|
|
||||||
|
int aa_may_ptrace(struct aa_label *tracer, struct aa_label *tracee,
|
||||||
|
u32 request);
|
||||||
|
int aa_may_signal(struct aa_label *sender, struct aa_label *target, int sig);
|
||||||
|
|
||||||
#endif /* __AA_IPC_H */
|
#endif /* __AA_IPC_H */
|
||||||
|
|
502
security/apparmor/include/label.h
Normal file
502
security/apparmor/include/label.h
Normal file
|
@ -0,0 +1,502 @@
|
||||||
|
/*
|
||||||
|
* AppArmor security module
|
||||||
|
*
|
||||||
|
* This file contains AppArmor label definitions
|
||||||
|
*
|
||||||
|
* Copyright 2013 Canonical Ltd.
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU General Public License as
|
||||||
|
* published by the Free Software Foundation, version 2 of the
|
||||||
|
* License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef __AA_LABEL_H
|
||||||
|
#define __AA_LABEL_H
|
||||||
|
|
||||||
|
#include <linux/atomic.h>
|
||||||
|
#include <linux/audit.h>
|
||||||
|
#include <linux/rbtree.h>
|
||||||
|
#include <linux/rcupdate.h>
|
||||||
|
|
||||||
|
#include "apparmor.h"
|
||||||
|
#include "lib.h"
|
||||||
|
|
||||||
|
struct aa_ns;
|
||||||
|
|
||||||
|
#define LOCAL_VEC_ENTRIES 8
|
||||||
|
#define DEFINE_VEC(T, V) \
|
||||||
|
struct aa_ ## T *(_ ## V ## _localtmp)[LOCAL_VEC_ENTRIES]; \
|
||||||
|
struct aa_ ## T **(V)
|
||||||
|
|
||||||
|
#define vec_setup(T, V, N, GFP) \
|
||||||
|
({ \
|
||||||
|
if ((N) <= LOCAL_VEC_ENTRIES) { \
|
||||||
|
typeof(N) i; \
|
||||||
|
(V) = (_ ## V ## _localtmp); \
|
||||||
|
for (i = 0; i < (N); i++) \
|
||||||
|
(V)[i] = NULL; \
|
||||||
|
} else \
|
||||||
|
(V) = kzalloc(sizeof(struct aa_ ## T *) * (N), (GFP)); \
|
||||||
|
(V) ? 0 : -ENOMEM; \
|
||||||
|
})
|
||||||
|
|
||||||
|
#define vec_cleanup(T, V, N) \
|
||||||
|
do { \
|
||||||
|
int i; \
|
||||||
|
for (i = 0; i < (N); i++) { \
|
||||||
|
if (!IS_ERR_OR_NULL((V)[i])) \
|
||||||
|
aa_put_ ## T ((V)[i]); \
|
||||||
|
} \
|
||||||
|
if ((V) != _ ## V ## _localtmp) \
|
||||||
|
kfree(V); \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
#define vec_last(VEC, SIZE) ((VEC)[(SIZE) - 1])
|
||||||
|
#define vec_ns(VEC, SIZE) (vec_last((VEC), (SIZE))->ns)
|
||||||
|
#define vec_labelset(VEC, SIZE) (&vec_ns((VEC), (SIZE))->labels)
|
||||||
|
#define cleanup_domain_vec(V, L) cleanup_label_vec((V), (L)->size)
|
||||||
|
|
||||||
|
struct aa_profile;
|
||||||
|
#define VEC_FLAG_TERMINATE 1
|
||||||
|
int aa_vec_unique(struct aa_profile **vec, int n, int flags);
|
||||||
|
struct aa_label *aa_vec_find_or_create_label(struct aa_profile **vec, int len,
|
||||||
|
gfp_t gfp);
|
||||||
|
#define aa_sort_and_merge_vec(N, V) \
|
||||||
|
aa_sort_and_merge_profiles((N), (struct aa_profile **)(V))
|
||||||
|
|
||||||
|
struct labelset_stats {
|
||||||
|
atomic_t sread;
|
||||||
|
atomic_t fread;
|
||||||
|
atomic_t msread;
|
||||||
|
atomic_t mfread;
|
||||||
|
|
||||||
|
atomic_t insert;
|
||||||
|
atomic_t existing;
|
||||||
|
atomic_t minsert;
|
||||||
|
atomic_t mexisting;
|
||||||
|
|
||||||
|
atomic_t stale; /* outstanding stale */
|
||||||
|
};
|
||||||
|
|
||||||
|
struct label_stats {
|
||||||
|
struct labelset_stats set_stats;
|
||||||
|
|
||||||
|
atomic_t allocated;
|
||||||
|
atomic_t failed;
|
||||||
|
atomic_t freed;
|
||||||
|
|
||||||
|
atomic_t printk_name_alloc;
|
||||||
|
atomic_t printk_name_fail;
|
||||||
|
atomic_t seq_print_name_alloc;
|
||||||
|
atomic_t seq_print_name_fail;
|
||||||
|
atomic_t audit_name_alloc;
|
||||||
|
atomic_t audit_name_fail;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
#ifdef AA_LABEL_STATS
|
||||||
|
#define labelstats_inc(X) atomic_inc(stats.(X))
|
||||||
|
#define labelstats_dec(X) atomic_dec(stats.(X))
|
||||||
|
#define labelsetstats_inc(LS, X) \
|
||||||
|
do { \
|
||||||
|
labelstats_inc(set_stats.##X); \
|
||||||
|
atomic_inc((LS)->stats.(X)); \
|
||||||
|
} while (0)
|
||||||
|
#define labelsetstats_dec(LS, X) \
|
||||||
|
do { \
|
||||||
|
labelstats_dec(set_stats.##X); \
|
||||||
|
atomic_dec((LS)->stats.(X)); \
|
||||||
|
} while (0)
|
||||||
|
#else
|
||||||
|
#define labelstats_inc(X)
|
||||||
|
#define labelstats_dec(X)
|
||||||
|
#define labelsetstats_inc(LS, X)
|
||||||
|
#define labelsetstats_dec(LS, X)
|
||||||
|
#endif
|
||||||
|
#define labelstats_init(X)
|
||||||
|
|
||||||
|
/* struct aa_labelset - set of labels for a namespace
|
||||||
|
*
|
||||||
|
* Labels are reference counted; aa_labelset does not contribute to label
|
||||||
|
* reference counts. Once a label's last refcount is put it is removed from
|
||||||
|
* the set.
|
||||||
|
*/
|
||||||
|
struct aa_labelset {
|
||||||
|
rwlock_t lock;
|
||||||
|
|
||||||
|
struct rb_root root;
|
||||||
|
|
||||||
|
/* stats */
|
||||||
|
#ifdef APPARMOR_LABEL_STATS
|
||||||
|
struct labelset_stats stats;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
#define __labelset_for_each(LS, N) \
|
||||||
|
for((N) = rb_first(&(LS)->root); (N); (N) = rb_next(N))
|
||||||
|
|
||||||
|
void aa_labelset_destroy(struct aa_labelset *ls);
|
||||||
|
void aa_labelset_init(struct aa_labelset *ls);
|
||||||
|
|
||||||
|
|
||||||
|
enum label_flags {
|
||||||
|
FLAG_HAT = 1, /* profile is a hat */
|
||||||
|
FLAG_UNCONFINED = 2, /* label unconfined only if all
|
||||||
|
* constituant profiles unconfined */
|
||||||
|
FLAG_NULL = 4, /* profile is null learning profile */
|
||||||
|
FLAG_IX_ON_NAME_ERROR = 8, /* fallback to ix on name lookup fail */
|
||||||
|
FLAG_IMMUTIBLE = 0x10, /* don't allow changes/replacement */
|
||||||
|
FLAG_USER_DEFINED = 0x20, /* user based profile - lower privs */
|
||||||
|
FLAG_NO_LIST_REF = 0x40, /* list doesn't keep profile ref */
|
||||||
|
FLAG_NS_COUNT = 0x80, /* carries NS ref count */
|
||||||
|
FLAG_IN_TREE = 0x100, /* label is in tree */
|
||||||
|
FLAG_PROFILE = 0x200, /* label is a profile */
|
||||||
|
FLAG_EXPLICIT = 0x400, /* explict static label */
|
||||||
|
FLAG_STALE = 0x800, /* replaced/removed */
|
||||||
|
FLAG_RENAMED = 0x1000, /* label has renaming in it */
|
||||||
|
FLAG_REVOKED = 0x2000, /* label has revocation in it */
|
||||||
|
|
||||||
|
/* These flags must correspond with PATH_flags */
|
||||||
|
/* TODO: add new path flags */
|
||||||
|
};
|
||||||
|
|
||||||
|
struct aa_label;
|
||||||
|
struct aa_proxy {
|
||||||
|
struct kref count;
|
||||||
|
struct aa_label __rcu *label;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct label_it {
|
||||||
|
int i, j;
|
||||||
|
};
|
||||||
|
|
||||||
|
/* struct aa_label - lazy labeling struct
|
||||||
|
* @count: ref count of active users
|
||||||
|
* @node: rbtree position
|
||||||
|
* @rcu: rcu callback struct
|
||||||
|
* @proxy: is set to the label that replaced this label
|
||||||
|
* @hname: text representation of the label (MAYBE_NULL)
|
||||||
|
* @flags: stale and other flags - values may change under label set lock
|
||||||
|
* @sid: sid that references this label
|
||||||
|
* @size: number of entries in @ent[]
|
||||||
|
* @ent: set of profiles for label, actual size determined by @size
|
||||||
|
*/
|
||||||
|
struct aa_label {
|
||||||
|
struct kref count;
|
||||||
|
struct rb_node node;
|
||||||
|
struct rcu_head rcu;
|
||||||
|
struct aa_proxy *proxy;
|
||||||
|
__counted char *hname;
|
||||||
|
long flags;
|
||||||
|
u32 sid;
|
||||||
|
int size;
|
||||||
|
struct aa_profile *vec[];
|
||||||
|
};
|
||||||
|
|
||||||
|
#define last_error(E, FN) \
|
||||||
|
do { \
|
||||||
|
int __subE = (FN); \
|
||||||
|
if (__subE) \
|
||||||
|
(E) = __subE; \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
#define label_isprofile(X) ((X)->flags & FLAG_PROFILE)
|
||||||
|
#define label_unconfined(X) ((X)->flags & FLAG_UNCONFINED)
|
||||||
|
#define unconfined(X) label_unconfined(X)
|
||||||
|
#define label_is_stale(X) ((X)->flags & FLAG_STALE)
|
||||||
|
#define __label_make_stale(X) do { \
|
||||||
|
labelsetstats_inc(labels_set(X), stale); \
|
||||||
|
((X)->flags |= FLAG_STALE); \
|
||||||
|
} while (0)
|
||||||
|
#define labels_ns(X) (vec_ns(&((X)->vec[0]), (X)->size))
|
||||||
|
#define labels_set(X) (&labels_ns(X)->labels)
|
||||||
|
#define labels_profile(X) ((X)->vec[(X)->size - 1])
|
||||||
|
|
||||||
|
|
||||||
|
int aa_label_next_confined(struct aa_label *l, int i);
|
||||||
|
|
||||||
|
/* for each profile in a label */
|
||||||
|
#define label_for_each_init(I) ((I).i = 0);
|
||||||
|
#define label_for_each_next(I) (++((I).i))
|
||||||
|
#define label_for_each_curr(I, L) ({ (L)->vec[(I).i] ; })
|
||||||
|
#define label_for_each(I, L, P) \
|
||||||
|
for ((I).i = 0; ((P) = (L)->vec[(I).i]); ++((I).i))
|
||||||
|
|
||||||
|
/* assumes break/goto ended label_for_each */
|
||||||
|
#define label_for_each_cont(I, L, P) \
|
||||||
|
for (++((I).i); ((P) = (L)->vec[(I).i]); ++((I).i))
|
||||||
|
|
||||||
|
#define next_comb(I, L1, L2) \
|
||||||
|
do { \
|
||||||
|
(I).j++; \
|
||||||
|
if ((I).j >= (L2)->size) { \
|
||||||
|
(I).i++; \
|
||||||
|
(I).j = 0; \
|
||||||
|
} \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
/* TODO: label_for_each_ns_comb */
|
||||||
|
|
||||||
|
/* for each combination of P1 in L1, and P2 in L2 */
|
||||||
|
#define label_for_each_comb(I, L1, L2, P1, P2) \
|
||||||
|
for ((I).i = (I).j = 0; \
|
||||||
|
((P1) = (L1)->vec[(I).i]) && ((P2) = (L2)->vec[(I).j]); \
|
||||||
|
(I) = next_comb(I, L1, L2))
|
||||||
|
|
||||||
|
#define fn_for_each_comb(L1, L2, P1, P2, FN) \
|
||||||
|
({ \
|
||||||
|
struct label_it i; \
|
||||||
|
int __E = 0; \
|
||||||
|
label_for_each_comb(i, (L1), (L2), (P1), (P2)) { \
|
||||||
|
last_error(__E, (FN)); \
|
||||||
|
} \
|
||||||
|
__E; \
|
||||||
|
})
|
||||||
|
|
||||||
|
/* internal cross check */
|
||||||
|
//fn_for_each_comb(L1, L2, P1, P2, xcheck(...));
|
||||||
|
|
||||||
|
/* external cross check */
|
||||||
|
// xcheck(fn_for_each_comb(L1, L2, ...),
|
||||||
|
// fn_for_each_comb(L2, L1, ...));
|
||||||
|
|
||||||
|
/* for each profile that is enforcing confinement in a label */
|
||||||
|
#define label_for_each_confined(I, L, P) \
|
||||||
|
for ((I).i = aa_label_next_confined((L), 0); \
|
||||||
|
((P) = (L)->vec[(I).i]); \
|
||||||
|
(I).i = aa_label_next_confined((L), (I).i + 1))
|
||||||
|
|
||||||
|
#define label_for_each_in_merge(I, A, B, P) \
|
||||||
|
for ((I).i = (I).j = 0; \
|
||||||
|
((P) = aa_label_next_in_merge(&(I), (A), (B))); \
|
||||||
|
)
|
||||||
|
|
||||||
|
#define label_for_each_not_in_set(I, SET, SUB, P) \
|
||||||
|
for ((I).i = (I).j = 0; \
|
||||||
|
((P) = __aa_label_next_not_in_set(&(I), (SET), (SUB))); \
|
||||||
|
)
|
||||||
|
|
||||||
|
#define next_in_ns(i, NS, L) \
|
||||||
|
({ \
|
||||||
|
typeof(i) ___i = (i); \
|
||||||
|
while ((L)->vec[___i] && (L)->vec[___i]->ns != (NS)) \
|
||||||
|
(___i)++; \
|
||||||
|
(___i); \
|
||||||
|
})
|
||||||
|
|
||||||
|
#define label_for_each_in_ns(I, NS, L, P) \
|
||||||
|
for ((I).i = next_in_ns(0, (NS), (L)); \
|
||||||
|
((P) = (L)->vec[(I).i]); \
|
||||||
|
(I).i = next_in_ns((I).i + 1, (NS), (L)))
|
||||||
|
|
||||||
|
#define fn_for_each_in_ns(L, P, FN) \
|
||||||
|
({ \
|
||||||
|
struct label_it __i; \
|
||||||
|
struct aa_ns *__ns = labels_ns(L); \
|
||||||
|
int __E = 0; \
|
||||||
|
label_for_each_in_ns(__i, __ns, (L), (P)) { \
|
||||||
|
last_error(__E, (FN)); \
|
||||||
|
} \
|
||||||
|
__E; \
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
#define fn_for_each_XXX(L, P, FN, ...) \
|
||||||
|
({ \
|
||||||
|
struct label_it i; \
|
||||||
|
int __E = 0; \
|
||||||
|
label_for_each ## __VA_ARGS__ (i, (L), (P)) { \
|
||||||
|
last_error(__E, (FN)); \
|
||||||
|
} \
|
||||||
|
__E; \
|
||||||
|
})
|
||||||
|
|
||||||
|
#define fn_for_each(L, P, FN) fn_for_each_XXX(L, P, FN)
|
||||||
|
#define fn_for_each_confined(L, P, FN) fn_for_each_XXX(L, P, FN, _confined)
|
||||||
|
|
||||||
|
#define fn_for_each2_XXX(L1, L2, P, FN, ...) \
|
||||||
|
({ \
|
||||||
|
struct label_it i; \
|
||||||
|
int __E = 0; \
|
||||||
|
label_for_each ## __VA_ARGS__(i, (L1), (L2), (P)) { \
|
||||||
|
last_error(__E, (FN)); \
|
||||||
|
} \
|
||||||
|
__E; \
|
||||||
|
})
|
||||||
|
|
||||||
|
#define fn_for_each_in_merge(L1, L2, P, FN) \
|
||||||
|
fn_for_each2_XXX((L1), (L2), P, FN, _in_merge)
|
||||||
|
#define fn_for_each_not_in_set(L1, L2, P, FN) \
|
||||||
|
fn_for_each2_XXX((L1), (L2), P, FN, _not_in_set)
|
||||||
|
|
||||||
|
#define LABEL_MEDIATES(L, C) \
|
||||||
|
({ \
|
||||||
|
struct aa_profile *profile; \
|
||||||
|
struct label_it i; \
|
||||||
|
int ret = 0; \
|
||||||
|
label_for_each(i, (L), profile) { \
|
||||||
|
if (PROFILE_MEDIATES(profile, (C))) { \
|
||||||
|
ret = 1; \
|
||||||
|
break; \
|
||||||
|
} \
|
||||||
|
} \
|
||||||
|
ret; \
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
void aa_labelset_destroy(struct aa_labelset *ls);
|
||||||
|
void aa_labelset_init(struct aa_labelset *ls);
|
||||||
|
void __aa_labelset_update_subtree(struct aa_ns *ns);
|
||||||
|
|
||||||
|
void aa_label_free(struct aa_label *label);
|
||||||
|
void aa_label_kref(struct kref *kref);
|
||||||
|
bool aa_label_init(struct aa_label *label, int size);
|
||||||
|
struct aa_label *aa_label_alloc(int size, struct aa_proxy *proxy, gfp_t gfp);
|
||||||
|
|
||||||
|
bool aa_label_is_subset(struct aa_label *set, struct aa_label *sub);
|
||||||
|
struct aa_profile *__aa_label_next_not_in_set(struct label_it *I,
|
||||||
|
struct aa_label *set,
|
||||||
|
struct aa_label *sub);
|
||||||
|
bool aa_label_remove(struct aa_label *label);
|
||||||
|
struct aa_label *aa_label_insert(struct aa_labelset *ls, struct aa_label *l);
|
||||||
|
bool aa_label_replace(struct aa_label *old, struct aa_label *new);
|
||||||
|
bool aa_label_make_newest(struct aa_labelset *ls, struct aa_label *old,
|
||||||
|
struct aa_label *new);
|
||||||
|
|
||||||
|
struct aa_label *aa_label_find(struct aa_label *l);
|
||||||
|
|
||||||
|
struct aa_profile *aa_label_next_in_merge(struct label_it *I,
|
||||||
|
struct aa_label *a,
|
||||||
|
struct aa_label *b);
|
||||||
|
struct aa_label *aa_label_find_merge(struct aa_label *a, struct aa_label *b);
|
||||||
|
struct aa_label *aa_label_merge(struct aa_label *a, struct aa_label *b,
|
||||||
|
gfp_t gfp);
|
||||||
|
|
||||||
|
|
||||||
|
bool aa_update_label_name(struct aa_ns *ns, struct aa_label *label, gfp_t gfp);
|
||||||
|
|
||||||
|
#define FLAGS_NONE 0
|
||||||
|
#define FLAG_SHOW_MODE 1
|
||||||
|
#define FLAG_VIEW_SUBNS 2
|
||||||
|
#define FLAG_HIDDEN_UNCONFINED 4
|
||||||
|
int aa_profile_snxprint(char *str, size_t size, struct aa_ns *ns,
|
||||||
|
struct aa_profile *profile, int flags);
|
||||||
|
int aa_label_snxprint(char *str, size_t size, struct aa_ns *ns,
|
||||||
|
struct aa_label *label, int flags);
|
||||||
|
int aa_label_asxprint(char **strp, struct aa_ns *ns, struct aa_label *label,
|
||||||
|
int flags, gfp_t gfp);
|
||||||
|
int aa_label_acntsxprint(char __counted **strp, struct aa_ns *ns,
|
||||||
|
struct aa_label *label, int flags, gfp_t gfp);
|
||||||
|
void aa_label_xaudit(struct audit_buffer *ab, struct aa_ns *ns,
|
||||||
|
struct aa_label *label, int flags, gfp_t gfp);
|
||||||
|
void aa_label_seq_xprint(struct seq_file *f, struct aa_ns *ns,
|
||||||
|
struct aa_label *label, int flags, gfp_t gfp);
|
||||||
|
void aa_label_xprintk(struct aa_ns *ns, struct aa_label *label, int flags,
|
||||||
|
gfp_t gfp);
|
||||||
|
void aa_label_audit(struct audit_buffer *ab, struct aa_label *label, gfp_t gfp);
|
||||||
|
void aa_label_seq_print(struct seq_file *f, struct aa_label *label, gfp_t gfp);
|
||||||
|
void aa_label_printk(struct aa_label *label, gfp_t gfp);
|
||||||
|
|
||||||
|
struct aa_label *aa_label_parse(struct aa_label *base, const char *str,
|
||||||
|
gfp_t gfp, bool create, bool force_stack);
|
||||||
|
|
||||||
|
|
||||||
|
struct aa_perms;
|
||||||
|
int aa_label_match(struct aa_profile *profile, struct aa_label *label,
|
||||||
|
unsigned int state, bool subns, u32 request,
|
||||||
|
struct aa_perms *perms);
|
||||||
|
|
||||||
|
|
||||||
|
static inline struct aa_label *aa_get_label(struct aa_label *l)
|
||||||
|
{
|
||||||
|
if (l)
|
||||||
|
kref_get(&(l->count));
|
||||||
|
|
||||||
|
return l;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline struct aa_label *aa_get_label_not0(struct aa_label *l)
|
||||||
|
{
|
||||||
|
if (l && kref_get_not0(&l->count))
|
||||||
|
return l;
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* aa_get_label_rcu - increment refcount on a label that can be replaced
|
||||||
|
* @l: pointer to label that can be replaced (NOT NULL)
|
||||||
|
*
|
||||||
|
* Returns: pointer to a refcounted label.
|
||||||
|
* else NULL if no label
|
||||||
|
*/
|
||||||
|
static inline struct aa_label *aa_get_label_rcu(struct aa_label __rcu **l)
|
||||||
|
{
|
||||||
|
struct aa_label *c;
|
||||||
|
|
||||||
|
rcu_read_lock();
|
||||||
|
do {
|
||||||
|
c = rcu_dereference(*l);
|
||||||
|
} while (c && !kref_get_not0(&c->count));
|
||||||
|
rcu_read_unlock();
|
||||||
|
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* aa_get_newest_label - find the newest version of @l
|
||||||
|
* @l: the label to check for newer versions of
|
||||||
|
*
|
||||||
|
* Returns: refcounted newest version of @l taking into account
|
||||||
|
* replacement, renames and removals
|
||||||
|
* return @l.
|
||||||
|
*/
|
||||||
|
static inline struct aa_label *aa_get_newest_label(struct aa_label *l)
|
||||||
|
{
|
||||||
|
if (!l)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
if (label_is_stale(l)) {
|
||||||
|
struct aa_label *tmp;
|
||||||
|
AA_BUG(!l->proxy);
|
||||||
|
AA_BUG(!l->proxy->label);
|
||||||
|
/* BUG: only way this can happen is @l ref count and its
|
||||||
|
* replacement count have gone to 0 and are on their way
|
||||||
|
* to destruction. ie. we have a refcounting error
|
||||||
|
*/
|
||||||
|
AA_BUG(!(tmp = aa_get_label_rcu(&l->proxy->label)));
|
||||||
|
return tmp;
|
||||||
|
}
|
||||||
|
|
||||||
|
return aa_get_label(l);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void aa_put_label(struct aa_label *l)
|
||||||
|
{
|
||||||
|
if (l)
|
||||||
|
kref_put(&l->count, aa_label_kref);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
struct aa_proxy *aa_alloc_proxy(struct aa_label *l, gfp_t gfp);
|
||||||
|
void aa_proxy_kref(struct kref *kref);
|
||||||
|
|
||||||
|
static inline struct aa_proxy *aa_get_proxy(struct aa_proxy *proxy)
|
||||||
|
{
|
||||||
|
if (proxy)
|
||||||
|
kref_get(&(proxy->count));
|
||||||
|
|
||||||
|
return proxy;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void aa_put_proxy(struct aa_proxy *proxy)
|
||||||
|
{
|
||||||
|
if (proxy)
|
||||||
|
kref_put(&proxy->count, aa_proxy_kref);
|
||||||
|
}
|
||||||
|
|
||||||
|
void __aa_proxy_redirect(struct aa_label *orig, struct aa_label *new);
|
||||||
|
|
||||||
|
#endif /* __AA_LABEL_H */
|
317
security/apparmor/include/lib.h
Normal file
317
security/apparmor/include/lib.h
Normal file
|
@ -0,0 +1,317 @@
|
||||||
|
/*
|
||||||
|
* AppArmor security module
|
||||||
|
*
|
||||||
|
* This file contains AppArmor lib definitions
|
||||||
|
*
|
||||||
|
* 2016 Canonical Ltd.
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU General Public License as
|
||||||
|
* published by the Free Software Foundation, version 2 of the
|
||||||
|
* License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef __AA_LIB_H
|
||||||
|
#define __AA_LIB_H
|
||||||
|
|
||||||
|
#include <linux/slab.h>
|
||||||
|
#include <linux/fs.h>
|
||||||
|
|
||||||
|
#include "match.h"
|
||||||
|
|
||||||
|
/* Provide our own test for whether a write lock is held for asserts
|
||||||
|
* this is because on none SMP systems write_can_lock will always
|
||||||
|
* resolve to true, which is what you want for code making decisions
|
||||||
|
* based on it, but wrong for asserts checking that the lock is held
|
||||||
|
*/
|
||||||
|
#ifdef CONFIG_SMP
|
||||||
|
#define write_is_locked(X) !write_can_lock(X)
|
||||||
|
#else
|
||||||
|
#define write_is_locked(X) (1)
|
||||||
|
#endif /* CONFIG_SMP */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* DEBUG remains global (no per profile flag) since it is mostly used in sysctl
|
||||||
|
* which is not related to profile accesses.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define DEBUG_ON (aa_g_debug && printk_ratelimit())
|
||||||
|
#define dbg_printk(__fmt, __args...) printk(KERN_DEBUG __fmt, ##__args)
|
||||||
|
#define AA_DEBUG(fmt, args...) \
|
||||||
|
do { \
|
||||||
|
if (DEBUG_ON) \
|
||||||
|
dbg_printk("AppArmor: " fmt, ##args); \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
#define AA_WARN(X) WARN((X), "APPARMOR WARN %s: %s\n", __FUNCTION__, #X)
|
||||||
|
|
||||||
|
#define AA_BUG(X, args...) AA_BUG_FMT((X), "" args )
|
||||||
|
#define AA_BUG_FMT(X, fmt, args...) \
|
||||||
|
WARN((X), "AppArmor WARN %s: (" #X "): " fmt, __FUNCTION__ , ##args )
|
||||||
|
|
||||||
|
#define AA_ERROR(fmt, args...) \
|
||||||
|
do { \
|
||||||
|
if (printk_ratelimit()) \
|
||||||
|
printk(KERN_ERR "AppArmor: " fmt, ##args); \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
/* Flag indicating whether initialization completed */
|
||||||
|
extern int apparmor_initialized;
|
||||||
|
|
||||||
|
/* fn's in lib */
|
||||||
|
char *aa_split_fqname(char *args, char **ns_name);
|
||||||
|
const char *aa_splitn_fqname(const char *fqname, size_t n, const char **ns_name,
|
||||||
|
size_t *ns_len);
|
||||||
|
void aa_info_message(const char *str);
|
||||||
|
void *__aa_kvmalloc(size_t size, gfp_t flags);
|
||||||
|
|
||||||
|
static inline void *kvmalloc(size_t size)
|
||||||
|
{
|
||||||
|
return __aa_kvmalloc(size, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void *kvzalloc(size_t size)
|
||||||
|
{
|
||||||
|
return __aa_kvmalloc(size, __GFP_ZERO);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* returns 0 if kref not incremented */
|
||||||
|
static inline int kref_get_not0(struct kref *kref)
|
||||||
|
{
|
||||||
|
return atomic_inc_not_zero(&kref->refcount);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* aa_strneq - compare null terminated @str to a non null terminated substring
|
||||||
|
* @str: a null terminated string
|
||||||
|
* @sub: a substring, not necessarily null terminated
|
||||||
|
* @len: length of @sub to compare
|
||||||
|
*
|
||||||
|
* The @str string must be full consumed for this to be considered a match
|
||||||
|
*/
|
||||||
|
static inline bool aa_strneq(const char *str, const char *sub, int len)
|
||||||
|
{
|
||||||
|
return !strncmp(str, sub, len) && !str[len];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* aa_dfa_null_transition - step to next state after null character
|
||||||
|
* @dfa: the dfa to match against
|
||||||
|
* @start: the state of the dfa to start matching in
|
||||||
|
*
|
||||||
|
* aa_dfa_null_transition transitions to the next state after a null
|
||||||
|
* character which is not used in standard matching and is only
|
||||||
|
* used to separate pairs.
|
||||||
|
*/
|
||||||
|
static inline unsigned int aa_dfa_null_transition(struct aa_dfa *dfa,
|
||||||
|
unsigned int start)
|
||||||
|
{
|
||||||
|
/* the null transition only needs the string's null terminator byte */
|
||||||
|
return aa_dfa_next(dfa, start, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline bool path_mediated_fs(struct dentry *dentry)
|
||||||
|
{
|
||||||
|
return !(dentry->d_sb->s_flags & MS_NOUSER);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
struct counted_str {
|
||||||
|
struct kref count;
|
||||||
|
char name[];
|
||||||
|
};
|
||||||
|
|
||||||
|
#define str_to_counted(str) \
|
||||||
|
((struct counted_str *)(str - offsetof(struct counted_str,name)))
|
||||||
|
|
||||||
|
#define __counted /* atm just a notation */
|
||||||
|
|
||||||
|
void aa_str_kref(struct kref *kref);
|
||||||
|
char *aa_str_alloc(int size, gfp_t gfp);
|
||||||
|
|
||||||
|
|
||||||
|
static inline __counted char *aa_get_str(__counted char *str)
|
||||||
|
{
|
||||||
|
if (str)
|
||||||
|
kref_get(&(str_to_counted(str)->count));
|
||||||
|
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void aa_put_str(__counted char *str)
|
||||||
|
{
|
||||||
|
if (str)
|
||||||
|
kref_put(&str_to_counted(str)->count, aa_str_kref);
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *aa_imode_name(umode_t mode);
|
||||||
|
|
||||||
|
|
||||||
|
/* struct aa_policy - common part of both namespaces and profiles
|
||||||
|
* @name: name of the object
|
||||||
|
* @hname - The hierarchical name, NOTE: is .name of struct counted_str
|
||||||
|
* @list: list policy object is on
|
||||||
|
* @profiles: head of the profiles list contained in the object
|
||||||
|
*/
|
||||||
|
struct aa_policy {
|
||||||
|
const char *name;
|
||||||
|
__counted char *hname;
|
||||||
|
struct list_head list;
|
||||||
|
struct list_head profiles;
|
||||||
|
};
|
||||||
|
|
||||||
|
#define aa_peer_name(peer) (peer)->base.hname
|
||||||
|
|
||||||
|
/**
|
||||||
|
* basename - find the last component of an hname
|
||||||
|
* @name: hname to find the base profile name component of (NOT NULL)
|
||||||
|
*
|
||||||
|
* Returns: the tail (base profile name) name component of an hname
|
||||||
|
*/
|
||||||
|
static inline const char *basename(const char *hname)
|
||||||
|
{
|
||||||
|
char *split;
|
||||||
|
hname = strim((char *)hname);
|
||||||
|
for (split = strstr(hname, "//"); split; split = strstr(hname, "//"))
|
||||||
|
hname = split + 2;
|
||||||
|
|
||||||
|
return hname;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* __policy_find - find a policy by @name on a policy list
|
||||||
|
* @head: list to search (NOT NULL)
|
||||||
|
* @name: name to search for (NOT NULL)
|
||||||
|
*
|
||||||
|
* Requires: rcu_read_lock be held
|
||||||
|
*
|
||||||
|
* Returns: unrefcounted policy that match @name or NULL if not found
|
||||||
|
*/
|
||||||
|
static inline struct aa_policy *__policy_find(struct list_head *head,
|
||||||
|
const char *name)
|
||||||
|
{
|
||||||
|
struct aa_policy *policy;
|
||||||
|
|
||||||
|
list_for_each_entry_rcu(policy, head, list) {
|
||||||
|
if (!strcmp(policy->name, name))
|
||||||
|
return policy;
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* __policy_strn_find - find a policy that's name matches @len chars of @str
|
||||||
|
* @head: list to search (NOT NULL)
|
||||||
|
* @str: string to search for (NOT NULL)
|
||||||
|
* @len: length of match required
|
||||||
|
*
|
||||||
|
* Requires: rcu_read_lock be held
|
||||||
|
*
|
||||||
|
* Returns: unrefcounted policy that match @str or NULL if not found
|
||||||
|
*
|
||||||
|
* if @len == strlen(@strlen) then this is equiv to __policy_find
|
||||||
|
* other wise it allows searching for policy by a partial match of name
|
||||||
|
*/
|
||||||
|
static inline struct aa_policy *__policy_strn_find(struct list_head *head,
|
||||||
|
const char *str, int len)
|
||||||
|
{
|
||||||
|
struct aa_policy *policy;
|
||||||
|
|
||||||
|
list_for_each_entry_rcu(policy, head, list) {
|
||||||
|
if (aa_strneq(policy->name, str, len))
|
||||||
|
return policy;
|
||||||
|
}
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool aa_policy_init(struct aa_policy *policy, const char *prefix,
|
||||||
|
const char *name, gfp_t gfp);
|
||||||
|
void aa_policy_destroy(struct aa_policy *policy);
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* fn_label_build - abstract out the build of a label transition
|
||||||
|
* @L: label the transition is being computed for
|
||||||
|
* @P: profile parameter derived from L by this macro, can be passed to FN
|
||||||
|
* @GFP: memory allocation type to use
|
||||||
|
* @FN: fn to call for each profile transition. @P is set to the profile
|
||||||
|
*
|
||||||
|
* Returns: new label on success
|
||||||
|
* ERR_PTR if build @FN fails
|
||||||
|
* NULL if label_build fails due to low memory conditions
|
||||||
|
*
|
||||||
|
* @FN must return a label or ERR_PTR on failure. NULL is not allowed
|
||||||
|
*/
|
||||||
|
#define fn_label_build(L, P, GFP, FN) \
|
||||||
|
({ \
|
||||||
|
__label__ __cleanup, __done; \
|
||||||
|
struct aa_label *__new_; \
|
||||||
|
\
|
||||||
|
if ((L)->size > 1) { \
|
||||||
|
/* TODO: add cache of transitions already done */ \
|
||||||
|
struct label_it __i; \
|
||||||
|
int __j, __k, __count; \
|
||||||
|
DEFINE_VEC(label, __lvec); \
|
||||||
|
DEFINE_VEC(profile, __pvec); \
|
||||||
|
if (vec_setup(label, __lvec, (L)->size, (GFP))) { \
|
||||||
|
__new_ = NULL; \
|
||||||
|
goto __done; \
|
||||||
|
} \
|
||||||
|
__j = 0; \
|
||||||
|
label_for_each(__i, (L), (P)) { \
|
||||||
|
__new_ = (FN); \
|
||||||
|
AA_BUG(!__new_); \
|
||||||
|
if (IS_ERR(__new_)) \
|
||||||
|
goto __cleanup; \
|
||||||
|
__lvec[__j++] = __new_; \
|
||||||
|
} \
|
||||||
|
for (__j = __count = 0; __j < (L)->size; __j++) \
|
||||||
|
__count += __lvec[__j]->size; \
|
||||||
|
if (!vec_setup(profile, __pvec, __count, (GFP))) { \
|
||||||
|
for (__j = __k = 0; __j < (L)->size; __j++) { \
|
||||||
|
label_for_each(__i, __lvec[__j], (P)) \
|
||||||
|
__pvec[__k++] = aa_get_profile(P); \
|
||||||
|
} \
|
||||||
|
__count -= aa_vec_unique(__pvec, __count, 0); \
|
||||||
|
if (__count > 1) { \
|
||||||
|
__new_ = aa_vec_find_or_create_label(__pvec,\
|
||||||
|
__count, (GFP)); \
|
||||||
|
/* only fails if out of Mem */ \
|
||||||
|
if (!__new_) \
|
||||||
|
__new_ = NULL; \
|
||||||
|
} else \
|
||||||
|
__new_ = aa_get_label(&__pvec[0]->label); \
|
||||||
|
vec_cleanup(profile, __pvec, __count); \
|
||||||
|
} else \
|
||||||
|
__new_ = NULL; \
|
||||||
|
__cleanup: \
|
||||||
|
vec_cleanup(label, __lvec, (L)->size); \
|
||||||
|
} else { \
|
||||||
|
(P) = labels_profile(L); \
|
||||||
|
__new_ = (FN); \
|
||||||
|
} \
|
||||||
|
__done: \
|
||||||
|
if (!__new_) \
|
||||||
|
AA_DEBUG("label build failed\n"); \
|
||||||
|
(__new_); \
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
#define __fn_build_in_ns(NS, P, NS_FN, OTHER_FN) \
|
||||||
|
({ \
|
||||||
|
struct aa_label *__new; \
|
||||||
|
if ((P)->ns != (NS)) \
|
||||||
|
__new = (OTHER_FN); \
|
||||||
|
else \
|
||||||
|
__new = (NS_FN); \
|
||||||
|
(__new); \
|
||||||
|
})
|
||||||
|
|
||||||
|
#define fn_label_build_in_ns(L, P, GFP, NS_FN, OTHER_FN) \
|
||||||
|
({ \
|
||||||
|
fn_label_build((L), (P), (GFP), \
|
||||||
|
__fn_build_in_ns(labels_ns(L), (P), (NS_FN), (OTHER_FN))); \
|
||||||
|
})
|
||||||
|
|
||||||
|
#endif /* __AA_LIB_H */
|
|
@ -62,6 +62,7 @@ struct table_set_header {
|
||||||
#define YYTD_ID_ACCEPT2 6
|
#define YYTD_ID_ACCEPT2 6
|
||||||
#define YYTD_ID_NXT 7
|
#define YYTD_ID_NXT 7
|
||||||
#define YYTD_ID_TSIZE 8
|
#define YYTD_ID_TSIZE 8
|
||||||
|
#define YYTD_ID_MAX 8
|
||||||
|
|
||||||
#define YYTD_DATA8 1
|
#define YYTD_DATA8 1
|
||||||
#define YYTD_DATA16 2
|
#define YYTD_DATA16 2
|
||||||
|
@ -99,6 +100,8 @@ struct aa_dfa {
|
||||||
struct table_header *tables[YYTD_ID_TSIZE];
|
struct table_header *tables[YYTD_ID_TSIZE];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
extern struct aa_dfa *nulldfa;
|
||||||
|
|
||||||
#define byte_to_byte(X) (X)
|
#define byte_to_byte(X) (X)
|
||||||
|
|
||||||
#define UNPACK_ARRAY(TABLE, BLOB, LEN, TYPE, NTOHX) \
|
#define UNPACK_ARRAY(TABLE, BLOB, LEN, TYPE, NTOHX) \
|
||||||
|
@ -116,6 +119,9 @@ static inline size_t table_size(size_t len, size_t el_size)
|
||||||
return ALIGN(sizeof(struct table_header) + len * el_size, 8);
|
return ALIGN(sizeof(struct table_header) + len * el_size, 8);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int aa_setup_dfa_engine(void);
|
||||||
|
void aa_teardown_dfa_engine(void);
|
||||||
|
|
||||||
struct aa_dfa *aa_dfa_unpack(void *blob, size_t size, int flags);
|
struct aa_dfa *aa_dfa_unpack(void *blob, size_t size, int flags);
|
||||||
unsigned int aa_dfa_match_len(struct aa_dfa *dfa, unsigned int start,
|
unsigned int aa_dfa_match_len(struct aa_dfa *dfa, unsigned int start,
|
||||||
const char *str, int len);
|
const char *str, int len);
|
||||||
|
@ -126,6 +132,21 @@ unsigned int aa_dfa_next(struct aa_dfa *dfa, unsigned int state,
|
||||||
|
|
||||||
void aa_dfa_free_kref(struct kref *kref);
|
void aa_dfa_free_kref(struct kref *kref);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* aa_get_dfa - increment refcount on dfa @p
|
||||||
|
* @dfa: dfa (MAYBE NULL)
|
||||||
|
*
|
||||||
|
* Returns: pointer to @dfa if @dfa is NULL will return NULL
|
||||||
|
* Requires: @dfa must be held with valid refcount when called
|
||||||
|
*/
|
||||||
|
static inline struct aa_dfa *aa_get_dfa(struct aa_dfa *dfa)
|
||||||
|
{
|
||||||
|
if (dfa)
|
||||||
|
kref_get(&(dfa->count));
|
||||||
|
|
||||||
|
return dfa;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* aa_put_dfa - put a dfa refcount
|
* aa_put_dfa - put a dfa refcount
|
||||||
* @dfa: dfa to put refcount (MAYBE NULL)
|
* @dfa: dfa to put refcount (MAYBE NULL)
|
||||||
|
|
54
security/apparmor/include/mount.h
Normal file
54
security/apparmor/include/mount.h
Normal 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, const struct path *path,
|
||||||
|
unsigned long flags, void *data);
|
||||||
|
|
||||||
|
int aa_bind_mount(struct aa_label *label, const struct path *path,
|
||||||
|
const char *old_name, unsigned long flags);
|
||||||
|
|
||||||
|
|
||||||
|
int aa_mount_change_type(struct aa_label *label, const struct path *path,
|
||||||
|
unsigned long flags);
|
||||||
|
|
||||||
|
int aa_move_mount(struct aa_label *label, const struct path *path,
|
||||||
|
const char *old_name);
|
||||||
|
|
||||||
|
int aa_new_mount(struct aa_label *label, const char *dev_name,
|
||||||
|
const 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, const struct path *old_path,
|
||||||
|
const struct path *new_path);
|
||||||
|
|
||||||
|
#endif /* __AA_MOUNT_H */
|
124
security/apparmor/include/net.h
Normal file
124
security/apparmor/include/net.h
Normal file
|
@ -0,0 +1,124 @@
|
||||||
|
/*
|
||||||
|
* AppArmor security module
|
||||||
|
*
|
||||||
|
* This file contains AppArmor network mediation definitions.
|
||||||
|
*
|
||||||
|
* Copyright (C) 1998-2008 Novell/SUSE
|
||||||
|
* Copyright 2009-2014 Canonical Ltd.
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU General Public License as
|
||||||
|
* published by the Free Software Foundation, version 2 of the
|
||||||
|
* License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef __AA_NET_H
|
||||||
|
#define __AA_NET_H
|
||||||
|
|
||||||
|
#include <net/sock.h>
|
||||||
|
#include <linux/path.h>
|
||||||
|
|
||||||
|
#include "apparmorfs.h"
|
||||||
|
#include "label.h"
|
||||||
|
#include "perms.h"
|
||||||
|
#include "policy.h"
|
||||||
|
|
||||||
|
#define AA_MAY_SEND AA_MAY_WRITE
|
||||||
|
#define AA_MAY_RECEIVE AA_MAY_READ
|
||||||
|
|
||||||
|
#define AA_MAY_SHUTDOWN AA_MAY_DELETE
|
||||||
|
|
||||||
|
#define AA_MAY_CONNECT AA_MAY_OPEN
|
||||||
|
#define AA_MAY_ACCEPT 0x00100000
|
||||||
|
|
||||||
|
#define AA_MAY_BIND 0x00200000
|
||||||
|
#define AA_MAY_LISTEN 0x00400000
|
||||||
|
|
||||||
|
#define AA_MAY_SETOPT 0x01000000
|
||||||
|
#define AA_MAY_GETOPT 0x02000000
|
||||||
|
|
||||||
|
#define NET_PERMS_MASK (AA_MAY_SEND | AA_MAY_RECEIVE | AA_MAY_CREATE | \
|
||||||
|
AA_MAY_SHUTDOWN | AA_MAY_BIND | AA_MAY_LISTEN | \
|
||||||
|
AA_MAY_CONNECT | AA_MAY_ACCEPT | AA_MAY_SETATTR | \
|
||||||
|
AA_MAY_GETATTR | AA_MAY_SETOPT | AA_MAY_GETOPT)
|
||||||
|
|
||||||
|
#define NET_FS_PERMS (AA_MAY_SEND | AA_MAY_RECEIVE | AA_MAY_CREATE | \
|
||||||
|
AA_MAY_SHUTDOWN | AA_MAY_CONNECT | AA_MAY_RENAME |\
|
||||||
|
AA_MAY_SETATTR | AA_MAY_GETATTR | AA_MAY_CHMOD | \
|
||||||
|
AA_MAY_CHOWN | AA_MAY_CHGRP | AA_MAY_LOCK | \
|
||||||
|
AA_MAY_MPROT)
|
||||||
|
|
||||||
|
#define NET_PEER_MASK (AA_MAY_SEND | AA_MAY_RECEIVE | AA_MAY_CONNECT | \
|
||||||
|
AA_MAY_ACCEPT)
|
||||||
|
struct aa_sk_ctx {
|
||||||
|
struct aa_label *label;
|
||||||
|
struct aa_label *peer;
|
||||||
|
struct path path;
|
||||||
|
};
|
||||||
|
|
||||||
|
#define SK_CTX(X) (X)->sk_security
|
||||||
|
#define SOCK_ctx(X) SOCK_INODE(X)->i_security
|
||||||
|
#define DEFINE_AUDIT_NET(NAME, OP, SK, F, T, P) \
|
||||||
|
struct lsm_network_audit NAME ## _net = { .sk = (SK), \
|
||||||
|
.family = (F)}; \
|
||||||
|
DEFINE_AUDIT_DATA(NAME, \
|
||||||
|
((SK) && (F) != AF_UNIX) ? LSM_AUDIT_DATA_NET : \
|
||||||
|
LSM_AUDIT_DATA_NONE, \
|
||||||
|
OP); \
|
||||||
|
NAME.u.net = &(NAME ## _net); \
|
||||||
|
aad(&NAME)->net.type = (T); \
|
||||||
|
aad(&NAME)->net.protocol = (P)
|
||||||
|
|
||||||
|
#define DEFINE_AUDIT_SK(NAME, OP, SK) \
|
||||||
|
DEFINE_AUDIT_NET(NAME, OP, SK, (SK)->sk_family, (SK)->sk_type, \
|
||||||
|
(SK)->sk_protocol)
|
||||||
|
|
||||||
|
/* struct aa_net - network confinement data
|
||||||
|
* @allowed: basic network families permissions
|
||||||
|
* @audit_network: which network permissions to force audit
|
||||||
|
* @quiet_network: which network permissions to quiet rejects
|
||||||
|
*/
|
||||||
|
struct aa_net {
|
||||||
|
u16 allow[AF_MAX];
|
||||||
|
u16 audit[AF_MAX];
|
||||||
|
u16 quiet[AF_MAX];
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
extern struct aa_fs_entry aa_fs_entry_network[];
|
||||||
|
|
||||||
|
void audit_net_cb(struct audit_buffer *ab, void *va);
|
||||||
|
int aa_profile_af_perm(struct aa_profile *profile, struct common_audit_data *sa,
|
||||||
|
u32 request, u16 family, int type);
|
||||||
|
static inline int aa_profile_af_sk_perm(struct aa_profile *profile,
|
||||||
|
struct common_audit_data *sa,
|
||||||
|
u32 request,
|
||||||
|
struct sock *sk)
|
||||||
|
{
|
||||||
|
return aa_profile_af_perm(profile, sa, request, sk->sk_family,
|
||||||
|
sk->sk_type);
|
||||||
|
}
|
||||||
|
|
||||||
|
int aa_sock_perm(const char *op, u32 request, struct socket *sock);
|
||||||
|
int aa_sock_create_perm(struct aa_label *label, int family, int type,
|
||||||
|
int protocol);
|
||||||
|
int aa_sock_bind_perm(struct socket *sock, struct sockaddr *address,
|
||||||
|
int addrlen);
|
||||||
|
int aa_sock_connect_perm(struct socket *sock, struct sockaddr *address,
|
||||||
|
int addrlen);
|
||||||
|
int aa_sock_listen_perm(struct socket *sock, int backlog);
|
||||||
|
int aa_sock_accept_perm(struct socket *sock, struct socket *newsock);
|
||||||
|
int aa_sock_msg_perm(const char *op, u32 request, struct socket *sock,
|
||||||
|
struct msghdr *msg, int size);
|
||||||
|
int aa_sock_opt_perm(const char *op, u32 request, struct socket *sock, int level,
|
||||||
|
int optname);
|
||||||
|
int aa_sock_file_perm(struct aa_label *label, const char *op, u32 request,
|
||||||
|
struct socket *sock);
|
||||||
|
|
||||||
|
|
||||||
|
static inline void aa_free_net_rules(struct aa_net *new)
|
||||||
|
{
|
||||||
|
/* NOP */
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* __AA_NET_H */
|
|
@ -18,15 +18,72 @@
|
||||||
|
|
||||||
enum path_flags {
|
enum path_flags {
|
||||||
PATH_IS_DIR = 0x1, /* path is a directory */
|
PATH_IS_DIR = 0x1, /* path is a directory */
|
||||||
|
PATH_SOCK_COND = 0x2,
|
||||||
PATH_CONNECT_PATH = 0x4, /* connect disconnected paths to / */
|
PATH_CONNECT_PATH = 0x4, /* connect disconnected paths to / */
|
||||||
PATH_CHROOT_REL = 0x8, /* do path lookup relative to chroot */
|
PATH_CHROOT_REL = 0x8, /* do path lookup relative to chroot */
|
||||||
PATH_CHROOT_NSCONNECT = 0x10, /* connect paths that are at ns root */
|
PATH_CHROOT_NSCONNECT = 0x10, /* connect paths that are at ns root */
|
||||||
|
|
||||||
PATH_DELEGATE_DELETED = 0x08000, /* delegate deleted files */
|
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,
|
int aa_path_name(const struct path *path, int flags, char *buffer,
|
||||||
const char **name, const char **info);
|
const char **name, const char **info, const char *disconnect);
|
||||||
|
|
||||||
|
#define MAX_PATH_BUFFERS 2
|
||||||
|
|
||||||
|
/* Per cpu buffers used during mediation */
|
||||||
|
/* preallocated buffers to use during path lookups */
|
||||||
|
struct aa_buffers {
|
||||||
|
char *buf[MAX_PATH_BUFFERS];
|
||||||
|
};
|
||||||
|
|
||||||
|
#include <linux/percpu.h>
|
||||||
|
#include <linux/preempt.h>
|
||||||
|
|
||||||
|
DECLARE_PER_CPU(struct aa_buffers, aa_buffers);
|
||||||
|
|
||||||
|
#define COUNT_ARGS(X...) COUNT_ARGS_HELPER ( , ##X ,9,8,7,6,5,4,3,2,1,0)
|
||||||
|
#define COUNT_ARGS_HELPER(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,n,X...) n
|
||||||
|
#define CONCAT(X, Y) X ## Y
|
||||||
|
#define CONCAT_AFTER(X, Y) CONCAT(X, Y)
|
||||||
|
|
||||||
|
#define ASSIGN(FN, X, N) do { (X) = FN(N); } while (0)
|
||||||
|
#define EVAL1(FN, X) ASSIGN(FN, X, 0) /*X = FN(0)*/
|
||||||
|
#define EVAL2(FN, X, Y...) ASSIGN(FN, X, 1); /*X = FN(1);*/ EVAL1(FN, Y)
|
||||||
|
#define EVAL(FN, X...) CONCAT_AFTER(EVAL, COUNT_ARGS(X))(FN, X)
|
||||||
|
|
||||||
|
#define for_each_cpu_buffer(I) for ((I) = 0; (I) < MAX_PATH_BUFFERS; (I)++)
|
||||||
|
|
||||||
|
#ifdef CONFIG_DEBUG_PREEMPT
|
||||||
|
#define AA_BUG_PREEMPT_ENABLED(X) AA_BUG(preempt_count() <= 0, X)
|
||||||
|
#else
|
||||||
|
#define AA_BUG_PREEMPT_ENABLED(X) /* nop */
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define __get_buffer(N) ({ \
|
||||||
|
struct aa_buffers *__cpu_var; \
|
||||||
|
AA_BUG_PREEMPT_ENABLED("__get_buffer without preempt disabled"); \
|
||||||
|
__cpu_var = this_cpu_ptr(&aa_buffers); \
|
||||||
|
__cpu_var->buf[(N)]; })
|
||||||
|
|
||||||
|
#define __get_buffers(X...) \
|
||||||
|
do { \
|
||||||
|
EVAL(__get_buffer, X); \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
#define __put_buffers(X, Y...) (void)&(X)
|
||||||
|
|
||||||
|
#define get_buffers(X...) \
|
||||||
|
do { \
|
||||||
|
preempt_disable(); \
|
||||||
|
__get_buffers(X); \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
#define put_buffers(X, Y...) \
|
||||||
|
do { \
|
||||||
|
__put_buffers(X, Y); \
|
||||||
|
preempt_enable(); \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
#endif /* __AA_PATH_H */
|
#endif /* __AA_PATH_H */
|
||||||
|
|
184
security/apparmor/include/perms.h
Normal file
184
security/apparmor/include/perms.h
Normal file
|
@ -0,0 +1,184 @@
|
||||||
|
/*
|
||||||
|
* AppArmor security module
|
||||||
|
*
|
||||||
|
* This file contains AppArmor basic permission sets definitions.
|
||||||
|
*
|
||||||
|
* Copyright 2013 Canonical Ltd.
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU General Public License as
|
||||||
|
* published by the Free Software Foundation, version 2 of the
|
||||||
|
* License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef __AA_PERM_H
|
||||||
|
#define __AA_PERM_H
|
||||||
|
|
||||||
|
#include <linux/fs.h>
|
||||||
|
#include "label.h"
|
||||||
|
|
||||||
|
#define AA_MAY_EXEC MAY_EXEC
|
||||||
|
#define AA_MAY_WRITE MAY_WRITE
|
||||||
|
#define AA_MAY_READ MAY_READ
|
||||||
|
#define AA_MAY_APPEND MAY_APPEND
|
||||||
|
|
||||||
|
#define AA_MAY_CREATE 0x0010
|
||||||
|
#define AA_MAY_DELETE 0x0020
|
||||||
|
#define AA_MAY_OPEN 0x0040
|
||||||
|
#define AA_MAY_RENAME 0x0080 /* pair */
|
||||||
|
|
||||||
|
#define AA_MAY_SETATTR 0x0100 /* meta write */
|
||||||
|
#define AA_MAY_GETATTR 0x0200 /* meta read */
|
||||||
|
#define AA_MAY_SETCRED 0x0400 /* security cred/attr */
|
||||||
|
#define AA_MAY_GETCRED 0x0800
|
||||||
|
|
||||||
|
#define AA_MAY_CHMOD 0x1000 /* pair */
|
||||||
|
#define AA_MAY_CHOWN 0x2000 /* pair */
|
||||||
|
#define AA_MAY_CHGRP 0x4000 /* pair */
|
||||||
|
#define AA_MAY_LOCK 0x8000 /* LINK_SUBSET overlaid */
|
||||||
|
|
||||||
|
#define AA_EXEC_MMAP 0x00010000
|
||||||
|
#define AA_MAY_MPROT 0x00020000 /* extend conditions */
|
||||||
|
#define AA_MAY_LINK 0x00040000 /* pair */
|
||||||
|
#define AA_MAY_SNAPSHOT 0x00080000 /* pair */
|
||||||
|
|
||||||
|
#define AA_MAY_DELEGATE
|
||||||
|
#define AA_CONT_MATCH 0x08000000
|
||||||
|
|
||||||
|
#define AA_MAY_STACK 0x10000000
|
||||||
|
#define AA_MAY_ONEXEC 0x20000000 /* either stack or change_profile */
|
||||||
|
#define AA_MAY_CHANGE_PROFILE 0x40000000
|
||||||
|
#define AA_MAY_CHANGEHAT 0x80000000
|
||||||
|
|
||||||
|
#define AA_LINK_SUBSET AA_MAY_LOCK /* overlaid */
|
||||||
|
|
||||||
|
|
||||||
|
#define PERMS_CHRS_MASK (MAY_READ | MAY_WRITE | AA_MAY_CREATE | \
|
||||||
|
AA_MAY_DELETE | AA_MAY_LINK | AA_MAY_LOCK | \
|
||||||
|
AA_MAY_EXEC | AA_EXEC_MMAP | AA_MAY_APPEND)
|
||||||
|
|
||||||
|
#define PERMS_NAMES_MASK (PERMS_CHRS_MASK | AA_MAY_OPEN | AA_MAY_RENAME | \
|
||||||
|
AA_MAY_SETATTR | AA_MAY_GETATTR | AA_MAY_SETCRED | \
|
||||||
|
AA_MAY_GETCRED | AA_MAY_CHMOD | AA_MAY_CHOWN | \
|
||||||
|
AA_MAY_CHGRP | AA_MAY_MPROT | AA_MAY_SNAPSHOT | \
|
||||||
|
AA_MAY_STACK | AA_MAY_ONEXEC | \
|
||||||
|
AA_MAY_CHANGE_PROFILE | AA_MAY_CHANGEHAT)
|
||||||
|
|
||||||
|
extern const char aa_file_perm_chrs[];
|
||||||
|
extern const char *aa_file_perm_names[];
|
||||||
|
|
||||||
|
|
||||||
|
struct aa_perms {
|
||||||
|
u32 allow;
|
||||||
|
u32 audit; /* set only when allow is set */
|
||||||
|
|
||||||
|
u32 deny; /* explicit deny, or conflict if allow also set */
|
||||||
|
u32 quiet; /* set only when ~allow | deny */
|
||||||
|
u32 kill; /* set only when ~allow | deny */
|
||||||
|
u32 stop; /* set only when ~allow | deny */
|
||||||
|
|
||||||
|
u32 complain; /* accumulates only used when ~allow & ~deny */
|
||||||
|
u32 cond; /* set only when ~allow and ~deny */
|
||||||
|
|
||||||
|
u32 hide; /* set only when ~allow | deny */
|
||||||
|
u32 prompt; /* accumulates only used when ~allow & ~deny */
|
||||||
|
|
||||||
|
/* Reserved:
|
||||||
|
* u32 subtree; / * set only when allow is set * /
|
||||||
|
*/
|
||||||
|
u16 xindex;
|
||||||
|
};
|
||||||
|
|
||||||
|
#define ALL_PERMS_MASK 0xffffffff
|
||||||
|
|
||||||
|
#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)
|
||||||
|
|
||||||
|
extern struct aa_perms nullperms;
|
||||||
|
extern struct aa_perms allperms;
|
||||||
|
|
||||||
|
|
||||||
|
#define xcheck(FN1, FN2) \
|
||||||
|
({ \
|
||||||
|
int e, error = FN1; \
|
||||||
|
e = FN2; \
|
||||||
|
if (e) \
|
||||||
|
error = e; \
|
||||||
|
error; \
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* TODO: update for labels pointing to labels instead of profiles
|
||||||
|
* TODO: optimize the walk, currently does subwalk of L2 for each P in L1
|
||||||
|
* gah this doesn't allow for label compound check!!!!
|
||||||
|
*/
|
||||||
|
#define xcheck_ns_profile_profile(P1, P2, FN, args...) \
|
||||||
|
({ \
|
||||||
|
int ____e = 0; \
|
||||||
|
if (P1->ns == P2->ns) \
|
||||||
|
____e = FN((P1), (P2), args); \
|
||||||
|
(____e); \
|
||||||
|
})
|
||||||
|
|
||||||
|
#define xcheck_ns_profile_label(P, L, FN, args...) \
|
||||||
|
({ \
|
||||||
|
struct aa_profile *__p2; \
|
||||||
|
fn_for_each((L), __p2, \
|
||||||
|
xcheck_ns_profile_profile((P), __p2, (FN), args)); \
|
||||||
|
})
|
||||||
|
|
||||||
|
#define xcheck_ns_labels(L1, L2, FN, args...) \
|
||||||
|
({ \
|
||||||
|
struct aa_profile *__p1; \
|
||||||
|
fn_for_each((L1), __p1, FN(__p1, (L2), args)); \
|
||||||
|
})
|
||||||
|
|
||||||
|
/* Do the cross check but applying FN at the profiles level */
|
||||||
|
#define xcheck_labels_profiles(L1, L2, FN, args...) \
|
||||||
|
xcheck_ns_labels((L1), (L2), xcheck_ns_profile_label, (FN), args)
|
||||||
|
|
||||||
|
|
||||||
|
#define FINAL_CHECK true
|
||||||
|
|
||||||
|
void aa_perm_mask_to_str(char *str, const char *chrs, u32 mask);
|
||||||
|
void aa_audit_perm_names(struct audit_buffer *ab, const char **names, u32 mask);
|
||||||
|
void aa_audit_perm_mask(struct audit_buffer *ab, u32 mask, const char *chrs,
|
||||||
|
u32 chrsmask, const char **names, u32 namesmask);
|
||||||
|
void aa_apply_modes_to_perms(struct aa_profile *profile,
|
||||||
|
struct aa_perms *perms);
|
||||||
|
void aa_compute_perms(struct aa_dfa *dfa, unsigned int state,
|
||||||
|
struct aa_perms *perms);
|
||||||
|
void aa_perms_accum(struct aa_perms *accum, struct aa_perms *addend);
|
||||||
|
void aa_perms_accum_raw(struct aa_perms *accum, struct aa_perms *addend);
|
||||||
|
void aa_profile_match_label(struct aa_profile *profile, struct aa_label *label,
|
||||||
|
int type, u32 request, struct aa_perms *perms);
|
||||||
|
int aa_profile_label_perm(struct aa_profile *profile, struct aa_profile *target,
|
||||||
|
u32 request, int type, u32 *deny,
|
||||||
|
struct common_audit_data *sa);
|
||||||
|
int aa_check_perms(struct aa_profile *profile, struct aa_perms *perms,
|
||||||
|
u32 request, struct common_audit_data *sa,
|
||||||
|
void (*cb) (struct audit_buffer *, void *));
|
||||||
|
|
||||||
|
|
||||||
|
static inline int aa_xlabel_perm(struct aa_profile *profile,
|
||||||
|
struct aa_profile *target,
|
||||||
|
int type, u32 request, u32 reverse,
|
||||||
|
u32 * deny, struct common_audit_data *sa)
|
||||||
|
{
|
||||||
|
/* TODO: ??? 2nd aa_profile_label_perm needs to reverse perms */
|
||||||
|
return xcheck(aa_profile_label_perm(profile, target, request, type,
|
||||||
|
deny, sa),
|
||||||
|
aa_profile_label_perm(target, profile, request /*??*/, type,
|
||||||
|
deny, sa));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#endif /* __AA_PERM_H */
|
|
@ -18,6 +18,7 @@
|
||||||
#include <linux/capability.h>
|
#include <linux/capability.h>
|
||||||
#include <linux/cred.h>
|
#include <linux/cred.h>
|
||||||
#include <linux/kref.h>
|
#include <linux/kref.h>
|
||||||
|
#include <linux/rhashtable.h>
|
||||||
#include <linux/sched.h>
|
#include <linux/sched.h>
|
||||||
#include <linux/slab.h>
|
#include <linux/slab.h>
|
||||||
#include <linux/socket.h>
|
#include <linux/socket.h>
|
||||||
|
@ -27,8 +28,15 @@
|
||||||
#include "capability.h"
|
#include "capability.h"
|
||||||
#include "domain.h"
|
#include "domain.h"
|
||||||
#include "file.h"
|
#include "file.h"
|
||||||
|
#include "label.h"
|
||||||
|
#include "net.h"
|
||||||
|
#include "perms.h"
|
||||||
#include "resource.h"
|
#include "resource.h"
|
||||||
|
|
||||||
|
struct aa_ns;
|
||||||
|
|
||||||
|
extern int unprivileged_userns_apparmor_policy;
|
||||||
|
|
||||||
extern const char *const aa_profile_mode_names[];
|
extern const char *const aa_profile_mode_names[];
|
||||||
#define APPARMOR_MODE_NAMES_MAX_INDEX 4
|
#define APPARMOR_MODE_NAMES_MAX_INDEX 4
|
||||||
|
|
||||||
|
@ -40,9 +48,9 @@ extern const char *const aa_profile_mode_names[];
|
||||||
|
|
||||||
#define KILL_MODE(_profile) PROFILE_MODE((_profile), APPARMOR_KILL)
|
#define KILL_MODE(_profile) PROFILE_MODE((_profile), APPARMOR_KILL)
|
||||||
|
|
||||||
#define PROFILE_IS_HAT(_profile) ((_profile)->flags & PFLAG_HAT)
|
#define PROFILE_IS_HAT(_profile) ((_profile)->label.flags & FLAG_HAT)
|
||||||
|
|
||||||
#define PROFILE_INVALID(_profile) ((_profile)->flags & PFLAG_INVALID)
|
#define profile_is_stale(_profile) (label_is_stale(&(_profile)->label))
|
||||||
|
|
||||||
#define on_list_rcu(X) (!list_empty(X) && (X)->prev != LIST_POISON2)
|
#define on_list_rcu(X) (!list_empty(X) && (X)->prev != LIST_POISON2)
|
||||||
|
|
||||||
|
@ -59,86 +67,6 @@ enum profile_mode {
|
||||||
APPARMOR_UNCONFINED, /* profile set to unconfined */
|
APPARMOR_UNCONFINED, /* profile set to unconfined */
|
||||||
};
|
};
|
||||||
|
|
||||||
enum profile_flags {
|
|
||||||
PFLAG_HAT = 1, /* profile is a hat */
|
|
||||||
PFLAG_NULL = 4, /* profile is null learning profile */
|
|
||||||
PFLAG_IX_ON_NAME_ERROR = 8, /* fallback to ix on name lookup fail */
|
|
||||||
PFLAG_IMMUTABLE = 0x10, /* don't allow changes/replacement */
|
|
||||||
PFLAG_USER_DEFINED = 0x20, /* user based profile - lower privs */
|
|
||||||
PFLAG_NO_LIST_REF = 0x40, /* list doesn't keep profile ref */
|
|
||||||
PFLAG_OLD_NULL_TRANS = 0x100, /* use // as the null transition */
|
|
||||||
PFLAG_INVALID = 0x200, /* profile replaced/removed */
|
|
||||||
PFLAG_NS_COUNT = 0x400, /* carries NS ref count */
|
|
||||||
|
|
||||||
/* These flags must correspond with PATH_flags */
|
|
||||||
PFLAG_MEDIATE_DELETED = 0x10000, /* mediate instead delegate deleted */
|
|
||||||
};
|
|
||||||
|
|
||||||
struct aa_profile;
|
|
||||||
|
|
||||||
/* struct aa_policy - common part of both namespaces and profiles
|
|
||||||
* @name: name of the object
|
|
||||||
* @hname - The hierarchical name
|
|
||||||
* @list: list policy object is on
|
|
||||||
* @profiles: head of the profiles list contained in the object
|
|
||||||
*/
|
|
||||||
struct aa_policy {
|
|
||||||
char *name;
|
|
||||||
char *hname;
|
|
||||||
struct list_head list;
|
|
||||||
struct list_head profiles;
|
|
||||||
};
|
|
||||||
|
|
||||||
/* struct aa_ns_acct - accounting of profiles in namespace
|
|
||||||
* @max_size: maximum space allowed for all profiles in namespace
|
|
||||||
* @max_count: maximum number of profiles that can be in this namespace
|
|
||||||
* @size: current size of profiles
|
|
||||||
* @count: current count of profiles (includes null profiles)
|
|
||||||
*/
|
|
||||||
struct aa_ns_acct {
|
|
||||||
int max_size;
|
|
||||||
int max_count;
|
|
||||||
int size;
|
|
||||||
int count;
|
|
||||||
};
|
|
||||||
|
|
||||||
/* struct aa_namespace - namespace for a set of profiles
|
|
||||||
* @base: common policy
|
|
||||||
* @parent: parent of namespace
|
|
||||||
* @lock: lock for modifying the object
|
|
||||||
* @acct: accounting for the namespace
|
|
||||||
* @unconfined: special unconfined profile for the namespace
|
|
||||||
* @sub_ns: list of namespaces under the current namespace.
|
|
||||||
* @uniq_null: uniq value used for null learning profiles
|
|
||||||
* @uniq_id: a unique id count for the profiles in the namespace
|
|
||||||
* @dents: dentries for the namespaces file entries in apparmorfs
|
|
||||||
*
|
|
||||||
* An aa_namespace defines the set profiles that are searched to determine
|
|
||||||
* which profile to attach to a task. Profiles can not be shared between
|
|
||||||
* aa_namespaces and profile names within a namespace are guaranteed to be
|
|
||||||
* unique. When profiles in separate namespaces have the same name they
|
|
||||||
* are NOT considered to be equivalent.
|
|
||||||
*
|
|
||||||
* Namespaces are hierarchical and only namespaces and profiles below the
|
|
||||||
* current namespace are visible.
|
|
||||||
*
|
|
||||||
* Namespace names must be unique and can not contain the characters :/\0
|
|
||||||
*
|
|
||||||
* FIXME TODO: add vserver support of namespaces (can it all be done in
|
|
||||||
* userspace?)
|
|
||||||
*/
|
|
||||||
struct aa_namespace {
|
|
||||||
struct aa_policy base;
|
|
||||||
struct aa_namespace *parent;
|
|
||||||
struct mutex lock;
|
|
||||||
struct aa_ns_acct acct;
|
|
||||||
struct aa_profile *unconfined;
|
|
||||||
struct list_head sub_ns;
|
|
||||||
atomic_t uniq_null;
|
|
||||||
long uniq_id;
|
|
||||||
|
|
||||||
struct dentry *dents[AAFS_NS_SIZEOF];
|
|
||||||
};
|
|
||||||
|
|
||||||
/* struct aa_policydb - match engine for a policy
|
/* struct aa_policydb - match engine for a policy
|
||||||
* dfa: dfa pattern match
|
* dfa: dfa pattern match
|
||||||
|
@ -151,43 +79,47 @@ struct aa_policydb {
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct aa_replacedby {
|
/* struct aa_data - generic data structure
|
||||||
struct kref count;
|
* key: name for retrieving this data
|
||||||
struct aa_profile __rcu *profile;
|
* size: size of data in bytes
|
||||||
|
* data: binary data
|
||||||
|
* head: reserved for rhashtable
|
||||||
|
*/
|
||||||
|
struct aa_data {
|
||||||
|
char *key;
|
||||||
|
size_t size;
|
||||||
|
char *data;
|
||||||
|
struct rhash_head head;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/* struct aa_profile - basic confinement data
|
/* struct aa_profile - basic confinement data
|
||||||
* @base - base components of the profile (name, refcount, lists, lock ...)
|
* @base - base components of the profile (name, refcount, lists, lock ...)
|
||||||
* @count: reference count of the obj
|
* @label - label this profile is an extension of
|
||||||
* @rcu: rcu head used when removing from @list
|
|
||||||
* @parent: parent of profile
|
* @parent: parent of profile
|
||||||
* @ns: namespace the profile is in
|
* @ns: namespace the profile is in
|
||||||
* @replacedby: is set to the profile that replaced this profile
|
|
||||||
* @rename: optional profile name that this profile renamed
|
* @rename: optional profile name that this profile renamed
|
||||||
* @attach: human readable attachment string
|
* @attach: human readable attachment string
|
||||||
* @xmatch: optional extended matching for unconfined executables names
|
* @xmatch: optional extended matching for unconfined executables names
|
||||||
* @xmatch_len: xmatch prefix len, used to determine xmatch priority
|
* @xmatch_len: xmatch prefix len, used to determine xmatch priority
|
||||||
* @audit: the auditing mode of the profile
|
* @audit: the auditing mode of the profile
|
||||||
* @mode: the enforcement mode of the profile
|
* @mode: the enforcement mode of the profile
|
||||||
* @flags: flags controlling profile behavior
|
|
||||||
* @path_flags: flags controlling path generation behavior
|
* @path_flags: flags controlling path generation behavior
|
||||||
|
* @disconnected: what to prepend if attach_disconnected is specified
|
||||||
* @size: the memory consumed by this profiles rules
|
* @size: the memory consumed by this profiles rules
|
||||||
* @policy: general match rules governing policy
|
* @policy: general match rules governing policy
|
||||||
* @file: The set of rules governing basic file access and domain transitions
|
* @file: The set of rules governing basic file access and domain transitions
|
||||||
* @caps: capabilities for the profile
|
* @caps: capabilities for the profile
|
||||||
|
* @net: network controls for the profile
|
||||||
* @rlimits: rlimits for the profile
|
* @rlimits: rlimits for the profile
|
||||||
*
|
|
||||||
* @dents: dentries for the profiles file entries in apparmorfs
|
* @dents: dentries for the profiles file entries in apparmorfs
|
||||||
* @dirname: name of the profile dir in apparmorfs
|
* @dirname: name of the profile dir in apparmorfs
|
||||||
|
* @data: hashtable for free-form policy aa_data
|
||||||
*
|
*
|
||||||
* The AppArmor profile contains the basic confinement data. Each profile
|
* The AppArmor profile contains the basic confinement data. Each profile
|
||||||
* has a name, and exists in a namespace. The @name and @exec_match are
|
* has a name, and exists in a namespace. The @name and @exec_match are
|
||||||
* used to determine profile attachment against unconfined tasks. All other
|
* used to determine profile attachment against unconfined tasks. All other
|
||||||
* attachments are determined by profile X transition rules.
|
* attachments are determined by profile X transition rules.
|
||||||
*
|
*
|
||||||
* The @replacedby struct is write protected by the profile lock.
|
|
||||||
*
|
|
||||||
* Profiles have a hierarchy where hats and children profiles keep
|
* Profiles have a hierarchy where hats and children profiles keep
|
||||||
* a reference to their parent.
|
* a reference to their parent.
|
||||||
*
|
*
|
||||||
|
@ -197,12 +129,9 @@ struct aa_replacedby {
|
||||||
*/
|
*/
|
||||||
struct aa_profile {
|
struct aa_profile {
|
||||||
struct aa_policy base;
|
struct aa_policy base;
|
||||||
struct kref count;
|
|
||||||
struct rcu_head rcu;
|
|
||||||
struct aa_profile __rcu *parent;
|
struct aa_profile __rcu *parent;
|
||||||
|
|
||||||
struct aa_namespace *ns;
|
struct aa_ns *ns;
|
||||||
struct aa_replacedby *replacedby;
|
|
||||||
const char *rename;
|
const char *rename;
|
||||||
|
|
||||||
const char *attach;
|
const char *attach;
|
||||||
|
@ -210,57 +139,94 @@ struct aa_profile {
|
||||||
int xmatch_len;
|
int xmatch_len;
|
||||||
enum audit_mode audit;
|
enum audit_mode audit;
|
||||||
long mode;
|
long mode;
|
||||||
long flags;
|
|
||||||
u32 path_flags;
|
u32 path_flags;
|
||||||
|
const char *disconnected;
|
||||||
int size;
|
int size;
|
||||||
|
|
||||||
struct aa_policydb policy;
|
struct aa_policydb policy;
|
||||||
struct aa_file_rules file;
|
struct aa_file_rules file;
|
||||||
struct aa_caps caps;
|
struct aa_caps caps;
|
||||||
|
struct aa_net net;
|
||||||
struct aa_rlimit rlimits;
|
struct aa_rlimit rlimits;
|
||||||
|
|
||||||
|
struct aa_loaddata *rawdata;
|
||||||
unsigned char *hash;
|
unsigned char *hash;
|
||||||
char *dirname;
|
char *dirname;
|
||||||
struct dentry *dents[AAFS_PROF_SIZEOF];
|
struct dentry *dents[AAFS_PROF_SIZEOF];
|
||||||
|
struct rhashtable *data;
|
||||||
|
struct aa_label label;
|
||||||
};
|
};
|
||||||
|
|
||||||
extern struct aa_namespace *root_ns;
|
|
||||||
extern enum profile_mode aa_g_profile_mode;
|
extern enum profile_mode aa_g_profile_mode;
|
||||||
|
|
||||||
|
#define AA_MAY_LOAD_POLICY AA_MAY_APPEND
|
||||||
|
#define AA_MAY_REPLACE_POLICY AA_MAY_WRITE
|
||||||
|
#define AA_MAY_REMOVE_POLICY AA_MAY_DELETE
|
||||||
|
|
||||||
|
#define profiles_ns(P) ((P)->ns)
|
||||||
|
#define name_is_shared(A, B) ((A)->hname && (A)->hname == (B)->hname)
|
||||||
|
|
||||||
void aa_add_profile(struct aa_policy *common, struct aa_profile *profile);
|
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);
|
|
||||||
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,
|
struct aa_label *aa_setup_default_label(void);
|
||||||
const char *name);
|
|
||||||
|
|
||||||
|
struct aa_profile *aa_alloc_profile(const char *name, struct aa_proxy *proxy,
|
||||||
void aa_free_replacedby_kref(struct kref *kref);
|
gfp_t gfp);
|
||||||
struct aa_profile *aa_alloc_profile(const char *name);
|
struct aa_profile *aa_null_profile(struct aa_profile *parent, bool hat,
|
||||||
struct aa_profile *aa_new_null_profile(struct aa_profile *parent, int hat);
|
const char *base, gfp_t gfp);
|
||||||
void aa_free_profile(struct aa_profile *profile);
|
void aa_free_profile(struct aa_profile *profile);
|
||||||
void aa_free_profile_kref(struct kref *kref);
|
void aa_free_profile_kref(struct kref *kref);
|
||||||
struct aa_profile *aa_find_child(struct aa_profile *parent, const char *name);
|
struct aa_profile *aa_find_child(struct aa_profile *parent, const char *name);
|
||||||
struct aa_profile *aa_lookup_profile(struct aa_namespace *ns, const char *name);
|
struct aa_profile *aa_lookupn_profile(struct aa_ns *ns, const char *hname,
|
||||||
struct aa_profile *aa_match_profile(struct aa_namespace *ns, const char *name);
|
size_t n);
|
||||||
|
struct aa_profile *aa_lookup_profile(struct aa_ns *ns, const char *name);
|
||||||
|
struct aa_profile *aa_fqlookupn_profile(struct aa_label *base,
|
||||||
|
const char *fqname, size_t n);
|
||||||
|
struct aa_profile *aa_match_profile(struct aa_ns *ns, const char *name);
|
||||||
|
|
||||||
ssize_t aa_replace_profiles(void *udata, size_t size, bool noreplace);
|
ssize_t aa_replace_profiles(struct aa_ns *view, struct aa_label *label,
|
||||||
ssize_t aa_remove_profiles(char *name, size_t size);
|
u32 mask, struct aa_loaddata *udata);
|
||||||
|
ssize_t aa_remove_profiles(struct aa_ns *view, struct aa_label *label,
|
||||||
|
char *name, size_t size);
|
||||||
|
void __aa_profile_list_release(struct list_head *head);
|
||||||
|
|
||||||
#define PROF_ADD 1
|
#define PROF_ADD 1
|
||||||
#define PROF_REPLACE 0
|
#define PROF_REPLACE 0
|
||||||
|
|
||||||
#define unconfined(X) ((X)->mode == APPARMOR_UNCONFINED)
|
#define profile_unconfined(X) ((X)->mode == APPARMOR_UNCONFINED)
|
||||||
|
|
||||||
|
/**
|
||||||
static inline struct aa_profile *aa_deref_parent(struct aa_profile *p)
|
* aa_get_newest_profile - simple wrapper fn to wrap the label version
|
||||||
|
* @p: profile (NOT NULL)
|
||||||
|
*
|
||||||
|
* Returns refcount to newest version of the profile (maybe @p)
|
||||||
|
*
|
||||||
|
* Requires: @p must be held with a valid refcount
|
||||||
|
*/
|
||||||
|
static inline struct aa_profile *aa_get_newest_profile(struct aa_profile *p)
|
||||||
{
|
{
|
||||||
return rcu_dereference_protected(p->parent,
|
return labels_profile(aa_get_newest_label(&p->label));
|
||||||
mutex_is_locked(&p->ns->lock));
|
}
|
||||||
|
|
||||||
|
#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);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -273,7 +239,7 @@ static inline struct aa_profile *aa_deref_parent(struct aa_profile *p)
|
||||||
static inline struct aa_profile *aa_get_profile(struct aa_profile *p)
|
static inline struct aa_profile *aa_get_profile(struct aa_profile *p)
|
||||||
{
|
{
|
||||||
if (p)
|
if (p)
|
||||||
kref_get(&(p->count));
|
kref_get(&(p->label.count));
|
||||||
|
|
||||||
return p;
|
return p;
|
||||||
}
|
}
|
||||||
|
@ -287,7 +253,7 @@ static inline struct aa_profile *aa_get_profile(struct aa_profile *p)
|
||||||
*/
|
*/
|
||||||
static inline struct aa_profile *aa_get_profile_not0(struct aa_profile *p)
|
static inline struct aa_profile *aa_get_profile_not0(struct aa_profile *p)
|
||||||
{
|
{
|
||||||
if (p && kref_get_not0(&p->count))
|
if (p && kref_get_not0(&p->label.count))
|
||||||
return p;
|
return p;
|
||||||
|
|
||||||
return NULL;
|
return NULL;
|
||||||
|
@ -307,31 +273,12 @@ static inline struct aa_profile *aa_get_profile_rcu(struct aa_profile __rcu **p)
|
||||||
rcu_read_lock();
|
rcu_read_lock();
|
||||||
do {
|
do {
|
||||||
c = rcu_dereference(*p);
|
c = rcu_dereference(*p);
|
||||||
} while (c && !kref_get_not0(&c->count));
|
} while (c && !kref_get_not0(&c->label.count));
|
||||||
rcu_read_unlock();
|
rcu_read_unlock();
|
||||||
|
|
||||||
return c;
|
return c;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* aa_get_newest_profile - find the newest version of @profile
|
|
||||||
* @profile: the profile to check for newer versions of
|
|
||||||
*
|
|
||||||
* Returns: refcounted newest version of @profile taking into account
|
|
||||||
* replacement, renames and removals
|
|
||||||
* return @profile.
|
|
||||||
*/
|
|
||||||
static inline struct aa_profile *aa_get_newest_profile(struct aa_profile *p)
|
|
||||||
{
|
|
||||||
if (!p)
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
if (PROFILE_INVALID(p))
|
|
||||||
return aa_get_profile_rcu(&p->replacedby->profile);
|
|
||||||
|
|
||||||
return aa_get_profile(p);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* aa_put_profile - decrement refcount on profile @p
|
* aa_put_profile - decrement refcount on profile @p
|
||||||
* @p: profile (MAYBE NULL)
|
* @p: profile (MAYBE NULL)
|
||||||
|
@ -339,60 +286,7 @@ static inline struct aa_profile *aa_get_newest_profile(struct aa_profile *p)
|
||||||
static inline void aa_put_profile(struct aa_profile *p)
|
static inline void aa_put_profile(struct aa_profile *p)
|
||||||
{
|
{
|
||||||
if (p)
|
if (p)
|
||||||
kref_put(&p->count, aa_free_profile_kref);
|
kref_put(&p->label.count, aa_label_kref);
|
||||||
}
|
|
||||||
|
|
||||||
static inline struct aa_replacedby *aa_get_replacedby(struct aa_replacedby *p)
|
|
||||||
{
|
|
||||||
if (p)
|
|
||||||
kref_get(&(p->count));
|
|
||||||
|
|
||||||
return p;
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline void aa_put_replacedby(struct aa_replacedby *p)
|
|
||||||
{
|
|
||||||
if (p)
|
|
||||||
kref_put(&p->count, aa_free_replacedby_kref);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* requires profile list write lock held */
|
|
||||||
static inline void __aa_update_replacedby(struct aa_profile *orig,
|
|
||||||
struct aa_profile *new)
|
|
||||||
{
|
|
||||||
struct aa_profile *tmp;
|
|
||||||
tmp = rcu_dereference_protected(orig->replacedby->profile,
|
|
||||||
mutex_is_locked(&orig->ns->lock));
|
|
||||||
rcu_assign_pointer(orig->replacedby->profile, aa_get_profile(new));
|
|
||||||
orig->flags |= PFLAG_INVALID;
|
|
||||||
aa_put_profile(tmp);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* aa_get_namespace - increment references count on @ns
|
|
||||||
* @ns: namespace to increment reference count of (MAYBE NULL)
|
|
||||||
*
|
|
||||||
* Returns: pointer to @ns, if @ns is NULL returns NULL
|
|
||||||
* Requires: @ns must be held with valid refcount when called
|
|
||||||
*/
|
|
||||||
static inline struct aa_namespace *aa_get_namespace(struct aa_namespace *ns)
|
|
||||||
{
|
|
||||||
if (ns)
|
|
||||||
aa_get_profile(ns->unconfined);
|
|
||||||
|
|
||||||
return ns;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* aa_put_namespace - decrement refcount on @ns
|
|
||||||
* @ns: namespace to put reference of
|
|
||||||
*
|
|
||||||
* Decrement reference count of @ns and if no longer in use free it
|
|
||||||
*/
|
|
||||||
static inline void aa_put_namespace(struct aa_namespace *ns)
|
|
||||||
{
|
|
||||||
if (ns)
|
|
||||||
aa_put_profile(ns->unconfined);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline int AUDIT_MODE(struct aa_profile *profile)
|
static inline int AUDIT_MODE(struct aa_profile *profile)
|
||||||
|
@ -403,6 +297,9 @@ static inline int AUDIT_MODE(struct aa_profile *profile)
|
||||||
return profile->audit;
|
return profile->audit;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool aa_may_manage_policy(int op);
|
bool policy_view_capable(struct aa_ns *ns);
|
||||||
|
bool policy_admin_capable(struct aa_ns *ns);
|
||||||
|
bool aa_may_open_profiles(void);
|
||||||
|
int aa_may_manage_policy(struct aa_label *label, struct aa_ns *ns, u32 mask);
|
||||||
|
|
||||||
#endif /* __AA_POLICY_H */
|
#endif /* __AA_POLICY_H */
|
||||||
|
|
150
security/apparmor/include/policy_ns.h
Normal file
150
security/apparmor/include/policy_ns.h
Normal file
|
@ -0,0 +1,150 @@
|
||||||
|
/*
|
||||||
|
* AppArmor security module
|
||||||
|
*
|
||||||
|
* This file contains AppArmor policy definitions.
|
||||||
|
*
|
||||||
|
* Copyright (C) 1998-2008 Novell/SUSE
|
||||||
|
* Copyright 2009-2015 Canonical Ltd.
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU General Public License as
|
||||||
|
* published by the Free Software Foundation, version 2 of the
|
||||||
|
* License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef __AA_NAMESPACE_H
|
||||||
|
#define __AA_NAMESPACE_H
|
||||||
|
|
||||||
|
#include <linux/kref.h>
|
||||||
|
|
||||||
|
#include "apparmor.h"
|
||||||
|
#include "apparmorfs.h"
|
||||||
|
#include "label.h"
|
||||||
|
#include "policy.h"
|
||||||
|
|
||||||
|
|
||||||
|
/* struct aa_ns_acct - accounting of profiles in namespace
|
||||||
|
* @max_size: maximum space allowed for all profiles in namespace
|
||||||
|
* @max_count: maximum number of profiles that can be in this namespace
|
||||||
|
* @size: current size of profiles
|
||||||
|
* @count: current count of profiles (includes null profiles)
|
||||||
|
*/
|
||||||
|
struct aa_ns_acct {
|
||||||
|
int max_size;
|
||||||
|
int max_count;
|
||||||
|
int size;
|
||||||
|
int count;
|
||||||
|
};
|
||||||
|
|
||||||
|
/* struct aa_ns - namespace for a set of profiles
|
||||||
|
* @base: common policy
|
||||||
|
* @parent: parent of namespace
|
||||||
|
* @lock: lock for modifying the object
|
||||||
|
* @acct: accounting for the namespace
|
||||||
|
* @unconfined: special unconfined profile for the namespace
|
||||||
|
* @sub_ns: list of namespaces under the current namespace.
|
||||||
|
* @uniq_null: uniq value used for null learning profiles
|
||||||
|
* @uniq_id: a unique id count for the profiles in the namespace
|
||||||
|
* @dents: dentries for the namespaces file entries in apparmorfs
|
||||||
|
*
|
||||||
|
* An aa_ns defines the set profiles that are searched to determine which
|
||||||
|
* profile to attach to a task. Profiles can not be shared between aa_ns
|
||||||
|
* and profile names within a namespace are guaranteed to be unique. When
|
||||||
|
* profiles in separate namespaces have the same name they are NOT considered
|
||||||
|
* to be equivalent.
|
||||||
|
*
|
||||||
|
* Namespaces are hierarchical and only namespaces and profiles below the
|
||||||
|
* current namespace are visible.
|
||||||
|
*
|
||||||
|
* Namespace names must be unique and can not contain the characters :/\0
|
||||||
|
*/
|
||||||
|
struct aa_ns {
|
||||||
|
struct aa_policy base;
|
||||||
|
struct aa_ns *parent;
|
||||||
|
struct mutex lock;
|
||||||
|
struct aa_ns_acct acct;
|
||||||
|
struct aa_profile *unconfined;
|
||||||
|
struct list_head sub_ns;
|
||||||
|
atomic_t uniq_null;
|
||||||
|
long uniq_id;
|
||||||
|
int level;
|
||||||
|
struct aa_labelset labels;
|
||||||
|
|
||||||
|
struct dentry *dents[AAFS_NS_SIZEOF];
|
||||||
|
};
|
||||||
|
|
||||||
|
extern struct aa_ns *root_ns;
|
||||||
|
|
||||||
|
extern const char *aa_hidden_ns_name;
|
||||||
|
|
||||||
|
#define ns_unconfined(NS) (&(NS)->unconfined->label)
|
||||||
|
|
||||||
|
bool aa_ns_visible(struct aa_ns *curr, struct aa_ns *view, bool subns);
|
||||||
|
const char *aa_ns_name(struct aa_ns *parent, struct aa_ns *child, bool subns);
|
||||||
|
void aa_free_ns(struct aa_ns *ns);
|
||||||
|
int aa_alloc_root_ns(void);
|
||||||
|
void aa_free_root_ns(void);
|
||||||
|
void aa_free_ns_kref(struct kref *kref);
|
||||||
|
|
||||||
|
struct aa_ns *aa_find_ns(struct aa_ns *root, const char *name);
|
||||||
|
struct aa_ns *aa_findn_ns(struct aa_ns *root, const char *name, size_t n);
|
||||||
|
struct aa_ns *aa_create_ns(struct aa_ns *parent, const char *name,
|
||||||
|
struct dentry *dir);
|
||||||
|
struct aa_ns *aa_prepare_ns(struct aa_ns *root, const char *name);
|
||||||
|
void __aa_remove_ns(struct aa_ns *ns);
|
||||||
|
|
||||||
|
static inline struct aa_profile *aa_deref_parent(struct aa_profile *p)
|
||||||
|
{
|
||||||
|
return rcu_dereference_protected(p->parent,
|
||||||
|
mutex_is_locked(&p->ns->lock));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* aa_get_ns - increment references count on @ns
|
||||||
|
* @ns: namespace to increment reference count of (MAYBE NULL)
|
||||||
|
*
|
||||||
|
* Returns: pointer to @ns, if @ns is NULL returns NULL
|
||||||
|
* Requires: @ns must be held with valid refcount when called
|
||||||
|
*/
|
||||||
|
static inline struct aa_ns *aa_get_ns(struct aa_ns *ns)
|
||||||
|
{
|
||||||
|
if (ns)
|
||||||
|
aa_get_profile(ns->unconfined);
|
||||||
|
|
||||||
|
return ns;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* aa_put_ns - decrement refcount on @ns
|
||||||
|
* @ns: ns to put reference of
|
||||||
|
*
|
||||||
|
* Decrement reference count of @ns and if no longer in use free it
|
||||||
|
*/
|
||||||
|
static inline void aa_put_ns(struct aa_ns *ns)
|
||||||
|
{
|
||||||
|
if (ns)
|
||||||
|
aa_put_profile(ns->unconfined);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* __aa_findn_ns - find a namespace on a list by @name
|
||||||
|
* @head: list to search for namespace on (NOT NULL)
|
||||||
|
* @name: name of namespace to look for (NOT NULL)
|
||||||
|
* @n: length of @name
|
||||||
|
* Returns: unrefcounted namespace
|
||||||
|
*
|
||||||
|
* Requires: rcu_read_lock be held
|
||||||
|
*/
|
||||||
|
static inline struct aa_ns *__aa_findn_ns(struct list_head *head,
|
||||||
|
const char *name, size_t n)
|
||||||
|
{
|
||||||
|
return (struct aa_ns *)__policy_strn_find(head, name, n);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline struct aa_ns *__aa_find_ns(struct list_head *head,
|
||||||
|
const char *name)
|
||||||
|
{
|
||||||
|
return __aa_findn_ns(head, name, strlen(name));
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* AA_NAMESPACE_H */
|
|
@ -16,12 +16,14 @@
|
||||||
#define __POLICY_INTERFACE_H
|
#define __POLICY_INTERFACE_H
|
||||||
|
|
||||||
#include <linux/list.h>
|
#include <linux/list.h>
|
||||||
|
#include <linux/kref.h>
|
||||||
|
|
||||||
struct aa_load_ent {
|
struct aa_load_ent {
|
||||||
struct list_head list;
|
struct list_head list;
|
||||||
struct aa_profile *new;
|
struct aa_profile *new;
|
||||||
struct aa_profile *old;
|
struct aa_profile *old;
|
||||||
struct aa_profile *rename;
|
struct aa_profile *rename;
|
||||||
|
const char *ns_name;
|
||||||
};
|
};
|
||||||
|
|
||||||
void aa_load_ent_free(struct aa_load_ent *ent);
|
void aa_load_ent_free(struct aa_load_ent *ent);
|
||||||
|
@ -34,6 +36,30 @@ struct aa_load_ent *aa_load_ent_alloc(void);
|
||||||
#define PACKED_MODE_KILL 2
|
#define PACKED_MODE_KILL 2
|
||||||
#define PACKED_MODE_UNCONFINED 3
|
#define PACKED_MODE_UNCONFINED 3
|
||||||
|
|
||||||
int aa_unpack(void *udata, size_t size, struct list_head *lh, const char **ns);
|
/* struct aa_loaddata - buffer of policy load data set */
|
||||||
|
struct aa_loaddata {
|
||||||
|
struct kref count;
|
||||||
|
size_t size;
|
||||||
|
int abi;
|
||||||
|
unsigned char *hash;
|
||||||
|
char data[];
|
||||||
|
};
|
||||||
|
|
||||||
|
int aa_unpack(struct aa_loaddata *udata, struct list_head *lh, const char **ns);
|
||||||
|
|
||||||
|
static inline struct aa_loaddata *
|
||||||
|
aa_get_loaddata(struct aa_loaddata *data)
|
||||||
|
{
|
||||||
|
if (data)
|
||||||
|
kref_get(&(data->count));
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
void aa_loaddata_kref(struct kref *kref);
|
||||||
|
static inline void aa_put_loaddata(struct aa_loaddata *data)
|
||||||
|
{
|
||||||
|
if (data)
|
||||||
|
kref_put(&data->count, aa_loaddata_kref);
|
||||||
|
}
|
||||||
|
|
||||||
#endif /* __POLICY_INTERFACE_H */
|
#endif /* __POLICY_INTERFACE_H */
|
||||||
|
|
|
@ -18,8 +18,7 @@
|
||||||
#define AA_DO_TEST 1
|
#define AA_DO_TEST 1
|
||||||
#define AA_ONEXEC 1
|
#define AA_ONEXEC 1
|
||||||
|
|
||||||
int aa_getprocattr(struct aa_profile *profile, char **string);
|
int aa_getprocattr(struct aa_label *label, char **string);
|
||||||
int aa_setprocattr_changehat(char *args, size_t size, int test);
|
int aa_setprocattr_changehat(char *args, size_t size, int test);
|
||||||
int aa_setprocattr_changeprofile(char *fqname, bool onexec, int test);
|
|
||||||
|
|
||||||
#endif /* __AA_PROCATTR_H */
|
#endif /* __AA_PROCATTR_H */
|
||||||
|
|
|
@ -37,10 +37,10 @@ struct aa_rlimit {
|
||||||
extern struct aa_fs_entry aa_fs_entry_rlimit[];
|
extern struct aa_fs_entry aa_fs_entry_rlimit[];
|
||||||
|
|
||||||
int aa_map_resource(int resource);
|
int aa_map_resource(int resource);
|
||||||
int aa_task_setrlimit(struct aa_profile *profile, struct task_struct *,
|
int aa_task_setrlimit(struct aa_label *label, struct task_struct *,
|
||||||
unsigned int resource, struct rlimit *new_rlim);
|
unsigned int resource, struct rlimit *new_rlim);
|
||||||
|
|
||||||
void __aa_transition_rlimits(struct aa_profile *old, struct aa_profile *new);
|
void __aa_transition_rlimits(struct aa_label *old, struct aa_label *new);
|
||||||
|
|
||||||
static inline void aa_free_rlimit_rules(struct aa_rlimit *rlims)
|
static inline void aa_free_rlimit_rules(struct aa_rlimit *rlims)
|
||||||
{
|
{
|
||||||
|
|
95
security/apparmor/include/sig_names.h
Normal file
95
security/apparmor/include/sig_names.h
Normal 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 */
|
||||||
|
};
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
* This file contains AppArmor ipc mediation
|
* This file contains AppArmor ipc mediation
|
||||||
*
|
*
|
||||||
* Copyright (C) 1998-2008 Novell/SUSE
|
* Copyright (C) 1998-2008 Novell/SUSE
|
||||||
* Copyright 2009-2010 Canonical Ltd.
|
* Copyright 2009-2013 Canonical Ltd.
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or
|
* This program is free software; you can redistribute it and/or
|
||||||
* modify it under the terms of the GNU General Public License as
|
* modify it under the terms of the GNU General Public License as
|
||||||
|
@ -20,92 +20,200 @@
|
||||||
#include "include/context.h"
|
#include "include/context.h"
|
||||||
#include "include/policy.h"
|
#include "include/policy.h"
|
||||||
#include "include/ipc.h"
|
#include "include/ipc.h"
|
||||||
|
#include "include/sig_names.h"
|
||||||
/* 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* aa_audit_ptrace - do auditing for ptrace
|
* audit_ptrace_mask - convert mask to permission string
|
||||||
* @profile: profile being enforced (NOT NULL)
|
* @buffer: buffer to write string to (NOT NULL)
|
||||||
* @target: profile being traced (NOT NULL)
|
* @mask: permission mask to convert
|
||||||
* @error: error condition
|
|
||||||
*
|
|
||||||
* Returns: %0 or error code
|
|
||||||
*/
|
*/
|
||||||
static int aa_audit_ptrace(struct aa_profile *profile,
|
static void audit_ptrace_mask(struct audit_buffer *ab, u32 mask)
|
||||||
struct aa_profile *target, int error)
|
|
||||||
{
|
{
|
||||||
struct common_audit_data sa;
|
switch (mask) {
|
||||||
struct apparmor_audit_data aad = {0,};
|
case MAY_READ:
|
||||||
sa.type = LSM_AUDIT_DATA_NONE;
|
audit_log_string(ab, "read");
|
||||||
sa.aad = &aad;
|
break;
|
||||||
aad.op = OP_PTRACE;
|
case MAY_WRITE:
|
||||||
aad.target = target;
|
audit_log_string(ab, "trace");
|
||||||
aad.error = error;
|
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,
|
/* call back to audit ptrace fields */
|
||||||
audit_cb);
|
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=");
|
||||||
|
aa_label_xaudit(ab, labels_ns(aad(sa)->label), aad(sa)->peer,
|
||||||
|
FLAGS_NONE, GFP_ATOMIC);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 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)->peer = &peer->label;
|
||||||
|
aa_profile_match_label(profile, &peer->label, AA_CLASS_PTRACE, request,
|
||||||
|
&perms);
|
||||||
|
aa_apply_modes_to_perms(profile, &perms);
|
||||||
|
return aa_check_perms(profile, &perms, request, sa, audit_ptrace_cb);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int cross_ptrace_perm(struct aa_profile *tracer,
|
||||||
|
struct aa_profile *tracee, u32 request,
|
||||||
|
struct common_audit_data *sa)
|
||||||
|
{
|
||||||
|
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)->peer = &tracee->label;
|
||||||
|
aad(sa)->request = 0;
|
||||||
|
aad(sa)->error = aa_capable(&tracer->label, CAP_SYS_PTRACE, 1);
|
||||||
|
return aa_audit(AUDIT_APPARMOR_AUTO, tracer, sa, audit_ptrace_cb);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* aa_may_ptrace - test if tracer task can trace the tracee
|
* aa_may_ptrace - test if tracer task can trace the tracee
|
||||||
* @tracer: profile of the task doing the tracing (NOT NULL)
|
* @tracer: label of the task doing the tracing (NOT NULL)
|
||||||
* @tracee: task to be traced
|
* @tracee: task label to be traced
|
||||||
* @mode: whether PTRACE_MODE_READ || PTRACE_MODE_ATTACH
|
* @request: permission request
|
||||||
*
|
*
|
||||||
* Returns: %0 else error code if permission denied or error
|
* Returns: %0 else error code if permission denied or error
|
||||||
*/
|
*/
|
||||||
int aa_may_ptrace(struct aa_profile *tracer, struct aa_profile *tracee,
|
int aa_may_ptrace(struct aa_label *tracer, struct aa_label *tracee,
|
||||||
unsigned int mode)
|
u32 request)
|
||||||
{
|
{
|
||||||
/* TODO: currently only based on capability, not extended ptrace
|
DEFINE_AUDIT_DATA(sa, LSM_AUDIT_DATA_NONE, OP_PTRACE);
|
||||||
* rules,
|
|
||||||
* Test mode for PTRACE_MODE_READ || PTRACE_MODE_ATTACH
|
|
||||||
*/
|
|
||||||
|
|
||||||
if (unconfined(tracer) || tracer == tracee)
|
return xcheck_labels_profiles(tracer, tracee, cross_ptrace_perm,
|
||||||
return 0;
|
request, &sa);
|
||||||
/* log this capability request */
|
}
|
||||||
return aa_capable(tracer, CAP_SYS_PTRACE, 1);
|
|
||||||
|
|
||||||
|
static inline int map_signal_num(int sig)
|
||||||
|
{
|
||||||
|
if (sig > SIGRTMAX)
|
||||||
|
return SIGUNKNOWN;
|
||||||
|
else if (sig >= SIGRTMIN)
|
||||||
|
return sig - SIGRTMIN + 128; /* rt sigs mapped to 128 */
|
||||||
|
else if (sig <= MAXMAPPED_SIG)
|
||||||
|
return sig_map[sig];
|
||||||
|
return SIGUNKNOWN;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* aa_ptrace - do ptrace permission check and auditing
|
* audit_file_mask - convert mask to permission string
|
||||||
* @tracer: task doing the tracing (NOT NULL)
|
* @buffer: buffer to write string to (NOT NULL)
|
||||||
* @tracee: task being traced (NOT NULL)
|
* @mask: permission mask to convert
|
||||||
* @mode: ptrace mode either PTRACE_MODE_READ || PTRACE_MODE_ATTACH
|
|
||||||
*
|
|
||||||
* Returns: %0 else error code if permission denied or error
|
|
||||||
*/
|
*/
|
||||||
int aa_ptrace(struct task_struct *tracer, struct task_struct *tracee,
|
static void audit_signal_mask(struct audit_buffer *ab, u32 mask)
|
||||||
unsigned int mode)
|
|
||||||
{
|
{
|
||||||
/*
|
if (mask & MAY_READ)
|
||||||
* tracer can ptrace tracee when
|
audit_log_string(ab, "receive");
|
||||||
* - tracer is unconfined ||
|
if (mask & MAY_WRITE)
|
||||||
* - tracer is in complain mode
|
audit_log_string(ab, "send");
|
||||||
* - tracer has rules allowing it to trace tracee currently this is:
|
}
|
||||||
* - confined by the same profile ||
|
|
||||||
* - tracer profile has CAP_SYS_PTRACE
|
/**
|
||||||
*/
|
* audit_cb - call back for signal specific audit fields
|
||||||
|
* @ab: audit_buffer (NOT NULL)
|
||||||
struct aa_profile *tracer_p = aa_get_task_profile(tracer);
|
* @va: audit struct to audit values of (NOT NULL)
|
||||||
int error = 0;
|
*/
|
||||||
|
static void audit_signal_cb(struct audit_buffer *ab, void *va)
|
||||||
if (!unconfined(tracer_p)) {
|
{
|
||||||
struct aa_profile *tracee_p = aa_get_task_profile(tracee);
|
struct common_audit_data *sa = va;
|
||||||
|
|
||||||
error = aa_may_ptrace(tracer_p, tracee_p, mode);
|
if (aad(sa)->request & AA_SIGNAL_PERM_MASK) {
|
||||||
error = aa_audit_ptrace(tracer_p, tracee_p, error);
|
audit_log_format(ab, " requested_mask=");
|
||||||
|
audit_signal_mask(ab, aad(sa)->request);
|
||||||
aa_put_profile(tracee_p);
|
if (aad(sa)->denied & AA_SIGNAL_PERM_MASK) {
|
||||||
}
|
audit_log_format(ab, " denied_mask=");
|
||||||
aa_put_profile(tracer_p);
|
audit_signal_mask(ab, aad(sa)->denied);
|
||||||
|
}
|
||||||
return error;
|
}
|
||||||
|
if (aad(sa)->signal <= MAXMAPPED_SIG)
|
||||||
|
audit_log_format(ab, " signal=%s", sig_names[aad(sa)->signal]);
|
||||||
|
else
|
||||||
|
audit_log_format(ab, " signal=rtmin+%d",
|
||||||
|
aad(sa)->signal - 128);
|
||||||
|
audit_log_format(ab, " peer=");
|
||||||
|
aa_label_xaudit(ab, labels_ns(aad(sa)->label), aad(sa)->peer,
|
||||||
|
FLAGS_NONE, GFP_ATOMIC);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* TODO: update to handle compound name&name2, conditionals */
|
||||||
|
static void profile_match_signal(struct aa_profile *profile, const char *label,
|
||||||
|
int signal, struct aa_perms *perms)
|
||||||
|
{
|
||||||
|
unsigned int state;
|
||||||
|
/* TODO: secondary cache check <profile, profile, perm> */
|
||||||
|
state = aa_dfa_next(profile->policy.dfa,
|
||||||
|
profile->policy.start[AA_CLASS_SIGNAL],
|
||||||
|
signal);
|
||||||
|
state = aa_dfa_match(profile->policy.dfa, state, label);
|
||||||
|
aa_compute_perms(profile->policy.dfa, state, perms);
|
||||||
|
}
|
||||||
|
|
||||||
|
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)->peer = &peer->label;
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
|
|
2119
security/apparmor/label.c
Normal file
2119
security/apparmor/label.c
Normal file
File diff suppressed because it is too large
Load diff
|
@ -4,7 +4,7 @@
|
||||||
* This file contains basic common functions used in AppArmor
|
* This file contains basic common functions used in AppArmor
|
||||||
*
|
*
|
||||||
* Copyright (C) 1998-2008 Novell/SUSE
|
* Copyright (C) 1998-2008 Novell/SUSE
|
||||||
* Copyright 2009-2010 Canonical Ltd.
|
* Copyright 2009-2013 Canonical Ltd.
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or
|
* This program is free software; you can redistribute it and/or
|
||||||
* modify it under the terms of the GNU General Public License as
|
* modify it under the terms of the GNU General Public License as
|
||||||
|
@ -12,14 +12,23 @@
|
||||||
* License.
|
* License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include <linux/ctype.h>
|
||||||
#include <linux/mm.h>
|
#include <linux/mm.h>
|
||||||
#include <linux/slab.h>
|
#include <linux/slab.h>
|
||||||
#include <linux/string.h>
|
#include <linux/string.h>
|
||||||
#include <linux/vmalloc.h>
|
#include <linux/vmalloc.h>
|
||||||
|
|
||||||
#include "include/audit.h"
|
|
||||||
#include "include/apparmor.h"
|
#include "include/apparmor.h"
|
||||||
|
#include "include/audit.h"
|
||||||
|
#include "include/label.h"
|
||||||
|
#include "include/lib.h"
|
||||||
|
#include "include/perms.h"
|
||||||
|
#include "include/policy.h"
|
||||||
|
|
||||||
|
struct aa_perms nullperms;
|
||||||
|
struct aa_perms allperms = { .allow = ALL_PERMS_MASK,
|
||||||
|
.quiet = ALL_PERMS_MASK,
|
||||||
|
.hide = ALL_PERMS_MASK };
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* aa_split_fqname - split a fqname into a profile and namespace name
|
* aa_split_fqname - split a fqname into a profile and namespace name
|
||||||
|
@ -59,6 +68,57 @@ char *aa_split_fqname(char *fqname, char **ns_name)
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* skipn_spaces - Removes leading whitespace from @str.
|
||||||
|
* @str: The string to be stripped.
|
||||||
|
*
|
||||||
|
* Returns a pointer to the first non-whitespace character in @str.
|
||||||
|
* if all whitespace will return NULL
|
||||||
|
*/
|
||||||
|
|
||||||
|
static const char *skipn_spaces(const char *str, size_t n)
|
||||||
|
{
|
||||||
|
for (;n && isspace(*str); --n)
|
||||||
|
++str;
|
||||||
|
if (n)
|
||||||
|
return (char *)str;
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *aa_splitn_fqname(const char *fqname, size_t n, const char **ns_name,
|
||||||
|
size_t *ns_len)
|
||||||
|
{
|
||||||
|
const char *end = fqname + n;
|
||||||
|
const char *name = skipn_spaces(fqname, n);
|
||||||
|
if (!name)
|
||||||
|
return NULL;
|
||||||
|
*ns_name = NULL;
|
||||||
|
*ns_len = 0;
|
||||||
|
if (name[0] == ':') {
|
||||||
|
char *split = strnchr(&name[1], end - &name[1], ':');
|
||||||
|
*ns_name = skipn_spaces(&name[1], end - &name[1]);
|
||||||
|
if (!*ns_name)
|
||||||
|
return NULL;
|
||||||
|
if (split) {
|
||||||
|
*ns_len = split - *ns_name;
|
||||||
|
if (*ns_len == 0)
|
||||||
|
*ns_name = NULL;
|
||||||
|
split++;
|
||||||
|
if (end - split > 1 && strncmp(split, "//", 2) == 0)
|
||||||
|
split += 2;
|
||||||
|
name = skipn_spaces(split, end - split);
|
||||||
|
} else {
|
||||||
|
/* a ns name without a following profile is allowed */
|
||||||
|
name = NULL;
|
||||||
|
*ns_len = end - *ns_name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (name && *name == 0)
|
||||||
|
name = NULL;
|
||||||
|
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* aa_info_message - log a none profile related status message
|
* aa_info_message - log a none profile related status message
|
||||||
* @str: message to log
|
* @str: message to log
|
||||||
|
@ -66,11 +126,8 @@ char *aa_split_fqname(char *fqname, char **ns_name)
|
||||||
void aa_info_message(const char *str)
|
void aa_info_message(const char *str)
|
||||||
{
|
{
|
||||||
if (audit_enabled) {
|
if (audit_enabled) {
|
||||||
struct common_audit_data sa;
|
DEFINE_AUDIT_DATA(sa, LSM_AUDIT_DATA_NONE, NULL);
|
||||||
struct apparmor_audit_data aad = {0,};
|
aad(&sa)->info = str;
|
||||||
sa.type = LSM_AUDIT_DATA_NONE;
|
|
||||||
sa.aad = &aad;
|
|
||||||
aad.info = str;
|
|
||||||
aa_audit_msg(AUDIT_APPARMOR_STATUS, &sa, NULL);
|
aa_audit_msg(AUDIT_APPARMOR_STATUS, &sa, NULL);
|
||||||
}
|
}
|
||||||
printk(KERN_INFO "AppArmor: %s\n", str);
|
printk(KERN_INFO "AppArmor: %s\n", str);
|
||||||
|
@ -104,3 +161,405 @@ void *__aa_kvmalloc(size_t size, gfp_t flags)
|
||||||
}
|
}
|
||||||
return buffer;
|
return buffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
__counted char *aa_str_alloc(int size, gfp_t gfp)
|
||||||
|
{
|
||||||
|
struct counted_str *str;
|
||||||
|
str = kmalloc(sizeof(struct counted_str) + size, gfp);
|
||||||
|
if (!str)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
kref_init(&str->count);
|
||||||
|
return str->name;
|
||||||
|
}
|
||||||
|
|
||||||
|
void aa_str_kref(struct kref *kref)
|
||||||
|
{
|
||||||
|
kfree(container_of(kref, struct counted_str, count));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const char aa_file_perm_chrs[] = "xwracd km l ";
|
||||||
|
const char *aa_file_perm_names[] = {
|
||||||
|
"exec",
|
||||||
|
"write",
|
||||||
|
"read",
|
||||||
|
"append",
|
||||||
|
|
||||||
|
"create",
|
||||||
|
"delete",
|
||||||
|
"open",
|
||||||
|
"rename",
|
||||||
|
|
||||||
|
"setattr",
|
||||||
|
"getattr",
|
||||||
|
"setcred",
|
||||||
|
"getcred",
|
||||||
|
|
||||||
|
"chmod",
|
||||||
|
"chown",
|
||||||
|
"chgrp",
|
||||||
|
"lock",
|
||||||
|
|
||||||
|
"mmap",
|
||||||
|
"mprot",
|
||||||
|
"link",
|
||||||
|
"snapshot",
|
||||||
|
|
||||||
|
"unknown",
|
||||||
|
"unknown",
|
||||||
|
"unknown",
|
||||||
|
"unknown",
|
||||||
|
|
||||||
|
"unknown",
|
||||||
|
"unknown",
|
||||||
|
"unknown",
|
||||||
|
"unknown",
|
||||||
|
|
||||||
|
"stack",
|
||||||
|
"change_onexec",
|
||||||
|
"change_profile",
|
||||||
|
"change_hat",
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* aa_perm_mask_to_str - convert a perm mask to its short string
|
||||||
|
* @str: character buffer to store string in (at least 10 characters)
|
||||||
|
* @mask: permission mask to convert
|
||||||
|
*/
|
||||||
|
void aa_perm_mask_to_str(char *str, const char *chrs, u32 mask)
|
||||||
|
{
|
||||||
|
unsigned int i, perm = 1;
|
||||||
|
for (i = 0; i < 32; perm <<= 1, i++) {
|
||||||
|
if (mask & perm)
|
||||||
|
*str++ = chrs[i];
|
||||||
|
}
|
||||||
|
*str = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
void aa_audit_perm_names(struct audit_buffer *ab, const char **names, u32 mask)
|
||||||
|
{
|
||||||
|
const char *fmt = "%s";
|
||||||
|
unsigned int i, perm = 1;
|
||||||
|
bool prev = false;
|
||||||
|
for (i = 0; i < 32; perm <<= 1, i++) {
|
||||||
|
if (mask & perm) {
|
||||||
|
audit_log_format(ab, fmt, names[i]);
|
||||||
|
if (!prev) {
|
||||||
|
prev = true;
|
||||||
|
fmt = " %s";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void aa_audit_perm_mask(struct audit_buffer *ab, u32 mask, const char *chrs,
|
||||||
|
u32 chrsmask, const char **names, u32 namesmask)
|
||||||
|
{
|
||||||
|
char str[33];
|
||||||
|
|
||||||
|
audit_log_format(ab, "\"");
|
||||||
|
if ((mask & chrsmask) && chrs) {
|
||||||
|
aa_perm_mask_to_str(str, chrs, mask & chrsmask);
|
||||||
|
mask &= ~chrsmask;
|
||||||
|
audit_log_format(ab, "%s", str);
|
||||||
|
if (mask & namesmask)
|
||||||
|
audit_log_format(ab, " ");
|
||||||
|
}
|
||||||
|
if ((mask & namesmask) && names)
|
||||||
|
aa_audit_perm_names(ab, names, mask & namesmask);
|
||||||
|
audit_log_format(ab, "\"");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* aa_audit_perms_cb - generic callback fn for auditing perms
|
||||||
|
* @ab: audit buffer (NOT NULL)
|
||||||
|
* @va: audit struct to audit values of (NOT NULL)
|
||||||
|
*/
|
||||||
|
static void aa_audit_perms_cb(struct audit_buffer *ab, void *va)
|
||||||
|
{
|
||||||
|
struct common_audit_data *sa = va;
|
||||||
|
|
||||||
|
if (aad(sa)->request) {
|
||||||
|
audit_log_format(ab, " requested_mask=");
|
||||||
|
aa_audit_perm_mask(ab, aad(sa)->request, aa_file_perm_chrs,
|
||||||
|
PERMS_CHRS_MASK, aa_file_perm_names,
|
||||||
|
PERMS_NAMES_MASK);
|
||||||
|
}
|
||||||
|
if (aad(sa)->denied) {
|
||||||
|
audit_log_format(ab, "denied_mask=");
|
||||||
|
aa_audit_perm_mask(ab, aad(sa)->denied, aa_file_perm_chrs,
|
||||||
|
PERMS_CHRS_MASK, aa_file_perm_names,
|
||||||
|
PERMS_NAMES_MASK);
|
||||||
|
}
|
||||||
|
audit_log_format(ab, " peer=");
|
||||||
|
aa_label_xaudit(ab, labels_ns(aad(sa)->label), aad(sa)->peer,
|
||||||
|
FLAGS_NONE, GFP_ATOMIC);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* aa_apply_modes_to_perms - apply namespace and profile flags to perms
|
||||||
|
* @profile: that perms where computed from
|
||||||
|
* @perms: perms to apply mode modifiers to
|
||||||
|
*
|
||||||
|
* TODO: split into profile and ns based flags for when accumulating perms
|
||||||
|
*/
|
||||||
|
void aa_apply_modes_to_perms(struct aa_profile *profile, struct aa_perms *perms)
|
||||||
|
{
|
||||||
|
switch (AUDIT_MODE(profile)) {
|
||||||
|
case AUDIT_ALL:
|
||||||
|
perms->audit = ALL_PERMS_MASK;
|
||||||
|
/* fall through */
|
||||||
|
case AUDIT_NOQUIET:
|
||||||
|
perms->quiet = 0;
|
||||||
|
break;
|
||||||
|
case AUDIT_QUIET:
|
||||||
|
perms->audit = 0;
|
||||||
|
/* fall through */
|
||||||
|
case AUDIT_QUIET_DENIED:
|
||||||
|
perms->quiet = ALL_PERMS_MASK;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (KILL_MODE(profile))
|
||||||
|
perms->kill = ALL_PERMS_MASK;
|
||||||
|
else if (COMPLAIN_MODE(profile))
|
||||||
|
perms->complain = ALL_PERMS_MASK;
|
||||||
|
/* TODO:
|
||||||
|
else if (PROMPT_MODE(profile))
|
||||||
|
perms->prompt = ALL_PERMS_MASK;
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
static u32 map_other(u32 x)
|
||||||
|
{
|
||||||
|
return ((x & 0x3) << 8) | /* SETATTR/GETATTR */
|
||||||
|
((x & 0x1c) << 18) | /* ACCEPT/BIND/LISTEN */
|
||||||
|
((x & 0x60) << 19); /* SETOPT/GETOPT */
|
||||||
|
}
|
||||||
|
|
||||||
|
void aa_compute_perms(struct aa_dfa *dfa, unsigned int state,
|
||||||
|
struct aa_perms *perms)
|
||||||
|
{
|
||||||
|
perms->deny = 0;
|
||||||
|
perms->kill = perms->stop = 0;
|
||||||
|
perms->complain = perms->cond = 0;
|
||||||
|
perms->hide = 0;
|
||||||
|
perms->prompt = 0;
|
||||||
|
perms->allow = dfa_user_allow(dfa, state);
|
||||||
|
perms->audit = dfa_user_audit(dfa, state);
|
||||||
|
perms->quiet = dfa_user_quiet(dfa, state);
|
||||||
|
|
||||||
|
/* for v5 perm mapping in the policydb, the other set is used
|
||||||
|
* to extend the general perm set
|
||||||
|
*/
|
||||||
|
perms->allow |= map_other(dfa_other_allow(dfa, state)) | AA_MAY_LOCK;
|
||||||
|
perms->audit |= map_other(dfa_other_audit(dfa, state));
|
||||||
|
perms->quiet |= map_other(dfa_other_quiet(dfa, state));
|
||||||
|
// perms->xindex = dfa_user_xindex(dfa, state);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* aa_perms_accum_raw - accumulate perms with out masking off overlapping perms
|
||||||
|
* @accum - perms struct to accumulate into
|
||||||
|
* @addend - perms struct to add to @accum
|
||||||
|
*/
|
||||||
|
void aa_perms_accum_raw(struct aa_perms *accum, struct aa_perms *addend)
|
||||||
|
{
|
||||||
|
accum->deny |= addend->deny;
|
||||||
|
accum->allow &= addend->allow & ~addend->deny;
|
||||||
|
accum->audit |= addend->audit & addend->allow;
|
||||||
|
accum->quiet &= addend->quiet & ~addend->allow;
|
||||||
|
accum->kill |= addend->kill & ~addend->allow;
|
||||||
|
accum->stop |= addend->stop & ~addend->allow;
|
||||||
|
accum->complain |= addend->complain & ~addend->allow & ~addend->deny;
|
||||||
|
accum->cond |= addend->cond & ~addend->allow & ~addend->deny;
|
||||||
|
accum->hide &= addend->hide & ~addend->allow;
|
||||||
|
accum->prompt |= addend->prompt & ~addend->allow & ~addend->deny;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* aa_perms_accum - accumulate perms, masking off overlapping perms
|
||||||
|
* @accum - perms struct to accumulate into
|
||||||
|
* @addend - perms struct to add to @accum
|
||||||
|
*/
|
||||||
|
void aa_perms_accum(struct aa_perms *accum, struct aa_perms *addend)
|
||||||
|
{
|
||||||
|
accum->deny |= addend->deny;
|
||||||
|
accum->allow &= addend->allow & ~accum->deny;
|
||||||
|
accum->audit |= addend->audit & accum->allow;
|
||||||
|
accum->quiet &= addend->quiet & ~accum->allow;
|
||||||
|
accum->kill |= addend->kill & ~accum->allow;
|
||||||
|
accum->stop |= addend->stop & ~accum->allow;
|
||||||
|
accum->complain |= addend->complain & ~accum->allow & ~accum->deny;
|
||||||
|
accum->cond |= addend->cond & ~accum->allow & ~accum->deny;
|
||||||
|
accum->hide &= addend->hide & ~accum->allow;
|
||||||
|
accum->prompt |= addend->prompt & ~accum->allow & ~accum->deny;
|
||||||
|
}
|
||||||
|
|
||||||
|
void aa_profile_match_label(struct aa_profile *profile, struct aa_label *label,
|
||||||
|
int type, u32 request, struct aa_perms *perms)
|
||||||
|
{
|
||||||
|
/* TODO: doesn't yet handle extended types */
|
||||||
|
unsigned int state;
|
||||||
|
state = aa_dfa_next(profile->policy.dfa,
|
||||||
|
profile->policy.start[AA_CLASS_LABEL],
|
||||||
|
type);
|
||||||
|
aa_label_match(profile, label, state, false, request, perms);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* currently unused */
|
||||||
|
int aa_profile_label_perm(struct aa_profile *profile, struct aa_profile *target,
|
||||||
|
u32 request, int type, u32 *deny,
|
||||||
|
struct common_audit_data *sa)
|
||||||
|
{
|
||||||
|
struct aa_perms perms;
|
||||||
|
aad(sa)->label = &profile->label;
|
||||||
|
aad(sa)->peer = &target->label;
|
||||||
|
aad(sa)->request = request;
|
||||||
|
|
||||||
|
aa_profile_match_label(profile, &target->label, type, request, &perms);
|
||||||
|
aa_apply_modes_to_perms(profile, &perms);
|
||||||
|
*deny |= request & perms.deny;
|
||||||
|
return aa_check_perms(profile, &perms, request, sa, aa_audit_perms_cb);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* aa_check_perms - do audit mode selection based on perms set
|
||||||
|
* @profile: profile being checked
|
||||||
|
* @perms: perms computed for the request
|
||||||
|
* @request: requested perms
|
||||||
|
* @deny: Returns: explicit deny set
|
||||||
|
* @sa: initialized audit structure (MAY BE NULL if not auditing)
|
||||||
|
* @cb: callback fn for tpye specific fields (MAY BE NULL)
|
||||||
|
*
|
||||||
|
* Returns: 0 if permission else error code
|
||||||
|
*
|
||||||
|
* Note: profile audit modes need to be set before calling by setting the
|
||||||
|
* perm masks appropriately.
|
||||||
|
*
|
||||||
|
* If not auditing then complain mode is not enabled and the
|
||||||
|
* error code will indicate whether there was an explicit deny
|
||||||
|
* with a positive value.
|
||||||
|
*/
|
||||||
|
int aa_check_perms(struct aa_profile *profile, struct aa_perms *perms,
|
||||||
|
u32 request, struct common_audit_data *sa,
|
||||||
|
void (*cb) (struct audit_buffer *, void *))
|
||||||
|
{
|
||||||
|
int type, error;
|
||||||
|
bool stop = false;
|
||||||
|
u32 denied = request & (~perms->allow | perms->deny);
|
||||||
|
if (likely(!denied)) {
|
||||||
|
/* mask off perms that are not being force audited */
|
||||||
|
request &= perms->audit;
|
||||||
|
if (!request || !sa)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
type = AUDIT_APPARMOR_AUDIT;
|
||||||
|
error = 0;
|
||||||
|
} else {
|
||||||
|
error = -EACCES;
|
||||||
|
|
||||||
|
if (denied & perms->kill)
|
||||||
|
type = AUDIT_APPARMOR_KILL;
|
||||||
|
else if (denied == (denied & perms->complain))
|
||||||
|
type = AUDIT_APPARMOR_ALLOWED;
|
||||||
|
else
|
||||||
|
type = AUDIT_APPARMOR_DENIED;
|
||||||
|
|
||||||
|
if (denied & perms->stop)
|
||||||
|
stop = true;
|
||||||
|
if (denied == (denied & perms->hide))
|
||||||
|
error = -ENOENT;
|
||||||
|
|
||||||
|
denied &= ~perms->quiet;
|
||||||
|
if (!sa || !denied)
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sa) {
|
||||||
|
aad(sa)->label = &profile->label;
|
||||||
|
aad(sa)->request = request;
|
||||||
|
aad(sa)->denied = denied;
|
||||||
|
aad(sa)->error = error;
|
||||||
|
aa_audit_msg(type, sa, cb);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type == AUDIT_APPARMOR_ALLOWED)
|
||||||
|
error = 0;
|
||||||
|
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *aa_imode_name(umode_t mode)
|
||||||
|
{
|
||||||
|
switch(mode & S_IFMT) {
|
||||||
|
case S_IFSOCK:
|
||||||
|
return "sock";
|
||||||
|
case S_IFLNK:
|
||||||
|
return "link";
|
||||||
|
case S_IFREG:
|
||||||
|
return "reg";
|
||||||
|
case S_IFBLK:
|
||||||
|
return "blkdev";
|
||||||
|
case S_IFDIR:
|
||||||
|
return "dir";
|
||||||
|
case S_IFCHR:
|
||||||
|
return "chrdev";
|
||||||
|
case S_IFIFO:
|
||||||
|
return "fifo";
|
||||||
|
}
|
||||||
|
return "unknown";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* aa_policy_init - initialize a policy structure
|
||||||
|
* @policy: policy to initialize (NOT NULL)
|
||||||
|
* @prefix: prefix name if any is required. (MAYBE NULL)
|
||||||
|
* @name: name of the policy, init will make a copy of it (NOT NULL)
|
||||||
|
* @gfp: allocation mode
|
||||||
|
*
|
||||||
|
* Note: this fn creates a copy of strings passed in
|
||||||
|
*
|
||||||
|
* Returns: true if policy init successful
|
||||||
|
*/
|
||||||
|
bool aa_policy_init(struct aa_policy *policy, const char *prefix,
|
||||||
|
const char *name, gfp_t gfp)
|
||||||
|
{
|
||||||
|
char *hname;
|
||||||
|
|
||||||
|
/* freed by policy_free */
|
||||||
|
if (prefix) {
|
||||||
|
hname = aa_str_alloc(strlen(prefix) + strlen(name) + 3, gfp);
|
||||||
|
if (hname)
|
||||||
|
sprintf(hname, "%s//%s", prefix, name);
|
||||||
|
} else {
|
||||||
|
hname = aa_str_alloc(strlen(name) + 1, gfp);
|
||||||
|
if (hname)
|
||||||
|
strcpy(hname, name);
|
||||||
|
}
|
||||||
|
if (!hname)
|
||||||
|
return 0;
|
||||||
|
policy->hname = hname;
|
||||||
|
/* base.name is a substring of fqname */
|
||||||
|
policy->name = (char *) basename(policy->hname);
|
||||||
|
INIT_LIST_HEAD(&policy->list);
|
||||||
|
INIT_LIST_HEAD(&policy->profiles);
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* aa_policy_destroy - free the elements referenced by @policy
|
||||||
|
* @policy: policy that is to have its elements freed (NOT NULL)
|
||||||
|
*/
|
||||||
|
void aa_policy_destroy(struct aa_policy *policy)
|
||||||
|
{
|
||||||
|
AA_BUG(on_list_rcu(&policy->profiles));
|
||||||
|
AA_BUG(on_list_rcu(&policy->list));
|
||||||
|
|
||||||
|
/* don't free name as its a subset of hname */
|
||||||
|
aa_put_str(policy->hname);
|
||||||
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -20,11 +20,38 @@
|
||||||
#include <linux/err.h>
|
#include <linux/err.h>
|
||||||
#include <linux/kref.h>
|
#include <linux/kref.h>
|
||||||
|
|
||||||
#include "include/apparmor.h"
|
#include "include/lib.h"
|
||||||
#include "include/match.h"
|
#include "include/match.h"
|
||||||
|
|
||||||
#define base_idx(X) ((X) & 0xffffff)
|
#define base_idx(X) ((X) & 0xffffff)
|
||||||
|
|
||||||
|
static char nulldfa_src[] = {
|
||||||
|
#include "nulldfa.in"
|
||||||
|
};
|
||||||
|
struct aa_dfa *nulldfa;
|
||||||
|
|
||||||
|
int aa_setup_dfa_engine(void)
|
||||||
|
{
|
||||||
|
int error;
|
||||||
|
|
||||||
|
nulldfa = aa_dfa_unpack(nulldfa_src, sizeof(nulldfa_src),
|
||||||
|
TO_ACCEPT1_FLAG(YYTD_DATA32) |
|
||||||
|
TO_ACCEPT2_FLAG(YYTD_DATA32));
|
||||||
|
if (!IS_ERR(nulldfa))
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
error = PTR_ERR(nulldfa);
|
||||||
|
nulldfa = NULL;
|
||||||
|
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
|
void aa_teardown_dfa_engine(void)
|
||||||
|
{
|
||||||
|
aa_put_dfa(nulldfa);
|
||||||
|
nulldfa = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* unpack_table - unpack a dfa table (one of accept, default, base, next check)
|
* unpack_table - unpack a dfa table (one of accept, default, base, next check)
|
||||||
* @blob: data to unpack (NOT NULL)
|
* @blob: data to unpack (NOT NULL)
|
||||||
|
@ -47,6 +74,8 @@ static struct table_header *unpack_table(char *blob, size_t bsize)
|
||||||
* it every time we use td_id as an index
|
* it every time we use td_id as an index
|
||||||
*/
|
*/
|
||||||
th.td_id = be16_to_cpu(*(u16 *) (blob)) - 1;
|
th.td_id = be16_to_cpu(*(u16 *) (blob)) - 1;
|
||||||
|
if (th.td_id > YYTD_ID_MAX)
|
||||||
|
goto out;
|
||||||
th.td_flags = be16_to_cpu(*(u16 *) (blob + 2));
|
th.td_flags = be16_to_cpu(*(u16 *) (blob + 2));
|
||||||
th.td_lolen = be32_to_cpu(*(u32 *) (blob + 8));
|
th.td_lolen = be32_to_cpu(*(u32 *) (blob + 8));
|
||||||
blob += sizeof(struct table_header);
|
blob += sizeof(struct table_header);
|
||||||
|
@ -61,7 +90,9 @@ static struct table_header *unpack_table(char *blob, size_t bsize)
|
||||||
|
|
||||||
table = kvzalloc(tsize);
|
table = kvzalloc(tsize);
|
||||||
if (table) {
|
if (table) {
|
||||||
*table = th;
|
table->td_id = th.td_id;
|
||||||
|
table->td_flags = th.td_flags;
|
||||||
|
table->td_lolen = th.td_lolen;
|
||||||
if (th.td_flags == YYTD_DATA8)
|
if (th.td_flags == YYTD_DATA8)
|
||||||
UNPACK_ARRAY(table->td_data, blob, th.td_lolen,
|
UNPACK_ARRAY(table->td_data, blob, th.td_lolen,
|
||||||
u8, byte_to_byte);
|
u8, byte_to_byte);
|
||||||
|
@ -73,14 +104,14 @@ static struct table_header *unpack_table(char *blob, size_t bsize)
|
||||||
u32, be32_to_cpu);
|
u32, be32_to_cpu);
|
||||||
else
|
else
|
||||||
goto fail;
|
goto fail;
|
||||||
|
/* if table was vmalloced make sure the page tables are synced
|
||||||
|
* before it is used, as it goes live to all cpus.
|
||||||
|
*/
|
||||||
|
if (is_vmalloc_addr(table))
|
||||||
|
vm_unmap_aliases();
|
||||||
}
|
}
|
||||||
|
|
||||||
out:
|
out:
|
||||||
/* if table was vmalloced make sure the page tables are synced
|
|
||||||
* before it is used, as it goes live to all cpus.
|
|
||||||
*/
|
|
||||||
if (is_vmalloc_addr(table))
|
|
||||||
vm_unmap_aliases();
|
|
||||||
return table;
|
return table;
|
||||||
fail:
|
fail:
|
||||||
kvfree(table);
|
kvfree(table);
|
||||||
|
|
705
security/apparmor/mount.c
Normal file
705
security/apparmor/mount.c
Normal file
|
@ -0,0 +1,705 @@
|
||||||
|
/*
|
||||||
|
* AppArmor security module
|
||||||
|
*
|
||||||
|
* This file contains AppArmor mediation of files
|
||||||
|
*
|
||||||
|
* Copyright (C) 1998-2008 Novell/SUSE
|
||||||
|
* Copyright 2009-2012 Canonical Ltd.
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU General Public License as
|
||||||
|
* published by the Free Software Foundation, version 2 of the
|
||||||
|
* License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <linux/fs.h>
|
||||||
|
#include <linux/mount.h>
|
||||||
|
#include <linux/namei.h>
|
||||||
|
|
||||||
|
#include "include/apparmor.h"
|
||||||
|
#include "include/audit.h"
|
||||||
|
#include "include/context.h"
|
||||||
|
#include "include/domain.h"
|
||||||
|
#include "include/file.h"
|
||||||
|
#include "include/match.h"
|
||||||
|
#include "include/mount.h"
|
||||||
|
#include "include/path.h"
|
||||||
|
#include "include/policy.h"
|
||||||
|
|
||||||
|
|
||||||
|
static void audit_mnt_flags(struct audit_buffer *ab, unsigned long flags)
|
||||||
|
{
|
||||||
|
if (flags & MS_RDONLY)
|
||||||
|
audit_log_format(ab, "ro");
|
||||||
|
else
|
||||||
|
audit_log_format(ab, "rw");
|
||||||
|
if (flags & MS_NOSUID)
|
||||||
|
audit_log_format(ab, ", nosuid");
|
||||||
|
if (flags & MS_NODEV)
|
||||||
|
audit_log_format(ab, ", nodev");
|
||||||
|
if (flags & MS_NOEXEC)
|
||||||
|
audit_log_format(ab, ", noexec");
|
||||||
|
if (flags & MS_SYNCHRONOUS)
|
||||||
|
audit_log_format(ab, ", sync");
|
||||||
|
if (flags & MS_REMOUNT)
|
||||||
|
audit_log_format(ab, ", remount");
|
||||||
|
if (flags & MS_MANDLOCK)
|
||||||
|
audit_log_format(ab, ", mand");
|
||||||
|
if (flags & MS_DIRSYNC)
|
||||||
|
audit_log_format(ab, ", dirsync");
|
||||||
|
if (flags & MS_NOATIME)
|
||||||
|
audit_log_format(ab, ", noatime");
|
||||||
|
if (flags & MS_NODIRATIME)
|
||||||
|
audit_log_format(ab, ", nodiratime");
|
||||||
|
if (flags & MS_BIND)
|
||||||
|
audit_log_format(ab, flags & MS_REC ? ", rbind" : ", bind");
|
||||||
|
if (flags & MS_MOVE)
|
||||||
|
audit_log_format(ab, ", move");
|
||||||
|
if (flags & MS_SILENT)
|
||||||
|
audit_log_format(ab, ", silent");
|
||||||
|
if (flags & MS_POSIXACL)
|
||||||
|
audit_log_format(ab, ", acl");
|
||||||
|
if (flags & MS_UNBINDABLE)
|
||||||
|
audit_log_format(ab, flags & MS_REC ? ", runbindable" :
|
||||||
|
", unbindable");
|
||||||
|
if (flags & MS_PRIVATE)
|
||||||
|
audit_log_format(ab, flags & MS_REC ? ", rprivate" :
|
||||||
|
", private");
|
||||||
|
if (flags & MS_SLAVE)
|
||||||
|
audit_log_format(ab, flags & MS_REC ? ", rslave" :
|
||||||
|
", slave");
|
||||||
|
if (flags & MS_SHARED)
|
||||||
|
audit_log_format(ab, flags & MS_REC ? ", rshared" :
|
||||||
|
", shared");
|
||||||
|
if (flags & MS_RELATIME)
|
||||||
|
audit_log_format(ab, ", relatime");
|
||||||
|
if (flags & MS_I_VERSION)
|
||||||
|
audit_log_format(ab, ", iversion");
|
||||||
|
if (flags & MS_STRICTATIME)
|
||||||
|
audit_log_format(ab, ", strictatime");
|
||||||
|
if (flags & MS_NOUSER)
|
||||||
|
audit_log_format(ab, ", nouser");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* audit_cb - call back for mount specific audit fields
|
||||||
|
* @ab: audit_buffer (NOT NULL)
|
||||||
|
* @va: audit struct to audit values of (NOT NULL)
|
||||||
|
*/
|
||||||
|
static void audit_cb(struct audit_buffer *ab, void *va)
|
||||||
|
{
|
||||||
|
struct common_audit_data *sa = va;
|
||||||
|
|
||||||
|
if (aad(sa)->mnt.type) {
|
||||||
|
audit_log_format(ab, " fstype=");
|
||||||
|
audit_log_untrustedstring(ab, aad(sa)->mnt.type);
|
||||||
|
}
|
||||||
|
if (aad(sa)->mnt.src_name) {
|
||||||
|
audit_log_format(ab, " srcname=");
|
||||||
|
audit_log_untrustedstring(ab, aad(sa)->mnt.src_name);
|
||||||
|
}
|
||||||
|
if (aad(sa)->mnt.trans) {
|
||||||
|
audit_log_format(ab, " trans=");
|
||||||
|
audit_log_untrustedstring(ab, aad(sa)->mnt.trans);
|
||||||
|
}
|
||||||
|
if (aad(sa)->mnt.flags) {
|
||||||
|
audit_log_format(ab, " flags=\"");
|
||||||
|
audit_mnt_flags(ab, aad(sa)->mnt.flags);
|
||||||
|
audit_log_format(ab, "\"");
|
||||||
|
}
|
||||||
|
if (aad(sa)->mnt.data) {
|
||||||
|
audit_log_format(ab, " options=");
|
||||||
|
audit_log_untrustedstring(ab, aad(sa)->mnt.data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* audit_mount - handle the auditing of mount operations
|
||||||
|
* @profile: the profile being enforced (NOT NULL)
|
||||||
|
* @op: operation being mediated (NOT NULL)
|
||||||
|
* @name: name of object being mediated (MAYBE NULL)
|
||||||
|
* @src_name: src_name of object being mediated (MAYBE_NULL)
|
||||||
|
* @type: type of filesystem (MAYBE_NULL)
|
||||||
|
* @trans: name of trans (MAYBE NULL)
|
||||||
|
* @flags: filesystem idependent mount flags
|
||||||
|
* @data: filesystem mount flags
|
||||||
|
* @request: permissions requested
|
||||||
|
* @perms: the permissions computed for the request (NOT NULL)
|
||||||
|
* @info: extra information message (MAYBE NULL)
|
||||||
|
* @error: 0 if operation allowed else failure error code
|
||||||
|
*
|
||||||
|
* Returns: %0 or error on failure
|
||||||
|
*/
|
||||||
|
static int audit_mount(struct aa_profile *profile, const char *op, const char *name,
|
||||||
|
const char *src_name, const char *type,
|
||||||
|
const char *trans, unsigned long flags,
|
||||||
|
const void *data, u32 request, struct aa_perms *perms,
|
||||||
|
const char *info, int error)
|
||||||
|
{
|
||||||
|
int audit_type = AUDIT_APPARMOR_AUTO;
|
||||||
|
DEFINE_AUDIT_DATA(sa, LSM_AUDIT_DATA_NONE, op);
|
||||||
|
|
||||||
|
if (likely(!error)) {
|
||||||
|
u32 mask = perms->audit;
|
||||||
|
|
||||||
|
if (unlikely(AUDIT_MODE(profile) == AUDIT_ALL))
|
||||||
|
mask = 0xffff;
|
||||||
|
|
||||||
|
/* mask off perms that are not being force audited */
|
||||||
|
request &= mask;
|
||||||
|
|
||||||
|
if (likely(!request))
|
||||||
|
return 0;
|
||||||
|
audit_type = AUDIT_APPARMOR_AUDIT;
|
||||||
|
} else {
|
||||||
|
/* only report permissions that were denied */
|
||||||
|
request = request & ~perms->allow;
|
||||||
|
|
||||||
|
if (request & perms->kill)
|
||||||
|
audit_type = AUDIT_APPARMOR_KILL;
|
||||||
|
|
||||||
|
/* quiet known rejects, assumes quiet and kill do not overlap */
|
||||||
|
if ((request & perms->quiet) &&
|
||||||
|
AUDIT_MODE(profile) != AUDIT_NOQUIET &&
|
||||||
|
AUDIT_MODE(profile) != AUDIT_ALL)
|
||||||
|
request &= ~perms->quiet;
|
||||||
|
|
||||||
|
if (!request)
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
|
aad(&sa)->name = name;
|
||||||
|
aad(&sa)->mnt.src_name = src_name;
|
||||||
|
aad(&sa)->mnt.type = type;
|
||||||
|
aad(&sa)->mnt.trans = trans;
|
||||||
|
aad(&sa)->mnt.flags = flags;
|
||||||
|
if (data && (perms->audit & AA_AUDIT_DATA))
|
||||||
|
aad(&sa)->mnt.data = data;
|
||||||
|
aad(&sa)->info = info;
|
||||||
|
aad(&sa)->error = error;
|
||||||
|
|
||||||
|
return aa_audit(audit_type, profile, &sa, audit_cb);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* match_mnt_flags - Do an ordered match on mount flags
|
||||||
|
* @dfa: dfa to match against
|
||||||
|
* @state: state to start in
|
||||||
|
* @flags: mount flags to match against
|
||||||
|
*
|
||||||
|
* Mount flags are encoded as an ordered match. This is done instead of
|
||||||
|
* checking against a simple bitmask, to allow for logical operations
|
||||||
|
* on the flags.
|
||||||
|
*
|
||||||
|
* Returns: next state after flags match
|
||||||
|
*/
|
||||||
|
static unsigned int match_mnt_flags(struct aa_dfa *dfa, unsigned int state,
|
||||||
|
unsigned long flags)
|
||||||
|
{
|
||||||
|
unsigned int i;
|
||||||
|
|
||||||
|
for (i = 0; i <= 31 ; ++i) {
|
||||||
|
if ((1 << i) & flags)
|
||||||
|
state = aa_dfa_next(dfa, state, i + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* compute_mnt_perms - compute mount permission associated with @state
|
||||||
|
* @dfa: dfa to match against (NOT NULL)
|
||||||
|
* @state: state match finished in
|
||||||
|
*
|
||||||
|
* Returns: mount permissions
|
||||||
|
*/
|
||||||
|
static struct aa_perms compute_mnt_perms(struct aa_dfa *dfa,
|
||||||
|
unsigned int state)
|
||||||
|
{
|
||||||
|
struct aa_perms perms;
|
||||||
|
|
||||||
|
perms.kill = 0;
|
||||||
|
perms.allow = dfa_user_allow(dfa, state);
|
||||||
|
perms.audit = dfa_user_audit(dfa, state);
|
||||||
|
perms.quiet = dfa_user_quiet(dfa, state);
|
||||||
|
perms.xindex = dfa_user_xindex(dfa, state);
|
||||||
|
|
||||||
|
return perms;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char *mnt_info_table[] = {
|
||||||
|
"match succeeded",
|
||||||
|
"failed mntpnt match",
|
||||||
|
"failed srcname match",
|
||||||
|
"failed type match",
|
||||||
|
"failed flags match",
|
||||||
|
"failed data match"
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Returns 0 on success else element that match failed in, this is the
|
||||||
|
* index into the mnt_info_table above
|
||||||
|
*/
|
||||||
|
static int do_match_mnt(struct aa_dfa *dfa, unsigned int start,
|
||||||
|
const char *mntpnt, const char *devname,
|
||||||
|
const char *type, unsigned long flags,
|
||||||
|
void *data, bool binary, struct aa_perms *perms)
|
||||||
|
{
|
||||||
|
unsigned int state;
|
||||||
|
|
||||||
|
AA_BUG(!dfa);
|
||||||
|
AA_BUG(!perms);
|
||||||
|
|
||||||
|
state = aa_dfa_match(dfa, start, mntpnt);
|
||||||
|
state = aa_dfa_null_transition(dfa, state);
|
||||||
|
if (!state)
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
if (devname)
|
||||||
|
state = aa_dfa_match(dfa, state, devname);
|
||||||
|
state = aa_dfa_null_transition(dfa, state);
|
||||||
|
if (!state)
|
||||||
|
return 2;
|
||||||
|
|
||||||
|
if (type)
|
||||||
|
state = aa_dfa_match(dfa, state, type);
|
||||||
|
state = aa_dfa_null_transition(dfa, state);
|
||||||
|
if (!state)
|
||||||
|
return 3;
|
||||||
|
|
||||||
|
state = match_mnt_flags(dfa, state, flags);
|
||||||
|
if (!state)
|
||||||
|
return 4;
|
||||||
|
*perms = compute_mnt_perms(dfa, state);
|
||||||
|
if (perms->allow & AA_MAY_MOUNT)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
/* only match data if not binary and the DFA flags data is expected */
|
||||||
|
if (data && !binary && (perms->allow & AA_MNT_CONT_MATCH)) {
|
||||||
|
state = aa_dfa_null_transition(dfa, state);
|
||||||
|
if (!state)
|
||||||
|
return 4;
|
||||||
|
|
||||||
|
state = aa_dfa_match(dfa, state, data);
|
||||||
|
if (!state)
|
||||||
|
return 5;
|
||||||
|
*perms = compute_mnt_perms(dfa, state);
|
||||||
|
if (perms->allow & AA_MAY_MOUNT)
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* failed at end of flags match */
|
||||||
|
return 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static int path_flags(struct aa_profile *profile, const struct path *path)
|
||||||
|
{
|
||||||
|
AA_BUG(!profile);
|
||||||
|
AA_BUG(!path);
|
||||||
|
|
||||||
|
return profile->path_flags |
|
||||||
|
(S_ISDIR(path->dentry->d_inode->i_mode) ? PATH_IS_DIR : 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* match_mnt_path_str - handle path matching for mount
|
||||||
|
* @profile: the confining profile
|
||||||
|
* @mntpath: for the mntpnt (NOT NULL)
|
||||||
|
* @buffer: buffer to be used to lookup mntpath
|
||||||
|
* @devnme: string for the devname/src_name (MAY BE NULL OR ERRPTR)
|
||||||
|
* @type: string for the dev type (MAYBE NULL)
|
||||||
|
* @flags: mount flags to match
|
||||||
|
* @data: fs mount data (MAYBE NULL)
|
||||||
|
* @binary: whether @data is binary
|
||||||
|
* @devinfo: error str if (IS_ERR(@devname))
|
||||||
|
*
|
||||||
|
* Returns: 0 on success else error
|
||||||
|
*/
|
||||||
|
static int match_mnt_path_str(struct aa_profile *profile, const struct path *mntpath,
|
||||||
|
char *buffer, const char *devname,
|
||||||
|
const char *type, unsigned long flags,
|
||||||
|
void *data, bool binary, const char *devinfo)
|
||||||
|
{
|
||||||
|
struct aa_perms perms = { };
|
||||||
|
const char *mntpnt = NULL, *info = NULL;
|
||||||
|
int pos, error;
|
||||||
|
|
||||||
|
AA_BUG(!profile);
|
||||||
|
AA_BUG(!mntpath);
|
||||||
|
AA_BUG(!buffer);
|
||||||
|
|
||||||
|
error = aa_path_name(mntpath, path_flags(profile, mntpath), buffer,
|
||||||
|
&mntpnt, &info, profile->disconnected);
|
||||||
|
if (error)
|
||||||
|
goto audit;
|
||||||
|
if (IS_ERR(devname)) {
|
||||||
|
error = PTR_ERR(devname);
|
||||||
|
devname = NULL;
|
||||||
|
info = devinfo;
|
||||||
|
goto audit;
|
||||||
|
}
|
||||||
|
|
||||||
|
error = -EACCES;
|
||||||
|
pos = do_match_mnt(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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* match_mnt - handle path matching for mount
|
||||||
|
* @profile: the confining profile
|
||||||
|
* @mntpath: for the mntpnt (NOT NULL)
|
||||||
|
* @buffer: buffer to be used to lookup mntpath
|
||||||
|
* @devpath: path devname/src_name (MAYBE NULL)
|
||||||
|
* @devbuffer: buffer to be used to lookup devname/src_name
|
||||||
|
* @type: string for the dev type (MAYBE NULL)
|
||||||
|
* @flags: mount flags to match
|
||||||
|
* @data: fs mount data (MAYBE NULL)
|
||||||
|
* @binary: whether @data is binary
|
||||||
|
*
|
||||||
|
* Returns: 0 on success else error
|
||||||
|
*/
|
||||||
|
static int match_mnt(struct aa_profile *profile, const struct path *path,
|
||||||
|
char *buffer, struct path *devpath, char *devbuffer,
|
||||||
|
const char *type, unsigned long flags, void *data,
|
||||||
|
bool binary)
|
||||||
|
{
|
||||||
|
const char *devname = NULL, *info = NULL;
|
||||||
|
int error = -EACCES;
|
||||||
|
|
||||||
|
AA_BUG(!profile);
|
||||||
|
AA_BUG(devpath && !devbuffer);
|
||||||
|
|
||||||
|
if (devpath) {
|
||||||
|
error = aa_path_name(devpath, path_flags(profile, devpath),
|
||||||
|
devbuffer, &devname, &info,
|
||||||
|
profile->disconnected);
|
||||||
|
if (error)
|
||||||
|
devname = ERR_PTR(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
return match_mnt_path_str(profile, path, buffer, devname, type, flags,
|
||||||
|
data, binary, info);
|
||||||
|
}
|
||||||
|
|
||||||
|
int aa_remount(struct aa_label *label, const struct path *path,
|
||||||
|
unsigned long flags, void *data)
|
||||||
|
{
|
||||||
|
struct aa_profile *profile;
|
||||||
|
char *buffer = NULL;
|
||||||
|
bool binary;
|
||||||
|
int error;
|
||||||
|
|
||||||
|
AA_BUG(!label);
|
||||||
|
AA_BUG(!path);
|
||||||
|
|
||||||
|
binary = path->dentry->d_sb->s_type->fs_flags & FS_BINARY_MOUNTDATA;
|
||||||
|
|
||||||
|
get_buffers(buffer);
|
||||||
|
error = fn_for_each_confined(label, profile,
|
||||||
|
match_mnt(profile, path, buffer, NULL, NULL, NULL,
|
||||||
|
flags, data, binary));
|
||||||
|
put_buffers(buffer);
|
||||||
|
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
|
int aa_bind_mount(struct aa_label *label, const struct path *path,
|
||||||
|
const char *dev_name, unsigned long flags)
|
||||||
|
{
|
||||||
|
struct aa_profile *profile;
|
||||||
|
char *buffer = NULL, *old_buffer = NULL;
|
||||||
|
struct path old_path;
|
||||||
|
int error;
|
||||||
|
|
||||||
|
AA_BUG(!label);
|
||||||
|
AA_BUG(!path);
|
||||||
|
|
||||||
|
if (!dev_name || !*dev_name)
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
flags &= MS_REC | MS_BIND;
|
||||||
|
|
||||||
|
error = kern_path(dev_name, LOOKUP_FOLLOW|LOOKUP_AUTOMOUNT, &old_path);
|
||||||
|
if (error)
|
||||||
|
return error;
|
||||||
|
|
||||||
|
get_buffers(buffer, old_buffer);
|
||||||
|
error = fn_for_each_confined(label, profile,
|
||||||
|
match_mnt(profile, path, buffer, &old_path, old_buffer,
|
||||||
|
NULL, flags, NULL, false));
|
||||||
|
put_buffers(buffer, old_buffer);
|
||||||
|
path_put(&old_path);
|
||||||
|
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
|
int aa_mount_change_type(struct aa_label *label, const struct path *path,
|
||||||
|
unsigned long flags)
|
||||||
|
{
|
||||||
|
struct aa_profile *profile;
|
||||||
|
char *buffer = NULL;
|
||||||
|
int error;
|
||||||
|
|
||||||
|
AA_BUG(!label);
|
||||||
|
AA_BUG(!path);
|
||||||
|
|
||||||
|
/* These are the flags allowed by do_change_type() */
|
||||||
|
flags &= (MS_REC | MS_SILENT | MS_SHARED | MS_PRIVATE | MS_SLAVE |
|
||||||
|
MS_UNBINDABLE);
|
||||||
|
|
||||||
|
get_buffers(buffer);
|
||||||
|
error = fn_for_each_confined(label, profile,
|
||||||
|
match_mnt(profile, path, buffer, NULL, NULL, NULL,
|
||||||
|
flags, NULL, false));
|
||||||
|
put_buffers(buffer);
|
||||||
|
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
|
int aa_move_mount(struct aa_label *label, const struct path *path,
|
||||||
|
const char *orig_name)
|
||||||
|
{
|
||||||
|
struct aa_profile *profile;
|
||||||
|
char *buffer = NULL, *old_buffer = NULL;
|
||||||
|
struct path old_path;
|
||||||
|
int error;
|
||||||
|
|
||||||
|
AA_BUG(!label);
|
||||||
|
AA_BUG(!path);
|
||||||
|
|
||||||
|
if (!orig_name || !*orig_name)
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
error = kern_path(orig_name, LOOKUP_FOLLOW, &old_path);
|
||||||
|
if (error)
|
||||||
|
return error;
|
||||||
|
|
||||||
|
get_buffers(buffer, old_buffer);
|
||||||
|
error = fn_for_each_confined(label, profile,
|
||||||
|
match_mnt(profile, path, buffer, &old_path, old_buffer,
|
||||||
|
NULL, MS_MOVE, NULL, false));
|
||||||
|
put_buffers(buffer, old_buffer);
|
||||||
|
path_put(&old_path);
|
||||||
|
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
|
int aa_new_mount(struct aa_label *label, const char *dev_name,
|
||||||
|
const struct path *path, const char *type, unsigned long flags,
|
||||||
|
void *data)
|
||||||
|
{
|
||||||
|
struct aa_profile *profile;
|
||||||
|
char *buffer = NULL, *dev_buffer = NULL;
|
||||||
|
bool binary = true;
|
||||||
|
int error;
|
||||||
|
int requires_dev = 0;
|
||||||
|
struct path tmp_path, *dev_path = NULL;
|
||||||
|
|
||||||
|
AA_BUG(!label);
|
||||||
|
AA_BUG(!path);
|
||||||
|
|
||||||
|
if (type) {
|
||||||
|
struct file_system_type *fstype;
|
||||||
|
fstype = get_fs_type(type);
|
||||||
|
if (!fstype)
|
||||||
|
return -ENODEV;
|
||||||
|
binary = fstype->fs_flags & FS_BINARY_MOUNTDATA;
|
||||||
|
requires_dev = fstype->fs_flags & FS_REQUIRES_DEV;
|
||||||
|
put_filesystem(fstype);
|
||||||
|
|
||||||
|
if (requires_dev) {
|
||||||
|
if (!dev_name || !*dev_name)
|
||||||
|
return -ENOENT;
|
||||||
|
|
||||||
|
error = kern_path(dev_name, LOOKUP_FOLLOW, &tmp_path);
|
||||||
|
if (error)
|
||||||
|
return error;
|
||||||
|
dev_path = &tmp_path;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get_buffers(buffer, dev_buffer);
|
||||||
|
if (dev_path) {
|
||||||
|
error = fn_for_each_confined(label, profile,
|
||||||
|
match_mnt(profile, path, buffer, dev_path, dev_buffer,
|
||||||
|
type, flags, data, binary));
|
||||||
|
} else {
|
||||||
|
error = fn_for_each_confined(label, profile,
|
||||||
|
match_mnt_path_str(profile, path, buffer, dev_name,
|
||||||
|
type, flags, data, binary, NULL));
|
||||||
|
}
|
||||||
|
put_buffers(buffer, dev_buffer);
|
||||||
|
if (dev_path)
|
||||||
|
path_put(dev_path);
|
||||||
|
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int profile_umount(struct aa_profile *profile, struct path *path,
|
||||||
|
char *buffer)
|
||||||
|
{
|
||||||
|
struct aa_perms perms = { };
|
||||||
|
const char *name = NULL, *info = NULL;
|
||||||
|
unsigned int state;
|
||||||
|
int error;
|
||||||
|
|
||||||
|
AA_BUG(!profile);
|
||||||
|
AA_BUG(!path);
|
||||||
|
|
||||||
|
error = aa_path_name(path, path_flags(profile, path), buffer, &name,
|
||||||
|
&info, profile->disconnected);
|
||||||
|
if (error)
|
||||||
|
goto audit;
|
||||||
|
|
||||||
|
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)
|
||||||
|
error = -EACCES;
|
||||||
|
|
||||||
|
audit:
|
||||||
|
return audit_mount(profile, OP_UMOUNT, name, NULL, NULL, NULL, 0, NULL,
|
||||||
|
AA_MAY_UMOUNT, &perms, info, error);
|
||||||
|
}
|
||||||
|
|
||||||
|
int aa_umount(struct aa_label *label, struct vfsmount *mnt, int flags)
|
||||||
|
{
|
||||||
|
struct aa_profile *profile;
|
||||||
|
char *buffer = NULL;
|
||||||
|
int error;
|
||||||
|
struct path path = { mnt, mnt->mnt_root };
|
||||||
|
|
||||||
|
AA_BUG(!label);
|
||||||
|
AA_BUG(!mnt);
|
||||||
|
|
||||||
|
get_buffers(buffer);
|
||||||
|
error = fn_for_each_confined(label, profile,
|
||||||
|
profile_umount(profile, &path, buffer));
|
||||||
|
put_buffers(buffer);
|
||||||
|
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* helper fn for transition on pivotroot
|
||||||
|
*
|
||||||
|
* Returns: label for transition or ERR_PTR. Does not return NULL
|
||||||
|
*/
|
||||||
|
static struct aa_label *build_pivotroot(struct aa_profile *profile,
|
||||||
|
const struct path *new_path,
|
||||||
|
char *new_buffer,
|
||||||
|
const struct path *old_path,
|
||||||
|
char *old_buffer)
|
||||||
|
{
|
||||||
|
const char *old_name, *new_name = NULL, *info = NULL;
|
||||||
|
const char *trans_name = NULL;
|
||||||
|
struct aa_label *target = NULL;
|
||||||
|
struct aa_perms perms = { };
|
||||||
|
unsigned int state;
|
||||||
|
int error;
|
||||||
|
|
||||||
|
AA_BUG(!profile);
|
||||||
|
AA_BUG(!new_path);
|
||||||
|
AA_BUG(!old_path);
|
||||||
|
|
||||||
|
if (profile_unconfined(profile))
|
||||||
|
return aa_get_newest_label(&profile->label);
|
||||||
|
|
||||||
|
error = aa_path_name(old_path, path_flags(profile, old_path),
|
||||||
|
old_buffer, &old_name, &info,
|
||||||
|
profile->disconnected);
|
||||||
|
if (error)
|
||||||
|
goto audit;
|
||||||
|
error = aa_path_name(new_path, path_flags(profile, new_path),
|
||||||
|
new_buffer, &new_name, &info,
|
||||||
|
profile->disconnected);
|
||||||
|
if (error)
|
||||||
|
goto audit;
|
||||||
|
|
||||||
|
error = -EACCES;
|
||||||
|
state = aa_dfa_match(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) {
|
||||||
|
error = 0;
|
||||||
|
if ((perms.xindex & AA_X_TYPE_MASK) == AA_X_TABLE) {
|
||||||
|
target = x_table_lookup(profile, perms.xindex,
|
||||||
|
&trans_name);
|
||||||
|
if (!target)
|
||||||
|
error = -ENOENT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
audit:
|
||||||
|
error = audit_mount(profile, OP_PIVOTROOT, new_name, old_name,
|
||||||
|
NULL, trans_name, 0, NULL, AA_MAY_PIVOTROOT,
|
||||||
|
&perms, info, error);
|
||||||
|
if (error) {
|
||||||
|
aa_put_label(target);
|
||||||
|
return ERR_PTR(error);
|
||||||
|
} else if (target)
|
||||||
|
return target;
|
||||||
|
|
||||||
|
return aa_get_newest_label(&profile->label);
|
||||||
|
}
|
||||||
|
|
||||||
|
int aa_pivotroot(struct aa_label *label, const struct path *old_path,
|
||||||
|
const struct path *new_path)
|
||||||
|
{
|
||||||
|
struct aa_profile *profile;
|
||||||
|
struct aa_label *target = NULL;
|
||||||
|
char *old_buffer = NULL, *new_buffer = NULL, *info = NULL;
|
||||||
|
int error;
|
||||||
|
|
||||||
|
AA_BUG(!label);
|
||||||
|
AA_BUG(!old_path);
|
||||||
|
AA_BUG(!new_path);
|
||||||
|
|
||||||
|
get_buffers(old_buffer, new_buffer);
|
||||||
|
target = fn_label_build(label, profile, GFP_ATOMIC,
|
||||||
|
build_pivotroot(profile, new_path, new_buffer,
|
||||||
|
old_path, old_buffer));
|
||||||
|
if (!target) {
|
||||||
|
info = "label build failed";
|
||||||
|
error = -ENOMEM;
|
||||||
|
goto fail;
|
||||||
|
} else if (!IS_ERR(target)) {
|
||||||
|
error = aa_replace_current_label(target);
|
||||||
|
if (error) {
|
||||||
|
/* TODO: audit target */
|
||||||
|
aa_put_label(target);
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
} else
|
||||||
|
/* already audited error */
|
||||||
|
error = PTR_ERR(target);
|
||||||
|
out:
|
||||||
|
put_buffers(old_buffer, new_buffer);
|
||||||
|
|
||||||
|
return error;
|
||||||
|
|
||||||
|
fail:
|
||||||
|
/* TODO: add back in auditing of new_name and old_name */
|
||||||
|
error = fn_for_each(label, profile,
|
||||||
|
audit_mount(profile, OP_PIVOTROOT, NULL /*new_name */,
|
||||||
|
NULL /* old_name */,
|
||||||
|
NULL, NULL,
|
||||||
|
0, NULL, AA_MAY_PIVOTROOT, &nullperms, info,
|
||||||
|
error));
|
||||||
|
goto out;
|
||||||
|
}
|
357
security/apparmor/net.c
Normal file
357
security/apparmor/net.c
Normal file
|
@ -0,0 +1,357 @@
|
||||||
|
/*
|
||||||
|
* AppArmor security module
|
||||||
|
*
|
||||||
|
* This file contains AppArmor network mediation
|
||||||
|
*
|
||||||
|
* Copyright (C) 1998-2008 Novell/SUSE
|
||||||
|
* Copyright 2009-2014 Canonical Ltd.
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU General Public License as
|
||||||
|
* published by the Free Software Foundation, version 2 of the
|
||||||
|
* License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "include/af_unix.h"
|
||||||
|
#include "include/apparmor.h"
|
||||||
|
#include "include/audit.h"
|
||||||
|
#include "include/context.h"
|
||||||
|
#include "include/label.h"
|
||||||
|
#include "include/net.h"
|
||||||
|
#include "include/policy.h"
|
||||||
|
|
||||||
|
#include "net_names.h"
|
||||||
|
|
||||||
|
|
||||||
|
struct aa_fs_entry aa_fs_entry_network[] = {
|
||||||
|
AA_FS_FILE_STRING("af_mask", AA_FS_AF_MASK),
|
||||||
|
AA_FS_FILE_BOOLEAN("af_unix", 1),
|
||||||
|
{ }
|
||||||
|
};
|
||||||
|
|
||||||
|
static const char *net_mask_names[] = {
|
||||||
|
"unknown",
|
||||||
|
"send",
|
||||||
|
"receive",
|
||||||
|
"unknown",
|
||||||
|
|
||||||
|
"create",
|
||||||
|
"shutdown",
|
||||||
|
"connect",
|
||||||
|
"unknown",
|
||||||
|
|
||||||
|
"setattr",
|
||||||
|
"getattr",
|
||||||
|
"setcred",
|
||||||
|
"getcred",
|
||||||
|
|
||||||
|
"chmod",
|
||||||
|
"chown",
|
||||||
|
"chgrp",
|
||||||
|
"lock",
|
||||||
|
|
||||||
|
"mmap",
|
||||||
|
"mprot",
|
||||||
|
"unknown",
|
||||||
|
"unknown",
|
||||||
|
|
||||||
|
"accept",
|
||||||
|
"bind",
|
||||||
|
"listen",
|
||||||
|
"unknown",
|
||||||
|
|
||||||
|
"setopt",
|
||||||
|
"getopt",
|
||||||
|
"unknown",
|
||||||
|
"unknown",
|
||||||
|
|
||||||
|
"unknown",
|
||||||
|
"unknown",
|
||||||
|
"unknown",
|
||||||
|
"unknown",
|
||||||
|
};
|
||||||
|
|
||||||
|
static void audit_unix_addr(struct audit_buffer *ab, const char *str,
|
||||||
|
struct sockaddr_un *addr, int addrlen)
|
||||||
|
{
|
||||||
|
int len = unix_addr_len(addrlen);
|
||||||
|
|
||||||
|
if (!addr || len <= 0) {
|
||||||
|
audit_log_format(ab, " %s=none", str);
|
||||||
|
} else if (addr->sun_path[0]) {
|
||||||
|
audit_log_format(ab, " %s=", str);
|
||||||
|
audit_log_untrustedstring(ab, addr->sun_path);
|
||||||
|
} else {
|
||||||
|
audit_log_format(ab, " %s=\"@", str);
|
||||||
|
if (audit_string_contains_control(&addr->sun_path[1], len - 1))
|
||||||
|
audit_log_n_hex(ab, &addr->sun_path[1], len - 1);
|
||||||
|
else
|
||||||
|
audit_log_format(ab, "%.*s", len - 1,
|
||||||
|
&addr->sun_path[1]);
|
||||||
|
audit_log_format(ab, "\"");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void audit_unix_sk_addr(struct audit_buffer *ab, const char *str,
|
||||||
|
struct sock *sk)
|
||||||
|
{
|
||||||
|
struct unix_sock *u = unix_sk(sk);
|
||||||
|
if (u && u->addr)
|
||||||
|
audit_unix_addr(ab, str, u->addr->name, u->addr->len);
|
||||||
|
else
|
||||||
|
audit_unix_addr(ab, str, NULL, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* audit callback for net specific fields */
|
||||||
|
void audit_net_cb(struct audit_buffer *ab, void *va)
|
||||||
|
{
|
||||||
|
struct common_audit_data *sa = va;
|
||||||
|
|
||||||
|
audit_log_format(ab, " family=");
|
||||||
|
if (address_family_names[sa->u.net->family]) {
|
||||||
|
audit_log_string(ab, address_family_names[sa->u.net->family]);
|
||||||
|
} else {
|
||||||
|
audit_log_format(ab, "\"unknown(%d)\"", sa->u.net->family);
|
||||||
|
}
|
||||||
|
audit_log_format(ab, " sock_type=");
|
||||||
|
if (sock_type_names[aad(sa)->net.type]) {
|
||||||
|
audit_log_string(ab, sock_type_names[aad(sa)->net.type]);
|
||||||
|
} else {
|
||||||
|
audit_log_format(ab, "\"unknown(%d)\"", aad(sa)->net.type);
|
||||||
|
}
|
||||||
|
audit_log_format(ab, " protocol=%d", aad(sa)->net.protocol);
|
||||||
|
|
||||||
|
if (aad(sa)->request & NET_PERMS_MASK) {
|
||||||
|
audit_log_format(ab, " requested_mask=");
|
||||||
|
aa_audit_perm_mask(ab, aad(sa)->request, NULL, 0,
|
||||||
|
net_mask_names, NET_PERMS_MASK);
|
||||||
|
|
||||||
|
if (aad(sa)->denied & NET_PERMS_MASK) {
|
||||||
|
audit_log_format(ab, " denied_mask=");
|
||||||
|
aa_audit_perm_mask(ab, aad(sa)->denied, NULL, 0,
|
||||||
|
net_mask_names, NET_PERMS_MASK);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (sa->u.net->family == AF_UNIX) {
|
||||||
|
if ((aad(sa)->request & ~NET_PEER_MASK) && aad(sa)->net.addr)
|
||||||
|
audit_unix_addr(ab, "addr",
|
||||||
|
unix_addr(aad(sa)->net.addr),
|
||||||
|
aad(sa)->net.addrlen);
|
||||||
|
else
|
||||||
|
audit_unix_sk_addr(ab, "addr", sa->u.net->sk);
|
||||||
|
if (aad(sa)->request & NET_PEER_MASK) {
|
||||||
|
if (aad(sa)->net.addr)
|
||||||
|
audit_unix_addr(ab, "peer_addr",
|
||||||
|
unix_addr(aad(sa)->net.addr),
|
||||||
|
aad(sa)->net.addrlen);
|
||||||
|
else
|
||||||
|
audit_unix_sk_addr(ab, "peer_addr",
|
||||||
|
aad(sa)->net.peer_sk);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (aad(sa)->peer) {
|
||||||
|
audit_log_format(ab, " peer=");
|
||||||
|
aa_label_xaudit(ab, labels_ns(aad(sa)->label), aad(sa)->peer,
|
||||||
|
FLAGS_NONE, GFP_ATOMIC);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Generic af perm */
|
||||||
|
int aa_profile_af_perm(struct aa_profile *profile, struct common_audit_data *sa,
|
||||||
|
u32 request, u16 family, int type)
|
||||||
|
{
|
||||||
|
struct aa_perms perms = { };
|
||||||
|
|
||||||
|
AA_BUG(family >= AF_MAX);
|
||||||
|
AA_BUG(type < 0 && type >= SOCK_MAX);
|
||||||
|
|
||||||
|
if (profile_unconfined(profile))
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
perms.allow = (profile->net.allow[family] & (1 << type)) ?
|
||||||
|
ALL_PERMS_MASK : 0;
|
||||||
|
perms.audit = (profile->net.audit[family] & (1 << type)) ?
|
||||||
|
ALL_PERMS_MASK : 0;
|
||||||
|
perms.quiet = (profile->net.quiet[family] & (1 << type)) ?
|
||||||
|
ALL_PERMS_MASK : 0;
|
||||||
|
aa_apply_modes_to_perms(profile, &perms);
|
||||||
|
|
||||||
|
return aa_check_perms(profile, &perms, request, sa, audit_net_cb);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int aa_af_perm(struct aa_label *label, const char *op, u32 request,
|
||||||
|
u16 family, int type, int protocol)
|
||||||
|
{
|
||||||
|
struct aa_profile *profile;
|
||||||
|
DEFINE_AUDIT_NET(sa, op, NULL, family, type, protocol);
|
||||||
|
|
||||||
|
return fn_for_each_confined(label, profile,
|
||||||
|
aa_profile_af_perm(profile, &sa, request, family, type));
|
||||||
|
}
|
||||||
|
|
||||||
|
static int aa_label_sk_perm(struct aa_label *label, const char *op, u32 request,
|
||||||
|
struct sock *sk)
|
||||||
|
{
|
||||||
|
struct aa_profile *profile;
|
||||||
|
DEFINE_AUDIT_SK(sa, op, sk);
|
||||||
|
|
||||||
|
AA_BUG(!label);
|
||||||
|
AA_BUG(!sk);
|
||||||
|
|
||||||
|
if (unconfined(label))
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
return fn_for_each_confined(label, profile,
|
||||||
|
aa_profile_af_sk_perm(profile, &sa, request, sk));
|
||||||
|
}
|
||||||
|
|
||||||
|
static int aa_sk_perm(const char *op, u32 request, struct sock *sk)
|
||||||
|
{
|
||||||
|
struct aa_label *label;
|
||||||
|
int error;
|
||||||
|
|
||||||
|
AA_BUG(!sk);
|
||||||
|
AA_BUG(in_interrupt());
|
||||||
|
|
||||||
|
/* TODO: switch to begin_current_label ???? */
|
||||||
|
label = aa_begin_current_label(DO_UPDATE);
|
||||||
|
error = aa_label_sk_perm(label, op, request, sk);
|
||||||
|
aa_end_current_label(label);
|
||||||
|
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
|
#define af_select(FAMILY, FN, DEF_FN) \
|
||||||
|
({ \
|
||||||
|
int __e; \
|
||||||
|
switch ((FAMILY)) { \
|
||||||
|
case AF_UNIX: \
|
||||||
|
__e = aa_unix_ ## FN; \
|
||||||
|
break; \
|
||||||
|
default: \
|
||||||
|
__e = DEF_FN; \
|
||||||
|
} \
|
||||||
|
__e; \
|
||||||
|
})
|
||||||
|
|
||||||
|
/* TODO: push into lsm.c ???? */
|
||||||
|
|
||||||
|
/* revaliation, get/set attr, shutdown */
|
||||||
|
int aa_sock_perm(const char *op, u32 request, struct socket *sock)
|
||||||
|
{
|
||||||
|
AA_BUG(!sock);
|
||||||
|
AA_BUG(!sock->sk);
|
||||||
|
AA_BUG(in_interrupt());
|
||||||
|
|
||||||
|
return af_select(sock->sk->sk_family,
|
||||||
|
sock_perm(op, request, sock),
|
||||||
|
aa_sk_perm(op, request, sock->sk));
|
||||||
|
}
|
||||||
|
|
||||||
|
int aa_sock_create_perm(struct aa_label *label, int family, int type,
|
||||||
|
int protocol)
|
||||||
|
{
|
||||||
|
AA_BUG(!label);
|
||||||
|
/* TODO: .... */
|
||||||
|
AA_BUG(in_interrupt());
|
||||||
|
|
||||||
|
return af_select(family,
|
||||||
|
create_perm(label, family, type, protocol),
|
||||||
|
aa_af_perm(label, OP_CREATE, AA_MAY_CREATE, family,
|
||||||
|
type, protocol));
|
||||||
|
}
|
||||||
|
|
||||||
|
int aa_sock_bind_perm(struct socket *sock, struct sockaddr *address,
|
||||||
|
int addrlen)
|
||||||
|
{
|
||||||
|
AA_BUG(!sock);
|
||||||
|
AA_BUG(!sock->sk);
|
||||||
|
AA_BUG(!address);
|
||||||
|
/* TODO: .... */
|
||||||
|
AA_BUG(in_interrupt());
|
||||||
|
|
||||||
|
return af_select(sock->sk->sk_family,
|
||||||
|
bind_perm(sock, address, addrlen),
|
||||||
|
aa_sk_perm(OP_BIND, AA_MAY_BIND, sock->sk));
|
||||||
|
}
|
||||||
|
|
||||||
|
int aa_sock_connect_perm(struct socket *sock, struct sockaddr *address,
|
||||||
|
int addrlen)
|
||||||
|
{
|
||||||
|
AA_BUG(!sock);
|
||||||
|
AA_BUG(!sock->sk);
|
||||||
|
AA_BUG(!address);
|
||||||
|
/* TODO: .... */
|
||||||
|
AA_BUG(in_interrupt());
|
||||||
|
|
||||||
|
return af_select(sock->sk->sk_family,
|
||||||
|
connect_perm(sock, address, addrlen),
|
||||||
|
aa_sk_perm(OP_CONNECT, AA_MAY_CONNECT, sock->sk));
|
||||||
|
}
|
||||||
|
|
||||||
|
int aa_sock_listen_perm(struct socket *sock, int backlog)
|
||||||
|
{
|
||||||
|
AA_BUG(!sock);
|
||||||
|
AA_BUG(!sock->sk);
|
||||||
|
/* TODO: .... */
|
||||||
|
AA_BUG(in_interrupt());
|
||||||
|
|
||||||
|
return af_select(sock->sk->sk_family,
|
||||||
|
listen_perm(sock, backlog),
|
||||||
|
aa_sk_perm(OP_LISTEN, AA_MAY_LISTEN, sock->sk));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ability of sock to connect, not peer address binding */
|
||||||
|
int aa_sock_accept_perm(struct socket *sock, struct socket *newsock)
|
||||||
|
{
|
||||||
|
AA_BUG(!sock);
|
||||||
|
AA_BUG(!sock->sk);
|
||||||
|
AA_BUG(!newsock);
|
||||||
|
/* TODO: .... */
|
||||||
|
AA_BUG(in_interrupt());
|
||||||
|
|
||||||
|
return af_select(sock->sk->sk_family,
|
||||||
|
accept_perm(sock, newsock),
|
||||||
|
aa_sk_perm(OP_ACCEPT, AA_MAY_ACCEPT, sock->sk));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* sendmsg, recvmsg */
|
||||||
|
int aa_sock_msg_perm(const char *op, u32 request, struct socket *sock,
|
||||||
|
struct msghdr *msg, int size)
|
||||||
|
{
|
||||||
|
AA_BUG(!sock);
|
||||||
|
AA_BUG(!sock->sk);
|
||||||
|
AA_BUG(!msg);
|
||||||
|
/* TODO: .... */
|
||||||
|
AA_BUG(in_interrupt());
|
||||||
|
|
||||||
|
return af_select(sock->sk->sk_family,
|
||||||
|
msg_perm(op, request, sock, msg, size),
|
||||||
|
aa_sk_perm(op, request, sock->sk));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* revaliation, get/set attr, opt */
|
||||||
|
int aa_sock_opt_perm(const char *op, u32 request, struct socket *sock, int level,
|
||||||
|
int optname)
|
||||||
|
{
|
||||||
|
AA_BUG(!sock);
|
||||||
|
AA_BUG(!sock->sk);
|
||||||
|
AA_BUG(in_interrupt());
|
||||||
|
|
||||||
|
return af_select(sock->sk->sk_family,
|
||||||
|
opt_perm(op, request, sock, level, optname),
|
||||||
|
aa_sk_perm(op, request, sock->sk));
|
||||||
|
}
|
||||||
|
|
||||||
|
int aa_sock_file_perm(struct aa_label *label, const char *op, u32 request,
|
||||||
|
struct socket *sock)
|
||||||
|
{
|
||||||
|
AA_BUG(!label);
|
||||||
|
AA_BUG(!sock);
|
||||||
|
AA_BUG(!sock->sk);
|
||||||
|
|
||||||
|
return af_select(sock->sk->sk_family,
|
||||||
|
file_perm(label, op, request, sock),
|
||||||
|
aa_label_sk_perm(label, op, request, sock->sk));
|
||||||
|
}
|
1
security/apparmor/nulldfa.in
Normal file
1
security/apparmor/nulldfa.in
Normal file
File diff suppressed because one or more lines are too long
|
@ -25,7 +25,6 @@
|
||||||
#include "include/path.h"
|
#include "include/path.h"
|
||||||
#include "include/policy.h"
|
#include "include/policy.h"
|
||||||
|
|
||||||
|
|
||||||
/* modified from dcache.c */
|
/* modified from dcache.c */
|
||||||
static int prepend(char **buffer, int buflen, const char *str, int namelen)
|
static int prepend(char **buffer, int buflen, const char *str, int namelen)
|
||||||
{
|
{
|
||||||
|
@ -39,13 +38,50 @@ static int prepend(char **buffer, int buflen, const char *str, int namelen)
|
||||||
|
|
||||||
#define CHROOT_NSCONNECT (PATH_CHROOT_REL | PATH_CHROOT_NSCONNECT)
|
#define CHROOT_NSCONNECT (PATH_CHROOT_REL | PATH_CHROOT_NSCONNECT)
|
||||||
|
|
||||||
|
/* If the path is not connected to the expected root,
|
||||||
|
* check if it is a sysctl and handle specially else remove any
|
||||||
|
* leading / that __d_path may have returned.
|
||||||
|
* Unless
|
||||||
|
* specifically directed to connect the path,
|
||||||
|
* OR
|
||||||
|
* if in a chroot and doing chroot relative paths and the path
|
||||||
|
* resolves to the namespace root (would be connected outside
|
||||||
|
* of chroot) and specifically directed to connect paths to
|
||||||
|
* namespace root.
|
||||||
|
*/
|
||||||
|
static int disconnect(const struct path *path, char *buf, char **name,
|
||||||
|
int flags, const char *disconnected)
|
||||||
|
{
|
||||||
|
int error = 0;
|
||||||
|
|
||||||
|
if (!(flags & PATH_CONNECT_PATH) &&
|
||||||
|
!(((flags & CHROOT_NSCONNECT) == CHROOT_NSCONNECT) &&
|
||||||
|
our_mnt(path->mnt))) {
|
||||||
|
/* disconnected path, don't return pathname starting
|
||||||
|
* with '/'
|
||||||
|
*/
|
||||||
|
error = -EACCES;
|
||||||
|
if (**name == '/')
|
||||||
|
*name = *name + 1;
|
||||||
|
} else {
|
||||||
|
if (**name != '/')
|
||||||
|
/* CONNECT_PATH with missing root */
|
||||||
|
error = prepend(name, *name - buf, "/", 1);
|
||||||
|
if (!error && disconnected)
|
||||||
|
error = prepend(name, *name - buf, disconnected,
|
||||||
|
strlen(disconnected));
|
||||||
|
}
|
||||||
|
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* d_namespace_path - lookup a name associated with a given path
|
* d_namespace_path - lookup a name associated with a given path
|
||||||
* @path: path to lookup (NOT NULL)
|
* @path: path to lookup (NOT NULL)
|
||||||
* @buf: buffer to store path to (NOT NULL)
|
* @buf: buffer to store path to (NOT NULL)
|
||||||
* @buflen: length of @buf
|
|
||||||
* @name: Returns - pointer for start of path name with in @buf (NOT NULL)
|
* @name: Returns - pointer for start of path name with in @buf (NOT NULL)
|
||||||
* @flags: flags controlling path lookup
|
* @flags: flags controlling path lookup
|
||||||
|
* @disconnected: string to prefix to disconnected paths
|
||||||
*
|
*
|
||||||
* Handle path name lookup.
|
* Handle path name lookup.
|
||||||
*
|
*
|
||||||
|
@ -53,12 +89,14 @@ static int prepend(char **buffer, int buflen, const char *str, int namelen)
|
||||||
* When no error the path name is returned in @name which points to
|
* When no error the path name is returned in @name which points to
|
||||||
* to a position in @buf
|
* to a position in @buf
|
||||||
*/
|
*/
|
||||||
static int d_namespace_path(struct path *path, char *buf, int buflen,
|
static int d_namespace_path(const struct path *path, char *buf, char **name,
|
||||||
char **name, int flags)
|
int flags, const char *disconnected)
|
||||||
{
|
{
|
||||||
char *res;
|
char *res;
|
||||||
int error = 0;
|
int error = 0;
|
||||||
int connected = 1;
|
int connected = 1;
|
||||||
|
int isdir = (flags & PATH_IS_DIR) ? 1 : 0;
|
||||||
|
int buflen = aa_g_path_max - isdir;
|
||||||
|
|
||||||
if (path->mnt->mnt_flags & MNT_INTERNAL) {
|
if (path->mnt->mnt_flags & MNT_INTERNAL) {
|
||||||
/* it's not mounted anywhere */
|
/* it's not mounted anywhere */
|
||||||
|
@ -73,9 +111,12 @@ static int d_namespace_path(struct path *path, char *buf, int buflen,
|
||||||
/* TODO: convert over to using a per namespace
|
/* TODO: convert over to using a per namespace
|
||||||
* control instead of hard coded /proc
|
* control instead of hard coded /proc
|
||||||
*/
|
*/
|
||||||
return prepend(name, *name - buf, "/proc", 5);
|
error = prepend(name, *name - buf, "/proc", 5);
|
||||||
}
|
goto out;
|
||||||
return 0;
|
} else
|
||||||
|
error = disconnect(path, buf, name, flags,
|
||||||
|
disconnected);
|
||||||
|
goto out;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* resolve paths relative to chroot?*/
|
/* resolve paths relative to chroot?*/
|
||||||
|
@ -94,8 +135,11 @@ static int d_namespace_path(struct path *path, char *buf, int buflen,
|
||||||
* be returned.
|
* be returned.
|
||||||
*/
|
*/
|
||||||
if (!res || IS_ERR(res)) {
|
if (!res || IS_ERR(res)) {
|
||||||
if (PTR_ERR(res) == -ENAMETOOLONG)
|
if (PTR_ERR(res) == -ENAMETOOLONG) {
|
||||||
return -ENAMETOOLONG;
|
error = -ENAMETOOLONG;
|
||||||
|
*name = buf;
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
connected = 0;
|
connected = 0;
|
||||||
res = dentry_path_raw(path->dentry, buf, buflen);
|
res = dentry_path_raw(path->dentry, buf, buflen);
|
||||||
if (IS_ERR(res)) {
|
if (IS_ERR(res)) {
|
||||||
|
@ -108,6 +152,9 @@ static int d_namespace_path(struct path *path, char *buf, int buflen,
|
||||||
|
|
||||||
*name = res;
|
*name = res;
|
||||||
|
|
||||||
|
if (!connected)
|
||||||
|
error = disconnect(path, buf, name, flags, disconnected);
|
||||||
|
|
||||||
/* Handle two cases:
|
/* Handle two cases:
|
||||||
* 1. A deleted dentry && profile is not allowing mediation of deleted
|
* 1. A deleted dentry && profile is not allowing mediation of deleted
|
||||||
* 2. On some filesystems, newly allocated dentries appear to the
|
* 2. On some filesystems, newly allocated dentries appear to the
|
||||||
|
@ -115,83 +162,30 @@ static int d_namespace_path(struct path *path, char *buf, int buflen,
|
||||||
* allocated.
|
* allocated.
|
||||||
*/
|
*/
|
||||||
if (d_unlinked(path->dentry) && d_is_positive(path->dentry) &&
|
if (d_unlinked(path->dentry) && d_is_positive(path->dentry) &&
|
||||||
!(flags & PATH_MEDIATE_DELETED)) {
|
!(flags & (PATH_MEDIATE_DELETED | PATH_DELEGATE_DELETED))) {
|
||||||
error = -ENOENT;
|
error = -ENOENT;
|
||||||
goto out;
|
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
out:
|
out:
|
||||||
return error;
|
/*
|
||||||
}
|
* Append "/" to the pathname. The root directory is a special
|
||||||
|
* case; it already ends in slash.
|
||||||
/**
|
*/
|
||||||
* get_name_to_buffer - get the pathname to a buffer ensure dir / is appended
|
if (!error && isdir && ((*name)[1] != '\0' || (*name)[0] != '/'))
|
||||||
* @path: path to get name for (NOT NULL)
|
strcpy(&buf[aa_g_path_max - 2], "/");
|
||||||
* @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 == -EACCES)
|
|
||||||
*info = "Failed name lookup - disconnected path";
|
|
||||||
else if (error == -ENAMETOOLONG)
|
|
||||||
*info = "Failed name lookup - name too long";
|
|
||||||
else
|
|
||||||
*info = "Failed name lookup";
|
|
||||||
}
|
|
||||||
|
|
||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* aa_path_name - compute the pathname of a file
|
* aa_path_name - get the pathname to a buffer ensure dir / is appended
|
||||||
* @path: path the file (NOT NULL)
|
* @path: path the file (NOT NULL)
|
||||||
* @flags: flags controlling path name generation
|
* @flags: flags controlling path name generation
|
||||||
* @buffer: buffer that aa_get_name() allocated (NOT NULL)
|
* @buffer: buffer to put name in (NOT NULL)
|
||||||
* @name: Returns - the generated path name if !error (NOT NULL)
|
* @name: Returns - the generated path name if !error (NOT NULL)
|
||||||
* @info: Returns - information on why the path lookup failed (MAYBE NULL)
|
* @info: Returns - information on why the path lookup failed (MAYBE NULL)
|
||||||
|
* @disconnected: string to prepend to disconnected paths
|
||||||
*
|
*
|
||||||
* @name is a pointer to the beginning of the pathname (which usually differs
|
* @name is a pointer to the beginning of the pathname (which usually differs
|
||||||
* from the beginning of the buffer), or NULL. If there is an error @name
|
* from the beginning of the buffer), or NULL. If there is an error @name
|
||||||
|
@ -204,33 +198,24 @@ static int get_name_to_buffer(struct path *path, int flags, char *buffer,
|
||||||
*
|
*
|
||||||
* Returns: %0 else error code if could retrieve name
|
* Returns: %0 else error code if could retrieve name
|
||||||
*/
|
*/
|
||||||
int aa_path_name(struct path *path, int flags, char **buffer, const char **name,
|
int aa_path_name(const struct path *path, int flags, char *buffer,
|
||||||
const char **info)
|
const char **name, const char **info, const char *disconnected)
|
||||||
{
|
{
|
||||||
char *buf, *str = NULL;
|
char *str = NULL;
|
||||||
int size = 256;
|
int error = d_namespace_path(path, buffer, &str, flags, disconnected);
|
||||||
int error;
|
|
||||||
|
|
||||||
*name = NULL;
|
|
||||||
*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 (info && error) {
|
||||||
if (error != -ENAMETOOLONG)
|
if (error == -ENOENT)
|
||||||
break;
|
*info = "Failed name lookup - deleted entry";
|
||||||
|
else if (error == -EACCES)
|
||||||
kfree(buf);
|
*info = "Failed name lookup - disconnected path";
|
||||||
size <<= 1;
|
else if (error == -ENAMETOOLONG)
|
||||||
if (size > aa_g_path_max)
|
*info = "Failed name lookup - name too long";
|
||||||
return -ENAMETOOLONG;
|
else
|
||||||
*info = NULL;
|
*info = "Failed name lookup";
|
||||||
}
|
}
|
||||||
*buffer = buf;
|
|
||||||
*name = str;
|
|
||||||
|
|
||||||
|
*name = str;
|
||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load diff
353
security/apparmor/policy_ns.c
Normal file
353
security/apparmor/policy_ns.c
Normal file
|
@ -0,0 +1,353 @@
|
||||||
|
/*
|
||||||
|
* AppArmor security module
|
||||||
|
*
|
||||||
|
* This file contains AppArmor policy manipulation functions
|
||||||
|
*
|
||||||
|
* Copyright (C) 1998-2008 Novell/SUSE
|
||||||
|
* Copyright 2009-2015 Canonical Ltd.
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU General Public License as
|
||||||
|
* published by the Free Software Foundation, version 2 of the
|
||||||
|
* License.
|
||||||
|
*
|
||||||
|
* AppArmor policy namespaces, allow for different sets of policies
|
||||||
|
* to be loaded for tasks within the namespace.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <linux/list.h>
|
||||||
|
#include <linux/mutex.h>
|
||||||
|
#include <linux/slab.h>
|
||||||
|
#include <linux/string.h>
|
||||||
|
|
||||||
|
#include "include/apparmor.h"
|
||||||
|
#include "include/context.h"
|
||||||
|
#include "include/policy_ns.h"
|
||||||
|
#include "include/label.h"
|
||||||
|
#include "include/policy.h"
|
||||||
|
|
||||||
|
/* root profile namespace */
|
||||||
|
struct aa_ns *root_ns;
|
||||||
|
const char *aa_hidden_ns_name = "---";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* aa_ns_visible - test if @view is visible from @curr
|
||||||
|
* @curr: namespace to treat as the parent (NOT NULL)
|
||||||
|
* @view: namespace to test if visible from @curr (NOT NULL)
|
||||||
|
* @subns: whether view of a subns is allowed
|
||||||
|
*
|
||||||
|
* Returns: true if @view is visible from @curr else false
|
||||||
|
*/
|
||||||
|
bool aa_ns_visible(struct aa_ns *curr, struct aa_ns *view, bool subns)
|
||||||
|
{
|
||||||
|
if (curr == view)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (!subns)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
for ( ; view; view = view->parent) {
|
||||||
|
if (view->parent == curr)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* aa_na_name - Find the ns name to display for @view from @curr
|
||||||
|
* @curr - current namespace (NOT NULL)
|
||||||
|
* @view - namespace attempting to view (NOT NULL)
|
||||||
|
* @subns - are subns visible
|
||||||
|
*
|
||||||
|
* Returns: name of @view visible from @curr
|
||||||
|
*/
|
||||||
|
const char *aa_ns_name(struct aa_ns *curr, struct aa_ns *view, bool subns)
|
||||||
|
{
|
||||||
|
/* if view == curr then the namespace name isn't displayed */
|
||||||
|
if (curr == view)
|
||||||
|
return "";
|
||||||
|
|
||||||
|
if (aa_ns_visible(curr, view, subns)) {
|
||||||
|
/* at this point if a ns is visible it is in a view ns
|
||||||
|
* thus the curr ns.hname is a prefix of its name.
|
||||||
|
* Only output the virtualized portion of the name
|
||||||
|
* Add + 2 to skip over // separating curr hname prefix
|
||||||
|
* from the visible tail of the views hname
|
||||||
|
*/
|
||||||
|
return view->base.hname + strlen(curr->base.hname) + 2;
|
||||||
|
} else
|
||||||
|
return aa_hidden_ns_name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* alloc_ns - allocate, initialize and return a new namespace
|
||||||
|
* @prefix: parent namespace name (MAYBE NULL)
|
||||||
|
* @name: a preallocated name (NOT NULL)
|
||||||
|
*
|
||||||
|
* Returns: refcounted namespace or NULL on failure.
|
||||||
|
*/
|
||||||
|
static struct aa_ns *alloc_ns(const char *prefix, const char *name)
|
||||||
|
{
|
||||||
|
struct aa_ns *ns;
|
||||||
|
|
||||||
|
ns = kzalloc(sizeof(*ns), GFP_KERNEL);
|
||||||
|
AA_DEBUG("%s(%p)\n", __func__, ns);
|
||||||
|
if (!ns)
|
||||||
|
return NULL;
|
||||||
|
if (!aa_policy_init(&ns->base, prefix, name, GFP_KERNEL))
|
||||||
|
goto fail_ns;
|
||||||
|
|
||||||
|
INIT_LIST_HEAD(&ns->sub_ns);
|
||||||
|
mutex_init(&ns->lock);
|
||||||
|
|
||||||
|
/* released by free_namespace */
|
||||||
|
ns->unconfined = aa_alloc_profile("unconfined", NULL, GFP_KERNEL);
|
||||||
|
if (!ns->unconfined)
|
||||||
|
goto fail_unconfined;
|
||||||
|
|
||||||
|
ns->unconfined->label.flags |= FLAG_IX_ON_NAME_ERROR |
|
||||||
|
FLAG_IMMUTIBLE | FLAG_NS_COUNT | FLAG_UNCONFINED;
|
||||||
|
ns->unconfined->mode = APPARMOR_UNCONFINED;
|
||||||
|
|
||||||
|
/* ns and ns->unconfined share ns->unconfined refcount */
|
||||||
|
ns->unconfined->ns = ns;
|
||||||
|
|
||||||
|
atomic_set(&ns->uniq_null, 0);
|
||||||
|
|
||||||
|
aa_labelset_init(&ns->labels);
|
||||||
|
|
||||||
|
return ns;
|
||||||
|
|
||||||
|
fail_unconfined:
|
||||||
|
kzfree(ns->base.hname);
|
||||||
|
fail_ns:
|
||||||
|
kzfree(ns);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* aa_free_ns - free a profile namespace
|
||||||
|
* @ns: the namespace to free (MAYBE NULL)
|
||||||
|
*
|
||||||
|
* Requires: All references to the namespace must have been put, if the
|
||||||
|
* namespace was referenced by a profile confining a task,
|
||||||
|
*/
|
||||||
|
void aa_free_ns(struct aa_ns *ns)
|
||||||
|
{
|
||||||
|
if (!ns)
|
||||||
|
return;
|
||||||
|
|
||||||
|
aa_policy_destroy(&ns->base);
|
||||||
|
aa_labelset_destroy(&ns->labels);
|
||||||
|
aa_put_ns(ns->parent);
|
||||||
|
|
||||||
|
ns->unconfined->ns = NULL;
|
||||||
|
aa_free_profile(ns->unconfined);
|
||||||
|
kzfree(ns);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* aa_find_ns - look up a profile namespace on the namespace list
|
||||||
|
* @root: namespace to search in (NOT NULL)
|
||||||
|
* @name: name of namespace to find (NOT NULL)
|
||||||
|
* @n: length of @name
|
||||||
|
*
|
||||||
|
* Returns: a refcounted namespace on the list, or NULL if no namespace
|
||||||
|
* called @name exists.
|
||||||
|
*
|
||||||
|
* refcount released by caller
|
||||||
|
*/
|
||||||
|
struct aa_ns *aa_findn_ns(struct aa_ns *root, const char *name, size_t n)
|
||||||
|
{
|
||||||
|
struct aa_ns *ns = NULL;
|
||||||
|
|
||||||
|
rcu_read_lock();
|
||||||
|
ns = aa_get_ns(__aa_findn_ns(&root->sub_ns, name, n));
|
||||||
|
rcu_read_unlock();
|
||||||
|
|
||||||
|
return ns;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* aa_find_ns - look up a profile namespace on the namespace list
|
||||||
|
* @root: namespace to search in (NOT NULL)
|
||||||
|
* @name: name of namespace to find (NOT NULL)
|
||||||
|
*
|
||||||
|
* Returns: a refcounted namespace on the list, or NULL if no namespace
|
||||||
|
* called @name exists.
|
||||||
|
*
|
||||||
|
* refcount released by caller
|
||||||
|
*/
|
||||||
|
struct aa_ns *aa_find_ns(struct aa_ns *root, const char *name)
|
||||||
|
{
|
||||||
|
return aa_findn_ns(root, name, strlen(name));
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct aa_ns *__aa_create_ns(struct aa_ns *parent, const char *name,
|
||||||
|
struct dentry *dir)
|
||||||
|
{
|
||||||
|
struct aa_ns *ns;
|
||||||
|
int error;
|
||||||
|
|
||||||
|
AA_BUG(!parent);
|
||||||
|
AA_BUG(!name);
|
||||||
|
AA_BUG(!mutex_is_locked(&parent->lock));
|
||||||
|
|
||||||
|
ns = alloc_ns(parent->base.hname, name);
|
||||||
|
if (!ns)
|
||||||
|
return NULL;
|
||||||
|
mutex_lock(&ns->lock);
|
||||||
|
error = __aa_fs_ns_mkdir(ns, ns_subns_dir(parent), name, dir);
|
||||||
|
if (error) {
|
||||||
|
AA_ERROR("Failed to create interface for ns %s\n",
|
||||||
|
ns->base.name);
|
||||||
|
mutex_unlock(&ns->lock);
|
||||||
|
aa_free_ns(ns);
|
||||||
|
return ERR_PTR(error);
|
||||||
|
} else {
|
||||||
|
ns->parent = aa_get_ns(parent);
|
||||||
|
ns->level = parent->level + 1;
|
||||||
|
list_add_rcu(&ns->base.list, &parent->sub_ns);
|
||||||
|
/* add list ref */
|
||||||
|
aa_get_ns(ns);
|
||||||
|
}
|
||||||
|
mutex_unlock(&ns->lock);
|
||||||
|
|
||||||
|
return ns;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* aa_create_ns - create an ns, fail if it already exists
|
||||||
|
* @parent: the parent of the namespace being created
|
||||||
|
* @name: the name of the namespace
|
||||||
|
* @dir: if not null the dir to put the ns entries in
|
||||||
|
*
|
||||||
|
* Returns: the a refcounted ns that has been add or an ERR_PTR
|
||||||
|
*/
|
||||||
|
struct aa_ns *aa_create_ns(struct aa_ns *parent, const char *name,
|
||||||
|
struct dentry *dir)
|
||||||
|
{
|
||||||
|
struct aa_ns *ns;
|
||||||
|
|
||||||
|
mutex_lock(&parent->lock);
|
||||||
|
/* try and find the specified ns */
|
||||||
|
/* released by caller */
|
||||||
|
ns = aa_get_ns(__aa_find_ns(&parent->sub_ns, name));
|
||||||
|
if (!ns)
|
||||||
|
ns = __aa_create_ns(parent, name, dir);
|
||||||
|
else
|
||||||
|
ns = ERR_PTR(-EEXIST);
|
||||||
|
mutex_unlock(&parent->lock);
|
||||||
|
|
||||||
|
/* return ref */
|
||||||
|
return ns;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* aa_prepare_ns - find an existing or create a new namespace of @name
|
||||||
|
* @parent: ns to treat as parent
|
||||||
|
* @name: the namespace to find or add (NOT NULL)
|
||||||
|
*
|
||||||
|
* Returns: refcounted namespace or PTR_ERR if failed to create one
|
||||||
|
*/
|
||||||
|
struct aa_ns *aa_prepare_ns(struct aa_ns *parent, const char *name)
|
||||||
|
{
|
||||||
|
struct aa_ns *ns;
|
||||||
|
|
||||||
|
mutex_lock(&parent->lock);
|
||||||
|
/* try and find the specified ns and if it doesn't exist create it */
|
||||||
|
/* released by caller */
|
||||||
|
ns = aa_get_ns(__aa_find_ns(&parent->sub_ns, name));
|
||||||
|
if (!ns)
|
||||||
|
ns = __aa_create_ns(parent, name, NULL);
|
||||||
|
mutex_unlock(&parent->lock);
|
||||||
|
|
||||||
|
/* return ref */
|
||||||
|
return ns;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void __ns_list_release(struct list_head *head);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* destroy_namespace - remove everything contained by @ns
|
||||||
|
* @ns: namespace to have it contents removed (NOT NULL)
|
||||||
|
*/
|
||||||
|
static void destroy_ns(struct aa_ns *ns)
|
||||||
|
{
|
||||||
|
if (!ns)
|
||||||
|
return;
|
||||||
|
|
||||||
|
mutex_lock(&ns->lock);
|
||||||
|
/* release all profiles in this namespace */
|
||||||
|
__aa_profile_list_release(&ns->base.profiles);
|
||||||
|
|
||||||
|
/* release all sub namespaces */
|
||||||
|
__ns_list_release(&ns->sub_ns);
|
||||||
|
|
||||||
|
if (ns->parent) {
|
||||||
|
unsigned long flags;
|
||||||
|
write_lock_irqsave(&ns->labels.lock, flags);
|
||||||
|
__aa_proxy_redirect(ns_unconfined(ns),
|
||||||
|
ns_unconfined(ns->parent));
|
||||||
|
write_unlock_irqrestore(&ns->labels.lock, flags);
|
||||||
|
}
|
||||||
|
__aa_fs_ns_rmdir(ns);
|
||||||
|
mutex_unlock(&ns->lock);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* __aa_remove_ns - remove a namespace and all its children
|
||||||
|
* @ns: namespace to be removed (NOT NULL)
|
||||||
|
*
|
||||||
|
* Requires: ns->parent->lock be held and ns removed from parent.
|
||||||
|
*/
|
||||||
|
void __aa_remove_ns(struct aa_ns *ns)
|
||||||
|
{
|
||||||
|
/* remove ns from namespace list */
|
||||||
|
list_del_rcu(&ns->base.list);
|
||||||
|
destroy_ns(ns);
|
||||||
|
aa_put_ns(ns);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* __ns_list_release - remove all profile namespaces on the list put refs
|
||||||
|
* @head: list of profile namespaces (NOT NULL)
|
||||||
|
*
|
||||||
|
* Requires: namespace lock be held
|
||||||
|
*/
|
||||||
|
static void __ns_list_release(struct list_head *head)
|
||||||
|
{
|
||||||
|
struct aa_ns *ns, *tmp;
|
||||||
|
list_for_each_entry_safe(ns, tmp, head, base.list)
|
||||||
|
__aa_remove_ns(ns);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* aa_alloc_root_ns - allocate the root profile namespace
|
||||||
|
*
|
||||||
|
* Returns: %0 on success else error
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
int __init aa_alloc_root_ns(void)
|
||||||
|
{
|
||||||
|
/* released by aa_free_root_ns - used as list ref*/
|
||||||
|
root_ns = alloc_ns(NULL, "root");
|
||||||
|
if (!root_ns)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* aa_free_root_ns - free the root profile namespace
|
||||||
|
*/
|
||||||
|
void __init aa_free_root_ns(void)
|
||||||
|
{
|
||||||
|
struct aa_ns *ns = root_ns;
|
||||||
|
root_ns = NULL;
|
||||||
|
|
||||||
|
destroy_ns(ns);
|
||||||
|
aa_put_ns(ns);
|
||||||
|
}
|
|
@ -20,15 +20,26 @@
|
||||||
#include <asm/unaligned.h>
|
#include <asm/unaligned.h>
|
||||||
#include <linux/ctype.h>
|
#include <linux/ctype.h>
|
||||||
#include <linux/errno.h>
|
#include <linux/errno.h>
|
||||||
|
#include <linux/string.h>
|
||||||
|
#include <linux/jhash.h>
|
||||||
|
|
||||||
#include "include/apparmor.h"
|
#include "include/apparmor.h"
|
||||||
#include "include/audit.h"
|
#include "include/audit.h"
|
||||||
#include "include/context.h"
|
#include "include/context.h"
|
||||||
#include "include/crypto.h"
|
#include "include/crypto.h"
|
||||||
#include "include/match.h"
|
#include "include/match.h"
|
||||||
|
#include "include/path.h"
|
||||||
#include "include/policy.h"
|
#include "include/policy.h"
|
||||||
#include "include/policy_unpack.h"
|
#include "include/policy_unpack.h"
|
||||||
|
|
||||||
|
#define K_ABI_MASK 0x3ff
|
||||||
|
#define FORCE_COMPLAIN_FLAG 0x800
|
||||||
|
#define VERSION_CMP(OP, X, Y) (((X) & K_ABI_MASK) OP ((Y) & K_ABI_MASK))
|
||||||
|
|
||||||
|
#define v5 5 /* base version */
|
||||||
|
#define v6 6 /* per entry policydb mediation check */
|
||||||
|
#define v7 7 /* full network masking */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* The AppArmor interface treats data as a type byte followed by the
|
* The AppArmor interface treats data as a type byte followed by the
|
||||||
* actual data. The interface has the notion of a a named entry
|
* actual data. The interface has the notion of a a named entry
|
||||||
|
@ -70,18 +81,23 @@ struct aa_ext {
|
||||||
static void audit_cb(struct audit_buffer *ab, void *va)
|
static void audit_cb(struct audit_buffer *ab, void *va)
|
||||||
{
|
{
|
||||||
struct common_audit_data *sa = va;
|
struct common_audit_data *sa = va;
|
||||||
if (sa->aad->iface.target) {
|
|
||||||
struct aa_profile *name = sa->aad->iface.target;
|
if (aad(sa)->iface.ns) {
|
||||||
audit_log_format(ab, " name=");
|
audit_log_format(ab, " ns=");
|
||||||
audit_log_untrustedstring(ab, name->base.hname);
|
audit_log_untrustedstring(ab, aad(sa)->iface.ns);
|
||||||
}
|
}
|
||||||
if (sa->aad->iface.pos)
|
if (aad(sa)->name) {
|
||||||
audit_log_format(ab, " offset=%ld", sa->aad->iface.pos);
|
audit_log_format(ab, " name=");
|
||||||
|
audit_log_untrustedstring(ab, aad(sa)->name);
|
||||||
|
}
|
||||||
|
if (aad(sa)->iface.pos)
|
||||||
|
audit_log_format(ab, " offset=%ld", aad(sa)->iface.pos);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* audit_iface - do audit message for policy unpacking/load/replace/remove
|
* audit_iface - do audit message for policy unpacking/load/replace/remove
|
||||||
* @new: profile if it has been allocated (MAYBE NULL)
|
* @new: profile if it has been allocated (MAYBE NULL)
|
||||||
|
* @ns_name: name of the ns the profile is to be loaded to (MAY BE NULL)
|
||||||
* @name: name of the profile being manipulated (MAYBE NULL)
|
* @name: name of the profile being manipulated (MAYBE NULL)
|
||||||
* @info: any extra info about the failure (MAYBE NULL)
|
* @info: any extra info about the failure (MAYBE NULL)
|
||||||
* @e: buffer position info
|
* @e: buffer position info
|
||||||
|
@ -89,23 +105,33 @@ static void audit_cb(struct audit_buffer *ab, void *va)
|
||||||
*
|
*
|
||||||
* Returns: %0 or error
|
* Returns: %0 or error
|
||||||
*/
|
*/
|
||||||
static int audit_iface(struct aa_profile *new, const char *name,
|
static int audit_iface(struct aa_profile *new, const char *ns_name,
|
||||||
const char *info, struct aa_ext *e, int error)
|
const char *name, const char *info, struct aa_ext *e,
|
||||||
|
int error)
|
||||||
{
|
{
|
||||||
struct aa_profile *profile = __aa_current_profile();
|
struct aa_profile *profile = labels_profile(aa_current_raw_label());
|
||||||
struct common_audit_data sa;
|
DEFINE_AUDIT_DATA(sa, LSM_AUDIT_DATA_NONE, NULL);
|
||||||
struct apparmor_audit_data aad = {0,};
|
|
||||||
sa.type = LSM_AUDIT_DATA_NONE;
|
|
||||||
sa.aad = &aad;
|
|
||||||
if (e)
|
if (e)
|
||||||
aad.iface.pos = e->pos - e->start;
|
aad(&sa)->iface.pos = e->pos - e->start;
|
||||||
aad.iface.target = new;
|
|
||||||
aad.name = name;
|
|
||||||
aad.info = info;
|
|
||||||
aad.error = error;
|
|
||||||
|
|
||||||
return aa_audit(AUDIT_APPARMOR_STATUS, profile, GFP_KERNEL, &sa,
|
aad(&sa)->iface.ns = ns_name;
|
||||||
audit_cb);
|
if (new)
|
||||||
|
aad(&sa)->name = new->base.hname;
|
||||||
|
else
|
||||||
|
aad(&sa)->name = name;
|
||||||
|
aad(&sa)->info = info;
|
||||||
|
aad(&sa)->error = error;
|
||||||
|
|
||||||
|
return aa_audit(AUDIT_APPARMOR_STATUS, profile, &sa, audit_cb);
|
||||||
|
}
|
||||||
|
|
||||||
|
void aa_loaddata_kref(struct kref *kref)
|
||||||
|
{
|
||||||
|
struct aa_loaddata *d = container_of(kref, struct aa_loaddata, count);
|
||||||
|
if (d) {
|
||||||
|
kzfree(d->hash);
|
||||||
|
kvfree(d);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* test if read will be in packed data bounds */
|
/* test if read will be in packed data bounds */
|
||||||
|
@ -177,7 +203,7 @@ static bool unpack_nameX(struct aa_ext *e, enum aa_code code, const char *name)
|
||||||
char *tag = NULL;
|
char *tag = NULL;
|
||||||
size_t size = unpack_u16_chunk(e, &tag);
|
size_t size = unpack_u16_chunk(e, &tag);
|
||||||
/* if a name is specified it must match. otherwise skip tag */
|
/* if a name is specified it must match. otherwise skip tag */
|
||||||
if (name && (!size || strcmp(name, tag)))
|
if (name && (!size || tag[size-1] != '\0' || strcmp(name, tag)))
|
||||||
goto fail;
|
goto fail;
|
||||||
} else if (name) {
|
} else if (name) {
|
||||||
/* if a name is specified and there is no name tag fail */
|
/* if a name is specified and there is no name tag fail */
|
||||||
|
@ -193,6 +219,19 @@ fail:
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool unpack_u16(struct aa_ext *e, u16 *data, const char *name)
|
||||||
|
{
|
||||||
|
if (unpack_nameX(e, AA_U16, name)) {
|
||||||
|
if (!inbounds(e, sizeof(u16)))
|
||||||
|
return 0;
|
||||||
|
if (data)
|
||||||
|
*data = le16_to_cpu(get_unaligned((u16 *) e->pos));
|
||||||
|
e->pos += sizeof(u16);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
static bool unpack_u32(struct aa_ext *e, u32 *data, const char *name)
|
static bool unpack_u32(struct aa_ext *e, u32 *data, const char *name)
|
||||||
{
|
{
|
||||||
if (unpack_nameX(e, AA_U32, name)) {
|
if (unpack_nameX(e, AA_U32, name)) {
|
||||||
|
@ -340,12 +379,7 @@ static struct aa_dfa *unpack_dfa(struct aa_ext *e)
|
||||||
((e->pos - e->start) & 7);
|
((e->pos - e->start) & 7);
|
||||||
size_t pad = ALIGN(sz, 8) - sz;
|
size_t pad = ALIGN(sz, 8) - sz;
|
||||||
int flags = TO_ACCEPT1_FLAG(YYTD_DATA32) |
|
int flags = TO_ACCEPT1_FLAG(YYTD_DATA32) |
|
||||||
TO_ACCEPT2_FLAG(YYTD_DATA32);
|
TO_ACCEPT2_FLAG(YYTD_DATA32) | DFA_FLAG_VERIFY_STATES;
|
||||||
|
|
||||||
|
|
||||||
if (aa_g_paranoid_load)
|
|
||||||
flags |= DFA_FLAG_VERIFY_STATES;
|
|
||||||
|
|
||||||
dfa = aa_dfa_unpack(blob + pad, size - pad, flags);
|
dfa = aa_dfa_unpack(blob + pad, size - pad, flags);
|
||||||
|
|
||||||
if (IS_ERR(dfa))
|
if (IS_ERR(dfa))
|
||||||
|
@ -389,7 +423,7 @@ static bool unpack_trans_table(struct aa_ext *e, struct aa_profile *profile)
|
||||||
profile->file.trans.size = size;
|
profile->file.trans.size = size;
|
||||||
for (i = 0; i < size; i++) {
|
for (i = 0; i < size; i++) {
|
||||||
char *str;
|
char *str;
|
||||||
int c, j, size2 = unpack_strdup(e, &str, NULL);
|
int c, j, pos, size2 = unpack_strdup(e, &str, NULL);
|
||||||
/* unpack_strdup verifies that the last character is
|
/* unpack_strdup verifies that the last character is
|
||||||
* null termination byte.
|
* null termination byte.
|
||||||
*/
|
*/
|
||||||
|
@ -401,19 +435,24 @@ static bool unpack_trans_table(struct aa_ext *e, struct aa_profile *profile)
|
||||||
goto fail;
|
goto fail;
|
||||||
|
|
||||||
/* count internal # of internal \0 */
|
/* count internal # of internal \0 */
|
||||||
for (c = j = 0; j < size2 - 2; j++) {
|
for (c = j = 0; j < size2 - 1; j++) {
|
||||||
if (!str[j])
|
if (!str[j]) {
|
||||||
|
pos = j;
|
||||||
c++;
|
c++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (*str == ':') {
|
if (*str == ':') {
|
||||||
|
/* first character after : must be valid */
|
||||||
|
if (!str[1])
|
||||||
|
goto fail;
|
||||||
/* beginning with : requires an embedded \0,
|
/* beginning with : requires an embedded \0,
|
||||||
* verify that exactly 1 internal \0 exists
|
* verify that exactly 1 internal \0 exists
|
||||||
* trailing \0 already verified by unpack_strdup
|
* trailing \0 already verified by unpack_strdup
|
||||||
*/
|
*/
|
||||||
if (c != 1)
|
if (c == 1)
|
||||||
goto fail;
|
/* convert \0 back to : for label_parse */
|
||||||
/* first character after : must be valid */
|
str[pos] = ':';
|
||||||
if (!str[1])
|
else if (c > 1)
|
||||||
goto fail;
|
goto fail;
|
||||||
} else if (c)
|
} else if (c)
|
||||||
/* fail - all other cases with embedded \0 */
|
/* fail - all other cases with embedded \0 */
|
||||||
|
@ -466,27 +505,68 @@ fail:
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void *kvmemdup(const void *src, size_t len)
|
||||||
|
{
|
||||||
|
void *p = kvmalloc(len);
|
||||||
|
|
||||||
|
if (p)
|
||||||
|
memcpy(p, src, len);
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
static u32 strhash(const void *data, u32 len, u32 seed)
|
||||||
|
{
|
||||||
|
const char * const *key = data;
|
||||||
|
|
||||||
|
return jhash(*key, strlen(*key), seed);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int datacmp(struct rhashtable_compare_arg *arg, const void *obj)
|
||||||
|
{
|
||||||
|
const struct aa_data *data = obj;
|
||||||
|
const char * const *key = arg->key;
|
||||||
|
|
||||||
|
return strcmp(data->key, *key);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* unpack_profile - unpack a serialized profile
|
* unpack_profile - unpack a serialized profile
|
||||||
* @e: serialized data extent information (NOT NULL)
|
* @e: serialized data extent information (NOT NULL)
|
||||||
*
|
*
|
||||||
* NOTE: unpack profile sets audit struct if there is a failure
|
* NOTE: unpack profile sets audit struct if there is a failure
|
||||||
*/
|
*/
|
||||||
static struct aa_profile *unpack_profile(struct aa_ext *e)
|
static struct aa_profile *unpack_profile(struct aa_ext *e, char **ns_name)
|
||||||
{
|
{
|
||||||
struct aa_profile *profile = NULL;
|
struct aa_profile *profile = NULL;
|
||||||
const char *name = NULL;
|
const char *tmpname, *tmpns = NULL, *name = NULL;
|
||||||
|
const char *info = "failed to unpack profile";
|
||||||
|
size_t size = 0, ns_len;
|
||||||
|
struct rhashtable_params params = { 0 };
|
||||||
|
char *key = NULL;
|
||||||
|
struct aa_data *data;
|
||||||
int i, error = -EPROTO;
|
int i, error = -EPROTO;
|
||||||
kernel_cap_t tmpcap;
|
kernel_cap_t tmpcap;
|
||||||
u32 tmp;
|
u32 tmp;
|
||||||
|
|
||||||
|
*ns_name = NULL;
|
||||||
|
|
||||||
/* check that we have the right struct being passed */
|
/* check that we have the right struct being passed */
|
||||||
if (!unpack_nameX(e, AA_STRUCT, "profile"))
|
if (!unpack_nameX(e, AA_STRUCT, "profile"))
|
||||||
goto fail;
|
goto fail;
|
||||||
if (!unpack_str(e, &name, NULL))
|
if (!unpack_str(e, &name, NULL))
|
||||||
goto fail;
|
goto fail;
|
||||||
|
if (*name == '\0')
|
||||||
|
goto fail;
|
||||||
|
|
||||||
profile = aa_alloc_profile(name);
|
tmpname = aa_splitn_fqname(name, strlen(name), &tmpns, &ns_len);
|
||||||
|
if (tmpns) {
|
||||||
|
*ns_name = kstrndup(tmpns, ns_len, GFP_KERNEL);
|
||||||
|
if (!*ns_name)
|
||||||
|
goto fail;
|
||||||
|
name = tmpname;
|
||||||
|
}
|
||||||
|
|
||||||
|
profile = aa_alloc_profile(name, NULL, GFP_KERNEL);
|
||||||
if (!profile)
|
if (!profile)
|
||||||
return ERR_PTR(-ENOMEM);
|
return ERR_PTR(-ENOMEM);
|
||||||
|
|
||||||
|
@ -510,16 +590,19 @@ static struct aa_profile *unpack_profile(struct aa_ext *e)
|
||||||
profile->xmatch_len = tmp;
|
profile->xmatch_len = tmp;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* disconnected attachment string is optional */
|
||||||
|
(void) unpack_str(e, &profile->disconnected, "disconnected");
|
||||||
|
|
||||||
/* per profile debug flags (complain, audit) */
|
/* per profile debug flags (complain, audit) */
|
||||||
if (!unpack_nameX(e, AA_STRUCT, "flags"))
|
if (!unpack_nameX(e, AA_STRUCT, "flags"))
|
||||||
goto fail;
|
goto fail;
|
||||||
if (!unpack_u32(e, &tmp, NULL))
|
if (!unpack_u32(e, &tmp, NULL))
|
||||||
goto fail;
|
goto fail;
|
||||||
if (tmp & PACKED_FLAG_HAT)
|
if (tmp & PACKED_FLAG_HAT)
|
||||||
profile->flags |= PFLAG_HAT;
|
profile->label.flags |= FLAG_HAT;
|
||||||
if (!unpack_u32(e, &tmp, NULL))
|
if (!unpack_u32(e, &tmp, NULL))
|
||||||
goto fail;
|
goto fail;
|
||||||
if (tmp == PACKED_MODE_COMPLAIN)
|
if (tmp == PACKED_MODE_COMPLAIN || (e->version & FORCE_COMPLAIN_FLAG))
|
||||||
profile->mode = APPARMOR_COMPLAIN;
|
profile->mode = APPARMOR_COMPLAIN;
|
||||||
else if (tmp == PACKED_MODE_KILL)
|
else if (tmp == PACKED_MODE_KILL)
|
||||||
profile->mode = APPARMOR_KILL;
|
profile->mode = APPARMOR_KILL;
|
||||||
|
@ -534,11 +617,9 @@ static struct aa_profile *unpack_profile(struct aa_ext *e)
|
||||||
goto fail;
|
goto fail;
|
||||||
|
|
||||||
/* path_flags is optional */
|
/* path_flags is optional */
|
||||||
if (unpack_u32(e, &profile->path_flags, "path_flags"))
|
if (!unpack_u32(e, &profile->path_flags, "path_flags"))
|
||||||
profile->path_flags |= profile->flags & PFLAG_MEDIATE_DELETED;
|
|
||||||
else
|
|
||||||
/* set a default value if path_flags field is not present */
|
/* set a default value if path_flags field is not present */
|
||||||
profile->path_flags = PFLAG_MEDIATE_DELETED;
|
profile->path_flags = PATH_MEDIATE_DELETED;
|
||||||
|
|
||||||
if (!unpack_u32(e, &(profile->caps.allow.cap[0]), NULL))
|
if (!unpack_u32(e, &(profile->caps.allow.cap[0]), NULL))
|
||||||
goto fail;
|
goto fail;
|
||||||
|
@ -576,6 +657,37 @@ static struct aa_profile *unpack_profile(struct aa_ext *e)
|
||||||
if (!unpack_rlimits(e, profile))
|
if (!unpack_rlimits(e, profile))
|
||||||
goto fail;
|
goto fail;
|
||||||
|
|
||||||
|
size = unpack_array(e, "net_allowed_af");
|
||||||
|
if (size) {
|
||||||
|
|
||||||
|
for (i = 0; i < size; i++) {
|
||||||
|
/* discard extraneous rules that this kernel will
|
||||||
|
* never request
|
||||||
|
*/
|
||||||
|
if (i >= AF_MAX) {
|
||||||
|
u16 tmp;
|
||||||
|
if (!unpack_u16(e, &tmp, NULL) ||
|
||||||
|
!unpack_u16(e, &tmp, NULL) ||
|
||||||
|
!unpack_u16(e, &tmp, NULL))
|
||||||
|
goto fail;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!unpack_u16(e, &profile->net.allow[i], NULL))
|
||||||
|
goto fail;
|
||||||
|
if (!unpack_u16(e, &profile->net.audit[i], NULL))
|
||||||
|
goto fail;
|
||||||
|
if (!unpack_u16(e, &profile->net.quiet[i], NULL))
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
if (!unpack_nameX(e, AA_ARRAYEND, NULL))
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
if (VERSION_CMP(<, e->version, v7)) {
|
||||||
|
/* old policy always allowed these too */
|
||||||
|
profile->net.allow[AF_UNIX] = 0xffff;
|
||||||
|
profile->net.allow[AF_NETLINK] = 0xffff;
|
||||||
|
}
|
||||||
|
|
||||||
if (unpack_nameX(e, AA_STRUCT, "policydb")) {
|
if (unpack_nameX(e, AA_STRUCT, "policydb")) {
|
||||||
/* generic policy dfa - optional and may be NULL */
|
/* generic policy dfa - optional and may be NULL */
|
||||||
profile->policy.dfa = unpack_dfa(e);
|
profile->policy.dfa = unpack_dfa(e);
|
||||||
|
@ -583,6 +695,9 @@ static struct aa_profile *unpack_profile(struct aa_ext *e)
|
||||||
error = PTR_ERR(profile->policy.dfa);
|
error = PTR_ERR(profile->policy.dfa);
|
||||||
profile->policy.dfa = NULL;
|
profile->policy.dfa = NULL;
|
||||||
goto fail;
|
goto fail;
|
||||||
|
} else if (!profile->policy.dfa) {
|
||||||
|
error = -EPROTO;
|
||||||
|
goto fail;
|
||||||
}
|
}
|
||||||
if (!unpack_u32(e, &profile->policy.start[0], "start"))
|
if (!unpack_u32(e, &profile->policy.start[0], "start"))
|
||||||
/* default start state */
|
/* default start state */
|
||||||
|
@ -596,7 +711,8 @@ static struct aa_profile *unpack_profile(struct aa_ext *e)
|
||||||
}
|
}
|
||||||
if (!unpack_nameX(e, AA_STRUCTEND, NULL))
|
if (!unpack_nameX(e, AA_STRUCTEND, NULL))
|
||||||
goto fail;
|
goto fail;
|
||||||
}
|
} else
|
||||||
|
profile->policy.dfa = aa_get_dfa(nulldfa);
|
||||||
|
|
||||||
/* get file rules */
|
/* get file rules */
|
||||||
profile->file.dfa = unpack_dfa(e);
|
profile->file.dfa = unpack_dfa(e);
|
||||||
|
@ -604,15 +720,59 @@ static struct aa_profile *unpack_profile(struct aa_ext *e)
|
||||||
error = PTR_ERR(profile->file.dfa);
|
error = PTR_ERR(profile->file.dfa);
|
||||||
profile->file.dfa = NULL;
|
profile->file.dfa = NULL;
|
||||||
goto fail;
|
goto fail;
|
||||||
}
|
} else if (profile->file.dfa) {
|
||||||
|
if (!unpack_u32(e, &profile->file.start, "dfa_start"))
|
||||||
if (!unpack_u32(e, &profile->file.start, "dfa_start"))
|
/* default start state */
|
||||||
/* default start state */
|
profile->file.start = DFA_START;
|
||||||
profile->file.start = DFA_START;
|
} else if (profile->policy.dfa &&
|
||||||
|
profile->policy.start[AA_CLASS_FILE]) {
|
||||||
|
profile->file.dfa = aa_get_dfa(profile->policy.dfa);
|
||||||
|
profile->file.start = profile->policy.start[AA_CLASS_FILE];
|
||||||
|
} else
|
||||||
|
profile->file.dfa = aa_get_dfa(nulldfa);
|
||||||
|
|
||||||
if (!unpack_trans_table(e, profile))
|
if (!unpack_trans_table(e, profile))
|
||||||
goto fail;
|
goto fail;
|
||||||
|
|
||||||
|
if (unpack_nameX(e, AA_STRUCT, "data")) {
|
||||||
|
profile->data = kzalloc(sizeof(*profile->data), GFP_KERNEL);
|
||||||
|
if (!profile->data)
|
||||||
|
goto fail;
|
||||||
|
|
||||||
|
params.nelem_hint = 3;
|
||||||
|
params.key_len = sizeof(void *);
|
||||||
|
params.key_offset = offsetof(struct aa_data, key);
|
||||||
|
params.head_offset = offsetof(struct aa_data, head);
|
||||||
|
params.hashfn = strhash;
|
||||||
|
params.obj_cmpfn = datacmp;
|
||||||
|
|
||||||
|
if (rhashtable_init(profile->data, ¶ms))
|
||||||
|
goto fail;
|
||||||
|
|
||||||
|
while (unpack_strdup(e, &key, NULL)) {
|
||||||
|
data = kzalloc(sizeof(*data), GFP_KERNEL);
|
||||||
|
if (!data) {
|
||||||
|
kzfree(key);
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
data->key = key;
|
||||||
|
data->size = unpack_blob(e, &data->data, NULL);
|
||||||
|
data->data = kvmemdup(data->data, data->size);
|
||||||
|
if (data->size && !data->data) {
|
||||||
|
kzfree(data->key);
|
||||||
|
kzfree(data);
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
rhashtable_insert_fast(profile->data, &data->head,
|
||||||
|
profile->data->p);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!unpack_nameX(e, AA_STRUCTEND, NULL))
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
if (!unpack_nameX(e, AA_STRUCTEND, NULL))
|
if (!unpack_nameX(e, AA_STRUCTEND, NULL))
|
||||||
goto fail;
|
goto fail;
|
||||||
|
|
||||||
|
@ -623,7 +783,7 @@ fail:
|
||||||
name = NULL;
|
name = NULL;
|
||||||
else if (!name)
|
else if (!name)
|
||||||
name = "unknown";
|
name = "unknown";
|
||||||
audit_iface(profile, name, "failed to unpack profile", e, error);
|
audit_iface(profile, NULL, name, info, e, error);
|
||||||
aa_free_profile(profile);
|
aa_free_profile(profile);
|
||||||
|
|
||||||
return ERR_PTR(error);
|
return ERR_PTR(error);
|
||||||
|
@ -646,24 +806,32 @@ static int verify_header(struct aa_ext *e, int required, const char **ns)
|
||||||
/* get the interface version */
|
/* get the interface version */
|
||||||
if (!unpack_u32(e, &e->version, "version")) {
|
if (!unpack_u32(e, &e->version, "version")) {
|
||||||
if (required) {
|
if (required) {
|
||||||
audit_iface(NULL, NULL, "invalid profile format", e,
|
audit_iface(NULL, NULL, NULL, "invalid profile format",
|
||||||
error);
|
|
||||||
return error;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* check that the interface version is currently supported */
|
|
||||||
if (e->version != 5) {
|
|
||||||
audit_iface(NULL, NULL, "unsupported interface version",
|
|
||||||
e, error);
|
e, error);
|
||||||
return error;
|
return 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, NULL, "unsupported interface version",
|
||||||
|
e, error);
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
/* read the namespace if present */
|
/* read the namespace if present */
|
||||||
if (unpack_str(e, &name, "namespace")) {
|
if (unpack_str(e, &name, "namespace")) {
|
||||||
|
if (*name == '\0') {
|
||||||
|
audit_iface(NULL, NULL, NULL, "invalid namespace name",
|
||||||
|
e, error);
|
||||||
|
return error;
|
||||||
|
}
|
||||||
if (*ns && strcmp(*ns, name))
|
if (*ns && strcmp(*ns, name))
|
||||||
audit_iface(NULL, NULL, "invalid ns change", e, error);
|
audit_iface(NULL, NULL, NULL, "invalid ns change", e,
|
||||||
|
error);
|
||||||
else if (!*ns)
|
else if (!*ns)
|
||||||
*ns = name;
|
*ns = name;
|
||||||
}
|
}
|
||||||
|
@ -676,7 +844,7 @@ static bool verify_xindex(int xindex, int table_size)
|
||||||
int index, xtype;
|
int index, xtype;
|
||||||
xtype = xindex & AA_X_TYPE_MASK;
|
xtype = xindex & AA_X_TYPE_MASK;
|
||||||
index = xindex & AA_X_INDEX_MASK;
|
index = xindex & AA_X_INDEX_MASK;
|
||||||
if (xtype == AA_X_TABLE && index > table_size)
|
if (xtype == AA_X_TABLE && index >= table_size)
|
||||||
return 0;
|
return 0;
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
@ -702,14 +870,12 @@ static bool verify_dfa_xindex(struct aa_dfa *dfa, int table_size)
|
||||||
*/
|
*/
|
||||||
static int verify_profile(struct aa_profile *profile)
|
static int verify_profile(struct aa_profile *profile)
|
||||||
{
|
{
|
||||||
if (aa_g_paranoid_load) {
|
if (profile->file.dfa &&
|
||||||
if (profile->file.dfa &&
|
!verify_dfa_xindex(profile->file.dfa,
|
||||||
!verify_dfa_xindex(profile->file.dfa,
|
profile->file.trans.size)) {
|
||||||
profile->file.trans.size)) {
|
audit_iface(profile, NULL, NULL,
|
||||||
audit_iface(profile, NULL, "Invalid named transition",
|
"Invalid named transition", NULL, -EPROTO);
|
||||||
NULL, -EPROTO);
|
return -EPROTO;
|
||||||
return -EPROTO;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -721,6 +887,7 @@ void aa_load_ent_free(struct aa_load_ent *ent)
|
||||||
aa_put_profile(ent->rename);
|
aa_put_profile(ent->rename);
|
||||||
aa_put_profile(ent->old);
|
aa_put_profile(ent->old);
|
||||||
aa_put_profile(ent->new);
|
aa_put_profile(ent->new);
|
||||||
|
kfree(ent->ns_name);
|
||||||
kzfree(ent);
|
kzfree(ent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -736,7 +903,6 @@ struct aa_load_ent *aa_load_ent_alloc(void)
|
||||||
/**
|
/**
|
||||||
* aa_unpack - unpack packed binary profile(s) 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)
|
* @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
|
* @lh: list to place unpacked profiles in a aa_repl_ws
|
||||||
* @ns: Returns namespace profile is in if specified else NULL (NOT NULL)
|
* @ns: Returns namespace profile is in if specified else NULL (NOT NULL)
|
||||||
*
|
*
|
||||||
|
@ -746,26 +912,26 @@ struct aa_load_ent *aa_load_ent_alloc(void)
|
||||||
*
|
*
|
||||||
* Returns: profile(s) on @lh else error pointer if fails to unpack
|
* Returns: profile(s) on @lh else error pointer if fails to unpack
|
||||||
*/
|
*/
|
||||||
int aa_unpack(void *udata, size_t size, struct list_head *lh, const char **ns)
|
int aa_unpack(struct aa_loaddata *udata, struct list_head *lh, const char **ns)
|
||||||
{
|
{
|
||||||
struct aa_load_ent *tmp, *ent;
|
struct aa_load_ent *tmp, *ent;
|
||||||
struct aa_profile *profile = NULL;
|
struct aa_profile *profile = NULL;
|
||||||
int error;
|
int error;
|
||||||
struct aa_ext e = {
|
struct aa_ext e = {
|
||||||
.start = udata,
|
.start = udata->data,
|
||||||
.end = udata + size,
|
.end = udata->data + udata->size,
|
||||||
.pos = udata,
|
.pos = udata->data,
|
||||||
};
|
};
|
||||||
|
|
||||||
*ns = NULL;
|
*ns = NULL;
|
||||||
while (e.pos < e.end) {
|
while (e.pos < e.end) {
|
||||||
|
char *ns_name = NULL;
|
||||||
void *start;
|
void *start;
|
||||||
error = verify_header(&e, e.pos == e.start, ns);
|
error = verify_header(&e, e.pos == e.start, ns);
|
||||||
if (error)
|
if (error)
|
||||||
goto fail;
|
goto fail;
|
||||||
|
|
||||||
start = e.pos;
|
start = e.pos;
|
||||||
profile = unpack_profile(&e);
|
profile = unpack_profile(&e, &ns_name);
|
||||||
if (IS_ERR(profile)) {
|
if (IS_ERR(profile)) {
|
||||||
error = PTR_ERR(profile);
|
error = PTR_ERR(profile);
|
||||||
goto fail;
|
goto fail;
|
||||||
|
@ -775,8 +941,9 @@ int aa_unpack(void *udata, size_t size, struct list_head *lh, const char **ns)
|
||||||
if (error)
|
if (error)
|
||||||
goto fail_profile;
|
goto fail_profile;
|
||||||
|
|
||||||
error = aa_calc_profile_hash(profile, e.version, start,
|
if (aa_g_hash_policy)
|
||||||
e.pos - start);
|
error = aa_calc_profile_hash(profile, e.version, start,
|
||||||
|
e.pos - start);
|
||||||
if (error)
|
if (error)
|
||||||
goto fail_profile;
|
goto fail_profile;
|
||||||
|
|
||||||
|
@ -787,9 +954,18 @@ int aa_unpack(void *udata, size_t size, struct list_head *lh, const char **ns)
|
||||||
}
|
}
|
||||||
|
|
||||||
ent->new = profile;
|
ent->new = profile;
|
||||||
|
ent->ns_name = ns_name;
|
||||||
list_add_tail(&ent->list, lh);
|
list_add_tail(&ent->list, lh);
|
||||||
}
|
}
|
||||||
|
udata->abi = e.version & K_ABI_MASK;
|
||||||
|
if (aa_g_hash_policy) {
|
||||||
|
udata->hash = aa_calc_hash(udata->data, udata->size);
|
||||||
|
if (IS_ERR(udata->hash)) {
|
||||||
|
error = PTR_ERR(udata->hash);
|
||||||
|
udata->hash = NULL;
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
}
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
fail_profile:
|
fail_profile:
|
||||||
|
|
|
@ -33,50 +33,41 @@
|
||||||
*
|
*
|
||||||
* Returns: size of string placed in @string else error code on failure
|
* Returns: size of string placed in @string else error code on failure
|
||||||
*/
|
*/
|
||||||
int aa_getprocattr(struct aa_profile *profile, char **string)
|
int aa_getprocattr(struct aa_label *label, char **string)
|
||||||
{
|
{
|
||||||
char *str;
|
struct aa_ns *ns = labels_ns(label);
|
||||||
int len = 0, mode_len = 0, ns_len = 0, name_len;
|
struct aa_ns *current_ns = aa_get_current_ns();
|
||||||
const char *mode_str = aa_profile_mode_names[profile->mode];
|
int len;
|
||||||
const char *ns_name = NULL;
|
|
||||||
struct aa_namespace *ns = profile->ns;
|
|
||||||
struct aa_namespace *current_ns = __aa_current_profile()->ns;
|
|
||||||
char *s;
|
|
||||||
|
|
||||||
if (!aa_ns_visible(current_ns, ns))
|
if (!aa_ns_visible(current_ns, ns, true)) {
|
||||||
|
aa_put_ns(current_ns);
|
||||||
return -EACCES;
|
return -EACCES;
|
||||||
|
|
||||||
ns_name = aa_ns_name(current_ns, ns);
|
|
||||||
ns_len = strlen(ns_name);
|
|
||||||
|
|
||||||
/* if the visible ns_name is > 0 increase size for : :// seperator */
|
|
||||||
if (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)
|
|
||||||
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;
|
|
||||||
|
|
||||||
/* NOTE: len does not include \0 of string, not saved as part of file */
|
len = aa_label_snxprint(NULL, 0, current_ns, label,
|
||||||
return len;
|
FLAG_SHOW_MODE | FLAG_VIEW_SUBNS |
|
||||||
|
FLAG_HIDDEN_UNCONFINED);
|
||||||
|
AA_BUG(len < 0);
|
||||||
|
|
||||||
|
*string = kmalloc(len + 2, GFP_KERNEL);
|
||||||
|
if (!*string) {
|
||||||
|
aa_put_ns(current_ns);
|
||||||
|
return -ENOMEM;
|
||||||
|
}
|
||||||
|
|
||||||
|
len = aa_label_snxprint(*string, len + 2, current_ns, label,
|
||||||
|
FLAG_SHOW_MODE | FLAG_VIEW_SUBNS |
|
||||||
|
FLAG_HIDDEN_UNCONFINED);
|
||||||
|
if (len < 0) {
|
||||||
|
aa_put_ns(current_ns);
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
|
||||||
|
(*string)[len] = '\n';
|
||||||
|
(*string)[len + 1] = 0;
|
||||||
|
|
||||||
|
aa_put_ns(current_ns);
|
||||||
|
return len + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -87,13 +78,13 @@ int aa_getprocattr(struct aa_profile *profile, char **string)
|
||||||
*
|
*
|
||||||
* Returns: start position of name after token else NULL on failure
|
* Returns: start position of name after token else NULL on failure
|
||||||
*/
|
*/
|
||||||
static char *split_token_from_name(int op, char *args, u64 * token)
|
static char *split_token_from_name(const char *op, char *args, u64 * token)
|
||||||
{
|
{
|
||||||
char *name;
|
char *name;
|
||||||
|
|
||||||
*token = simple_strtoull(args, &name, 16);
|
*token = simple_strtoull(args, &name, 16);
|
||||||
if ((name == args) || *name != '^') {
|
if ((name == args) || *name != '^') {
|
||||||
AA_ERROR("%s: Invalid input '%s'", op_table[op], args);
|
AA_ERROR("%s: Invalid input '%s'", op, args);
|
||||||
return ERR_PTR(-EINVAL);
|
return ERR_PTR(-EINVAL);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -138,28 +129,13 @@ int aa_setprocattr_changehat(char *args, size_t size, int test)
|
||||||
for (count = 0; (hat < end) && count < 16; ++count) {
|
for (count = 0; (hat < end) && count < 16; ++count) {
|
||||||
char *next = hat + strlen(hat) + 1;
|
char *next = hat + strlen(hat) + 1;
|
||||||
hats[count] = hat;
|
hats[count] = hat;
|
||||||
|
AA_DEBUG("%s: (pid %d) Magic 0x%llx count %d hat '%s'\n"
|
||||||
|
, __func__, current->pid, token, count, hat);
|
||||||
hat = next;
|
hat = next;
|
||||||
}
|
}
|
||||||
}
|
} else
|
||||||
|
AA_DEBUG("%s: (pid %d) Magic 0x%llx count %d Hat '%s'\n",
|
||||||
AA_DEBUG("%s: Magic 0x%llx Hat '%s'\n",
|
__func__, current->pid, token, count, "<NULL>");
|
||||||
__func__, token, hat ? hat : NULL);
|
|
||||||
|
|
||||||
return aa_change_hat(hats, count, token, test);
|
return aa_change_hat(hats, count, token, test);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* aa_setprocattr_changeprofile - handle procattr interface to changeprofile
|
|
||||||
* @fqname: args received from writting to /proc/<pid>/attr/current (NOT NULL)
|
|
||||||
* @onexec: true if change_profile should be delayed until exec
|
|
||||||
* @test: true if this is a test of change_profile permissions
|
|
||||||
*
|
|
||||||
* Returns: %0 or error code if change_profile fails
|
|
||||||
*/
|
|
||||||
int aa_setprocattr_changeprofile(char *fqname, bool onexec, int test)
|
|
||||||
{
|
|
||||||
char *name, *ns_name;
|
|
||||||
|
|
||||||
name = aa_split_fqname(fqname, &ns_name);
|
|
||||||
return aa_change_profile(ns_name, name, onexec, test);
|
|
||||||
}
|
|
||||||
|
|
|
@ -35,7 +35,7 @@ static void audit_cb(struct audit_buffer *ab, void *va)
|
||||||
struct common_audit_data *sa = va;
|
struct common_audit_data *sa = va;
|
||||||
|
|
||||||
audit_log_format(ab, " rlimit=%s value=%lu",
|
audit_log_format(ab, " rlimit=%s value=%lu",
|
||||||
rlim_names[sa->aad->rlim.rlim], sa->aad->rlim.max);
|
rlim_names[aad(sa)->rlim.rlim], aad(sa)->rlim.max);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -50,17 +50,11 @@ static void audit_cb(struct audit_buffer *ab, void *va)
|
||||||
static int audit_resource(struct aa_profile *profile, unsigned int resource,
|
static int audit_resource(struct aa_profile *profile, unsigned int resource,
|
||||||
unsigned long value, int error)
|
unsigned long value, int error)
|
||||||
{
|
{
|
||||||
struct common_audit_data sa;
|
DEFINE_AUDIT_DATA(sa, LSM_AUDIT_DATA_NONE, OP_SETRLIMIT);
|
||||||
struct apparmor_audit_data aad = {0,};
|
aad(&sa)->rlim.rlim = resource;
|
||||||
|
aad(&sa)->rlim.max = value;
|
||||||
sa.type = LSM_AUDIT_DATA_NONE;
|
aad(&sa)->error = error;
|
||||||
sa.aad = &aad;
|
return aa_audit(AUDIT_APPARMOR_AUTO, profile, &sa, audit_cb);
|
||||||
aad.op = OP_SETRLIMIT,
|
|
||||||
aad.rlim.rlim = resource;
|
|
||||||
aad.rlim.max = value;
|
|
||||||
aad.error = error;
|
|
||||||
return aa_audit(AUDIT_APPARMOR_AUTO, profile, GFP_KERNEL, &sa,
|
|
||||||
audit_cb);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -77,9 +71,20 @@ int aa_map_resource(int resource)
|
||||||
return rlim_map[resource];
|
return rlim_map[resource];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int profile_setrlimit(struct aa_profile *profile, unsigned int resource,
|
||||||
|
struct rlimit *new_rlim)
|
||||||
|
{
|
||||||
|
int e = 0;
|
||||||
|
|
||||||
|
if (profile->rlimits.mask & (1 << resource) && new_rlim->rlim_max >
|
||||||
|
profile->rlimits.limits[resource].rlim_max)
|
||||||
|
e = -EACCES;
|
||||||
|
return audit_resource(profile, resource, new_rlim->rlim_max, e);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* aa_task_setrlimit - test permission to set an rlimit
|
* aa_task_setrlimit - test permission to set an rlimit
|
||||||
* @profile - profile confining the task (NOT NULL)
|
* @label - label confining the task (NOT NULL)
|
||||||
* @task - task the resource is being set on
|
* @task - task the resource is being set on
|
||||||
* @resource - the resource being set
|
* @resource - the resource being set
|
||||||
* @new_rlim - the new resource limit (NOT NULL)
|
* @new_rlim - the new resource limit (NOT NULL)
|
||||||
|
@ -88,67 +93,84 @@ int aa_map_resource(int resource)
|
||||||
*
|
*
|
||||||
* Returns: 0 or error code if setting resource failed
|
* Returns: 0 or error code if setting resource failed
|
||||||
*/
|
*/
|
||||||
int aa_task_setrlimit(struct aa_profile *profile, struct task_struct *task,
|
int aa_task_setrlimit(struct aa_label *label, struct task_struct *task,
|
||||||
unsigned int resource, struct rlimit *new_rlim)
|
unsigned int resource, struct rlimit *new_rlim)
|
||||||
{
|
{
|
||||||
struct aa_profile *task_profile;
|
struct aa_profile *profile;
|
||||||
|
struct aa_label *peer;
|
||||||
int error = 0;
|
int error = 0;
|
||||||
|
|
||||||
rcu_read_lock();
|
rcu_read_lock();
|
||||||
task_profile = aa_get_profile(aa_cred_profile(__task_cred(task)));
|
peer = aa_get_newest_cred_label(__task_cred(task));
|
||||||
rcu_read_unlock();
|
rcu_read_unlock();
|
||||||
|
|
||||||
/* TODO: extend resource control to handle other (non current)
|
/* TODO: extend resource control to handle other (non current)
|
||||||
* profiles. AppArmor rules currently have the implicit assumption
|
* profiles. AppArmor rules currently have the implicit assumption
|
||||||
* that the task is setting the resource of a task confined with
|
* that the task is setting the resource of a task confined with
|
||||||
* the same profile.
|
* the same profile or that the task setting the resource of another
|
||||||
|
* task has CAP_SYS_RESOURCE.
|
||||||
*/
|
*/
|
||||||
if (profile != task_profile ||
|
|
||||||
(profile->rlimits.mask & (1 << resource) &&
|
|
||||||
new_rlim->rlim_max > profile->rlimits.limits[resource].rlim_max))
|
|
||||||
error = -EACCES;
|
|
||||||
|
|
||||||
aa_put_profile(task_profile);
|
if (label != peer &&
|
||||||
|
!aa_capable(label, CAP_SYS_RESOURCE, SECURITY_CAP_NOAUDIT))
|
||||||
|
error = fn_for_each(label, profile,
|
||||||
|
audit_resource(profile, resource,
|
||||||
|
new_rlim->rlim_max, EACCES));
|
||||||
|
else
|
||||||
|
error = fn_for_each_confined(label, profile,
|
||||||
|
profile_setrlimit(profile, resource, new_rlim));
|
||||||
|
aa_put_label(peer);
|
||||||
|
|
||||||
return audit_resource(profile, resource, new_rlim->rlim_max, error);
|
return error;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* __aa_transition_rlimits - apply new profile rlimits
|
* __aa_transition_rlimits - apply new profile rlimits
|
||||||
* @old: old profile on task (NOT NULL)
|
* @old_l: old label on task (NOT NULL)
|
||||||
* @new: new profile with rlimits to apply (NOT NULL)
|
* @new_l: new label with rlimits to apply (NOT NULL)
|
||||||
*/
|
*/
|
||||||
void __aa_transition_rlimits(struct aa_profile *old, struct aa_profile *new)
|
void __aa_transition_rlimits(struct aa_label *old_l, struct aa_label *new_l)
|
||||||
{
|
{
|
||||||
unsigned int mask = 0;
|
unsigned int mask = 0;
|
||||||
struct rlimit *rlim, *initrlim;
|
struct rlimit *rlim, *initrlim;
|
||||||
int i;
|
struct aa_profile *old, *new;
|
||||||
|
struct label_it i;
|
||||||
|
|
||||||
/* for any rlimits the profile controlled reset the soft limit
|
old = labels_profile(old_l);
|
||||||
* to the less of the tasks hard limit and the init tasks soft limit
|
new = labels_profile(new_l);
|
||||||
|
|
||||||
|
/* for any rlimits the profile controlled, reset the soft limit
|
||||||
|
* to the lesser of the tasks hard limit and the init tasks soft limit
|
||||||
*/
|
*/
|
||||||
if (old->rlimits.mask) {
|
label_for_each_confined(i, old_l, old) {
|
||||||
for (i = 0, mask = 1; i < RLIM_NLIMITS; i++, mask <<= 1) {
|
if (old->rlimits.mask) {
|
||||||
if (old->rlimits.mask & mask) {
|
int j;
|
||||||
rlim = current->signal->rlim + i;
|
for (j = 0, mask = 1; j < RLIM_NLIMITS; j++,
|
||||||
initrlim = init_task.signal->rlim + i;
|
mask <<= 1) {
|
||||||
rlim->rlim_cur = min(rlim->rlim_max,
|
if (old->rlimits.mask & mask) {
|
||||||
initrlim->rlim_cur);
|
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 */
|
/* set any new hard limits as dictated by the new profile */
|
||||||
if (!new->rlimits.mask)
|
label_for_each_confined(i, new_l, new) {
|
||||||
return;
|
int j;
|
||||||
for (i = 0, mask = 1; i < RLIM_NLIMITS; i++, mask <<= 1) {
|
if (!new->rlimits.mask)
|
||||||
if (!(new->rlimits.mask & mask))
|
|
||||||
continue;
|
continue;
|
||||||
|
for (j = 0, mask = 1; j < RLIM_NLIMITS; j++, mask <<= 1) {
|
||||||
|
if (!(new->rlimits.mask & mask))
|
||||||
|
continue;
|
||||||
|
|
||||||
rlim = current->signal->rlim + i;
|
rlim = current->signal->rlim + j;
|
||||||
rlim->rlim_max = min(rlim->rlim_max,
|
rlim->rlim_max = min(rlim->rlim_max,
|
||||||
new->rlimits.limits[i].rlim_max);
|
new->rlimits.limits[j].rlim_max);
|
||||||
/* soft limit should not exceed hard limit */
|
/* soft limit should not exceed hard limit */
|
||||||
rlim->rlim_cur = min(rlim->rlim_cur, rlim->rlim_max);
|
rlim->rlim_cur = min(rlim->rlim_cur, rlim->rlim_max);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
116
security/inode.c
116
security/inode.c
|
@ -46,6 +46,122 @@ static struct file_system_type fs_type = {
|
||||||
.kill_sb = kill_litter_super,
|
.kill_sb = kill_litter_super,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
int securityfs_pin_fs(void)
|
||||||
|
{
|
||||||
|
return simple_pin_fs(&fs_type, &mount, &mount_count);
|
||||||
|
}
|
||||||
|
|
||||||
|
int __securityfs_setup_d_inode(struct inode *dir, struct dentry *dentry,
|
||||||
|
umode_t mode, void *data,
|
||||||
|
const struct file_operations *fops,
|
||||||
|
const struct inode_operations *iops)
|
||||||
|
{
|
||||||
|
bool is_dir = S_ISDIR(mode);
|
||||||
|
struct inode *inode = new_inode(dir->i_sb);
|
||||||
|
if (!inode)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
inode->i_ino = get_next_ino();
|
||||||
|
inode->i_mode = mode;
|
||||||
|
inode->i_atime = inode->i_mtime = inode->i_ctime = CURRENT_TIME;
|
||||||
|
inode->i_private = data;
|
||||||
|
if (is_dir) {
|
||||||
|
inode->i_op = iops ? iops : &simple_dir_inode_operations;
|
||||||
|
inode->i_fop = &simple_dir_operations;
|
||||||
|
inc_nlink(inode);
|
||||||
|
inc_nlink(dir);
|
||||||
|
} else {
|
||||||
|
inode->i_fop = fops;
|
||||||
|
}
|
||||||
|
d_instantiate(dentry, inode);
|
||||||
|
dget(dentry);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
EXPORT_SYMBOL_GPL(__securityfs_setup_d_inode);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* securityfs_create_dentry - create a file/dir in the securityfs filesystem
|
||||||
|
*
|
||||||
|
* @name: a pointer to a string containing the name of the file to create.
|
||||||
|
* @mode: the permission that the file should have
|
||||||
|
* @parent: a pointer to the parent dentry for this file. This should be a
|
||||||
|
* directory dentry if set. If this parameter is %NULL, then the
|
||||||
|
* file will be created in the root of the securityfs filesystem.
|
||||||
|
* @data: a pointer to something that the caller will want to get to later
|
||||||
|
* on. The inode.i_private pointer will point to this value on
|
||||||
|
* the open() call.
|
||||||
|
* @fops: a pointer to a struct file_operations that should be used for
|
||||||
|
* this file.
|
||||||
|
* @iops: a point to a struct of inode_operations that should be used for
|
||||||
|
* this file/dir
|
||||||
|
*
|
||||||
|
* This is the basic "create a xxx" function for securityfs. It allows for a
|
||||||
|
* wide range of flexibility in creating a file, or a directory (if you
|
||||||
|
* want to create a directory, the securityfs_create_dir() function is
|
||||||
|
* recommended to be used instead).
|
||||||
|
*
|
||||||
|
* This function returns a pointer to a dentry if it succeeds. This
|
||||||
|
* pointer must be passed to the securityfs_remove() function when the file is
|
||||||
|
* to be removed (no automatic cleanup happens if your module is unloaded,
|
||||||
|
* you are responsible here). If an error occurs, the function will return
|
||||||
|
* the error value (via ERR_PTR).
|
||||||
|
*
|
||||||
|
* If securityfs is not enabled in the kernel, the value %-ENODEV is
|
||||||
|
* returned.
|
||||||
|
*/
|
||||||
|
struct dentry *securityfs_create_dentry(const char *name, umode_t mode,
|
||||||
|
struct dentry *parent, void *data,
|
||||||
|
const struct file_operations *fops,
|
||||||
|
const struct inode_operations *iops)
|
||||||
|
{
|
||||||
|
struct dentry *dentry;
|
||||||
|
int is_dir = S_ISDIR(mode);
|
||||||
|
struct inode *dir;
|
||||||
|
int error;
|
||||||
|
|
||||||
|
if (!is_dir) {
|
||||||
|
BUG_ON(!fops);
|
||||||
|
mode = (mode & S_IALLUGO) | S_IFREG;
|
||||||
|
}
|
||||||
|
|
||||||
|
pr_debug("securityfs: creating file '%s'\n",name);
|
||||||
|
|
||||||
|
error = simple_pin_fs(&fs_type, &mount, &mount_count);
|
||||||
|
if (error)
|
||||||
|
return ERR_PTR(error);
|
||||||
|
|
||||||
|
if (!parent)
|
||||||
|
parent = mount->mnt_root;
|
||||||
|
|
||||||
|
dir = d_inode(parent);
|
||||||
|
|
||||||
|
inode_lock(dir);
|
||||||
|
dentry = lookup_one_len(name, parent, strlen(name));
|
||||||
|
if (IS_ERR(dentry))
|
||||||
|
goto out;
|
||||||
|
|
||||||
|
if (d_really_is_positive(dentry)) {
|
||||||
|
error = -EEXIST;
|
||||||
|
goto out1;
|
||||||
|
}
|
||||||
|
|
||||||
|
error = __securityfs_setup_d_inode(dir, dentry, mode, data, fops, iops);
|
||||||
|
if (error)
|
||||||
|
goto out1;
|
||||||
|
inode_unlock(dir);
|
||||||
|
return dentry;
|
||||||
|
|
||||||
|
out1:
|
||||||
|
dput(dentry);
|
||||||
|
dentry = ERR_PTR(error);
|
||||||
|
out:
|
||||||
|
inode_unlock(dir);
|
||||||
|
simple_release_fs(&mount, &mount_count);
|
||||||
|
return dentry;
|
||||||
|
}
|
||||||
|
EXPORT_SYMBOL_GPL(securityfs_create_dentry);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* securityfs_create_file - create a file in the securityfs filesystem
|
* securityfs_create_file - create a file in the securityfs filesystem
|
||||||
*
|
*
|
||||||
|
|
Loading…
Add table
Reference in a new issue