+ Features

- switch to zstd compression for profile raw data
 
 + Cleanups
   - Simplify obtain the newest label on a cred
   - remove useless static inline functions
   - compute permission conversion on policy unpack
   - refactor code to share common permissins
   - refactor unpack to group policy backwards compatiblity code
   - add __init annotation to aa_{setup/teardown}_dfa_engine()
 
 + Bug Fixes
   - fix a memleak in
     - multi_transaction_new()
     - free_ruleset()
     - unpack_profile()
     - alloc_ns()
   - fix lockdep warning when removing a namespace
   - fix regression in stacking due to label flags
   - fix loading of child before parent
   - fix kernel-doc comments that differ from fns
   - fix spelling errors in comments
   - store return value of unpack_perms_table() to signed variable
 -----BEGIN PGP SIGNATURE-----
 
 iQIzBAABCgAdFiEE7cSDD705q2rFEEf7BS82cBjVw9gFAmOZwywACgkQBS82cBjV
 w9jBjRAAmj4gyK0L3eGY4IV2BpvnkHwHY4lOObJulTwILOOj0Pz8CJqRCa/HDCGj
 aOlnwqksPsAjadzzfi58D6TnT+3fOuskbcMgTyvX5jraTXPrUl90+hXorbXKuLrw
 iaX6QxW8soNW/s3oJhrC2HxbIhGA9VpVnmQpVZpJMmz5bU2xmzL62FCN8x88kytr
 9CygaudPrvwYJf5pPd62p7ltj2S6lFwZ6dVCyiDQGTc+Gyng4G8p4MCfI1CwMMyo
 mAUeeRnoeeBwH3tSy/Wsr72jPKjsMASpcMHo3ns/dVSw/ug2FYYToZbfxT/uAa6O
 WVHfS1Kv/5afG9xxyfocWecd+Yp3lsXq9F+q36uOT9NeJmlej9aJr5sWMcvV3sru
 QVNN7tFZbHqCnLhpl6RDH/NiguweNYQXrl2lukXZe/FKu/KDasFIOzL+IAt2TqZE
 3mWrha7Q7j/gdBw8+fHHGtXCx0NSQlz1oFLo/y/mI7ztwUPJsBYbH5+108iP0ys/
 7Kd+jkYRucJB4upGH4meQbN6f/rrs3+m/b/j0Q8RCFHAs2f+mYZeN/JOHCo0T4YH
 KO1W60846fPs+7yZTVxWYFpR/kIuXksyxMWpEEZFFtF4MNoaeM1uypBWqm/JmKYr
 8oDtEyiOd/qmZnWRcuO3/bmdoJUZY1zTXWA0dlScYc8vR4KC+EE=
 =6GKy
 -----END PGP SIGNATURE-----

Merge tag 'apparmor-pr-2022-12-14' of git://git.kernel.org/pub/scm/linux/kernel/git/jj/linux-apparmor

Pull apparmor updates from John Johansen:
 "Features:
   - switch to zstd compression for profile raw data

  Cleanups:
   - simplify obtaining the newest label on a cred
   - remove useless static inline functions
   - compute permission conversion on policy unpack
   - refactor code to share common permissins
   - refactor unpack to group policy backwards compatiblity code
   - add __init annotation to aa_{setup/teardown}_dfa_engine()

  Bug Fixes:
   - fix a memleak in
       - multi_transaction_new()
       - free_ruleset()
       - unpack_profile()
       - alloc_ns()
   - fix lockdep warning when removing a namespace
   - fix regression in stacking due to label flags
   - fix loading of child before parent
   - fix kernel-doc comments that differ from fns
   - fix spelling errors in comments
   - store return value of unpack_perms_table() to signed variable"

* tag 'apparmor-pr-2022-12-14' of git://git.kernel.org/pub/scm/linux/kernel/git/jj/linux-apparmor: (64 commits)
  apparmor: Fix uninitialized symbol 'array_size' in policy_unpack_test.c
  apparmor: Add __init annotation to aa_{setup/teardown}_dfa_engine()
  apparmor: Fix memleak in alloc_ns()
  apparmor: Fix memleak issue in unpack_profile()
  apparmor: fix a memleak in free_ruleset()
  apparmor: Fix spelling of function name in comment block
  apparmor: Use pointer to struct aa_label for lbs_cred
  AppArmor: Fix kernel-doc
  LSM: Fix kernel-doc
  AppArmor: Fix kernel-doc
  apparmor: Fix loading of child before parent
  apparmor: refactor code that alloc null profiles
  apparmor: fix obsoleted comments for aa_getprocattr() and audit_resource()
  apparmor: remove useless static inline functions
  apparmor: Fix unpack_profile() warn: passing zero to 'ERR_PTR'
  apparmor: fix uninitialize table variable in error in unpack_trans_table
  apparmor: store return value of unpack_perms_table() to signed variable
  apparmor: Fix kunit test for out of bounds array
  apparmor: Fix decompression of rawdata for read back to userspace
  apparmor: Fix undefined references to zstd_ symbols
  ...
This commit is contained in:
Linus Torvalds 2022-12-14 13:42:09 -08:00
commit 93761c93e9
35 changed files with 1633 additions and 915 deletions

View File

@ -85,8 +85,8 @@ config SECURITY_APPARMOR_HASH_DEFAULT
config SECURITY_APPARMOR_EXPORT_BINARY
bool "Allow exporting the raw binary policy"
depends on SECURITY_APPARMOR_INTROSPECT_POLICY
select ZLIB_INFLATE
select ZLIB_DEFLATE
select ZSTD_COMPRESS
select ZSTD_DECOMPRESS
default y
help
This option allows reading back binary policy as it was loaded.

View File

@ -5,7 +5,8 @@ obj-$(CONFIG_SECURITY_APPARMOR) += apparmor.o
apparmor-y := apparmorfs.o audit.o capability.o task.o ipc.o lib.o match.o \
path.o domain.o policy.o policy_unpack.o procattr.o lsm.o \
resource.o secid.o file.o policy_ns.o label.o mount.o net.o
resource.o secid.o file.o policy_ns.o label.o mount.o net.o \
policy_compat.o
apparmor-$(CONFIG_SECURITY_APPARMOR_HASH) += crypto.o
obj-$(CONFIG_SECURITY_APPARMOR_KUNIT_TEST) += apparmor_policy_unpack_test.o

View File

@ -21,7 +21,7 @@
#include <linux/fs.h>
#include <linux/fs_context.h>
#include <linux/poll.h>
#include <linux/zlib.h>
#include <linux/zstd.h>
#include <uapi/linux/major.h>
#include <uapi/linux/magic.h>
@ -611,29 +611,30 @@ static const struct file_operations aa_fs_ns_revision_fops = {
static void profile_query_cb(struct aa_profile *profile, struct aa_perms *perms,
const char *match_str, size_t match_len)
{
struct aa_ruleset *rules = list_first_entry(&profile->rules,
typeof(*rules), list);
struct aa_perms tmp = { };
struct aa_dfa *dfa;
unsigned int state = 0;
aa_state_t state = DFA_NOMATCH;
if (profile_unconfined(profile))
return;
if (profile->file.dfa && *match_str == AA_CLASS_FILE) {
dfa = profile->file.dfa;
state = aa_dfa_match_len(dfa, profile->file.start,
if (rules->file.dfa && *match_str == AA_CLASS_FILE) {
state = aa_dfa_match_len(rules->file.dfa,
rules->file.start[AA_CLASS_FILE],
match_str + 1, match_len - 1);
if (state) {
struct path_cond cond = { };
tmp = aa_compute_fperms(dfa, state, &cond);
tmp = *(aa_lookup_fperms(&(rules->file), state, &cond));
}
} else if (profile->policy.dfa) {
if (!PROFILE_MEDIATES(profile, *match_str))
} else if (rules->policy.dfa) {
if (!RULE_MEDIATES(rules, *match_str))
return; /* no change to current perms */
dfa = profile->policy.dfa;
state = aa_dfa_match_len(dfa, profile->policy.start[0],
state = aa_dfa_match_len(rules->policy.dfa,
rules->policy.start[0],
match_str, match_len);
if (state)
aa_compute_perms(dfa, state, &tmp);
tmp = *aa_lookup_perms(&rules->policy, state);
}
aa_apply_modes_to_perms(profile, &tmp);
aa_perms_accum_raw(perms, &tmp);
@ -868,8 +869,10 @@ static struct multi_transaction *multi_transaction_new(struct file *file,
if (!t)
return ERR_PTR(-ENOMEM);
kref_init(&t->count);
if (copy_from_user(t->data, buf, size))
if (copy_from_user(t->data, buf, size)) {
put_multi_transaction(t);
return ERR_PTR(-EFAULT);
}
return t;
}
@ -1090,9 +1093,9 @@ static int seq_profile_attach_show(struct seq_file *seq, void *v)
struct aa_proxy *proxy = seq->private;
struct aa_label *label = aa_get_label_rcu(&proxy->label);
struct aa_profile *profile = labels_profile(label);
if (profile->attach)
seq_printf(seq, "%s\n", profile->attach);
else if (profile->xmatch)
if (profile->attach.xmatch_str)
seq_printf(seq, "%s\n", profile->attach.xmatch_str);
else if (profile->attach.xmatch.dfa)
seq_puts(seq, "<unknown>\n");
else
seq_printf(seq, "%s\n", profile->base.name);
@ -1197,10 +1200,24 @@ static int seq_ns_name_show(struct seq_file *seq, void *v)
return 0;
}
static int seq_ns_compress_min_show(struct seq_file *seq, void *v)
{
seq_printf(seq, "%d\n", AA_MIN_CLEVEL);
return 0;
}
static int seq_ns_compress_max_show(struct seq_file *seq, void *v)
{
seq_printf(seq, "%d\n", AA_MAX_CLEVEL);
return 0;
}
SEQ_NS_FOPS(stacked);
SEQ_NS_FOPS(nsstacked);
SEQ_NS_FOPS(level);
SEQ_NS_FOPS(name);
SEQ_NS_FOPS(compress_min);
SEQ_NS_FOPS(compress_max);
/* policy/raw_data/ * file ops */
@ -1295,42 +1312,34 @@ SEQ_RAWDATA_FOPS(revision);
SEQ_RAWDATA_FOPS(hash);
SEQ_RAWDATA_FOPS(compressed_size);
static int deflate_decompress(char *src, size_t slen, char *dst, size_t dlen)
static int decompress_zstd(char *src, size_t slen, char *dst, size_t dlen)
{
#ifdef CONFIG_SECURITY_APPARMOR_EXPORT_BINARY
if (aa_g_rawdata_compression_level != 0) {
int error = 0;
struct z_stream_s strm;
if (slen < dlen) {
const size_t wksp_len = zstd_dctx_workspace_bound();
zstd_dctx *ctx;
void *wksp;
size_t out_len;
int ret = 0;
memset(&strm, 0, sizeof(strm));
strm.workspace = kvzalloc(zlib_inflate_workspacesize(), GFP_KERNEL);
if (!strm.workspace)
return -ENOMEM;
strm.next_in = src;
strm.avail_in = slen;
error = zlib_inflateInit(&strm);
if (error != Z_OK) {
error = -ENOMEM;
goto fail_inflate_init;
wksp = kvzalloc(wksp_len, GFP_KERNEL);
if (!wksp) {
ret = -ENOMEM;
goto cleanup;
}
strm.next_out = dst;
strm.avail_out = dlen;
error = zlib_inflate(&strm, Z_FINISH);
if (error != Z_STREAM_END)
error = -EINVAL;
else
error = 0;
zlib_inflateEnd(&strm);
fail_inflate_init:
kvfree(strm.workspace);
return error;
ctx = zstd_init_dctx(wksp, wksp_len);
if (ctx == NULL) {
ret = -ENOMEM;
goto cleanup;
}
out_len = zstd_decompress_dctx(ctx, dst, dlen, src, slen);
if (zstd_is_error(out_len)) {
ret = -EINVAL;
goto cleanup;
}
cleanup:
kvfree(wksp);
return ret;
}
#endif
@ -1379,9 +1388,9 @@ static int rawdata_open(struct inode *inode, struct file *file)
private->loaddata = loaddata;
error = deflate_decompress(loaddata->data, loaddata->compressed_size,
RAWDATA_F_DATA_BUF(private),
loaddata->size);
error = decompress_zstd(loaddata->data, loaddata->compressed_size,
RAWDATA_F_DATA_BUF(private),
loaddata->size);
if (error)
goto fail_decompress;
@ -2392,6 +2401,8 @@ static struct aa_sfs_entry aa_sfs_entry_apparmor[] = {
AA_SFS_FILE_FOPS(".ns_level", 0444, &seq_ns_level_fops),
AA_SFS_FILE_FOPS(".ns_name", 0444, &seq_ns_name_fops),
AA_SFS_FILE_FOPS("profiles", 0444, &aa_sfs_profiles_fops),
AA_SFS_FILE_FOPS("raw_data_compression_level_min", 0444, &seq_ns_compress_min_fops),
AA_SFS_FILE_FOPS("raw_data_compression_level_max", 0444, &seq_ns_compress_max_fops),
AA_SFS_DIR("features", aa_sfs_entry_features),
{ }
};

View File

@ -36,6 +36,43 @@ static const char *const aa_audit_type[] = {
"AUTO"
};
static const char *const aa_class_names[] = {
"none",
"unknown",
"file",
"cap",
"net",
"rlimits",
"domain",
"mount",
"unknown",
"ptrace",
"signal",
"xmatch",
"unknown",
"unknown",
"net",
"unknown",
"label",
"posix_mqueue",
"io_uring",
"module",
"lsm",
"unknown",
"unknown",
"unknown",
"unknown",
"unknown",
"unknown",
"unknown",
"unknown",
"unknown",
"unknown",
"X",
"dbus",
};
/*
* Currently AppArmor auditing is fed straight into the audit framework.
*
@ -46,7 +83,7 @@ static const char *const aa_audit_type[] = {
*/
/**
* audit_base - core AppArmor function.
* audit_pre() - core AppArmor function.
* @ab: audit buffer to fill (NOT NULL)
* @ca: audit structure containing data to audit (NOT NULL)
*
@ -65,6 +102,12 @@ static void audit_pre(struct audit_buffer *ab, void *ca)
audit_log_format(ab, " operation=\"%s\"", aad(sa)->op);
}
if (aad(sa)->class)
audit_log_format(ab, " class=\"%s\"",
aad(sa)->class <= AA_CLASS_LAST ?
aa_class_names[aad(sa)->class] :
"unknown");
if (aad(sa)->info) {
audit_log_format(ab, " info=\"%s\"", aad(sa)->info);
if (aad(sa)->error)

View File

@ -64,6 +64,8 @@ static void audit_cb(struct audit_buffer *ab, void *va)
static int audit_caps(struct common_audit_data *sa, struct aa_profile *profile,
int cap, int error)
{
struct aa_ruleset *rules = list_first_entry(&profile->rules,
typeof(*rules), list);
struct audit_cache *ent;
int type = AUDIT_APPARMOR_AUTO;
@ -72,13 +74,13 @@ static int audit_caps(struct common_audit_data *sa, struct aa_profile *profile,
if (likely(!error)) {
/* test if auditing is being forced */
if (likely((AUDIT_MODE(profile) != AUDIT_ALL) &&
!cap_raised(profile->caps.audit, cap)))
!cap_raised(rules->caps.audit, cap)))
return 0;
type = AUDIT_APPARMOR_AUDIT;
} else if (KILL_MODE(profile) ||
cap_raised(profile->caps.kill, cap)) {
cap_raised(rules->caps.kill, cap)) {
type = AUDIT_APPARMOR_KILL;
} else if (cap_raised(profile->caps.quiet, cap) &&
} else if (cap_raised(rules->caps.quiet, cap) &&
AUDIT_MODE(profile) != AUDIT_NOQUIET &&
AUDIT_MODE(profile) != AUDIT_ALL) {
/* quiet auditing */
@ -114,10 +116,12 @@ static int audit_caps(struct common_audit_data *sa, struct aa_profile *profile,
static int profile_capable(struct aa_profile *profile, int cap,
unsigned int opts, struct common_audit_data *sa)
{
struct aa_ruleset *rules = list_first_entry(&profile->rules,
typeof(*rules), list);
int error;
if (cap_raised(profile->caps.allow, cap) &&
!cap_raised(profile->caps.denied, cap))
if (cap_raised(rules->caps.allow, cap) &&
!cap_raised(rules->caps.denied, cap))
error = 0;
else
error = -EPERM;
@ -148,7 +152,7 @@ int aa_capable(struct aa_label *label, int cap, unsigned int opts)
{
struct aa_profile *profile;
int error = 0;
DEFINE_AUDIT_DATA(sa, LSM_AUDIT_DATA_CAP, OP_CAPABLE);
DEFINE_AUDIT_DATA(sa, LSM_AUDIT_DATA_CAP, AA_CLASS_CAP, OP_CAPABLE);
sa.u.cap = cap;
error = fn_for_each_confined(label, profile,

View File

@ -29,24 +29,6 @@
#include "include/policy.h"
#include "include/policy_ns.h"
/**
* aa_free_domain_entries - free entries in a domain table
* @domain: the domain table to free (MAYBE NULL)
*/
void aa_free_domain_entries(struct aa_domain *domain)
{
int i;
if (domain) {
if (!domain->table)
return;
for (i = 0; i < domain->size; i++)
kfree_sensitive(domain->table[i]);
kfree_sensitive(domain->table);
domain->table = NULL;
}
}
/**
* may_change_ptraced_domain - check if can change profile on ptraced task
* @to_label: profile to change to (NOT NULL)
@ -95,23 +77,25 @@ out:
* If a subns profile is not to be matched should be prescreened with
* visibility test.
*/
static inline unsigned int match_component(struct aa_profile *profile,
struct aa_profile *tp,
bool stack, unsigned int state)
static inline aa_state_t match_component(struct aa_profile *profile,
struct aa_profile *tp,
bool stack, aa_state_t state)
{
struct aa_ruleset *rules = list_first_entry(&profile->rules,
typeof(*rules), list);
const char *ns_name;
if (stack)
state = aa_dfa_match(profile->file.dfa, state, "&");
state = aa_dfa_match(rules->file.dfa, state, "&");
if (profile->ns == tp->ns)
return aa_dfa_match(profile->file.dfa, state, tp->base.hname);
return aa_dfa_match(rules->file.dfa, state, tp->base.hname);
/* try matching with namespace name and then profile */
ns_name = aa_ns_name(profile->ns, tp->ns, true);
state = aa_dfa_match_len(profile->file.dfa, state, ":", 1);
state = aa_dfa_match(profile->file.dfa, state, ns_name);
state = aa_dfa_match_len(profile->file.dfa, state, ":", 1);
return aa_dfa_match(profile->file.dfa, state, tp->base.hname);
state = aa_dfa_match_len(rules->file.dfa, state, ":", 1);
state = aa_dfa_match(rules->file.dfa, state, ns_name);
state = aa_dfa_match_len(rules->file.dfa, state, ":", 1);
return aa_dfa_match(rules->file.dfa, state, tp->base.hname);
}
/**
@ -132,9 +116,11 @@ static inline unsigned int match_component(struct aa_profile *profile,
*/
static int label_compound_match(struct aa_profile *profile,
struct aa_label *label, bool stack,
unsigned int state, bool subns, u32 request,
aa_state_t state, bool subns, u32 request,
struct aa_perms *perms)
{
struct aa_ruleset *rules = list_first_entry(&profile->rules,
typeof(*rules), list);
struct aa_profile *tp;
struct label_it i;
struct path_cond cond = { };
@ -157,12 +143,12 @@ next:
label_for_each_cont(i, label, tp) {
if (!aa_ns_visible(profile->ns, tp->ns, subns))
continue;
state = aa_dfa_match(profile->file.dfa, state, "//&");
state = aa_dfa_match(rules->file.dfa, state, "//&");
state = match_component(profile, tp, false, state);
if (!state)
goto fail;
}
*perms = aa_compute_fperms(profile->file.dfa, state, &cond);
*perms = *(aa_lookup_fperms(&(rules->file), state, &cond));
aa_apply_modes_to_perms(profile, perms);
if ((perms->allow & request) != request)
return -EACCES;
@ -192,14 +178,16 @@ fail:
*/
static int label_components_match(struct aa_profile *profile,
struct aa_label *label, bool stack,
unsigned int start, bool subns, u32 request,
aa_state_t start, bool subns, u32 request,
struct aa_perms *perms)
{
struct aa_ruleset *rules = list_first_entry(&profile->rules,
typeof(*rules), list);
struct aa_profile *tp;
struct label_it i;
struct aa_perms tmp;
struct path_cond cond = { };
unsigned int state = 0;
aa_state_t state = 0;
/* find first subcomponent to test */
label_for_each(i, label, tp) {
@ -215,7 +203,7 @@ static int label_components_match(struct aa_profile *profile,
return 0;
next:
tmp = aa_compute_fperms(profile->file.dfa, state, &cond);
tmp = *(aa_lookup_fperms(&(rules->file), state, &cond));
aa_apply_modes_to_perms(profile, &tmp);
aa_perms_accum(perms, &tmp);
label_for_each_cont(i, label, tp) {
@ -224,7 +212,7 @@ next:
state = match_component(profile, tp, stack, start);
if (!state)
goto fail;
tmp = aa_compute_fperms(profile->file.dfa, state, &cond);
tmp = *(aa_lookup_fperms(&(rules->file), state, &cond));
aa_apply_modes_to_perms(profile, &tmp);
aa_perms_accum(perms, &tmp);
}
@ -252,7 +240,7 @@ fail:
* Returns: the state the match finished in, may be the none matching state
*/
static int label_match(struct aa_profile *profile, struct aa_label *label,
bool stack, unsigned int state, bool subns, u32 request,
bool stack, aa_state_t state, bool subns, u32 request,
struct aa_perms *perms)
{
int error;
@ -286,7 +274,7 @@ static int label_match(struct aa_profile *profile, struct aa_label *label,
*/
static int change_profile_perms(struct aa_profile *profile,
struct aa_label *target, bool stack,
u32 request, unsigned int start,
u32 request, aa_state_t start,
struct aa_perms *perms)
{
if (profile_unconfined(profile)) {
@ -308,44 +296,47 @@ static int change_profile_perms(struct aa_profile *profile,
* Returns: number of extended attributes that matched, or < 0 on error
*/
static int aa_xattrs_match(const struct linux_binprm *bprm,
struct aa_profile *profile, unsigned int state)
struct aa_profile *profile, aa_state_t state)
{
int i;
struct dentry *d;
char *value = NULL;
int size, value_size = 0, ret = profile->xattr_count;
struct aa_attachment *attach = &profile->attach;
int size, value_size = 0, ret = attach->xattr_count;
if (!bprm || !profile->xattr_count)
if (!bprm || !attach->xattr_count)
return 0;
might_sleep();
/* transition from exec match to xattr set */
state = aa_dfa_outofband_transition(profile->xmatch, state);
state = aa_dfa_outofband_transition(attach->xmatch.dfa, state);
d = bprm->file->f_path.dentry;
for (i = 0; i < profile->xattr_count; i++) {
size = vfs_getxattr_alloc(&init_user_ns, d, profile->xattrs[i],
for (i = 0; i < attach->xattr_count; i++) {
size = vfs_getxattr_alloc(&init_user_ns, d, attach->xattrs[i],
&value, value_size, GFP_KERNEL);
if (size >= 0) {
u32 perm;
u32 index, perm;
/*
* Check the xattr presence before value. This ensure
* that not present xattr can be distinguished from a 0
* length value or rule that matches any value
*/
state = aa_dfa_null_transition(profile->xmatch, state);
state = aa_dfa_null_transition(attach->xmatch.dfa,
state);
/* Check xattr value */
state = aa_dfa_match_len(profile->xmatch, state, value,
size);
perm = dfa_user_allow(profile->xmatch, state);
state = aa_dfa_match_len(attach->xmatch.dfa, state,
value, size);
index = ACCEPT_TABLE(attach->xmatch.dfa)[state];
perm = attach->xmatch.perms[index].allow;
if (!(perm & MAY_EXEC)) {
ret = -EINVAL;
goto out;
}
}
/* transition to next element */
state = aa_dfa_outofband_transition(profile->xmatch, state);
state = aa_dfa_outofband_transition(attach->xmatch.dfa, state);
if (size < 0) {
/*
* No xattr match, so verify if transition to
@ -397,6 +388,8 @@ static struct aa_label *find_attach(const struct linux_binprm *bprm,
rcu_read_lock();
restart:
list_for_each_entry_rcu(profile, head, base.list) {
struct aa_attachment *attach = &profile->attach;
if (profile->label.flags & FLAG_NULL &&
&profile->label == ns_unconfined(profile->ns))
continue;
@ -412,13 +405,16 @@ restart:
* as another profile, signal a conflict and refuse to
* match.
*/
if (profile->xmatch) {
unsigned int state, count;
u32 perm;
if (attach->xmatch.dfa) {
unsigned int count;
aa_state_t state;
u32 index, perm;
state = aa_dfa_leftmatch(profile->xmatch, DFA_START,
name, &count);
perm = dfa_user_allow(profile->xmatch, state);
state = aa_dfa_leftmatch(attach->xmatch.dfa,
attach->xmatch.start[AA_CLASS_XMATCH],
name, &count);
index = ACCEPT_TABLE(attach->xmatch.dfa)[state];
perm = attach->xmatch.perms[index].allow;
/* any accepting state means a valid match. */
if (perm & MAY_EXEC) {
int ret = 0;
@ -426,7 +422,7 @@ restart:
if (count < candidate_len)
continue;
if (bprm && profile->xattr_count) {
if (bprm && attach->xattr_count) {
long rev = READ_ONCE(ns->revision);
if (!aa_get_profile_not0(profile))
@ -465,7 +461,7 @@ restart:
* xattrs, or a longer match
*/
candidate = profile;
candidate_len = max(count, profile->xmatch_len);
candidate_len = max(count, attach->xmatch_len);
candidate_xattrs = ret;
conflict = false;
}
@ -509,6 +505,8 @@ static const char *next_name(int xtype, const char *name)
struct aa_label *x_table_lookup(struct aa_profile *profile, u32 xindex,
const char **name)
{
struct aa_ruleset *rules = list_first_entry(&profile->rules,
typeof(*rules), list);
struct aa_label *label = NULL;
u32 xtype = xindex & AA_X_TYPE_MASK;
int index = xindex & AA_X_INDEX_MASK;
@ -519,7 +517,7 @@ struct aa_label *x_table_lookup(struct aa_profile *profile, u32 xindex,
/* TODO: move lookup parsing to unpack time so this is a straight
* index into the resultant label
*/
for (*name = profile->file.trans.table[index]; !label && *name;
for (*name = rules->file.trans.table[index]; !label && *name;
*name = next_name(xtype, *name)) {
if (xindex & AA_X_CHILD) {
struct aa_profile *new_profile;
@ -558,6 +556,8 @@ static struct aa_label *x_to_label(struct aa_profile *profile,
const char **lookupname,
const char **info)
{
struct aa_ruleset *rules = list_first_entry(&profile->rules,
typeof(*rules), list);
struct aa_label *new = NULL;
struct aa_ns *ns = profile->ns;
u32 xtype = xindex & AA_X_TYPE_MASK;
@ -570,7 +570,7 @@ static struct aa_label *x_to_label(struct aa_profile *profile,
break;
case AA_X_TABLE:
/* TODO: fix when perm mapping done at unload */
stack = profile->file.trans.table[xindex & AA_X_INDEX_MASK];
stack = rules->file.trans.table[xindex & AA_X_INDEX_MASK];
if (*stack != '&') {
/* released by caller */
new = x_table_lookup(profile, xindex, lookupname);
@ -624,9 +624,11 @@ static struct aa_label *profile_transition(struct aa_profile *profile,
char *buffer, struct path_cond *cond,
bool *secure_exec)
{
struct aa_ruleset *rules = list_first_entry(&profile->rules,
typeof(*rules), list);
struct aa_label *new = NULL;
const char *info = NULL, *name = NULL, *target = NULL;
unsigned int state = profile->file.start;
aa_state_t state = rules->file.start[AA_CLASS_FILE];
struct aa_perms perms = {};
bool nonewprivs = false;
int error = 0;
@ -660,7 +662,7 @@ static struct aa_label *profile_transition(struct aa_profile *profile,
}
/* find exec permissions for name */
state = aa_str_perms(profile->file.dfa, state, name, cond, &perms);
state = aa_str_perms(&(rules->file), state, name, cond, &perms);
if (perms.allow & MAY_EXEC) {
/* exec permission determine how to transition */
new = x_to_label(profile, bprm, name, perms.xindex, &target,
@ -678,8 +680,8 @@ static struct aa_label *profile_transition(struct aa_profile *profile,
/* no exec permission - learning mode */
struct aa_profile *new_profile = NULL;
new_profile = aa_new_null_profile(profile, false, name,
GFP_KERNEL);
new_profile = aa_new_learning_profile(profile, false, name,
GFP_KERNEL);
if (!new_profile) {
error = -ENOMEM;
info = "could not create null profile";
@ -722,7 +724,9 @@ static int profile_onexec(struct aa_profile *profile, struct aa_label *onexec,
char *buffer, struct path_cond *cond,
bool *secure_exec)
{
unsigned int state = profile->file.start;
struct aa_ruleset *rules = list_first_entry(&profile->rules,
typeof(*rules), list);
aa_state_t state = rules->file.start[AA_CLASS_FILE];
struct aa_perms perms = {};
const char *xname = NULL, *info = "change_profile onexec";
int error = -EACCES;
@ -755,7 +759,7 @@ static int profile_onexec(struct aa_profile *profile, struct aa_label *onexec,
}
/* find exec permissions for name */
state = aa_str_perms(profile->file.dfa, state, xname, cond, &perms);
state = aa_str_perms(&(rules->file), state, xname, cond, &perms);
if (!(perms.allow & AA_MAY_ONEXEC)) {
info = "no change_onexec valid for executable";
goto audit;
@ -764,7 +768,7 @@ static int profile_onexec(struct aa_profile *profile, struct aa_label *onexec,
* onexec permission is linked to exec with a standard pairing
* exec\0change_profile
*/
state = aa_dfa_null_transition(profile->file.dfa, state);
state = aa_dfa_null_transition(rules->file.dfa, state);
error = change_profile_perms(profile, onexec, stack, AA_MAY_ONEXEC,
state, &perms);
if (error) {
@ -1004,8 +1008,8 @@ static struct aa_label *build_change_hat(struct aa_profile *profile,
if (!hat) {
error = -ENOENT;
if (COMPLAIN_MODE(profile)) {
hat = aa_new_null_profile(profile, true, name,
GFP_KERNEL);
hat = aa_new_learning_profile(profile, true, name,
GFP_KERNEL);
if (!hat) {
info = "failed null profile create";
error = -ENOMEM;
@ -1261,12 +1265,15 @@ static int change_profile_perms_wrapper(const char *op, const char *name,
struct aa_label *target, bool stack,
u32 request, struct aa_perms *perms)
{
struct aa_ruleset *rules = list_first_entry(&profile->rules,
typeof(*rules), list);
const char *info = NULL;
int error = 0;
if (!error)
error = change_profile_perms(profile, target, stack, request,
profile->file.start, perms);
rules->file.start[AA_CLASS_FILE],
perms);
if (error)
error = aa_audit_file(profile, perms, op, request, name,
NULL, target, GLOBAL_ROOT_UID, info,
@ -1353,8 +1360,8 @@ int aa_change_profile(const char *fqname, int flags)
!COMPLAIN_MODE(labels_profile(label)))
goto audit;
/* released below */
tprofile = aa_new_null_profile(labels_profile(label), false,
fqname, GFP_KERNEL);
tprofile = aa_new_learning_profile(labels_profile(label), false,
fqname, GFP_KERNEL);
if (!tprofile) {
info = "failed null profile create";
error = -ENOMEM;

View File

@ -95,7 +95,7 @@ int aa_audit_file(struct aa_profile *profile, struct aa_perms *perms,
kuid_t ouid, const char *info, int error)
{
int type = AUDIT_APPARMOR_AUTO;
DEFINE_AUDIT_DATA(sa, LSM_AUDIT_DATA_TASK, op);
DEFINE_AUDIT_DATA(sa, LSM_AUDIT_DATA_TASK, AA_CLASS_FILE, op);
sa.u.tsk = NULL;
aad(&sa)->request = request;
@ -141,19 +141,6 @@ int aa_audit_file(struct aa_profile *profile, struct aa_perms *perms,
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: true if deleted else false
*/
static inline bool is_deleted(struct dentry *dentry)
{
if (d_unlinked(dentry) && d_backing_inode(dentry)->i_nlink == 0)
return true;
return false;
}
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)
@ -175,73 +162,28 @@ static int path_name(const char *op, struct aa_label *label,
}
/**
* map_old_perms - map old file perms layout to the new layout
* @old: permission set in old mapping
*
* Returns: new permission mapping
*/
static u32 map_old_perms(u32 old)
{
u32 new = old & 0xf;
if (old & MAY_READ)
new |= AA_MAY_GETATTR | AA_MAY_OPEN;
if (old & MAY_WRITE)
new |= AA_MAY_SETATTR | AA_MAY_CREATE | AA_MAY_DELETE |
AA_MAY_CHMOD | AA_MAY_CHOWN | AA_MAY_OPEN;
if (old & 0x10)
new |= AA_MAY_LINK;
/* the old mapping lock and link_subset flags where overlaid
* and use was determined by part of a pair that they were in
*/
if (old & 0x20)
new |= AA_MAY_LOCK | AA_LINK_SUBSET;
if (old & 0x40) /* AA_EXEC_MMAP */
new |= AA_EXEC_MMAP;
return new;
}
/**
* aa_compute_fperms - convert dfa compressed perms to internal perms
* @dfa: dfa to compute perms for (NOT NULL)
* aa_lookup_fperms - convert dfa compressed perms to internal perms
* @dfa: dfa to lookup perms for (NOT NULL)
* @state: state in dfa
* @cond: conditions to consider (NOT NULL)
*
* TODO: convert from dfa + state to permission entry, do computation conversion
* at load time.
* TODO: convert from dfa + state to permission entry
*
* Returns: computed permission set
* Returns: a pointer to a file permission set
*/
struct aa_perms aa_compute_fperms(struct aa_dfa *dfa, unsigned int state,
struct path_cond *cond)
struct aa_perms default_perms = {};
struct aa_perms *aa_lookup_fperms(struct aa_policydb *file_rules,
aa_state_t state, struct path_cond *cond)
{
/* FIXME: change over to new dfa format
* currently file perms are encoded in the dfa, new format
* splits the permissions from the dfa. This mapping can be
* done at profile load
*/
struct aa_perms perms = { };
unsigned int index = ACCEPT_TABLE(file_rules->dfa)[state];
if (uid_eq(current_fsuid(), cond->uid)) {
perms.allow = map_old_perms(dfa_user_allow(dfa, state));
perms.audit = map_old_perms(dfa_user_audit(dfa, state));
perms.quiet = map_old_perms(dfa_user_quiet(dfa, state));
perms.xindex = dfa_user_xindex(dfa, state);
} else {
perms.allow = map_old_perms(dfa_other_allow(dfa, state));
perms.audit = map_old_perms(dfa_other_audit(dfa, state));
perms.quiet = map_old_perms(dfa_other_quiet(dfa, state));
perms.xindex = dfa_other_xindex(dfa, state);
}
perms.allow |= AA_MAY_GETATTR;
if (!(file_rules->perms))
return &default_perms;
/* change_profile wasn't determined by ownership in old mapping */
if (ACCEPT_TABLE(dfa)[state] & 0x80000000)
perms.allow |= AA_MAY_CHANGE_PROFILE;
if (ACCEPT_TABLE(dfa)[state] & 0x40000000)
perms.allow |= AA_MAY_ONEXEC;
if (uid_eq(current_fsuid(), cond->uid))
return &(file_rules->perms[index]);
return perms;
return &(file_rules->perms[index + 1]);
}
/**
@ -254,26 +196,30 @@ struct aa_perms aa_compute_fperms(struct aa_dfa *dfa, unsigned int state,
*
* Returns: the final state in @dfa when beginning @start and walking @name
*/
unsigned int aa_str_perms(struct aa_dfa *dfa, unsigned int start,
const char *name, struct path_cond *cond,
struct aa_perms *perms)
aa_state_t aa_str_perms(struct aa_policydb *file_rules, aa_state_t start,
const char *name, struct path_cond *cond,
struct aa_perms *perms)
{
unsigned int state;
state = aa_dfa_match(dfa, start, name);
*perms = aa_compute_fperms(dfa, state, cond);
aa_state_t state;
state = aa_dfa_match(file_rules->dfa, start, name);
*perms = *(aa_lookup_fperms(file_rules, state, cond));
return state;
}
int __aa_path_perm(const char *op, struct aa_profile *profile, const char *name,
u32 request, struct path_cond *cond, int flags,
struct aa_perms *perms)
static int __aa_path_perm(const char *op, struct aa_profile *profile,
const char *name, u32 request,
struct path_cond *cond, int flags,
struct aa_perms *perms)
{
struct aa_ruleset *rules = list_first_entry(&profile->rules,
typeof(*rules), list);
int e = 0;
if (profile_unconfined(profile))
return 0;
aa_str_perms(profile->file.dfa, profile->file.start, name, cond, perms);
aa_str_perms(&(rules->file), rules->file.start[AA_CLASS_FILE],
name, cond, perms);
if (request & ~perms->allow)
e = -EACCES;
return aa_audit_file(profile, perms, op, request, name, NULL, NULL,
@ -360,11 +306,13 @@ static int profile_path_link(struct aa_profile *profile,
const struct path *target, char *buffer2,
struct path_cond *cond)
{
struct aa_ruleset *rules = list_first_entry(&profile->rules,
typeof(*rules), list);
const char *lname, *tname = NULL;
struct aa_perms lperms = {}, perms;
const char *info = NULL;
u32 request = AA_MAY_LINK;
unsigned int state;
aa_state_t state;
int error;
error = path_name(OP_LINK, &profile->label, link, profile->path_flags,
@ -380,15 +328,16 @@ static int profile_path_link(struct aa_profile *profile,
error = -EACCES;
/* 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(&(rules->file),
rules->file.start[AA_CLASS_FILE], lname,
cond, &lperms);
if (!(lperms.allow & AA_MAY_LINK))
goto audit;
/* test to see if target can be paired with link */
state = aa_dfa_null_transition(profile->file.dfa, state);
aa_str_perms(profile->file.dfa, state, tname, cond, &perms);
state = aa_dfa_null_transition(rules->file.dfa, state);
aa_str_perms(&(rules->file), state, tname, cond, &perms);
/* force audit/quiet masks for link are stored in the second entry
* in the link pair.
@ -410,8 +359,8 @@ static int profile_path_link(struct aa_profile *profile,
/* Do link perm subset test requiring allowed permission on link are
* a subset of the allowed permissions on target.
*/
aa_str_perms(profile->file.dfa, profile->file.start, tname, cond,
&perms);
aa_str_perms(&(rules->file), rules->file.start[AA_CLASS_FILE],
tname, cond, &perms);
/* AA_MAY_LINK is not considered in the subset test */
request = lperms.allow & ~AA_MAY_LINK;

View File

@ -16,7 +16,7 @@
/*
* Class of mediation types in the AppArmor policy db
*/
#define AA_CLASS_ENTRY 0
#define AA_CLASS_NONE 0
#define AA_CLASS_UNKNOWN 1
#define AA_CLASS_FILE 2
#define AA_CLASS_CAP 3
@ -26,10 +26,18 @@
#define AA_CLASS_MOUNT 7
#define AA_CLASS_PTRACE 9
#define AA_CLASS_SIGNAL 10
#define AA_CLASS_XMATCH 11
#define AA_CLASS_NET 14
#define AA_CLASS_LABEL 16
#define AA_CLASS_POSIX_MQUEUE 17
#define AA_CLASS_IO_URING 18
#define AA_CLASS_MODULE 19
#define AA_CLASS_DISPLAY_LSM 20
#define AA_CLASS_LAST AA_CLASS_LABEL
#define AA_CLASS_X 31
#define AA_CLASS_DBUS 32
#define AA_CLASS_LAST AA_CLASS_DBUS
/* Control parameters settable through module/boot flags */
extern enum audit_mode aa_g_audit;
@ -43,4 +51,15 @@ extern bool aa_g_logsyscall;
extern bool aa_g_paranoid_load;
extern unsigned int aa_g_path_max;
#ifdef CONFIG_SECURITY_APPARMOR_EXPORT_BINARY
#define AA_MIN_CLEVEL zstd_min_clevel()
#define AA_MAX_CLEVEL zstd_max_clevel()
#define AA_DEFAULT_CLEVEL ZSTD_CLEVEL_DEFAULT
#else
#define AA_MIN_CLEVEL 0
#define AA_MAX_CLEVEL 0
#define AA_DEFAULT_CLEVEL 0
#endif /* CONFIG_SECURITY_APPARMOR_EXPORT_BINARY */
#endif /* __APPARMOR_H */

View File

@ -107,6 +107,7 @@ enum audit_type {
struct apparmor_audit_data {
int error;
int type;
u16 class;
const char *op;
struct aa_label *label;
const char *name;
@ -155,9 +156,12 @@ struct apparmor_audit_data {
/* macros for dealing with apparmor_audit_data structure */
#define aad(SA) ((SA)->apparmor_audit_data)
#define DEFINE_AUDIT_DATA(NAME, T, X) \
#define DEFINE_AUDIT_DATA(NAME, T, C, X) \
/* TODO: cleanup audit init so we don't need _aad = {0,} */ \
struct apparmor_audit_data NAME ## _aad = { .op = (X), }; \
struct apparmor_audit_data NAME ## _aad = { \
.class = (C), \
.op = (X), \
}; \
struct common_audit_data NAME = \
{ \
.type = (T), \

View File

@ -63,19 +63,6 @@ static inline struct aa_label *aa_get_newest_cred_label(const struct cred *cred)
return aa_get_newest_label(aa_cred_raw_label(cred));
}
/**
* __aa_task_raw_label - retrieve another task's label
* @task: task to query (NOT NULL)
*
* Returns: @task's label without incrementing its ref count
*
* If @task != current needs to be called in RCU safe critical section
*/
static inline struct aa_label *__aa_task_raw_label(struct task_struct *task)
{
return aa_cred_raw_label(__task_cred(task));
}
/**
* aa_current_raw_label - find the current tasks confining label
*

View File

@ -16,11 +16,6 @@
#ifndef __AA_DOMAIN_H
#define __AA_DOMAIN_H
struct aa_domain {
int size;
char **table;
};
#define AA_CHANGE_NOFLAGS 0
#define AA_CHANGE_TEST 1
#define AA_CHANGE_CHILD 2
@ -32,7 +27,6 @@ struct aa_label *x_table_lookup(struct aa_profile *profile, u32 xindex,
int apparmor_bprm_creds_for_exec(struct linux_binprm *bprm);
void aa_free_domain_entries(struct aa_domain *domain);
int aa_change_hat(const char *hats[], int count, u64 token, int flags);
int aa_change_profile(const char *fqname, int flags);

View File

@ -17,6 +17,7 @@
#include "match.h"
#include "perms.h"
struct aa_policydb;
struct aa_profile;
struct path;
@ -87,18 +88,17 @@ static inline struct aa_label *aa_get_file_label(struct aa_file_ctx *ctx)
* - exec type - which determines how the executable name and index are used
* - flags - which modify how the destination name is applied
*/
#define AA_X_INDEX_MASK 0x03ff
#define AA_X_INDEX_MASK AA_INDEX_MASK
#define AA_X_TYPE_MASK 0x0c00
#define AA_X_TYPE_SHIFT 10
#define AA_X_NONE 0x0000
#define AA_X_NAME 0x0400 /* use executable name px */
#define AA_X_TABLE 0x0800 /* use a specified name ->n# */
#define AA_X_TYPE_MASK 0x0c000000
#define AA_X_NONE AA_INDEX_NONE
#define AA_X_NAME 0x04000000 /* use executable name px */
#define AA_X_TABLE 0x08000000 /* use a specified name ->n# */
#define AA_X_UNSAFE 0x1000
#define AA_X_CHILD 0x2000 /* make >AA_X_NONE apply to children */
#define AA_X_INHERIT 0x4000
#define AA_X_UNCONFINED 0x8000
#define AA_X_UNSAFE 0x10000000
#define AA_X_CHILD 0x20000000
#define AA_X_INHERIT 0x40000000
#define AA_X_UNCONFINED 0x80000000
/* need to make conditional which ones are being set */
struct path_cond {
@ -108,90 +108,17 @@ struct path_cond {
#define COMBINED_PERM_MASK(X) ((X).allow | (X).audit | (X).quiet | (X).kill)
/* FIXME: split perms from dfa and match this to description
* also add delegation info.
*/
static inline u16 dfa_map_xindex(u16 mask)
{
u16 old_index = (mask >> 10) & 0xf;
u16 index = 0;
if (mask & 0x100)
index |= AA_X_UNSAFE;
if (mask & 0x200)
index |= AA_X_INHERIT;
if (mask & 0x80)
index |= AA_X_UNCONFINED;
if (old_index == 1) {
index |= AA_X_UNCONFINED;
} else if (old_index == 2) {
index |= AA_X_NAME;
} else if (old_index == 3) {
index |= AA_X_NAME | AA_X_CHILD;
} else if (old_index) {
index |= AA_X_TABLE;
index |= old_index - 4;
}
return index;
}
/*
* map old dfa inline permissions to new format
*/
#define dfa_user_allow(dfa, state) (((ACCEPT_TABLE(dfa)[state]) & 0x7f) | \
((ACCEPT_TABLE(dfa)[state]) & 0x80000000))
#define dfa_user_xbits(dfa, state) (((ACCEPT_TABLE(dfa)[state]) >> 7) & 0x7f)
#define dfa_user_audit(dfa, state) ((ACCEPT_TABLE2(dfa)[state]) & 0x7f)
#define dfa_user_quiet(dfa, state) (((ACCEPT_TABLE2(dfa)[state]) >> 7) & 0x7f)
#define dfa_user_xindex(dfa, state) \
(dfa_map_xindex(ACCEPT_TABLE(dfa)[state] & 0x3fff))
#define dfa_other_allow(dfa, state) ((((ACCEPT_TABLE(dfa)[state]) >> 14) & \
0x7f) | \
((ACCEPT_TABLE(dfa)[state]) & 0x80000000))
#define dfa_other_xbits(dfa, state) \
((((ACCEPT_TABLE(dfa)[state]) >> 7) >> 14) & 0x7f)
#define dfa_other_audit(dfa, state) (((ACCEPT_TABLE2(dfa)[state]) >> 14) & 0x7f)
#define dfa_other_quiet(dfa, state) \
((((ACCEPT_TABLE2(dfa)[state]) >> 7) >> 14) & 0x7f)
#define dfa_other_xindex(dfa, state) \
dfa_map_xindex((ACCEPT_TABLE(dfa)[state] >> 14) & 0x3fff)
int aa_audit_file(struct aa_profile *profile, struct aa_perms *perms,
const char *op, u32 request, const char *name,
const char *target, struct aa_label *tlabel, kuid_t ouid,
const char *info, int error);
/**
* struct aa_file_rules - components used for file rule permissions
* @dfa: dfa to match path names and conditionals against
* @perms: permission table indexed by the matched state accept entry of @dfa
* @trans: transition table for indexed by named x transitions
*
* File permission are determined by matching a path against @dfa and
* then using the value of the accept entry for the matching state as
* an index into @perms. If a named exec transition is required it is
* looked up in the transition table.
*/
struct aa_file_rules {
unsigned int start;
struct aa_dfa *dfa;
/* struct perms perms; */
struct aa_domain trans;
/* TODO: add delegate table */
};
struct aa_perms *aa_lookup_fperms(struct aa_policydb *file_rules,
aa_state_t state, struct path_cond *cond);
aa_state_t aa_str_perms(struct aa_policydb *file_rules, aa_state_t start,
const char *name, struct path_cond *cond,
struct aa_perms *perms);
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,
const char *name, struct path_cond *cond,
struct aa_perms *perms);
int __aa_path_perm(const char *op, struct aa_profile *profile,
const char *name, u32 request, struct path_cond *cond,
int flags, struct aa_perms *perms);
int aa_path_perm(const char *op, struct aa_label *label,
const struct path *path, int flags, u32 request,
struct path_cond *cond);
@ -204,11 +131,6 @@ int aa_file_perm(const char *op, struct aa_label *label, struct file *file,
void aa_inherit_files(const struct cred *cred, struct files_struct *files);
static inline void aa_free_file_rules(struct aa_file_rules *rules)
{
aa_put_dfa(rules->dfa);
aa_free_domain_entries(&rules->trans);
}
/**
* aa_map_file_perms - map file flags to AppArmor permissions

View File

@ -261,7 +261,7 @@ for ((I).i = (I).j = 0; \
struct label_it i; \
int ret = 0; \
label_for_each(i, (L), profile) { \
if (PROFILE_MEDIATES(profile, (C))) { \
if (RULE_MEDIATES(&profile->rules, (C))) { \
ret = 1; \
break; \
} \
@ -333,7 +333,7 @@ struct aa_label *aa_label_parse(struct aa_label *base, const char *str,
static inline const char *aa_label_strn_split(const char *str, int n)
{
const char *pos;
unsigned int state;
aa_state_t state;
state = aa_dfa_matchn_until(stacksplitdfa, DFA_START, str, n, &pos);
if (!ACCEPT_TABLE(stacksplitdfa)[state])
@ -345,7 +345,7 @@ static inline const char *aa_label_strn_split(const char *str, int n)
static inline const char *aa_label_str_split(const char *str)
{
const char *pos;
unsigned int state;
aa_state_t state;
state = aa_dfa_match_until(stacksplitdfa, DFA_START, str, &pos);
if (!ACCEPT_TABLE(stacksplitdfa)[state])
@ -357,9 +357,10 @@ static inline const char *aa_label_str_split(const char *str)
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);
struct aa_ruleset;
int aa_label_match(struct aa_profile *profile, struct aa_ruleset *rules,
struct aa_label *label, aa_state_t state, bool subns,
u32 request, struct aa_perms *perms);
/**

View File

@ -87,8 +87,8 @@ static inline bool aa_strneq(const char *str, const char *sub, int len)
* character which is not used in standard matching and is only
* used to separate pairs.
*/
static inline unsigned int aa_dfa_null_transition(struct aa_dfa *dfa,
unsigned int start)
static inline aa_state_t aa_dfa_null_transition(struct aa_dfa *dfa,
aa_state_t start)
{
/* the null transition only needs the string's null terminator byte */
return aa_dfa_next(dfa, start, 0);
@ -99,6 +99,12 @@ static inline bool path_mediated_fs(struct dentry *dentry)
return !(dentry->d_sb->s_flags & SB_NOUSER);
}
struct aa_str_table {
int size;
char **table;
};
void aa_free_str_table(struct aa_str_table *table);
struct counted_str {
struct kref count;

View File

@ -125,19 +125,19 @@ static inline size_t table_size(size_t len, size_t el_size)
int aa_setup_dfa_engine(void);
void aa_teardown_dfa_engine(void);
#define aa_state_t unsigned int
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,
const char *str, int len);
unsigned int aa_dfa_match(struct aa_dfa *dfa, unsigned int start,
const char *str);
unsigned int aa_dfa_next(struct aa_dfa *dfa, unsigned int state,
const char c);
unsigned int aa_dfa_outofband_transition(struct aa_dfa *dfa,
unsigned int state);
unsigned int aa_dfa_match_until(struct aa_dfa *dfa, unsigned int start,
const char *str, const char **retpos);
unsigned int aa_dfa_matchn_until(struct aa_dfa *dfa, unsigned int start,
const char *str, int n, const char **retpos);
aa_state_t aa_dfa_match_len(struct aa_dfa *dfa, aa_state_t start,
const char *str, int len);
aa_state_t aa_dfa_match(struct aa_dfa *dfa, aa_state_t start,
const char *str);
aa_state_t aa_dfa_next(struct aa_dfa *dfa, aa_state_t state, const char c);
aa_state_t aa_dfa_outofband_transition(struct aa_dfa *dfa, aa_state_t state);
aa_state_t aa_dfa_match_until(struct aa_dfa *dfa, aa_state_t start,
const char *str, const char **retpos);
aa_state_t aa_dfa_matchn_until(struct aa_dfa *dfa, aa_state_t start,
const char *str, int n, const char **retpos);
void aa_dfa_free_kref(struct kref *kref);
@ -156,8 +156,8 @@ struct match_workbuf N = { \
.len = 0, \
}
unsigned int aa_dfa_leftmatch(struct aa_dfa *dfa, unsigned int start,
const char *str, unsigned int *count);
aa_state_t aa_dfa_leftmatch(struct aa_dfa *dfa, aa_state_t start,
const char *str, unsigned int *count);
/**
* aa_get_dfa - increment refcount on dfa @p

View File

@ -59,6 +59,7 @@ struct aa_sk_ctx {
DEFINE_AUDIT_DATA(NAME, \
((SK) && (F) != AF_UNIX) ? LSM_AUDIT_DATA_NET : \
LSM_AUDIT_DATA_NONE, \
AA_CLASS_NET, \
OP); \
NAME.u.net = &(NAME ## _net); \
aad(&NAME)->net.type = (T); \

View File

@ -65,29 +65,90 @@ 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 subtree; /* allow perm on full subtree only when allow is set */
u32 cond; /* set only when ~allow and ~deny */
u32 hide; /* set only when ~allow | deny */
u32 kill; /* set only when ~allow | deny */
u32 complain; /* accumulates only used when ~allow & ~deny */
u32 prompt; /* accumulates only used when ~allow & ~deny */
/* Reserved:
* u32 subtree; / * set only when allow is set * /
*/
u16 xindex;
u32 audit; /* set only when allow is set */
u32 quiet; /* set only when ~allow | deny */
u32 hide; /* set only when ~allow | deny */
u32 xindex;
u32 tag; /* tag string index, if present */
u32 label; /* label string index, if present */
};
/*
* Indexes are broken into a 24 bit index and 8 bit flag.
* For the index to be valid there must be a value in the flag
*/
#define AA_INDEX_MASK 0x00ffffff
#define AA_INDEX_FLAG_MASK 0xff000000
#define AA_INDEX_NONE 0
#define ALL_PERMS_MASK 0xffffffff
extern struct aa_perms nullperms;
extern struct aa_perms allperms;
/**
* aa_perms_accum_raw - accumulate perms with out masking off overlapping perms
* @accum - perms struct to accumulate into
* @addend - perms struct to add to @accum
*/
static inline void aa_perms_accum_raw(struct aa_perms *accum,
struct aa_perms *addend)
{
accum->deny |= addend->deny;
accum->allow &= addend->allow & ~addend->deny;
accum->audit |= addend->audit & addend->allow;
accum->quiet &= addend->quiet & ~addend->allow;
accum->kill |= addend->kill & ~addend->allow;
accum->complain |= addend->complain & ~addend->allow & ~addend->deny;
accum->cond |= addend->cond & ~addend->allow & ~addend->deny;
accum->hide &= addend->hide & ~addend->allow;
accum->prompt |= addend->prompt & ~addend->allow & ~addend->deny;
accum->subtree |= addend->subtree & ~addend->deny;
if (!accum->xindex)
accum->xindex = addend->xindex;
if (!accum->tag)
accum->tag = addend->tag;
if (!accum->label)
accum->label = addend->label;
}
/**
* aa_perms_accum - accumulate perms, masking off overlapping perms
* @accum - perms struct to accumulate into
* @addend - perms struct to add to @accum
*/
static inline void aa_perms_accum(struct aa_perms *accum,
struct aa_perms *addend)
{
accum->deny |= addend->deny;
accum->allow &= addend->allow & ~accum->deny;
accum->audit |= addend->audit & accum->allow;
accum->quiet &= addend->quiet & ~accum->allow;
accum->kill |= addend->kill & ~accum->allow;
accum->complain |= addend->complain & ~accum->allow & ~accum->deny;
accum->cond |= addend->cond & ~accum->allow & ~accum->deny;
accum->hide &= addend->hide & ~accum->allow;
accum->prompt |= addend->prompt & ~accum->allow & ~accum->deny;
accum->subtree &= addend->subtree & ~accum->deny;
if (!accum->xindex)
accum->xindex = addend->xindex;
if (!accum->tag)
accum->tag = addend->tag;
if (!accum->label)
accum->label = addend->label;
}
#define xcheck(FN1, FN2) \
({ \
@ -133,6 +194,9 @@ extern struct aa_perms allperms;
xcheck(fn_for_each((L1), (P), (FN1)), fn_for_each((L2), (P), (FN2)))
extern struct aa_perms default_perms;
void aa_perm_mask_to_str(char *str, size_t str_size, const char *chrs,
u32 mask);
void aa_audit_perm_names(struct audit_buffer *ab, const char * const *names,
@ -141,11 +205,10 @@ void aa_audit_perm_mask(struct audit_buffer *ab, u32 mask, const char *chrs,
u32 chrsmask, const char * const *names, u32 namesmask);
void aa_apply_modes_to_perms(struct aa_profile *profile,
struct aa_perms *perms);
void aa_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,
void aa_profile_match_label(struct aa_profile *profile,
struct aa_ruleset *rules, struct aa_label *label,
int type, u32 request, struct aa_perms *perms);
int aa_profile_label_perm(struct aa_profile *profile, struct aa_profile *target,
u32 request, int type, u32 *deny,

View File

@ -44,6 +44,8 @@ extern const char *const aa_profile_mode_names[];
#define COMPLAIN_MODE(_profile) PROFILE_MODE((_profile), APPARMOR_COMPLAIN)
#define USER_MODE(_profile) PROFILE_MODE((_profile), APPARMOR_USER)
#define KILL_MODE(_profile) PROFILE_MODE((_profile), APPARMOR_KILL)
#define PROFILE_IS_HAT(_profile) ((_profile)->label.flags & FLAG_HAT)
@ -67,20 +69,47 @@ enum profile_mode {
APPARMOR_COMPLAIN, /* allow and log access violations */
APPARMOR_KILL, /* kill task on access violation */
APPARMOR_UNCONFINED, /* profile set to unconfined */
APPARMOR_USER, /* modified complain mode to userspace */
};
/* struct aa_policydb - match engine for a policy
* dfa: dfa pattern match
* perms: table of permissions
* strs: table of strings, index by x
* start: set of start states for the different classes of data
*/
struct aa_policydb {
/* Generic policy DFA specific rule types will be subsections of it */
struct aa_dfa *dfa;
unsigned int start[AA_CLASS_LAST + 1];
struct {
struct aa_perms *perms;
u32 size;
};
struct aa_str_table trans;
aa_state_t start[AA_CLASS_LAST + 1];
};
static inline void aa_destroy_policydb(struct aa_policydb *policy)
{
aa_put_dfa(policy->dfa);
if (policy->perms)
kvfree(policy->perms);
aa_free_str_table(&policy->trans);
}
static inline struct aa_perms *aa_lookup_perms(struct aa_policydb *policy,
aa_state_t state)
{
unsigned int index = ACCEPT_TABLE(policy->dfa)[state];
if (!(policy->perms))
return &default_perms;
return &(policy->perms[index]);
}
/* struct aa_data - generic data structure
* key: name for retrieving this data
* size: size of data in bytes
@ -94,6 +123,47 @@ struct aa_data {
struct rhash_head head;
};
/* struct aa_ruleset - data covering mediation rules
* @list: list the rule is on
* @size: the memory consumed by this ruleset
* @policy: general match rules governing policy
* @file: The set of rules governing basic file access and domain transitions
* @caps: capabilities for the profile
* @rlimits: rlimits for the profile
* @secmark_count: number of secmark entries
* @secmark: secmark label match info
*/
struct aa_ruleset {
struct list_head list;
int size;
/* TODO: merge policy and file */
struct aa_policydb policy;
struct aa_policydb file;
struct aa_caps caps;
struct aa_rlimit rlimits;
int secmark_count;
struct aa_secmark *secmark;
};
/* struct aa_attachment - data and rules for a profiles attachment
* @list:
* @xmatch_str: human readable attachment string
* @xmatch: optional extended matching for unconfined executables names
* @xmatch_len: xmatch prefix len, used to determine xmatch priority
* @xattr_count: number of xattrs in table
* @xattrs: table of xattrs
*/
struct aa_attachment {
const char *xmatch_str;
struct aa_policydb xmatch;
unsigned int xmatch_len;
int xattr_count;
char **xattrs;
};
/* struct aa_profile - basic confinement data
* @base - base components of the profile (name, refcount, lists, lock ...)
@ -101,18 +171,13 @@ struct aa_data {
* @parent: parent of profile
* @ns: namespace the profile is in
* @rename: optional profile name that this profile renamed
* @attach: human readable attachment string
* @xmatch: optional extended matching for unconfined executables names
* @xmatch_len: xmatch prefix len, used to determine xmatch priority
*
* @audit: the auditing mode of the profile
* @mode: the enforcement mode of the profile
* @path_flags: flags controlling path generation behavior
* @disconnected: what to prepend if attach_disconnected is specified
* @size: the memory consumed by this profiles rules
* @policy: general match rules governing policy
* @file: The set of rules governing basic file access and domain transitions
* @caps: capabilities for the profile
* @rlimits: rlimits for the profile
* @attach: attachment rules for the profile
* @rules: rules to be enforced
*
* @dents: dentries for the profiles file entries in apparmorfs
* @dirname: name of the profile dir in apparmorfs
@ -137,26 +202,13 @@ struct aa_profile {
struct aa_ns *ns;
const char *rename;
const char *attach;
struct aa_dfa *xmatch;
unsigned int xmatch_len;
enum audit_mode audit;
long mode;
u32 path_flags;
const char *disconnected;
int size;
struct aa_policydb policy;
struct aa_file_rules file;
struct aa_caps caps;
int xattr_count;
char **xattrs;
struct aa_rlimit rlimits;
int secmark_count;
struct aa_secmark *secmark;
struct aa_attachment attach;
struct list_head rules;
struct aa_loaddata *rawdata;
unsigned char *hash;
@ -179,10 +231,13 @@ void aa_add_profile(struct aa_policy *common, struct aa_profile *profile);
void aa_free_proxy_kref(struct kref *kref);
struct aa_ruleset *aa_alloc_ruleset(gfp_t gfp);
struct aa_profile *aa_alloc_profile(const char *name, struct aa_proxy *proxy,
gfp_t gfp);
struct aa_profile *aa_new_null_profile(struct aa_profile *parent, bool hat,
const char *base, gfp_t gfp);
struct aa_profile *aa_alloc_null(struct aa_profile *parent, const char *name,
gfp_t gfp);
struct aa_profile *aa_new_learning_profile(struct aa_profile *parent, bool hat,
const char *base, gfp_t gfp);
void aa_free_profile(struct aa_profile *profile);
void aa_free_profile_kref(struct kref *kref);
struct aa_profile *aa_find_child(struct aa_profile *parent, const char *name);
@ -217,24 +272,34 @@ static inline struct aa_profile *aa_get_newest_profile(struct aa_profile *p)
return labels_profile(aa_get_newest_label(&p->label));
}
static inline unsigned int PROFILE_MEDIATES(struct aa_profile *profile,
unsigned char class)
static inline aa_state_t RULE_MEDIATES(struct aa_ruleset *rules,
unsigned char class)
{
if (class <= AA_CLASS_LAST)
return profile->policy.start[class];
return rules->policy.start[class];
else
return aa_dfa_match_len(profile->policy.dfa,
profile->policy.start[0], &class, 1);
return aa_dfa_match_len(rules->policy.dfa,
rules->policy.start[0], &class, 1);
}
static inline unsigned int PROFILE_MEDIATES_AF(struct aa_profile *profile,
u16 AF) {
unsigned int state = PROFILE_MEDIATES(profile, AA_CLASS_NET);
static inline aa_state_t RULE_MEDIATES_AF(struct aa_ruleset *rules, u16 AF)
{
aa_state_t state = RULE_MEDIATES(rules, AA_CLASS_NET);
__be16 be_af = cpu_to_be16(AF);
if (!state)
return 0;
return aa_dfa_match_len(profile->policy.dfa, state, (char *) &be_af, 2);
return DFA_NOMATCH;
return aa_dfa_match_len(rules->policy.dfa, state, (char *) &be_af, 2);
}
static inline aa_state_t ANY_RULE_MEDIATES(struct list_head *head,
unsigned char class)
{
struct aa_ruleset *rule;
/* TODO: change to list walk */
rule = list_first_entry(head, typeof(*rule), list);
return RULE_MEDIATES(rule, class);
}
/**

View File

@ -0,0 +1,33 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* AppArmor security module
*
* Code to provide backwards compatibility with older policy versions,
* by converting/mapping older policy formats into the newer internal
* formats.
*
* Copyright 2022 Canonical Ltd.
*/
#ifndef __POLICY_COMPAT_H
#define __POLICY_COMPAT_H
#include "policy.h"
#define K_ABI_MASK 0x3ff
#define FORCE_COMPLAIN_FLAG 0x800
#define VERSION_LT(X, Y) (((X) & K_ABI_MASK) < ((Y) & K_ABI_MASK))
#define VERSION_LE(X, Y) (((X) & K_ABI_MASK) <= ((Y) & K_ABI_MASK))
#define VERSION_GT(X, Y) (((X) & K_ABI_MASK) > ((Y) & K_ABI_MASK))
#define v5 5 /* base version */
#define v6 6 /* per entry policydb mediation check */
#define v7 7
#define v8 8 /* full network masking */
#define v9 9 /* xbits are used as permission bits in policydb */
int aa_compat_map_xmatch(struct aa_policydb *policy);
int aa_compat_map_policy(struct aa_policydb *policy, u32 version);
int aa_compat_map_file(struct aa_policydb *policy);
#endif /* __POLICY_COMPAT_H */

View File

@ -16,6 +16,7 @@
#include <linux/dcache.h>
#include <linux/workqueue.h>
struct aa_load_ent {
struct list_head list;
struct aa_profile *new;
@ -35,6 +36,7 @@ struct aa_load_ent *aa_load_ent_alloc(void);
#define PACKED_MODE_COMPLAIN 1
#define PACKED_MODE_KILL 2
#define PACKED_MODE_UNCONFINED 3
#define PACKED_MODE_USER 4
struct aa_ns;
@ -170,7 +172,7 @@ bool aa_unpack_X(struct aa_ext *e, enum aa_code code);
bool aa_unpack_nameX(struct aa_ext *e, enum aa_code code, const char *name);
bool aa_unpack_u32(struct aa_ext *e, u32 *data, const char *name);
bool aa_unpack_u64(struct aa_ext *e, u64 *data, const char *name);
size_t aa_unpack_array(struct aa_ext *e, const char *name);
bool aa_unpack_array(struct aa_ext *e, const char *name, u16 *size);
size_t aa_unpack_blob(struct aa_ext *e, char **blob, const char *name);
int aa_unpack_str(struct aa_ext *e, const char **string, const char *name);
int aa_unpack_strdup(struct aa_ext *e, char **string, const char *name);

View File

@ -45,7 +45,7 @@ static const char *audit_signal_mask(u32 mask)
}
/**
* audit_cb - call back for signal specific audit fields
* audit_signal_cb() - call back for signal specific audit fields
* @ab: audit_buffer (NOT NULL)
* @va: audit struct to audit values of (NOT NULL)
*/
@ -78,19 +78,21 @@ static int profile_signal_perm(struct aa_profile *profile,
struct aa_label *peer, u32 request,
struct common_audit_data *sa)
{
struct aa_ruleset *rules = list_first_entry(&profile->rules,
typeof(*rules), list);
struct aa_perms perms;
unsigned int state;
aa_state_t state;
if (profile_unconfined(profile) ||
!PROFILE_MEDIATES(profile, AA_CLASS_SIGNAL))
!ANY_RULE_MEDIATES(&profile->rules, AA_CLASS_SIGNAL))
return 0;
aad(sa)->peer = peer;
/* TODO: secondary cache check <profile, profile, perm> */
state = aa_dfa_next(profile->policy.dfa,
profile->policy.start[AA_CLASS_SIGNAL],
state = aa_dfa_next(rules->policy.dfa,
rules->policy.start[AA_CLASS_SIGNAL],
aad(sa)->signal);
aa_label_match(profile, peer, state, false, request, &perms);
aa_label_match(profile, rules, peer, state, false, request, &perms);
aa_apply_modes_to_perms(profile, &perms);
return aa_check_perms(profile, &perms, request, sa, audit_signal_cb);
}
@ -98,7 +100,7 @@ static int profile_signal_perm(struct aa_profile *profile,
int aa_may_signal(struct aa_label *sender, struct aa_label *target, int sig)
{
struct aa_profile *profile;
DEFINE_AUDIT_DATA(sa, LSM_AUDIT_DATA_NONE, OP_SIGNAL);
DEFINE_AUDIT_DATA(sa, LSM_AUDIT_DATA_NONE, AA_CLASS_SIGNAL, OP_SIGNAL);
aad(&sa)->signal = map_signal_num(sig);
aad(&sa)->unmappedsig = sig;

View File

@ -197,15 +197,18 @@ static bool vec_is_stale(struct aa_profile **vec, int n)
return false;
}
static long union_vec_flags(struct aa_profile **vec, int n, long mask)
static long accum_vec_flags(struct aa_profile **vec, int n)
{
long u = 0;
long u = FLAG_UNCONFINED;
int i;
AA_BUG(!vec);
for (i = 0; i < n; i++) {
u |= vec[i]->label.flags & mask;
u |= vec[i]->label.flags & (FLAG_DEBUG1 | FLAG_DEBUG2 |
FLAG_STALE);
if (!(u & vec[i]->label.flags & FLAG_UNCONFINED))
u &= ~FLAG_UNCONFINED;
}
return u;
@ -1097,8 +1100,7 @@ static struct aa_label *label_merge_insert(struct aa_label *new,
else if (k == b->size)
return aa_get_label(b);
}
new->flags |= union_vec_flags(new->vec, new->size, FLAG_UNCONFINED |
FLAG_DEBUG1 | FLAG_DEBUG2);
new->flags |= accum_vec_flags(new->vec, new->size);
ls = labels_set(new);
write_lock_irqsave(&ls->lock, flags);
label = __label_insert(labels_set(new), new, false);
@ -1254,32 +1256,27 @@ out:
return label;
}
static inline bool label_is_visible(struct aa_profile *profile,
struct aa_label *label)
{
return aa_ns_visible(profile->ns, labels_ns(label), true);
}
/* match a profile and its associated ns component if needed
* Assumes visibility test has already been done.
* If a subns profile is not to be matched should be prescreened with
* visibility test.
*/
static inline unsigned int match_component(struct aa_profile *profile,
struct aa_profile *tp,
unsigned int state)
static inline aa_state_t match_component(struct aa_profile *profile,
struct aa_ruleset *rules,
struct aa_profile *tp,
aa_state_t state)
{
const char *ns_name;
if (profile->ns == tp->ns)
return aa_dfa_match(profile->policy.dfa, state, tp->base.hname);
return aa_dfa_match(rules->policy.dfa, state, tp->base.hname);
/* try matching with namespace name and then profile */
ns_name = aa_ns_name(profile->ns, tp->ns, true);
state = aa_dfa_match_len(profile->policy.dfa, state, ":", 1);
state = aa_dfa_match(profile->policy.dfa, state, ns_name);
state = aa_dfa_match_len(profile->policy.dfa, state, ":", 1);
return aa_dfa_match(profile->policy.dfa, state, tp->base.hname);
state = aa_dfa_match_len(rules->policy.dfa, state, ":", 1);
state = aa_dfa_match(rules->policy.dfa, state, ns_name);
state = aa_dfa_match_len(rules->policy.dfa, state, ":", 1);
return aa_dfa_match(rules->policy.dfa, state, tp->base.hname);
}
/**
@ -1298,8 +1295,9 @@ static inline unsigned int match_component(struct aa_profile *profile,
* check to be stacked.
*/
static int label_compound_match(struct aa_profile *profile,
struct aa_ruleset *rules,
struct aa_label *label,
unsigned int state, bool subns, u32 request,
aa_state_t state, bool subns, u32 request,
struct aa_perms *perms)
{
struct aa_profile *tp;
@ -1309,7 +1307,7 @@ static int label_compound_match(struct aa_profile *profile,
label_for_each(i, label, tp) {
if (!aa_ns_visible(profile->ns, tp->ns, subns))
continue;
state = match_component(profile, tp, state);
state = match_component(profile, rules, tp, state);
if (!state)
goto fail;
goto next;
@ -1323,12 +1321,12 @@ next:
label_for_each_cont(i, label, tp) {
if (!aa_ns_visible(profile->ns, tp->ns, subns))
continue;
state = aa_dfa_match(profile->policy.dfa, state, "//&");
state = match_component(profile, tp, state);
state = aa_dfa_match(rules->policy.dfa, state, "//&");
state = match_component(profile, rules, tp, state);
if (!state)
goto fail;
}
aa_compute_perms(profile->policy.dfa, state, perms);
*perms = *aa_lookup_perms(&rules->policy, state);
aa_apply_modes_to_perms(profile, perms);
if ((perms->allow & request) != request)
return -EACCES;
@ -1343,6 +1341,7 @@ fail:
/**
* label_components_match - find perms for all subcomponents of a label
* @profile: profile to find perms for
* @rules: ruleset to search
* @label: label to check access permissions for
* @start: state to start match in
* @subns: whether to do permission checks on components in a subns
@ -1356,20 +1355,21 @@ fail:
* check to be stacked.
*/
static int label_components_match(struct aa_profile *profile,
struct aa_label *label, unsigned int start,
struct aa_ruleset *rules,
struct aa_label *label, aa_state_t start,
bool subns, u32 request,
struct aa_perms *perms)
{
struct aa_profile *tp;
struct label_it i;
struct aa_perms tmp;
unsigned int state = 0;
aa_state_t state = 0;
/* find first subcomponent to test */
label_for_each(i, label, tp) {
if (!aa_ns_visible(profile->ns, tp->ns, subns))
continue;
state = match_component(profile, tp, start);
state = match_component(profile, rules, tp, start);
if (!state)
goto fail;
goto next;
@ -1379,16 +1379,16 @@ static int label_components_match(struct aa_profile *profile,
return 0;
next:
aa_compute_perms(profile->policy.dfa, state, &tmp);
tmp = *aa_lookup_perms(&rules->policy, state);
aa_apply_modes_to_perms(profile, &tmp);
aa_perms_accum(perms, &tmp);
label_for_each_cont(i, label, tp) {
if (!aa_ns_visible(profile->ns, tp->ns, subns))
continue;
state = match_component(profile, tp, start);
state = match_component(profile, rules, tp, start);
if (!state)
goto fail;
aa_compute_perms(profile->policy.dfa, state, &tmp);
tmp = *aa_lookup_perms(&rules->policy, state);
aa_apply_modes_to_perms(profile, &tmp);
aa_perms_accum(perms, &tmp);
}
@ -1406,6 +1406,7 @@ fail:
/**
* aa_label_match - do a multi-component label match
* @profile: profile to match against (NOT NULL)
* @rules: ruleset to search
* @label: label to match (NOT NULL)
* @state: state to start in
* @subns: whether to match subns components
@ -1414,18 +1415,18 @@ fail:
*
* Returns: the state the match finished in, may be the none matching state
*/
int aa_label_match(struct aa_profile *profile, struct aa_label *label,
unsigned int state, bool subns, u32 request,
struct aa_perms *perms)
int aa_label_match(struct aa_profile *profile, struct aa_ruleset *rules,
struct aa_label *label, aa_state_t state, bool subns,
u32 request, struct aa_perms *perms)
{
int error = label_compound_match(profile, label, state, subns, request,
perms);
int error = label_compound_match(profile, rules, label, state, subns,
request, perms);
if (!error)
return error;
*perms = allperms;
return label_components_match(profile, label, state, subns, request,
perms);
return label_components_match(profile, rules, label, state, subns,
request, perms);
}

View File

@ -25,6 +25,25 @@ struct aa_perms allperms = { .allow = ALL_PERMS_MASK,
.quiet = ALL_PERMS_MASK,
.hide = ALL_PERMS_MASK };
/**
* aa_free_str_table - free entries str table
* @str: the string table to free (MAYBE NULL)
*/
void aa_free_str_table(struct aa_str_table *t)
{
int i;
if (t) {
if (!t->table)
return;
for (i = 0; i < t->size; i++)
kfree_sensitive(t->table[i]);
kfree_sensitive(t->table);
t->table = NULL;
}
}
/**
* aa_split_fqname - split a fqname into a profile and namespace name
* @fqname: a full qualified name in namespace profile format (NOT NULL)
@ -124,7 +143,7 @@ const char *aa_splitn_fqname(const char *fqname, size_t n, const char **ns_name,
void aa_info_message(const char *str)
{
if (audit_enabled) {
DEFINE_AUDIT_DATA(sa, LSM_AUDIT_DATA_NONE, NULL);
DEFINE_AUDIT_DATA(sa, LSM_AUDIT_DATA_NONE, AA_CLASS_NONE, NULL);
aad(&sa)->info = str;
aa_audit_msg(AUDIT_APPARMOR_STATUS, &sa, NULL);
@ -308,103 +327,22 @@ void aa_apply_modes_to_perms(struct aa_profile *profile, struct aa_perms *perms)
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;
*/
else if (USER_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 */
}
static u32 map_xbits(u32 x)
{
return ((x & 0x1) << 7) |
((x & 0x7e) << 9);
}
void aa_compute_perms(struct aa_dfa *dfa, unsigned int state,
struct aa_perms *perms)
{
/* This mapping is convulated due to history.
* v1-v4: only file perms
* v5: added policydb which dropped in perm user conditional to
* gain new perm bits, but had to map around the xbits because
* the userspace compiler was still munging them.
* v9: adds using the xbits in policydb because the compiler now
* supports treating policydb permission bits different.
* Unfortunately there is not way to force auditing on the
* perms represented by the xbits
*/
*perms = (struct aa_perms) {
.allow = dfa_user_allow(dfa, state) |
map_xbits(dfa_user_xbits(dfa, state)),
.audit = dfa_user_audit(dfa, state),
.quiet = dfa_user_quiet(dfa, state) |
map_xbits(dfa_other_xbits(dfa, state)),
};
/* for v5-v9 perm mapping in the policydb, the other set is used
* to extend the general perm set
*/
perms->allow |= map_other(dfa_other_allow(dfa, state));
perms->audit |= map_other(dfa_other_audit(dfa, state));
perms->quiet |= map_other(dfa_other_quiet(dfa, state));
}
/**
* 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,
void aa_profile_match_label(struct aa_profile *profile,
struct aa_ruleset *rules,
struct aa_label *label,
int type, u32 request, struct aa_perms *perms)
{
/* TODO: doesn't yet handle extended types */
unsigned int state;
aa_state_t state;
state = aa_dfa_next(profile->policy.dfa,
profile->policy.start[AA_CLASS_LABEL],
state = aa_dfa_next(rules->policy.dfa,
rules->policy.start[AA_CLASS_LABEL],
type);
aa_label_match(profile, label, state, false, request, perms);
aa_label_match(profile, rules, label, state, false, request, perms);
}
@ -413,13 +351,16 @@ 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_ruleset *rules = list_first_entry(&profile->rules,
typeof(*rules), list);
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_profile_match_label(profile, rules, &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);

View File

@ -21,7 +21,7 @@
#include <linux/user_namespace.h>
#include <linux/netfilter_ipv4.h>
#include <linux/netfilter_ipv6.h>
#include <linux/zlib.h>
#include <linux/zstd.h>
#include <net/sock.h>
#include <uapi/linux/mount.h>
@ -163,12 +163,15 @@ static int apparmor_capget(struct task_struct *target, kernel_cap_t *effective,
struct label_it i;
label_for_each_confined(i, label, profile) {
struct aa_ruleset *rules;
if (COMPLAIN_MODE(profile))
continue;
rules = list_first_entry(&profile->rules,
typeof(*rules), list);
*effective = cap_intersect(*effective,
profile->caps.allow);
rules->caps.allow);
*permitted = cap_intersect(*permitted,
profile->caps.allow);
rules->caps.allow);
}
}
rcu_read_unlock();
@ -661,7 +664,8 @@ static int apparmor_setprocattr(const char *name, void *value,
char *command, *largs = NULL, *args = value;
size_t arg_size;
int error;
DEFINE_AUDIT_DATA(sa, LSM_AUDIT_DATA_NONE, OP_SETPROCATTR);
DEFINE_AUDIT_DATA(sa, LSM_AUDIT_DATA_NONE, AA_CLASS_NONE,
OP_SETPROCATTR);
if (size == 0)
return -EINVAL;
@ -751,7 +755,7 @@ static void apparmor_bprm_committing_creds(struct linux_binprm *bprm)
}
/**
* apparmor_bprm_committed_cred - do cleanup after new creds committed
* apparmor_bprm_committed_creds() - do cleanup after new creds committed
* @bprm: binprm for the exec (NOT NULL)
*/
static void apparmor_bprm_committed_creds(struct linux_binprm *bprm)
@ -1205,10 +1209,10 @@ static int apparmor_inet_conn_request(const struct sock *sk, struct sk_buff *skb
#endif
/*
* The cred blob is a pointer to, not an instance of, an aa_task_ctx.
* The cred blob is a pointer to, not an instance of, an aa_label.
*/
struct lsm_blob_sizes apparmor_blob_sizes __lsm_ro_after_init = {
.lbs_cred = sizeof(struct aa_task_ctx *),
.lbs_cred = sizeof(struct aa_label *),
.lbs_file = sizeof(struct aa_file_ctx),
.lbs_task = sizeof(struct aa_task_ctx),
};
@ -1373,7 +1377,7 @@ module_param_named(export_binary, aa_g_export_binary, aabool, 0600);
#endif
/* policy loaddata compression level */
int aa_g_rawdata_compression_level = Z_DEFAULT_COMPRESSION;
int aa_g_rawdata_compression_level = AA_DEFAULT_CLEVEL;
module_param_named(rawdata_compression_level, aa_g_rawdata_compression_level,
aacompressionlevel, 0400);
@ -1555,9 +1559,8 @@ static int param_set_aacompressionlevel(const char *val,
error = param_set_int(val, kp);
aa_g_rawdata_compression_level = clamp(aa_g_rawdata_compression_level,
Z_NO_COMPRESSION,
Z_BEST_COMPRESSION);
pr_info("AppArmor: policy rawdata compression level set to %u\n",
AA_MIN_CLEVEL, AA_MAX_CLEVEL);
pr_info("AppArmor: policy rawdata compression level set to %d\n",
aa_g_rawdata_compression_level);
return error;

View File

@ -31,7 +31,7 @@ static char stacksplitdfa_src[] = {
};
struct aa_dfa *stacksplitdfa;
int aa_setup_dfa_engine(void)
int __init aa_setup_dfa_engine(void)
{
int error;
@ -59,7 +59,7 @@ int aa_setup_dfa_engine(void)
return 0;
}
void aa_teardown_dfa_engine(void)
void __init aa_teardown_dfa_engine(void)
{
aa_put_dfa(stacksplitdfa);
aa_put_dfa(nulldfa);
@ -436,17 +436,17 @@ do { \
*
* Returns: final state reached after input is consumed
*/
unsigned int aa_dfa_match_len(struct aa_dfa *dfa, unsigned int start,
const char *str, int len)
aa_state_t aa_dfa_match_len(struct aa_dfa *dfa, aa_state_t start,
const char *str, int len)
{
u16 *def = DEFAULT_TABLE(dfa);
u32 *base = BASE_TABLE(dfa);
u16 *next = NEXT_TABLE(dfa);
u16 *check = CHECK_TABLE(dfa);
unsigned int state = start;
aa_state_t state = start;
if (state == 0)
return 0;
if (state == DFA_NOMATCH)
return DFA_NOMATCH;
/* current state is <state>, matching character *str */
if (dfa->tables[YYTD_ID_EC]) {
@ -476,17 +476,16 @@ unsigned int aa_dfa_match_len(struct aa_dfa *dfa, unsigned int start,
*
* Returns: final state reached after input is consumed
*/
unsigned int aa_dfa_match(struct aa_dfa *dfa, unsigned int start,
const char *str)
aa_state_t aa_dfa_match(struct aa_dfa *dfa, aa_state_t start, const char *str)
{
u16 *def = DEFAULT_TABLE(dfa);
u32 *base = BASE_TABLE(dfa);
u16 *next = NEXT_TABLE(dfa);
u16 *check = CHECK_TABLE(dfa);
unsigned int state = start;
aa_state_t state = start;
if (state == 0)
return 0;
if (state == DFA_NOMATCH)
return DFA_NOMATCH;
/* current state is <state>, matching character *str */
if (dfa->tables[YYTD_ID_EC]) {
@ -515,8 +514,7 @@ unsigned int aa_dfa_match(struct aa_dfa *dfa, unsigned int start,
*
* Returns: state reach after input @c
*/
unsigned int aa_dfa_next(struct aa_dfa *dfa, unsigned int state,
const char c)
aa_state_t aa_dfa_next(struct aa_dfa *dfa, aa_state_t state, const char c)
{
u16 *def = DEFAULT_TABLE(dfa);
u32 *base = BASE_TABLE(dfa);
@ -534,7 +532,7 @@ unsigned int aa_dfa_next(struct aa_dfa *dfa, unsigned int state,
return state;
}
unsigned int aa_dfa_outofband_transition(struct aa_dfa *dfa, unsigned int state)
aa_state_t aa_dfa_outofband_transition(struct aa_dfa *dfa, aa_state_t state)
{
u16 *def = DEFAULT_TABLE(dfa);
u32 *base = BASE_TABLE(dfa);
@ -564,7 +562,7 @@ unsigned int aa_dfa_outofband_transition(struct aa_dfa *dfa, unsigned int state)
*
* Returns: final state reached after input is consumed
*/
unsigned int aa_dfa_match_until(struct aa_dfa *dfa, unsigned int start,
aa_state_t aa_dfa_match_until(struct aa_dfa *dfa, aa_state_t start,
const char *str, const char **retpos)
{
u16 *def = DEFAULT_TABLE(dfa);
@ -572,10 +570,10 @@ unsigned int aa_dfa_match_until(struct aa_dfa *dfa, unsigned int start,
u16 *next = NEXT_TABLE(dfa);
u16 *check = CHECK_TABLE(dfa);
u32 *accept = ACCEPT_TABLE(dfa);
unsigned int state = start, pos;
aa_state_t state = start, pos;
if (state == 0)
return 0;
if (state == DFA_NOMATCH)
return DFA_NOMATCH;
/* current state is <state>, matching character *str */
if (dfa->tables[YYTD_ID_EC]) {
@ -625,7 +623,7 @@ unsigned int aa_dfa_match_until(struct aa_dfa *dfa, unsigned int start,
*
* Returns: final state reached after input is consumed
*/
unsigned int aa_dfa_matchn_until(struct aa_dfa *dfa, unsigned int start,
aa_state_t aa_dfa_matchn_until(struct aa_dfa *dfa, aa_state_t start,
const char *str, int n, const char **retpos)
{
u16 *def = DEFAULT_TABLE(dfa);
@ -633,11 +631,11 @@ unsigned int aa_dfa_matchn_until(struct aa_dfa *dfa, unsigned int start,
u16 *next = NEXT_TABLE(dfa);
u16 *check = CHECK_TABLE(dfa);
u32 *accept = ACCEPT_TABLE(dfa);
unsigned int state = start, pos;
aa_state_t state = start, pos;
*retpos = NULL;
if (state == 0)
return 0;
if (state == DFA_NOMATCH)
return DFA_NOMATCH;
/* current state is <state>, matching character *str */
if (dfa->tables[YYTD_ID_EC]) {
@ -677,11 +675,11 @@ do { \
} while (0)
/* For DFAs that don't support extended tagging of states */
static bool is_loop(struct match_workbuf *wb, unsigned int state,
static bool is_loop(struct match_workbuf *wb, aa_state_t state,
unsigned int *adjust)
{
unsigned int pos = wb->pos;
unsigned int i;
aa_state_t pos = wb->pos;
aa_state_t i;
if (wb->history[pos] < state)
return false;
@ -700,7 +698,7 @@ static bool is_loop(struct match_workbuf *wb, unsigned int state,
return true;
}
static unsigned int leftmatch_fb(struct aa_dfa *dfa, unsigned int start,
static aa_state_t leftmatch_fb(struct aa_dfa *dfa, aa_state_t start,
const char *str, struct match_workbuf *wb,
unsigned int *count)
{
@ -708,7 +706,7 @@ static unsigned int leftmatch_fb(struct aa_dfa *dfa, unsigned int start,
u32 *base = BASE_TABLE(dfa);
u16 *next = NEXT_TABLE(dfa);
u16 *check = CHECK_TABLE(dfa);
unsigned int state = start, pos;
aa_state_t state = start, pos;
AA_BUG(!dfa);
AA_BUG(!str);
@ -716,8 +714,8 @@ static unsigned int leftmatch_fb(struct aa_dfa *dfa, unsigned int start,
AA_BUG(!count);
*count = 0;
if (state == 0)
return 0;
if (state == DFA_NOMATCH)
return DFA_NOMATCH;
/* current state is <state>, matching character *str */
if (dfa->tables[YYTD_ID_EC]) {
@ -781,8 +779,8 @@ out:
*
* Returns: final state reached after input is consumed
*/
unsigned int aa_dfa_leftmatch(struct aa_dfa *dfa, unsigned int start,
const char *str, unsigned int *count)
aa_state_t aa_dfa_leftmatch(struct aa_dfa *dfa, aa_state_t start,
const char *str, unsigned int *count)
{
DEFINE_MATCH_WB(wb);

View File

@ -134,7 +134,7 @@ static int audit_mount(struct aa_profile *profile, const char *op,
struct aa_perms *perms, const char *info, int error)
{
int audit_type = AUDIT_APPARMOR_AUTO;
DEFINE_AUDIT_DATA(sa, LSM_AUDIT_DATA_NONE, op);
DEFINE_AUDIT_DATA(sa, LSM_AUDIT_DATA_NONE, AA_CLASS_MOUNT, op);
if (likely(!error)) {
u32 mask = perms->audit;
@ -190,7 +190,7 @@ static int audit_mount(struct aa_profile *profile, const char *op,
*
* Returns: next state after flags match
*/
static unsigned int match_mnt_flags(struct aa_dfa *dfa, unsigned int state,
static aa_state_t match_mnt_flags(struct aa_dfa *dfa, aa_state_t state,
unsigned long flags)
{
unsigned int i;
@ -203,25 +203,6 @@ static unsigned int match_mnt_flags(struct aa_dfa *dfa, unsigned int state,
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 = {
.allow = dfa_user_allow(dfa, state),
.audit = dfa_user_audit(dfa, state),
.quiet = dfa_user_quiet(dfa, state),
};
return perms;
}
static const char * const mnt_info_table[] = {
"match succeeded",
"failed mntpnt match",
@ -236,50 +217,52 @@ static const char * const mnt_info_table[] = {
* 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,
static int do_match_mnt(struct aa_policydb *policy, aa_state_t start,
const char *mntpnt, const char *devname,
const char *type, unsigned long flags,
void *data, bool binary, struct aa_perms *perms)
{
unsigned int state;
aa_state_t state;
AA_BUG(!dfa);
AA_BUG(!policy);
AA_BUG(!policy->dfa);
AA_BUG(!policy->perms);
AA_BUG(!perms);
state = aa_dfa_match(dfa, start, mntpnt);
state = aa_dfa_null_transition(dfa, state);
state = aa_dfa_match(policy->dfa, start, mntpnt);
state = aa_dfa_null_transition(policy->dfa, state);
if (!state)
return 1;
if (devname)
state = aa_dfa_match(dfa, state, devname);
state = aa_dfa_null_transition(dfa, state);
state = aa_dfa_match(policy->dfa, state, devname);
state = aa_dfa_null_transition(policy->dfa, state);
if (!state)
return 2;
if (type)
state = aa_dfa_match(dfa, state, type);
state = aa_dfa_null_transition(dfa, state);
state = aa_dfa_match(policy->dfa, state, type);
state = aa_dfa_null_transition(policy->dfa, state);
if (!state)
return 3;
state = match_mnt_flags(dfa, state, flags);
state = match_mnt_flags(policy->dfa, state, flags);
if (!state)
return 4;
*perms = compute_mnt_perms(dfa, state);
*perms = *aa_lookup_perms(policy, state);
if (perms->allow & AA_MAY_MOUNT)
return 0;
/* only match data if not binary and the DFA flags data is expected */
if (data && !binary && (perms->allow & AA_MNT_CONT_MATCH)) {
state = aa_dfa_null_transition(dfa, state);
state = aa_dfa_null_transition(policy->dfa, state);
if (!state)
return 4;
state = aa_dfa_match(dfa, state, data);
state = aa_dfa_match(policy->dfa, state, data);
if (!state)
return 5;
*perms = compute_mnt_perms(dfa, state);
*perms = *aa_lookup_perms(policy, state);
if (perms->allow & AA_MAY_MOUNT)
return 0;
}
@ -320,13 +303,15 @@ static int match_mnt_path_str(struct aa_profile *profile,
{
struct aa_perms perms = { };
const char *mntpnt = NULL, *info = NULL;
struct aa_ruleset *rules = list_first_entry(&profile->rules,
typeof(*rules), list);
int pos, error;
AA_BUG(!profile);
AA_BUG(!mntpath);
AA_BUG(!buffer);
if (!PROFILE_MEDIATES(profile, AA_CLASS_MOUNT))
if (!RULE_MEDIATES(rules, AA_CLASS_MOUNT))
return 0;
error = aa_path_name(mntpath, path_flags(profile, mntpath), buffer,
@ -341,8 +326,8 @@ static int match_mnt_path_str(struct aa_profile *profile,
}
error = -EACCES;
pos = do_match_mnt(profile->policy.dfa,
profile->policy.start[AA_CLASS_MOUNT],
pos = do_match_mnt(&rules->policy,
rules->policy.start[AA_CLASS_MOUNT],
mntpnt, devname, type, flags, data, binary, &perms);
if (pos) {
info = mnt_info_table[pos];
@ -375,12 +360,14 @@ static int match_mnt(struct aa_profile *profile, const struct path *path,
bool binary)
{
const char *devname = NULL, *info = NULL;
struct aa_ruleset *rules = list_first_entry(&profile->rules,
typeof(*rules), list);
int error = -EACCES;
AA_BUG(!profile);
AA_BUG(devpath && !devbuffer);
if (!PROFILE_MEDIATES(profile, AA_CLASS_MOUNT))
if (!RULE_MEDIATES(rules, AA_CLASS_MOUNT))
return 0;
if (devpath) {
@ -582,15 +569,17 @@ out:
static int profile_umount(struct aa_profile *profile, const struct path *path,
char *buffer)
{
struct aa_ruleset *rules = list_first_entry(&profile->rules,
typeof(*rules), list);
struct aa_perms perms = { };
const char *name = NULL, *info = NULL;
unsigned int state;
aa_state_t state;
int error;
AA_BUG(!profile);
AA_BUG(!path);
if (!PROFILE_MEDIATES(profile, AA_CLASS_MOUNT))
if (!RULE_MEDIATES(rules, AA_CLASS_MOUNT))
return 0;
error = aa_path_name(path, path_flags(profile, path), buffer, &name,
@ -598,10 +587,10 @@ static int profile_umount(struct aa_profile *profile, const struct path *path,
if (error)
goto audit;
state = aa_dfa_match(profile->policy.dfa,
profile->policy.start[AA_CLASS_MOUNT],
state = aa_dfa_match(rules->policy.dfa,
rules->policy.start[AA_CLASS_MOUNT],
name);
perms = compute_mnt_perms(profile->policy.dfa, state);
perms = *aa_lookup_perms(&rules->policy, state);
if (AA_MAY_UMOUNT & ~perms.allow)
error = -EACCES;
@ -641,10 +630,12 @@ static struct aa_label *build_pivotroot(struct aa_profile *profile,
const struct path *old_path,
char *old_buffer)
{
struct aa_ruleset *rules = list_first_entry(&profile->rules,
typeof(*rules), list);
const char *old_name, *new_name = NULL, *info = NULL;
const char *trans_name = NULL;
struct aa_perms perms = { };
unsigned int state;
aa_state_t state;
int error;
AA_BUG(!profile);
@ -652,7 +643,7 @@ static struct aa_label *build_pivotroot(struct aa_profile *profile,
AA_BUG(!old_path);
if (profile_unconfined(profile) ||
!PROFILE_MEDIATES(profile, AA_CLASS_MOUNT))
!RULE_MEDIATES(rules, AA_CLASS_MOUNT))
return aa_get_newest_label(&profile->label);
error = aa_path_name(old_path, path_flags(profile, old_path),
@ -667,12 +658,12 @@ static struct aa_label *build_pivotroot(struct aa_profile *profile,
goto audit;
error = -EACCES;
state = aa_dfa_match(profile->policy.dfa,
profile->policy.start[AA_CLASS_MOUNT],
state = aa_dfa_match(rules->policy.dfa,
rules->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);
state = aa_dfa_null_transition(rules->policy.dfa, state);
state = aa_dfa_match(rules->policy.dfa, state, old_name);
perms = *aa_lookup_perms(&rules->policy, state);
if (AA_MAY_PIVOTROOT & perms.allow)
error = 0;

View File

@ -108,8 +108,10 @@ 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)
{
struct aa_ruleset *rules = list_first_entry(&profile->rules,
typeof(*rules), list);
struct aa_perms perms = { };
unsigned int state;
aa_state_t state;
__be16 buffer[2];
AA_BUG(family >= AF_MAX);
@ -117,15 +119,15 @@ int aa_profile_af_perm(struct aa_profile *profile, struct common_audit_data *sa,
if (profile_unconfined(profile))
return 0;
state = PROFILE_MEDIATES(profile, AA_CLASS_NET);
state = RULE_MEDIATES(rules, AA_CLASS_NET);
if (!state)
return 0;
buffer[0] = cpu_to_be16(family);
buffer[1] = cpu_to_be16((u16) type);
state = aa_dfa_match_len(profile->policy.dfa, state, (char *) &buffer,
state = aa_dfa_match_len(rules->policy.dfa, state, (char *) &buffer,
4);
aa_compute_perms(profile->policy.dfa, state, &perms);
perms = *aa_lookup_perms(&rules->policy, state);
aa_apply_modes_to_perms(profile, &perms);
return aa_check_perms(profile, &perms, request, sa, audit_net_cb);
@ -216,25 +218,27 @@ static int aa_secmark_perm(struct aa_profile *profile, u32 request, u32 secid,
{
int i, ret;
struct aa_perms perms = { };
struct aa_ruleset *rules = list_first_entry(&profile->rules,
typeof(*rules), list);
if (profile->secmark_count == 0)
if (rules->secmark_count == 0)
return 0;
for (i = 0; i < profile->secmark_count; i++) {
if (!profile->secmark[i].secid) {
ret = apparmor_secmark_init(&profile->secmark[i]);
for (i = 0; i < rules->secmark_count; i++) {
if (!rules->secmark[i].secid) {
ret = apparmor_secmark_init(&rules->secmark[i]);
if (ret)
return ret;
}
if (profile->secmark[i].secid == secid ||
profile->secmark[i].secid == AA_SECID_WILDCARD) {
if (profile->secmark[i].deny)
if (rules->secmark[i].secid == secid ||
rules->secmark[i].secid == AA_SECID_WILDCARD) {
if (rules->secmark[i].deny)
perms.deny = ALL_PERMS_MASK;
else
perms.allow = ALL_PERMS_MASK;
if (profile->secmark[i].audit)
if (rules->secmark[i].audit)
perms.audit = ALL_PERMS_MASK;
}
}

View File

@ -94,6 +94,7 @@ const char *const aa_profile_mode_names[] = {
"complain",
"kill",
"unconfined",
"user",
};
@ -192,6 +193,42 @@ static void aa_free_data(void *ptr, void *arg)
kfree_sensitive(data);
}
static void free_attachment(struct aa_attachment *attach)
{
int i;
for (i = 0; i < attach->xattr_count; i++)
kfree_sensitive(attach->xattrs[i]);
kfree_sensitive(attach->xattrs);
aa_destroy_policydb(&attach->xmatch);
}
static void free_ruleset(struct aa_ruleset *rules)
{
int i;
aa_destroy_policydb(&rules->file);
aa_destroy_policydb(&rules->policy);
aa_free_cap_rules(&rules->caps);
aa_free_rlimit_rules(&rules->rlimits);
for (i = 0; i < rules->secmark_count; i++)
kfree_sensitive(rules->secmark[i].label);
kfree_sensitive(rules->secmark);
kfree_sensitive(rules);
}
struct aa_ruleset *aa_alloc_ruleset(gfp_t gfp)
{
struct aa_ruleset *rules;
rules = kzalloc(sizeof(*rules), gfp);
if (rules)
INIT_LIST_HEAD(&rules->list);
return rules;
}
/**
* aa_free_profile - free a profile
* @profile: the profile to free (MAYBE NULL)
@ -204,8 +241,8 @@ static void aa_free_data(void *ptr, void *arg)
*/
void aa_free_profile(struct aa_profile *profile)
{
struct aa_ruleset *rule, *tmp;
struct rhashtable *rht;
int i;
AA_DEBUG("%s(%p)\n", __func__, profile);
@ -219,19 +256,17 @@ void aa_free_profile(struct aa_profile *profile)
aa_put_ns(profile->ns);
kfree_sensitive(profile->rename);
aa_free_file_rules(&profile->file);
aa_free_cap_rules(&profile->caps);
aa_free_rlimit_rules(&profile->rlimits);
free_attachment(&profile->attach);
for (i = 0; i < profile->xattr_count; i++)
kfree_sensitive(profile->xattrs[i]);
kfree_sensitive(profile->xattrs);
for (i = 0; i < profile->secmark_count; i++)
kfree_sensitive(profile->secmark[i].label);
kfree_sensitive(profile->secmark);
/*
* at this point there are no tasks that can have a reference
* to rules
*/
list_for_each_entry_safe(rule, tmp, &profile->rules, list) {
list_del_init(&rule->list);
free_ruleset(rule);
}
kfree_sensitive(profile->dirname);
aa_put_dfa(profile->xmatch);
aa_put_dfa(profile->policy.dfa);
if (profile->data) {
rht = profile->data;
@ -258,6 +293,7 @@ struct aa_profile *aa_alloc_profile(const char *hname, struct aa_proxy *proxy,
gfp_t gfp)
{
struct aa_profile *profile;
struct aa_ruleset *rules;
/* freed by free_profile - usually through aa_put_profile */
profile = kzalloc(struct_size(profile, label.vec, 2), gfp);
@ -269,6 +305,14 @@ struct aa_profile *aa_alloc_profile(const char *hname, struct aa_proxy *proxy,
if (!aa_label_init(&profile->label, 1, gfp))
goto fail;
INIT_LIST_HEAD(&profile->rules);
/* allocate the first ruleset, but leave it empty */
rules = aa_alloc_ruleset(gfp);
if (!rules)
goto fail;
list_add(&rules->list, &profile->rules);
/* update being set needed by fs interface */
if (!proxy) {
proxy = aa_alloc_proxy(&profile->label, gfp);
@ -380,6 +424,57 @@ static struct aa_policy *__lookup_parent(struct aa_ns *ns,
return &profile->base;
}
/**
* __create_missing_ancestors - create place holders for missing ancestores
* @ns: namespace to lookup profile in (NOT NULL)
* @hname: hierarchical profile name to find parent of (NOT NULL)
* @gfp: type of allocation.
*
* Returns: NULL on error, parent profile on success
*
* Requires: ns mutex lock held
*
* Returns: unrefcounted parent policy or NULL if error creating
* place holder profiles.
*/
static struct aa_policy *__create_missing_ancestors(struct aa_ns *ns,
const char *hname,
gfp_t gfp)
{
struct aa_policy *policy;
struct aa_profile *parent, *profile = NULL;
char *split;
AA_BUG(!ns);
AA_BUG(!hname);
policy = &ns->base;
for (split = strstr(hname, "//"); split;) {
parent = profile;
profile = __strn_find_child(&policy->profiles, hname,
split - hname);
if (!profile) {
const char *name = kstrndup(hname, split - hname,
gfp);
if (!name)
return NULL;
profile = aa_alloc_null(parent, name, gfp);
kfree(name);
if (!profile)
return NULL;
if (!parent)
profile->ns = aa_get_ns(ns);
}
policy = &profile->base;
hname = split + 2;
split = strstr(hname, "//");
}
if (!profile)
return &ns->base;
return &profile->base;
}
/**
* __lookupn_profile - lookup the profile matching @hname
* @base: base list to start looking up profile name from (NOT NULL)
@ -481,8 +576,36 @@ struct aa_profile *aa_fqlookupn_profile(struct aa_label *base,
return profile;
}
struct aa_profile *aa_alloc_null(struct aa_profile *parent, const char *name,
gfp_t gfp)
{
struct aa_profile *profile;
struct aa_ruleset *rules;
profile = aa_alloc_profile(name, NULL, gfp);
if (!profile)
return NULL;
/* TODO: ideally we should inherit abi from parent */
profile->label.flags |= FLAG_NULL;
rules = list_first_entry(&profile->rules, typeof(*rules), list);
rules->file.dfa = aa_get_dfa(nulldfa);
rules->policy.dfa = aa_get_dfa(nulldfa);
if (parent) {
profile->path_flags = parent->path_flags;
/* released on free_profile */
rcu_assign_pointer(profile->parent, aa_get_profile(parent));
profile->ns = aa_get_ns(parent->ns);
}
return profile;
}
/**
* aa_new_null_profile - create or find a null-X learning profile
* aa_new_learning_profile - create or find a null-X learning profile
* @parent: profile that caused this profile to be created (NOT NULL)
* @hat: true if the null- learning profile is a hat
* @base: name to base the null profile off of
@ -499,8 +622,8 @@ struct aa_profile *aa_fqlookupn_profile(struct aa_label *base,
*
* Returns: new refcounted profile else NULL on failure
*/
struct aa_profile *aa_new_null_profile(struct aa_profile *parent, bool hat,
const char *base, gfp_t gfp)
struct aa_profile *aa_new_learning_profile(struct aa_profile *parent, bool hat,
const char *base, gfp_t gfp)
{
struct aa_profile *p, *profile;
const char *bname;
@ -531,21 +654,12 @@ name:
if (profile)
goto out;
profile = aa_alloc_profile(name, NULL, gfp);
profile = aa_alloc_null(parent, name, gfp);
if (!profile)
goto fail;
profile->mode = APPARMOR_COMPLAIN;
profile->label.flags |= FLAG_NULL;
if (hat)
profile->label.flags |= FLAG_HAT;
profile->path_flags = parent->path_flags;
/* released on free_profile */
rcu_assign_pointer(profile->parent, aa_get_profile(parent));
profile->ns = aa_get_ns(parent->ns);
profile->file.dfa = aa_get_dfa(nulldfa);
profile->policy.dfa = aa_get_dfa(nulldfa);
mutex_lock_nested(&profile->ns->lock, profile->ns->level);
p = __find_child(&parent->base.profiles, bname);
@ -618,7 +732,7 @@ static int audit_policy(struct aa_label *label, const char *op,
const char *ns_name, const char *name,
const char *info, int error)
{
DEFINE_AUDIT_DATA(sa, LSM_AUDIT_DATA_NONE, op);
DEFINE_AUDIT_DATA(sa, LSM_AUDIT_DATA_NONE, AA_CLASS_NONE, op);
aad(&sa)->iface.ns = ns_name;
aad(&sa)->name = name;
@ -970,6 +1084,7 @@ ssize_t aa_replace_profiles(struct aa_ns *policy_ns, struct aa_label *label,
/* setup parent and ns info */
list_for_each_entry(ent, &lh, list) {
struct aa_policy *policy;
struct aa_profile *p;
if (aa_g_export_binary)
ent->new->rawdata = aa_get_loaddata(udata);
@ -994,21 +1109,38 @@ ssize_t aa_replace_profiles(struct aa_ns *policy_ns, struct aa_label *label,
continue;
/* no ref on policy only use inside lock */
p = NULL;
policy = __lookup_parent(ns, ent->new->base.hname);
if (!policy) {
struct aa_profile *p;
/* first check for parent in the load set */
p = __list_lookup_parent(&lh, ent->new);
if (!p) {
error = -ENOENT;
info = "parent does not exist";
goto fail_lock;
/*
* fill in missing parent with null
* profile that doesn't have
* permissions. This allows for
* individual profile loading where
* the child is loaded before the
* parent, and outside of the current
* atomic set. This unfortunately can
* happen with some userspaces. The
* null profile will be replaced once
* the parent is loaded.
*/
policy = __create_missing_ancestors(ns,
ent->new->base.hname,
GFP_KERNEL);
if (!policy) {
error = -ENOENT;
info = "parent does not exist";
goto fail_lock;
}
}
rcu_assign_pointer(ent->new->parent, aa_get_profile(p));
} else if (policy != &ns->base) {
/* released on profile replacement or free_profile */
struct aa_profile *p = (struct aa_profile *) policy;
rcu_assign_pointer(ent->new->parent, aa_get_profile(p));
}
if (!p && policy != &ns->base)
/* released on profile replacement or free_profile */
p = (struct aa_profile *) policy;
rcu_assign_pointer(ent->new->parent, aa_get_profile(p));
}
/* create new fs entries for introspection if needed */
@ -1170,7 +1302,7 @@ ssize_t aa_remove_profiles(struct aa_ns *policy_ns, struct aa_label *subj,
if (!name) {
/* remove namespace - can only happen if fqname[0] == ':' */
mutex_lock_nested(&ns->parent->lock, ns->level);
mutex_lock_nested(&ns->parent->lock, ns->parent->level);
__aa_bump_ns_revision(ns);
__aa_remove_ns(ns);
mutex_unlock(&ns->parent->lock);

View File

@ -0,0 +1,319 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* AppArmor security module
*
* This file contains AppArmor functions for unpacking policy loaded
* from userspace.
*
* Copyright (C) 1998-2008 Novell/SUSE
* Copyright 2009-2022 Canonical Ltd.
*
* Code to provide backwards compatibility with older policy versions,
* by converting/mapping older policy formats into the newer internal
* formats.
*/
#include <linux/ctype.h>
#include <linux/errno.h>
#include "include/lib.h"
#include "include/policy_unpack.h"
#include "include/policy_compat.h"
/* remap old accept table embedded permissions to separate permission table */
static u32 dfa_map_xindex(u16 mask)
{
u16 old_index = (mask >> 10) & 0xf;
u32 index = 0;
if (mask & 0x100)
index |= AA_X_UNSAFE;
if (mask & 0x200)
index |= AA_X_INHERIT;
if (mask & 0x80)
index |= AA_X_UNCONFINED;
if (old_index == 1) {
index |= AA_X_UNCONFINED;
} else if (old_index == 2) {
index |= AA_X_NAME;
} else if (old_index == 3) {
index |= AA_X_NAME | AA_X_CHILD;
} else if (old_index) {
index |= AA_X_TABLE;
index |= old_index - 4;
}
return index;
}
/*
* map old dfa inline permissions to new format
*/
#define dfa_user_allow(dfa, state) (((ACCEPT_TABLE(dfa)[state]) & 0x7f) | \
((ACCEPT_TABLE(dfa)[state]) & 0x80000000))
#define dfa_user_xbits(dfa, state) (((ACCEPT_TABLE(dfa)[state]) >> 7) & 0x7f)
#define dfa_user_audit(dfa, state) ((ACCEPT_TABLE2(dfa)[state]) & 0x7f)
#define dfa_user_quiet(dfa, state) (((ACCEPT_TABLE2(dfa)[state]) >> 7) & 0x7f)
#define dfa_user_xindex(dfa, state) \
(dfa_map_xindex(ACCEPT_TABLE(dfa)[state] & 0x3fff))
#define dfa_other_allow(dfa, state) ((((ACCEPT_TABLE(dfa)[state]) >> 14) & \
0x7f) | \
((ACCEPT_TABLE(dfa)[state]) & 0x80000000))
#define dfa_other_xbits(dfa, state) \
((((ACCEPT_TABLE(dfa)[state]) >> 7) >> 14) & 0x7f)
#define dfa_other_audit(dfa, state) (((ACCEPT_TABLE2(dfa)[state]) >> 14) & 0x7f)
#define dfa_other_quiet(dfa, state) \
((((ACCEPT_TABLE2(dfa)[state]) >> 7) >> 14) & 0x7f)
#define dfa_other_xindex(dfa, state) \
dfa_map_xindex((ACCEPT_TABLE(dfa)[state] >> 14) & 0x3fff)
/**
* map_old_perms - map old file perms layout to the new layout
* @old: permission set in old mapping
*
* Returns: new permission mapping
*/
static u32 map_old_perms(u32 old)
{
u32 new = old & 0xf;
if (old & MAY_READ)
new |= AA_MAY_GETATTR | AA_MAY_OPEN;
if (old & MAY_WRITE)
new |= AA_MAY_SETATTR | AA_MAY_CREATE | AA_MAY_DELETE |
AA_MAY_CHMOD | AA_MAY_CHOWN | AA_MAY_OPEN;
if (old & 0x10)
new |= AA_MAY_LINK;
/* the old mapping lock and link_subset flags where overlaid
* and use was determined by part of a pair that they were in
*/
if (old & 0x20)
new |= AA_MAY_LOCK | AA_LINK_SUBSET;
if (old & 0x40) /* AA_EXEC_MMAP */
new |= AA_EXEC_MMAP;
return new;
}
static void compute_fperms_allow(struct aa_perms *perms, struct aa_dfa *dfa,
aa_state_t state)
{
perms->allow |= AA_MAY_GETATTR;
/* change_profile wasn't determined by ownership in old mapping */
if (ACCEPT_TABLE(dfa)[state] & 0x80000000)
perms->allow |= AA_MAY_CHANGE_PROFILE;
if (ACCEPT_TABLE(dfa)[state] & 0x40000000)
perms->allow |= AA_MAY_ONEXEC;
}
static struct aa_perms compute_fperms_user(struct aa_dfa *dfa,
aa_state_t state)
{
struct aa_perms perms = { };
perms.allow = map_old_perms(dfa_user_allow(dfa, state));
perms.audit = map_old_perms(dfa_user_audit(dfa, state));
perms.quiet = map_old_perms(dfa_user_quiet(dfa, state));
perms.xindex = dfa_user_xindex(dfa, state);
compute_fperms_allow(&perms, dfa, state);
return perms;
}
static struct aa_perms compute_fperms_other(struct aa_dfa *dfa,
aa_state_t state)
{
struct aa_perms perms = { };
perms.allow = map_old_perms(dfa_other_allow(dfa, state));
perms.audit = map_old_perms(dfa_other_audit(dfa, state));
perms.quiet = map_old_perms(dfa_other_quiet(dfa, state));
perms.xindex = dfa_other_xindex(dfa, state);
compute_fperms_allow(&perms, dfa, state);
return perms;
}
/**
* compute_fperms - convert dfa compressed perms to internal perms and store
* them so they can be retrieved later.
* @dfa: a dfa using fperms to remap to internal permissions
*
* Returns: remapped perm table
*/
static struct aa_perms *compute_fperms(struct aa_dfa *dfa)
{
aa_state_t state;
unsigned int state_count;
struct aa_perms *table;
AA_BUG(!dfa);
state_count = dfa->tables[YYTD_ID_BASE]->td_lolen;
/* DFAs are restricted from having a state_count of less than 2 */
table = kvcalloc(state_count * 2, sizeof(struct aa_perms), GFP_KERNEL);
if (!table)
return NULL;
/* zero init so skip the trap state (state == 0) */
for (state = 1; state < state_count; state++) {
table[state * 2] = compute_fperms_user(dfa, state);
table[state * 2 + 1] = compute_fperms_other(dfa, state);
}
return table;
}
static struct aa_perms *compute_xmatch_perms(struct aa_dfa *xmatch)
{
struct aa_perms *perms;
int state;
int state_count;
AA_BUG(!xmatch);
state_count = xmatch->tables[YYTD_ID_BASE]->td_lolen;
/* DFAs are restricted from having a state_count of less than 2 */
perms = kvcalloc(state_count, sizeof(struct aa_perms), GFP_KERNEL);
/* zero init so skip the trap state (state == 0) */
for (state = 1; state < state_count; state++)
perms[state].allow = dfa_user_allow(xmatch, state);
return perms;
}
static u32 map_other(u32 x)
{
return ((x & 0x3) << 8) | /* SETATTR/GETATTR */
((x & 0x1c) << 18) | /* ACCEPT/BIND/LISTEN */
((x & 0x60) << 19); /* SETOPT/GETOPT */
}
static u32 map_xbits(u32 x)
{
return ((x & 0x1) << 7) |
((x & 0x7e) << 9);
}
static struct aa_perms compute_perms_entry(struct aa_dfa *dfa,
aa_state_t state,
u32 version)
{
struct aa_perms perms = { };
perms.allow = dfa_user_allow(dfa, state);
perms.audit = dfa_user_audit(dfa, state);
perms.quiet = dfa_user_quiet(dfa, state);
/*
* This mapping is convulated due to history.
* v1-v4: only file perms, which are handled by compute_fperms
* v5: added policydb which dropped user conditional to gain new
* perm bits, but had to map around the xbits because the
* userspace compiler was still munging them.
* v9: adds using the xbits in policydb because the compiler now
* supports treating policydb permission bits different.
* Unfortunately there is no way to force auditing on the
* perms represented by the xbits
*/
perms.allow |= map_other(dfa_other_allow(dfa, state));
if (VERSION_LE(version, v8))
perms.allow |= AA_MAY_LOCK;
else
perms.allow |= map_xbits(dfa_user_xbits(dfa, state));
/*
* for v5-v9 perm mapping in the policydb, the other set is used
* to extend the general perm set
*/
perms.audit |= map_other(dfa_other_audit(dfa, state));
perms.quiet |= map_other(dfa_other_quiet(dfa, state));
if (VERSION_GT(version, v8))
perms.quiet |= map_xbits(dfa_other_xbits(dfa, state));
return perms;
}
static struct aa_perms *compute_perms(struct aa_dfa *dfa, u32 version)
{
unsigned int state;
unsigned int state_count;
struct aa_perms *table;
AA_BUG(!dfa);
state_count = dfa->tables[YYTD_ID_BASE]->td_lolen;
/* DFAs are restricted from having a state_count of less than 2 */
table = kvcalloc(state_count, sizeof(struct aa_perms), GFP_KERNEL);
if (!table)
return NULL;
/* zero init so skip the trap state (state == 0) */
for (state = 1; state < state_count; state++)
table[state] = compute_perms_entry(dfa, state, version);
return table;
}
/**
* remap_dfa_accept - remap old dfa accept table to be an index
* @dfa: dfa to do the remapping on
* @factor: scaling factor for the index conversion.
*
* Used in conjunction with compute_Xperms, it converts old style perms
* that are encoded in the dfa accept tables to the new style where
* there is a permission table and the accept table is an index into
* the permission table.
*/
static void remap_dfa_accept(struct aa_dfa *dfa, unsigned int factor)
{
unsigned int state;
unsigned int state_count = dfa->tables[YYTD_ID_BASE]->td_lolen;
AA_BUG(!dfa);
for (state = 0; state < state_count; state++)
ACCEPT_TABLE(dfa)[state] = state * factor;
kvfree(dfa->tables[YYTD_ID_ACCEPT2]);
dfa->tables[YYTD_ID_ACCEPT2] = NULL;
}
/* TODO: merge different dfa mappings into single map_policy fn */
int aa_compat_map_xmatch(struct aa_policydb *policy)
{
policy->perms = compute_xmatch_perms(policy->dfa);
if (!policy->perms)
return -ENOMEM;
remap_dfa_accept(policy->dfa, 1);
return 0;
}
int aa_compat_map_policy(struct aa_policydb *policy, u32 version)
{
policy->perms = compute_perms(policy->dfa, version);
if (!policy->perms)
return -ENOMEM;
remap_dfa_accept(policy->dfa, 1);
return 0;
}
int aa_compat_map_file(struct aa_policydb *policy)
{
policy->perms = compute_fperms(policy->dfa);
if (!policy->perms)
return -ENOMEM;
remap_dfa_accept(policy->dfa, 2);
return 0;
}

View File

@ -84,15 +84,13 @@ static struct aa_profile *alloc_unconfined(const char *name)
{
struct aa_profile *profile;
profile = aa_alloc_profile(name, NULL, GFP_KERNEL);
profile = aa_alloc_null(NULL, name, GFP_KERNEL);
if (!profile)
return NULL;
profile->label.flags |= FLAG_IX_ON_NAME_ERROR |
FLAG_IMMUTIBLE | FLAG_NS_COUNT | FLAG_UNCONFINED;
profile->mode = APPARMOR_UNCONFINED;
profile->file.dfa = aa_get_dfa(nulldfa);
profile->policy.dfa = aa_get_dfa(nulldfa);
return profile;
}
@ -134,7 +132,7 @@ static struct aa_ns *alloc_ns(const char *prefix, const char *name)
return ns;
fail_unconfined:
kfree_sensitive(ns->base.hname);
aa_policy_destroy(&ns->base);
fail_ns:
kfree_sensitive(ns);
return NULL;

View File

@ -17,26 +17,18 @@
#include <kunit/visibility.h>
#include <linux/ctype.h>
#include <linux/errno.h>
#include <linux/zlib.h>
#include <linux/zstd.h>
#include "include/apparmor.h"
#include "include/audit.h"
#include "include/cred.h"
#include "include/crypto.h"
#include "include/file.h"
#include "include/match.h"
#include "include/path.h"
#include "include/policy.h"
#include "include/policy_unpack.h"
#define K_ABI_MASK 0x3ff
#define FORCE_COMPLAIN_FLAG 0x800
#define VERSION_LT(X, Y) (((X) & K_ABI_MASK) < ((Y) & K_ABI_MASK))
#define VERSION_GT(X, Y) (((X) & K_ABI_MASK) > ((Y) & K_ABI_MASK))
#define v5 5 /* base version */
#define v6 6 /* per entry policydb mediation check */
#define v7 7
#define v8 8 /* full network masking */
#include "include/policy_compat.h"
/* audit callback for unpack fields */
static void audit_cb(struct audit_buffer *ab, void *va)
@ -71,7 +63,7 @@ static int audit_iface(struct aa_profile *new, const char *ns_name,
int error)
{
struct aa_profile *profile = labels_profile(aa_current_raw_label());
DEFINE_AUDIT_DATA(sa, LSM_AUDIT_DATA_NONE, NULL);
DEFINE_AUDIT_DATA(sa, LSM_AUDIT_DATA_NONE, AA_CLASS_NONE, NULL);
if (e)
aad(&sa)->iface.pos = e->pos - e->start;
aad(&sa)->iface.ns = ns_name;
@ -321,22 +313,21 @@ fail:
}
EXPORT_SYMBOL_IF_KUNIT(aa_unpack_u64);
VISIBLE_IF_KUNIT size_t aa_unpack_array(struct aa_ext *e, const char *name)
VISIBLE_IF_KUNIT bool aa_unpack_array(struct aa_ext *e, const char *name, u16 *size)
{
void *pos = e->pos;
if (aa_unpack_nameX(e, AA_ARRAY, name)) {
int size;
if (!aa_inbounds(e, sizeof(u16)))
goto fail;
size = (int)le16_to_cpu(get_unaligned((__le16 *) e->pos));
*size = le16_to_cpu(get_unaligned((__le16 *) e->pos));
e->pos += sizeof(u16);
return size;
return true;
}
fail:
e->pos = pos;
return 0;
return false;
}
EXPORT_SYMBOL_IF_KUNIT(aa_unpack_array);
@ -411,10 +402,11 @@ EXPORT_SYMBOL_IF_KUNIT(aa_unpack_strdup);
/**
* unpack_dfa - unpack a file rule dfa
* @e: serialized data extent information (NOT NULL)
* @flags: dfa flags to check
*
* returns dfa or ERR_PTR or NULL if no dfa
*/
static struct aa_dfa *unpack_dfa(struct aa_ext *e)
static struct aa_dfa *unpack_dfa(struct aa_ext *e, int flags)
{
char *blob = NULL;
size_t size;
@ -430,8 +422,6 @@ static struct aa_dfa *unpack_dfa(struct aa_ext *e)
size_t sz = blob - (char *) e->start -
((e->pos - e->start) & 7);
size_t pad = ALIGN(sz, 8) - sz;
int flags = TO_ACCEPT1_FLAG(YYTD_DATA32) |
TO_ACCEPT2_FLAG(YYTD_DATA32);
if (aa_g_paranoid_load)
flags |= DFA_FLAG_VERIFY_STATES;
dfa = aa_dfa_unpack(blob + pad, size - pad, flags);
@ -447,28 +437,32 @@ static struct aa_dfa *unpack_dfa(struct aa_ext *e)
/**
* unpack_trans_table - unpack a profile transition table
* @e: serialized data extent information (NOT NULL)
* @profile: profile to add the accept table to (NOT NULL)
* @table: str table to unpack to (NOT NULL)
*
* Returns: true if table successfully unpacked
* Returns: true if table successfully unpacked or not present
*/
static bool unpack_trans_table(struct aa_ext *e, struct aa_profile *profile)
static bool unpack_trans_table(struct aa_ext *e, struct aa_str_table *strs)
{
void *saved_pos = e->pos;
char **table = NULL;
/* exec table is optional */
if (aa_unpack_nameX(e, AA_STRUCT, "xtable")) {
int i, size;
u16 size;
int i;
size = aa_unpack_array(e, NULL);
/* currently 4 exec bits and entries 0-3 are reserved iupcx */
if (size > 16 - 4)
if (!aa_unpack_array(e, NULL, &size))
/*
* Note: index into trans table array is a max
* of 2^24, but unpack array can only unpack
* an array of 2^16 in size atm so no need
* for size check here
*/
goto fail;
profile->file.trans.table = kcalloc(size, sizeof(char *),
GFP_KERNEL);
if (!profile->file.trans.table)
table = kcalloc(size, sizeof(char *), GFP_KERNEL);
if (!table)
goto fail;
profile->file.trans.size = size;
for (i = 0; i < size; i++) {
char *str;
int c, j, pos, size2 = aa_unpack_strdup(e, &str, NULL);
@ -477,7 +471,7 @@ static bool unpack_trans_table(struct aa_ext *e, struct aa_profile *profile)
*/
if (!size2)
goto fail;
profile->file.trans.table[i] = str;
table[i] = str;
/* verify that name doesn't start with space */
if (isspace(*str))
goto fail;
@ -511,11 +505,14 @@ static bool unpack_trans_table(struct aa_ext *e, struct aa_profile *profile)
goto fail;
if (!aa_unpack_nameX(e, AA_STRUCTEND, NULL))
goto fail;
strs->table = table;
strs->size = size;
}
return true;
fail:
aa_free_domain_entries(&profile->file.trans);
kfree_sensitive(table);
e->pos = saved_pos;
return false;
}
@ -525,15 +522,17 @@ static bool unpack_xattrs(struct aa_ext *e, struct aa_profile *profile)
void *pos = e->pos;
if (aa_unpack_nameX(e, AA_STRUCT, "xattrs")) {
int i, size;
u16 size;
int i;
size = aa_unpack_array(e, NULL);
profile->xattr_count = size;
profile->xattrs = kcalloc(size, sizeof(char *), GFP_KERNEL);
if (!profile->xattrs)
if (!aa_unpack_array(e, NULL, &size))
goto fail;
profile->attach.xattr_count = size;
profile->attach.xattrs = kcalloc(size, sizeof(char *), GFP_KERNEL);
if (!profile->attach.xattrs)
goto fail;
for (i = 0; i < size; i++) {
if (!aa_unpack_strdup(e, &profile->xattrs[i], NULL))
if (!aa_unpack_strdup(e, &profile->attach.xattrs[i], NULL))
goto fail;
}
if (!aa_unpack_nameX(e, AA_ARRAYEND, NULL))
@ -549,27 +548,29 @@ fail:
return false;
}
static bool unpack_secmark(struct aa_ext *e, struct aa_profile *profile)
static bool unpack_secmark(struct aa_ext *e, struct aa_ruleset *rules)
{
void *pos = e->pos;
int i, size;
u16 size;
int i;
if (aa_unpack_nameX(e, AA_STRUCT, "secmark")) {
size = aa_unpack_array(e, NULL);
profile->secmark = kcalloc(size, sizeof(struct aa_secmark),
GFP_KERNEL);
if (!profile->secmark)
if (!aa_unpack_array(e, NULL, &size))
goto fail;
profile->secmark_count = size;
rules->secmark = kcalloc(size, sizeof(struct aa_secmark),
GFP_KERNEL);
if (!rules->secmark)
goto fail;
rules->secmark_count = size;
for (i = 0; i < size; i++) {
if (!unpack_u8(e, &profile->secmark[i].audit, NULL))
if (!unpack_u8(e, &rules->secmark[i].audit, NULL))
goto fail;
if (!unpack_u8(e, &profile->secmark[i].deny, NULL))
if (!unpack_u8(e, &rules->secmark[i].deny, NULL))
goto fail;
if (!aa_unpack_strdup(e, &profile->secmark[i].label, NULL))
if (!aa_unpack_strdup(e, &rules->secmark[i].label, NULL))
goto fail;
}
if (!aa_unpack_nameX(e, AA_ARRAYEND, NULL))
@ -581,39 +582,40 @@ static bool unpack_secmark(struct aa_ext *e, struct aa_profile *profile)
return true;
fail:
if (profile->secmark) {
if (rules->secmark) {
for (i = 0; i < size; i++)
kfree(profile->secmark[i].label);
kfree(profile->secmark);
profile->secmark_count = 0;
profile->secmark = NULL;
kfree(rules->secmark[i].label);
kfree(rules->secmark);
rules->secmark_count = 0;
rules->secmark = NULL;
}
e->pos = pos;
return false;
}
static bool unpack_rlimits(struct aa_ext *e, struct aa_profile *profile)
static bool unpack_rlimits(struct aa_ext *e, struct aa_ruleset *rules)
{
void *pos = e->pos;
/* rlimits are optional */
if (aa_unpack_nameX(e, AA_STRUCT, "rlimits")) {
int i, size;
u16 size;
int i;
u32 tmp = 0;
if (!aa_unpack_u32(e, &tmp, NULL))
goto fail;
profile->rlimits.mask = tmp;
rules->rlimits.mask = tmp;
size = aa_unpack_array(e, NULL);
if (size > RLIM_NLIMITS)
if (!aa_unpack_array(e, NULL, &size) ||
size > RLIM_NLIMITS)
goto fail;
for (i = 0; i < size; i++) {
u64 tmp2 = 0;
int a = aa_map_resource(i);
if (!aa_unpack_u64(e, &tmp2, NULL))
goto fail;
profile->rlimits.limits[a].rlim_max = tmp2;
rules->rlimits.limits[a].rlim_max = tmp2;
}
if (!aa_unpack_nameX(e, AA_ARRAYEND, NULL))
goto fail;
@ -627,6 +629,140 @@ fail:
return false;
}
static bool unpack_perm(struct aa_ext *e, u32 version, struct aa_perms *perm)
{
if (version != 1)
return false;
return aa_unpack_u32(e, &perm->allow, NULL) &&
aa_unpack_u32(e, &perm->allow, NULL) &&
aa_unpack_u32(e, &perm->deny, NULL) &&
aa_unpack_u32(e, &perm->subtree, NULL) &&
aa_unpack_u32(e, &perm->cond, NULL) &&
aa_unpack_u32(e, &perm->kill, NULL) &&
aa_unpack_u32(e, &perm->complain, NULL) &&
aa_unpack_u32(e, &perm->prompt, NULL) &&
aa_unpack_u32(e, &perm->audit, NULL) &&
aa_unpack_u32(e, &perm->quiet, NULL) &&
aa_unpack_u32(e, &perm->hide, NULL) &&
aa_unpack_u32(e, &perm->xindex, NULL) &&
aa_unpack_u32(e, &perm->tag, NULL) &&
aa_unpack_u32(e, &perm->label, NULL);
}
static ssize_t unpack_perms_table(struct aa_ext *e, struct aa_perms **perms)
{
void *pos = e->pos;
u16 size = 0;
AA_BUG(!perms);
/*
* policy perms are optional, in which case perms are embedded
* in the dfa accept table
*/
if (aa_unpack_nameX(e, AA_STRUCT, "perms")) {
int i;
u32 version;
if (!aa_unpack_u32(e, &version, "version"))
goto fail_reset;
if (!aa_unpack_array(e, NULL, &size))
goto fail_reset;
*perms = kcalloc(size, sizeof(struct aa_perms), GFP_KERNEL);
if (!*perms)
goto fail_reset;
for (i = 0; i < size; i++) {
if (!unpack_perm(e, version, &(*perms)[i]))
goto fail;
}
if (!aa_unpack_nameX(e, AA_ARRAYEND, NULL))
goto fail;
if (!aa_unpack_nameX(e, AA_STRUCTEND, NULL))
goto fail;
} else
*perms = NULL;
return size;
fail:
kfree(*perms);
fail_reset:
e->pos = pos;
return -EPROTO;
}
static int unpack_pdb(struct aa_ext *e, struct aa_policydb *policy,
bool required_dfa, bool required_trans,
const char **info)
{
void *pos = e->pos;
int i, flags, error = -EPROTO;
ssize_t size;
size = unpack_perms_table(e, &policy->perms);
if (size < 0) {
error = size;
policy->perms = NULL;
*info = "failed to unpack - perms";
goto fail;
}
policy->size = size;
if (policy->perms) {
/* perms table present accept is index */
flags = TO_ACCEPT1_FLAG(YYTD_DATA32);
} else {
/* packed perms in accept1 and accept2 */
flags = TO_ACCEPT1_FLAG(YYTD_DATA32) |
TO_ACCEPT2_FLAG(YYTD_DATA32);
}
policy->dfa = unpack_dfa(e, flags);
if (IS_ERR(policy->dfa)) {
error = PTR_ERR(policy->dfa);
policy->dfa = NULL;
*info = "failed to unpack - dfa";
goto fail;
} else if (!policy->dfa) {
if (required_dfa) {
*info = "missing required dfa";
goto fail;
}
goto out;
}
/*
* only unpack the following if a dfa is present
*
* sadly start was given different names for file and policydb
* but since it is optional we can try both
*/
if (!aa_unpack_u32(e, &policy->start[0], "start"))
/* default start state */
policy->start[0] = DFA_START;
if (!aa_unpack_u32(e, &policy->start[AA_CLASS_FILE], "dfa_start")) {
/* default start state for xmatch and file dfa */
policy->start[AA_CLASS_FILE] = DFA_START;
} /* setup class index */
for (i = AA_CLASS_FILE + 1; i <= AA_CLASS_LAST; i++) {
policy->start[i] = aa_dfa_next(policy->dfa, policy->start[0],
i);
}
if (!unpack_trans_table(e, &policy->trans) && required_trans) {
*info = "failed to unpack profile transition table";
goto fail;
}
/* TODO: move compat mapping here, requires dfa merging first */
/* TODO: move verify here, it has to be done after compat mappings */
out:
return 0;
fail:
e->pos = pos;
return error;
}
static u32 strhash(const void *data, u32 len, u32 seed)
{
const char * const *key = data;
@ -651,6 +787,7 @@ static int datacmp(struct rhashtable_compare_arg *arg, const void *obj)
*/
static struct aa_profile *unpack_profile(struct aa_ext *e, char **ns_name)
{
struct aa_ruleset *rules;
struct aa_profile *profile = NULL;
const char *tmpname, *tmpns = NULL, *name = NULL;
const char *info = "failed to unpack profile";
@ -658,7 +795,7 @@ static struct aa_profile *unpack_profile(struct aa_ext *e, char **ns_name)
struct rhashtable_params params = { 0 };
char *key = NULL;
struct aa_data *data;
int i, error = -EPROTO;
int error = -EPROTO;
kernel_cap_t tmpcap;
u32 tmp;
@ -677,36 +814,46 @@ static struct aa_profile *unpack_profile(struct aa_ext *e, char **ns_name)
*ns_name = kstrndup(tmpns, ns_len, GFP_KERNEL);
if (!*ns_name) {
info = "out of memory";
error = -ENOMEM;
goto fail;
}
name = tmpname;
}
profile = aa_alloc_profile(name, NULL, GFP_KERNEL);
if (!profile)
return ERR_PTR(-ENOMEM);
if (!profile) {
info = "out of memory";
error = -ENOMEM;
goto fail;
}
rules = list_first_entry(&profile->rules, typeof(*rules), list);
/* profile renaming is optional */
(void) aa_unpack_str(e, &profile->rename, "rename");
/* attachment string is optional */
(void) aa_unpack_str(e, &profile->attach, "attach");
(void) aa_unpack_str(e, &profile->attach.xmatch_str, "attach");
/* xmatch is optional and may be NULL */
profile->xmatch = unpack_dfa(e);
if (IS_ERR(profile->xmatch)) {
error = PTR_ERR(profile->xmatch);
profile->xmatch = NULL;
error = unpack_pdb(e, &profile->attach.xmatch, false, false, &info);
if (error) {
info = "bad xmatch";
goto fail;
}
/* xmatch_len is not optional if xmatch is set */
if (profile->xmatch) {
/* neither xmatch_len not xmatch_perms are optional if xmatch is set */
if (profile->attach.xmatch.dfa) {
if (!aa_unpack_u32(e, &tmp, NULL)) {
info = "missing xmatch len";
goto fail;
}
profile->xmatch_len = tmp;
profile->attach.xmatch_len = tmp;
profile->attach.xmatch.start[AA_CLASS_XMATCH] = DFA_START;
error = aa_compat_map_xmatch(&profile->attach.xmatch);
if (error) {
info = "failed to convert xmatch permission table";
goto fail;
}
}
/* disconnected attachment string is optional */
@ -737,6 +884,8 @@ static struct aa_profile *unpack_profile(struct aa_ext *e, char **ns_name)
} else if (tmp == PACKED_MODE_UNCONFINED) {
profile->mode = APPARMOR_UNCONFINED;
profile->label.flags |= FLAG_UNCONFINED;
} else if (tmp == PACKED_MODE_USER) {
profile->mode = APPARMOR_USER;
} else {
goto fail;
}
@ -757,11 +906,11 @@ static struct aa_profile *unpack_profile(struct aa_ext *e, char **ns_name)
profile->path_flags = PATH_MEDIATE_DELETED;
info = "failed to unpack profile capabilities";
if (!aa_unpack_u32(e, &(profile->caps.allow.cap[0]), NULL))
if (!aa_unpack_u32(e, &(rules->caps.allow.cap[0]), NULL))
goto fail;
if (!aa_unpack_u32(e, &(profile->caps.audit.cap[0]), NULL))
if (!aa_unpack_u32(e, &(rules->caps.audit.cap[0]), NULL))
goto fail;
if (!aa_unpack_u32(e, &(profile->caps.quiet.cap[0]), NULL))
if (!aa_unpack_u32(e, &(rules->caps.quiet.cap[0]), NULL))
goto fail;
if (!aa_unpack_u32(e, &tmpcap.cap[0], NULL))
goto fail;
@ -769,11 +918,11 @@ static struct aa_profile *unpack_profile(struct aa_ext *e, char **ns_name)
info = "failed to unpack upper profile capabilities";
if (aa_unpack_nameX(e, AA_STRUCT, "caps64")) {
/* optional upper half of 64 bit caps */
if (!aa_unpack_u32(e, &(profile->caps.allow.cap[1]), NULL))
if (!aa_unpack_u32(e, &(rules->caps.allow.cap[1]), NULL))
goto fail;
if (!aa_unpack_u32(e, &(profile->caps.audit.cap[1]), NULL))
if (!aa_unpack_u32(e, &(rules->caps.audit.cap[1]), NULL))
goto fail;
if (!aa_unpack_u32(e, &(profile->caps.quiet.cap[1]), NULL))
if (!aa_unpack_u32(e, &(rules->caps.quiet.cap[1]), NULL))
goto fail;
if (!aa_unpack_u32(e, &(tmpcap.cap[1]), NULL))
goto fail;
@ -784,9 +933,9 @@ static struct aa_profile *unpack_profile(struct aa_ext *e, char **ns_name)
info = "failed to unpack extended profile capabilities";
if (aa_unpack_nameX(e, AA_STRUCT, "capsx")) {
/* optional extended caps mediation mask */
if (!aa_unpack_u32(e, &(profile->caps.extended.cap[0]), NULL))
if (!aa_unpack_u32(e, &(rules->caps.extended.cap[0]), NULL))
goto fail;
if (!aa_unpack_u32(e, &(profile->caps.extended.cap[1]), NULL))
if (!aa_unpack_u32(e, &(rules->caps.extended.cap[1]), NULL))
goto fail;
if (!aa_unpack_nameX(e, AA_STRUCTEND, NULL))
goto fail;
@ -797,12 +946,12 @@ static struct aa_profile *unpack_profile(struct aa_ext *e, char **ns_name)
goto fail;
}
if (!unpack_rlimits(e, profile)) {
if (!unpack_rlimits(e, rules)) {
info = "failed to unpack profile rlimits";
goto fail;
}
if (!unpack_secmark(e, profile)) {
if (!unpack_secmark(e, rules)) {
info = "failed to unpack profile secmark rules";
goto fail;
}
@ -810,59 +959,52 @@ static struct aa_profile *unpack_profile(struct aa_ext *e, char **ns_name)
if (aa_unpack_nameX(e, AA_STRUCT, "policydb")) {
/* generic policy dfa - optional and may be NULL */
info = "failed to unpack policydb";
profile->policy.dfa = unpack_dfa(e);
if (IS_ERR(profile->policy.dfa)) {
error = PTR_ERR(profile->policy.dfa);
profile->policy.dfa = NULL;
error = unpack_pdb(e, &rules->policy, true, false,
&info);
if (error)
goto fail;
} else if (!profile->policy.dfa) {
error = -EPROTO;
goto fail;
}
if (!aa_unpack_u32(e, &profile->policy.start[0], "start"))
/* default start state */
profile->policy.start[0] = DFA_START;
/* setup class index */
for (i = AA_CLASS_FILE; i <= AA_CLASS_LAST; i++) {
profile->policy.start[i] =
aa_dfa_next(profile->policy.dfa,
profile->policy.start[0],
i);
}
/* Fixup: drop when we get rid of start array */
if (aa_dfa_next(rules->policy.dfa, rules->policy.start[0],
AA_CLASS_FILE))
rules->policy.start[AA_CLASS_FILE] =
aa_dfa_next(rules->policy.dfa,
rules->policy.start[0],
AA_CLASS_FILE);
if (!aa_unpack_nameX(e, AA_STRUCTEND, NULL))
goto fail;
error = aa_compat_map_policy(&rules->policy, e->version);
if (error) {
info = "failed to remap policydb permission table";
goto fail;
}
} else
profile->policy.dfa = aa_get_dfa(nulldfa);
rules->policy.dfa = aa_get_dfa(nulldfa);
/* get file rules */
profile->file.dfa = unpack_dfa(e);
if (IS_ERR(profile->file.dfa)) {
error = PTR_ERR(profile->file.dfa);
profile->file.dfa = NULL;
info = "failed to unpack profile file rules";
error = unpack_pdb(e, &rules->file, false, true, &info);
if (error) {
goto fail;
} else if (profile->file.dfa) {
if (!aa_unpack_u32(e, &profile->file.start, "dfa_start"))
/* default start state */
profile->file.start = DFA_START;
} else if (profile->policy.dfa &&
profile->policy.start[AA_CLASS_FILE]) {
profile->file.dfa = aa_get_dfa(profile->policy.dfa);
profile->file.start = profile->policy.start[AA_CLASS_FILE];
} else if (rules->file.dfa) {
error = aa_compat_map_file(&rules->file);
if (error) {
info = "failed to remap file permission table";
goto fail;
}
} else if (rules->policy.dfa &&
rules->policy.start[AA_CLASS_FILE]) {
rules->file.dfa = aa_get_dfa(rules->policy.dfa);
rules->file.start[AA_CLASS_FILE] = rules->policy.start[AA_CLASS_FILE];
} else
profile->file.dfa = aa_get_dfa(nulldfa);
if (!unpack_trans_table(e, profile)) {
info = "failed to unpack profile transition table";
goto fail;
}
rules->file.dfa = aa_get_dfa(nulldfa);
error = -EPROTO;
if (aa_unpack_nameX(e, AA_STRUCT, "data")) {
info = "out of memory";
profile->data = kzalloc(sizeof(*profile->data), GFP_KERNEL);
if (!profile->data)
if (!profile->data) {
error = -ENOMEM;
goto fail;
}
params.nelem_hint = 3;
params.key_len = sizeof(void *);
params.key_offset = offsetof(struct aa_data, key);
@ -879,6 +1021,7 @@ static struct aa_profile *unpack_profile(struct aa_ext *e, char **ns_name)
data = kzalloc(sizeof(*data), GFP_KERNEL);
if (!data) {
kfree_sensitive(key);
error = -ENOMEM;
goto fail;
}
@ -888,6 +1031,7 @@ static struct aa_profile *unpack_profile(struct aa_ext *e, char **ns_name)
if (data->size && !data->data) {
kfree_sensitive(data->key);
kfree_sensitive(data);
error = -ENOMEM;
goto fail;
}
@ -909,6 +1053,13 @@ static struct aa_profile *unpack_profile(struct aa_ext *e, char **ns_name)
return profile;
fail:
if (error == 0)
/* default error covers most cases */
error = -EPROTO;
if (*ns_name) {
kfree(*ns_name);
*ns_name = NULL;
}
if (profile)
name = NULL;
else if (!name)
@ -946,7 +1097,7 @@ static int verify_header(struct aa_ext *e, int required, const char **ns)
* if not specified use previous version
* Mask off everything that is not kernel abi version
*/
if (VERSION_LT(e->version, v5) || VERSION_GT(e->version, v7)) {
if (VERSION_LT(e->version, v5) || VERSION_GT(e->version, v9)) {
audit_iface(NULL, NULL, NULL, "unsupported interface version",
e, error);
return error;
@ -987,27 +1138,91 @@ static bool verify_dfa_xindex(struct aa_dfa *dfa, int table_size)
{
int i;
for (i = 0; i < dfa->tables[YYTD_ID_ACCEPT]->td_lolen; i++) {
if (!verify_xindex(dfa_user_xindex(dfa, i), table_size))
return false;
if (!verify_xindex(dfa_other_xindex(dfa, i), table_size))
if (!verify_xindex(ACCEPT_TABLE(dfa)[i], table_size))
return false;
}
return true;
}
static bool verify_perm(struct aa_perms *perm)
{
/* TODO: allow option to just force the perms into a valid state */
if (perm->allow & perm->deny)
return false;
if (perm->subtree & ~perm->allow)
return false;
if (perm->cond & (perm->allow | perm->deny))
return false;
if (perm->kill & perm->allow)
return false;
if (perm->complain & (perm->allow | perm->deny))
return false;
if (perm->prompt & (perm->allow | perm->deny))
return false;
if (perm->complain & perm->prompt)
return false;
if (perm->hide & perm->allow)
return false;
return true;
}
static bool verify_perms(struct aa_policydb *pdb)
{
int i;
for (i = 0; i < pdb->size; i++) {
if (!verify_perm(&pdb->perms[i]))
return false;
/* verify indexes into str table */
if (pdb->perms[i].xindex >= pdb->trans.size)
return false;
if (pdb->perms[i].tag >= pdb->trans.size)
return false;
if (pdb->perms[i].label >= pdb->trans.size)
return false;
}
return true;
}
/**
* verify_profile - Do post unpack analysis to verify profile consistency
* @profile: profile to verify (NOT NULL)
*
* Returns: 0 if passes verification else error
*
* This verification is post any unpack mapping or changes
*/
static int verify_profile(struct aa_profile *profile)
{
if (profile->file.dfa &&
!verify_dfa_xindex(profile->file.dfa,
profile->file.trans.size)) {
audit_iface(profile, NULL, NULL, "Invalid named transition",
NULL, -EPROTO);
struct aa_ruleset *rules = list_first_entry(&profile->rules,
typeof(*rules), list);
if (!rules)
return 0;
if ((rules->file.dfa && !verify_dfa_xindex(rules->file.dfa,
rules->file.trans.size)) ||
(rules->policy.dfa &&
!verify_dfa_xindex(rules->policy.dfa, rules->policy.trans.size))) {
audit_iface(profile, NULL, NULL,
"Unpack: Invalid named transition", NULL, -EPROTO);
return -EPROTO;
}
if (!verify_perms(&rules->file)) {
audit_iface(profile, NULL, NULL,
"Unpack: Invalid perm index", NULL, -EPROTO);
return -EPROTO;
}
if (!verify_perms(&rules->policy)) {
audit_iface(profile, NULL, NULL,
"Unpack: Invalid perm index", NULL, -EPROTO);
return -EPROTO;
}
if (!verify_perms(&profile->attach.xmatch)) {
audit_iface(profile, NULL, NULL,
"Unpack: Invalid perm index", NULL, -EPROTO);
return -EPROTO;
}
@ -1033,81 +1248,73 @@ struct aa_load_ent *aa_load_ent_alloc(void)
return ent;
}
static int deflate_compress(const char *src, size_t slen, char **dst,
size_t *dlen)
static int compress_zstd(const char *src, size_t slen, char **dst, size_t *dlen)
{
#ifdef CONFIG_SECURITY_APPARMOR_EXPORT_BINARY
int error;
struct z_stream_s strm;
void *stgbuf, *dstbuf;
size_t stglen = deflateBound(slen);
const zstd_parameters params =
zstd_get_params(aa_g_rawdata_compression_level, slen);
const size_t wksp_len = zstd_cctx_workspace_bound(&params.cParams);
void *wksp = NULL;
zstd_cctx *ctx = NULL;
size_t out_len = zstd_compress_bound(slen);
void *out = NULL;
int ret = 0;
memset(&strm, 0, sizeof(strm));
if (stglen < slen)
return -EFBIG;
strm.workspace = kvzalloc(zlib_deflate_workspacesize(MAX_WBITS,
MAX_MEM_LEVEL),
GFP_KERNEL);
if (!strm.workspace)
return -ENOMEM;
error = zlib_deflateInit(&strm, aa_g_rawdata_compression_level);
if (error != Z_OK) {
error = -ENOMEM;
goto fail_deflate_init;
out = kvzalloc(out_len, GFP_KERNEL);
if (!out) {
ret = -ENOMEM;
goto cleanup;
}
stgbuf = kvzalloc(stglen, GFP_KERNEL);
if (!stgbuf) {
error = -ENOMEM;
goto fail_stg_alloc;
wksp = kvzalloc(wksp_len, GFP_KERNEL);
if (!wksp) {
ret = -ENOMEM;
goto cleanup;
}
strm.next_in = src;
strm.avail_in = slen;
strm.next_out = stgbuf;
strm.avail_out = stglen;
error = zlib_deflate(&strm, Z_FINISH);
if (error != Z_STREAM_END) {
error = -EINVAL;
goto fail_deflate;
ctx = zstd_init_cctx(wksp, wksp_len);
if (!ctx) {
ret = -EINVAL;
goto cleanup;
}
error = 0;
if (is_vmalloc_addr(stgbuf)) {
dstbuf = kvzalloc(strm.total_out, GFP_KERNEL);
if (dstbuf) {
memcpy(dstbuf, stgbuf, strm.total_out);
kvfree(stgbuf);
out_len = zstd_compress_cctx(ctx, out, out_len, src, slen, &params);
if (zstd_is_error(out_len) || out_len >= slen) {
ret = -EINVAL;
goto cleanup;
}
if (is_vmalloc_addr(out)) {
*dst = kvzalloc(out_len, GFP_KERNEL);
if (*dst) {
memcpy(*dst, out, out_len);
kvfree(out);
out = NULL;
}
} else
} else {
/*
* If the staging buffer was kmalloc'd, then using krealloc is
* probably going to be faster. The destination buffer will
* always be smaller, so it's just shrunk, avoiding a memcpy
*/
dstbuf = krealloc(stgbuf, strm.total_out, GFP_KERNEL);
if (!dstbuf) {
error = -ENOMEM;
goto fail_deflate;
*dst = krealloc(out, out_len, GFP_KERNEL);
}
*dst = dstbuf;
*dlen = strm.total_out;
if (!*dst) {
ret = -ENOMEM;
goto cleanup;
}
fail_stg_alloc:
zlib_deflateEnd(&strm);
fail_deflate_init:
kvfree(strm.workspace);
return error;
*dlen = out_len;
fail_deflate:
kvfree(stgbuf);
goto fail_stg_alloc;
cleanup:
if (ret) {
kvfree(out);
*dst = NULL;
}
kvfree(wksp);
return ret;
#else
*dlen = slen;
return 0;
@ -1116,7 +1323,6 @@ fail_deflate:
static int compress_loaddata(struct aa_loaddata *data)
{
AA_BUG(data->compressed_size > 0);
/*
@ -1125,11 +1331,12 @@ static int compress_loaddata(struct aa_loaddata *data)
*/
if (aa_g_rawdata_compression_level != 0) {
void *udata = data->data;
int error = deflate_compress(udata, data->size, &data->data,
&data->compressed_size);
if (error)
int error = compress_zstd(udata, data->size, &data->data,
&data->compressed_size);
if (error) {
data->compressed_size = data->size;
return error;
}
if (udata != data->data)
kvfree(udata);
} else
@ -1155,6 +1362,7 @@ int aa_unpack(struct aa_loaddata *udata, struct list_head *lh,
{
struct aa_load_ent *tmp, *ent;
struct aa_profile *profile = NULL;
char *ns_name = NULL;
int error;
struct aa_ext e = {
.start = udata->data,
@ -1164,7 +1372,6 @@ int aa_unpack(struct aa_loaddata *udata, struct list_head *lh,
*ns = NULL;
while (e.pos < e.end) {
char *ns_name = NULL;
void *start;
error = verify_header(&e, e.pos == e.start, ns);
if (error)
@ -1195,6 +1402,7 @@ int aa_unpack(struct aa_loaddata *udata, struct list_head *lh,
ent->new = profile;
ent->ns_name = ns_name;
ns_name = NULL;
list_add_tail(&ent->list, lh);
}
udata->abi = e.version & K_ABI_MASK;
@ -1215,6 +1423,7 @@ int aa_unpack(struct aa_loaddata *udata, struct list_head *lh,
return 0;
fail_profile:
kfree(ns_name);
aa_put_profile(profile);
fail:

View File

@ -143,12 +143,11 @@ static void policy_unpack_test_inbounds_when_out_of_bounds(struct kunit *test)
static void policy_unpack_test_unpack_array_with_null_name(struct kunit *test)
{
struct policy_unpack_fixture *puf = test->priv;
u16 array_size;
u16 array_size = 0;
puf->e->pos += TEST_ARRAY_BUF_OFFSET;
array_size = aa_unpack_array(puf->e, NULL);
KUNIT_EXPECT_TRUE(test, aa_unpack_array(puf->e, NULL, &array_size));
KUNIT_EXPECT_EQ(test, array_size, (u16)TEST_ARRAY_SIZE);
KUNIT_EXPECT_PTR_EQ(test, puf->e->pos,
puf->e->start + TEST_ARRAY_BUF_OFFSET + sizeof(u16) + 1);
@ -158,12 +157,11 @@ static void policy_unpack_test_unpack_array_with_name(struct kunit *test)
{
struct policy_unpack_fixture *puf = test->priv;
const char name[] = TEST_ARRAY_NAME;
u16 array_size;
u16 array_size = 0;
puf->e->pos += TEST_NAMED_ARRAY_BUF_OFFSET;
array_size = aa_unpack_array(puf->e, name);
KUNIT_EXPECT_TRUE(test, aa_unpack_array(puf->e, name, &array_size));
KUNIT_EXPECT_EQ(test, array_size, (u16)TEST_ARRAY_SIZE);
KUNIT_EXPECT_PTR_EQ(test, puf->e->pos,
puf->e->start + TEST_ARRAY_BUF_OFFSET + sizeof(u16) + 1);
@ -178,9 +176,7 @@ static void policy_unpack_test_unpack_array_out_of_bounds(struct kunit *test)
puf->e->pos += TEST_NAMED_ARRAY_BUF_OFFSET;
puf->e->end = puf->e->start + TEST_ARRAY_BUF_OFFSET + sizeof(u16);
array_size = aa_unpack_array(puf->e, name);
KUNIT_EXPECT_EQ(test, array_size, 0);
KUNIT_EXPECT_FALSE(test, aa_unpack_array(puf->e, name, &array_size));
KUNIT_EXPECT_PTR_EQ(test, puf->e->pos,
puf->e->start + TEST_NAMED_ARRAY_BUF_OFFSET);
}

View File

@ -17,14 +17,13 @@
/**
* aa_getprocattr - Return the profile information for @profile
* @profile: the profile to print profile info about (NOT NULL)
* @string: Returns - string containing the profile info (NOT NULL)
* aa_getprocattr - Return the label information for @label
* @label: the label to print label info about (NOT NULL)
* @string: Returns - string containing the label info (NOT NULL)
*
* Requires: profile != NULL
* Requires: label != NULL && string != NULL
*
* Creates a string containing the namespace_name://profile_name for
* @profile.
* Creates a string containing the label information for @label.
*
* Returns: size of string placed in @string else error code on failure
*/

View File

@ -45,6 +45,8 @@ static void audit_cb(struct audit_buffer *ab, void *va)
* @profile: profile being enforced (NOT NULL)
* @resource: rlimit being auditing
* @value: value being set
* @peer: aa_albel of the task being set
* @info: info being auditing
* @error: error value
*
* Returns: 0 or sa->error else other error code on failure
@ -53,7 +55,8 @@ static int audit_resource(struct aa_profile *profile, unsigned int resource,
unsigned long value, struct aa_label *peer,
const char *info, int error)
{
DEFINE_AUDIT_DATA(sa, LSM_AUDIT_DATA_NONE, OP_SETRLIMIT);
DEFINE_AUDIT_DATA(sa, LSM_AUDIT_DATA_NONE, AA_CLASS_RLIMITS,
OP_SETRLIMIT);
aad(&sa)->rlim.rlim = resource;
aad(&sa)->rlim.max = value;
@ -65,7 +68,7 @@ static int audit_resource(struct aa_profile *profile, unsigned int resource,
}
/**
* aa_map_resouce - map compiled policy resource to internal #
* aa_map_resource - map compiled policy resource to internal #
* @resource: flattened policy resource number
*
* Returns: resource # for the current architecture.
@ -81,10 +84,12 @@ int aa_map_resource(int resource)
static int profile_setrlimit(struct aa_profile *profile, unsigned int resource,
struct rlimit *new_rlim)
{
struct aa_ruleset *rules = list_first_entry(&profile->rules,
typeof(*rules), list);
int e = 0;
if (profile->rlimits.mask & (1 << resource) && new_rlim->rlim_max >
profile->rlimits.limits[resource].rlim_max)
if (rules->rlimits.mask & (1 << resource) && new_rlim->rlim_max >
rules->rlimits.limits[resource].rlim_max)
e = -EACCES;
return audit_resource(profile, resource, new_rlim->rlim_max, NULL, NULL,
e);
@ -152,12 +157,15 @@ void __aa_transition_rlimits(struct aa_label *old_l, struct aa_label *new_l)
* to the lesser of the tasks hard limit and the init tasks soft limit
*/
label_for_each_confined(i, old_l, old) {
if (old->rlimits.mask) {
struct aa_ruleset *rules = list_first_entry(&old->rules,
typeof(*rules),
list);
if (rules->rlimits.mask) {
int j;
for (j = 0, mask = 1; j < RLIM_NLIMITS; j++,
mask <<= 1) {
if (old->rlimits.mask & mask) {
if (rules->rlimits.mask & mask) {
rlim = current->signal->rlim + j;
initrlim = init_task.signal->rlim + j;
rlim->rlim_cur = min(rlim->rlim_max,
@ -169,17 +177,20 @@ void __aa_transition_rlimits(struct aa_label *old_l, struct aa_label *new_l)
/* set any new hard limits as dictated by the new profile */
label_for_each_confined(i, new_l, new) {
struct aa_ruleset *rules = list_first_entry(&new->rules,
typeof(*rules),
list);
int j;
if (!new->rlimits.mask)
if (!rules->rlimits.mask)
continue;
for (j = 0, mask = 1; j < RLIM_NLIMITS; j++, mask <<= 1) {
if (!(new->rlimits.mask & mask))
if (!(rules->rlimits.mask & mask))
continue;
rlim = current->signal->rlim + j;
rlim->rlim_max = min(rlim->rlim_max,
new->rlimits.limits[j].rlim_max);
rules->rlimits.limits[j].rlim_max);
/* soft limit should not exceed hard limit */
rlim->rlim_cur = min(rlim->rlim_cur, rlim->rlim_max);
}

View File

@ -31,7 +31,7 @@ struct aa_label *aa_get_task_label(struct task_struct *task)
struct aa_label *p;
rcu_read_lock();
p = aa_get_newest_label(__aa_task_raw_label(task));
p = aa_get_newest_cred_label(__task_cred(task));
rcu_read_unlock();
return p;
@ -223,16 +223,18 @@ static void audit_ptrace_cb(struct audit_buffer *ab, void *va)
FLAGS_NONE, GFP_ATOMIC);
}
/* assumes check for PROFILE_MEDIATES is already done */
/* assumes check for RULE_MEDIATES is already done */
/* TODO: conditionals */
static int profile_ptrace_perm(struct aa_profile *profile,
struct aa_label *peer, u32 request,
struct common_audit_data *sa)
{
struct aa_ruleset *rules = list_first_entry(&profile->rules,
typeof(*rules), list);
struct aa_perms perms = { };
aad(sa)->peer = peer;
aa_profile_match_label(profile, peer, AA_CLASS_PTRACE, request,
aa_profile_match_label(profile, rules, peer, AA_CLASS_PTRACE, request,
&perms);
aa_apply_modes_to_perms(profile, &perms);
return aa_check_perms(profile, &perms, request, sa, audit_ptrace_cb);
@ -243,7 +245,7 @@ static int profile_tracee_perm(struct aa_profile *tracee,
struct common_audit_data *sa)
{
if (profile_unconfined(tracee) || unconfined(tracer) ||
!PROFILE_MEDIATES(tracee, AA_CLASS_PTRACE))
!ANY_RULE_MEDIATES(&tracee->rules, AA_CLASS_PTRACE))
return 0;
return profile_ptrace_perm(tracee, tracer, request, sa);
@ -256,7 +258,7 @@ static int profile_tracer_perm(struct aa_profile *tracer,
if (profile_unconfined(tracer))
return 0;
if (PROFILE_MEDIATES(tracer, AA_CLASS_PTRACE))
if (ANY_RULE_MEDIATES(&tracer->rules, AA_CLASS_PTRACE))
return profile_ptrace_perm(tracer, tracee, request, sa);
/* profile uses the old style capability check for ptrace */
@ -285,7 +287,7 @@ int aa_may_ptrace(struct aa_label *tracer, struct aa_label *tracee,
{
struct aa_profile *profile;
u32 xrequest = request << PTRACE_PERM_SHIFT;
DEFINE_AUDIT_DATA(sa, LSM_AUDIT_DATA_NONE, OP_PTRACE);
DEFINE_AUDIT_DATA(sa, LSM_AUDIT_DATA_NONE, AA_CLASS_PTRACE, OP_PTRACE);
return xcheck_labels(tracer, tracee, profile,
profile_tracer_perm(profile, tracee, request, &sa),