mirror of
https://github.com/torvalds/linux.git
synced 2026-03-08 01:04:41 +01:00
+ Features
- add .kunitconfig - audit execpath in userns mediation - add support loading per permission tagging + Cleanups - cleanup remove unused percpu critical sections in buffer management - document the buffer hold, add an overflow guard - split xxx_in_ns into its two separate semantic use cases - remove apply_modes_to_perms from label_match - refactor/cleanup cred helper fns. - guard against free attachment/data routines being called with a NULL - drop in_atomic flag in common_mmap, and common_file_perm, and cleanup - make str table more generic and be able to have multiple entries - Replace deprecated strcpy with memcpy in gen_symlink_name - Replace deprecated strcpy in d_namespace_path - Replace sprintf/strcpy with scnprintf/strscpy in aa_policy_init - replace sprintf with snprintf in aa_new_learning_profile + Bug Fixes - fix cast in format string DEBUG statement - fix make aa_labelmatch return consistent - fix fmt string type error in process_strs_entry - fix kernel-doc comments for inview - fix invalid deref of rawdata when export_binary is unset - avoid per-cpu hold underflow in aa_get_buffer - fix fast path cache check for unix sockets - fix rlimit for posix cpu timers - fix label and profile debug macros - move check for aa_null file to cover all cases - return -ENOMEM in unpack_perms_table upon alloc failure - fix boolean argument in apparmor_mmap_file - Fix & Optimize table creation from possibly unaligned memory - Allow apparmor to handle unaligned dfa tables - fix NULL deref in aa_sock_file_perm - fix NULL pointer dereference in __unix_needs_revalidation - fix signedness bug in unpack_tags() -----BEGIN PGP SIGNATURE----- iQIzBAABCgAdFiEE7cSDD705q2rFEEf7BS82cBjVw9gFAmmWHk0ACgkQBS82cBjV w9gtTw//e1Gg3tohE4aMGc4B4EawLKKCdkZ/YrNIJqD9Uer7SJNZxsIpiXQF7h3b 6t4gd9r7kMZHFq9B3zVp1uBrhCb7zay6/FgqizwnNPpgcsFY8Y//4MZHtx+pCxw7 3kWvTqO9edkibGW3Pob0w8RdjV+0Q1kmYMY061lBbP0dK6gW5A+xB+vnEeaNx8tD Xco9Aeixy9HhjjNTAC7uryHCp6r9pOGSw/ubvBPUeH/KFJDQaaMWbBttwlMe/Ti6 VCMGlqsS863Jm//bviMWRy0YYc5kgYAJphzRMsKQW4LpQkNoNGkv1h1CCau7SJ7U hueKoxbzmZwQyEA9ydaN2m6PX9mcsaqcVCOhAJmMwClra5jdNjN4FSliymGNCeXf T+NXiqomXFY60jdTuzOJsXkAjXMWk8BuxWd0auWnS/qlbDlLOYhoLzM/DANoCx13 CTAvjI4hB4kRtMfMySrjT5294LrQnYejV4kKQ1dXK3CL3s+6KPTfTkBE51DDTNRn 5cJ9KFcKaGuG4ACj0ZSMJT2TXeNkOOiHsrQX1MyZWI5w4RjitgaPZ+++Zcx4gH9a rVDkMW1QZy705asxQVneyqvtKYxYOMCzu9+imBAu+TRJptguSonWHf9/YEk+s/GO xxZWyDp7poVZoH8mORhaO3JuCbDRdeSHx3Pma9CVih407or3gYU= =FFMr -----END PGP SIGNATURE----- Merge tag 'apparmor-pr-2026-02-18' of git://git.kernel.org/pub/scm/linux/kernel/git/jj/linux-apparmor Pull AppArmor updates from John Johansen: "Features: - add .kunitconfig - audit execpath in userns mediation - add support loading per permission tagging Cleanups: - remove unused percpu critical sections in buffer management - document the buffer hold, add an overflow guard - split xxx_in_ns into its two separate semantic use cases - remove apply_modes_to_perms from label_match - refactor/cleanup cred helper fns. - guard against free attachment/data routines being called with NULL - drop in_atomic flag in common_mmap, common_file_perm, and cleanup - make str table more generic and be able to have multiple entries - Replace deprecated strcpy with memcpy in gen_symlink_name - Replace deprecated strcpy in d_namespace_path - Replace sprintf/strcpy with scnprintf/strscpy in aa_policy_init - replace sprintf with snprintf in aa_new_learning_profile Bug Fixes: - fix cast in format string DEBUG statement - fix make aa_labelmatch return consistent - fix fmt string type error in process_strs_entry - fix kernel-doc comments for inview - fix invalid deref of rawdata when export_binary is unset - avoid per-cpu hold underflow in aa_get_buffer - fix fast path cache check for unix sockets - fix rlimit for posix cpu timers - fix label and profile debug macros - move check for aa_null file to cover all cases - return -ENOMEM in unpack_perms_table upon alloc failure - fix boolean argument in apparmor_mmap_file - Fix & Optimize table creation from possibly unaligned memory - Allow apparmor to handle unaligned dfa tables - fix NULL deref in aa_sock_file_perm - fix NULL pointer dereference in __unix_needs_revalidation - fix signedness bug in unpack_tags()" * tag 'apparmor-pr-2026-02-18' of git://git.kernel.org/pub/scm/linux/kernel/git/jj/linux-apparmor: (34 commits) apparmor: fix signedness bug in unpack_tags() apparmor: fix cast in format string DEBUG statement apparmor: fix aa_label to return state from compount and component match apparmor: fix fmt string type error in process_strs_entry apparmor: fix kernel-doc comments for inview apparmor: fix invalid deref of rawdata when export_binary is unset apparmor: add .kunitconfig apparmor: cleanup remove unused percpu critical sections in buffer management apparmor: document the buffer hold, add an overflow guard apparmor: avoid per-cpu hold underflow in aa_get_buffer apparmor: split xxx_in_ns into its two separate semantic use cases apparmor: make label_match return a consistent value apparmor: remove apply_modes_to_perms from label_match apparmor: fix fast path cache check for unix sockets apparmor: fix rlimit for posix cpu timers apparmor: refactor/cleanup cred helper fns. apparmor: fix label and profile debug macros apparmor: move check for aa_null file to cover all cases apparmor: guard against free routines being called with a NULL apparmor: return -ENOMEM in unpack_perms_table upon alloc failure ...
This commit is contained in:
commit
219d7660c5
21 changed files with 690 additions and 243 deletions
5
security/apparmor/.kunitconfig
Normal file
5
security/apparmor/.kunitconfig
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
CONFIG_KUNIT=y
|
||||
CONFIG_NET=y
|
||||
CONFIG_SECURITY=y
|
||||
CONFIG_SECURITY_APPARMOR=y
|
||||
CONFIG_SECURITY_APPARMOR_KUNIT_TEST=y
|
||||
|
|
@ -416,7 +416,7 @@ static int profile_peer_perm(struct aa_profile *profile, u32 request,
|
|||
unix_sk(sk),
|
||||
peer_addr, peer_addrlen, &p, &ad->info);
|
||||
|
||||
return fn_for_each_in_ns(peer_label, peerp,
|
||||
return fn_for_each_in_scope(peer_label, peerp,
|
||||
match_label(profile, rules, state, request,
|
||||
peerp, p, ad));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -801,7 +801,7 @@ static ssize_t query_label(char *buf, size_t buf_len,
|
|||
|
||||
perms = allperms;
|
||||
if (view_only) {
|
||||
label_for_each_in_ns(i, labels_ns(label), label, profile) {
|
||||
label_for_each_in_scope(i, labels_ns(label), label, profile) {
|
||||
profile_query_cb(profile, &perms, match_str, match_len);
|
||||
}
|
||||
} else {
|
||||
|
|
@ -1607,16 +1607,20 @@ static char *gen_symlink_name(int depth, const char *dirname, const char *fname)
|
|||
{
|
||||
char *buffer, *s;
|
||||
int error;
|
||||
int size = depth * 6 + strlen(dirname) + strlen(fname) + 11;
|
||||
const char *path = "../../";
|
||||
size_t path_len = strlen(path);
|
||||
int size;
|
||||
|
||||
/* Extra 11 bytes: "raw_data" (9) + two slashes "//" (2) */
|
||||
size = depth * path_len + strlen(dirname) + strlen(fname) + 11;
|
||||
s = buffer = kmalloc(size, GFP_KERNEL);
|
||||
if (!buffer)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
for (; depth > 0; depth--) {
|
||||
strcpy(s, "../../");
|
||||
s += 6;
|
||||
size -= 6;
|
||||
memcpy(s, path, path_len);
|
||||
s += path_len;
|
||||
size -= path_len;
|
||||
}
|
||||
|
||||
error = snprintf(s, size, "raw_data/%s/%s", dirname, fname);
|
||||
|
|
@ -1644,6 +1648,15 @@ static const char *rawdata_get_link_base(struct dentry *dentry,
|
|||
|
||||
label = aa_get_label_rcu(&proxy->label);
|
||||
profile = labels_profile(label);
|
||||
|
||||
/* rawdata can be null when aa_g_export_binary is unset during
|
||||
* runtime and a profile is replaced
|
||||
*/
|
||||
if (!profile->rawdata) {
|
||||
aa_put_label(label);
|
||||
return ERR_PTR(-ENOENT);
|
||||
}
|
||||
|
||||
depth = profile_depth(profile);
|
||||
target = gen_symlink_name(depth, profile->rawdata->name, name);
|
||||
aa_put_label(label);
|
||||
|
|
|
|||
|
|
@ -115,7 +115,7 @@ static inline aa_state_t match_component(struct aa_profile *profile,
|
|||
* @label: label to check access permissions for
|
||||
* @stack: whether this is a stacking request
|
||||
* @state: state to start match in
|
||||
* @subns: whether to do permission checks on components in a subns
|
||||
* @inview: whether to match labels in view or only in scope
|
||||
* @request: permissions to request
|
||||
* @perms: perms struct to set
|
||||
*
|
||||
|
|
@ -127,7 +127,7 @@ static inline aa_state_t match_component(struct aa_profile *profile,
|
|||
*/
|
||||
static int label_compound_match(struct aa_profile *profile,
|
||||
struct aa_label *label, bool stack,
|
||||
aa_state_t state, bool subns, u32 request,
|
||||
aa_state_t state, bool inview, u32 request,
|
||||
struct aa_perms *perms)
|
||||
{
|
||||
struct aa_ruleset *rules = profile->label.rules[0];
|
||||
|
|
@ -135,9 +135,9 @@ static int label_compound_match(struct aa_profile *profile,
|
|||
struct label_it i;
|
||||
struct path_cond cond = { };
|
||||
|
||||
/* find first subcomponent that is visible */
|
||||
/* find first subcomponent that is in view and going to be interated with */
|
||||
label_for_each(i, label, tp) {
|
||||
if (!aa_ns_visible(profile->ns, tp->ns, subns))
|
||||
if (!aa_ns_visible(profile->ns, tp->ns, inview))
|
||||
continue;
|
||||
state = match_component(profile, tp, stack, state);
|
||||
if (!state)
|
||||
|
|
@ -151,7 +151,7 @@ static int label_compound_match(struct aa_profile *profile,
|
|||
|
||||
next:
|
||||
label_for_each_cont(i, label, tp) {
|
||||
if (!aa_ns_visible(profile->ns, tp->ns, subns))
|
||||
if (!aa_ns_visible(profile->ns, tp->ns, inview))
|
||||
continue;
|
||||
state = aa_dfa_match(rules->file->dfa, state, "//&");
|
||||
state = match_component(profile, tp, false, state);
|
||||
|
|
@ -177,7 +177,7 @@ fail:
|
|||
* @label: label to check access permissions for
|
||||
* @stack: whether this is a stacking request
|
||||
* @start: state to start match in
|
||||
* @subns: whether to do permission checks on components in a subns
|
||||
* @inview: whether to match labels in view or only in scope
|
||||
* @request: permissions to request
|
||||
* @perms: an initialized perms struct to add accumulation to
|
||||
*
|
||||
|
|
@ -189,7 +189,7 @@ fail:
|
|||
*/
|
||||
static int label_components_match(struct aa_profile *profile,
|
||||
struct aa_label *label, bool stack,
|
||||
aa_state_t start, bool subns, u32 request,
|
||||
aa_state_t start, bool inview, u32 request,
|
||||
struct aa_perms *perms)
|
||||
{
|
||||
struct aa_ruleset *rules = profile->label.rules[0];
|
||||
|
|
@ -201,7 +201,7 @@ static int label_components_match(struct aa_profile *profile,
|
|||
|
||||
/* find first subcomponent to test */
|
||||
label_for_each(i, label, tp) {
|
||||
if (!aa_ns_visible(profile->ns, tp->ns, subns))
|
||||
if (!aa_ns_visible(profile->ns, tp->ns, inview))
|
||||
continue;
|
||||
state = match_component(profile, tp, stack, start);
|
||||
if (!state)
|
||||
|
|
@ -218,7 +218,7 @@ next:
|
|||
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))
|
||||
if (!aa_ns_visible(profile->ns, tp->ns, inview))
|
||||
continue;
|
||||
state = match_component(profile, tp, stack, start);
|
||||
if (!state)
|
||||
|
|
@ -245,26 +245,26 @@ fail:
|
|||
* @label: label to match (NOT NULL)
|
||||
* @stack: whether this is a stacking request
|
||||
* @state: state to start in
|
||||
* @subns: whether to match subns components
|
||||
* @inview: whether to match labels in view or only in scope
|
||||
* @request: permission request
|
||||
* @perms: Returns computed perms (NOT NULL)
|
||||
*
|
||||
* Returns: the state the match finished in, may be the none matching state
|
||||
*/
|
||||
static int label_match(struct aa_profile *profile, struct aa_label *label,
|
||||
bool stack, aa_state_t state, bool subns, u32 request,
|
||||
bool stack, aa_state_t state, bool inview, u32 request,
|
||||
struct aa_perms *perms)
|
||||
{
|
||||
int error;
|
||||
|
||||
*perms = nullperms;
|
||||
error = label_compound_match(profile, label, stack, state, subns,
|
||||
error = label_compound_match(profile, label, stack, state, inview,
|
||||
request, perms);
|
||||
if (!error)
|
||||
return error;
|
||||
|
||||
*perms = allperms;
|
||||
return label_components_match(profile, label, stack, state, subns,
|
||||
return label_components_match(profile, label, stack, state, inview,
|
||||
request, perms);
|
||||
}
|
||||
|
||||
|
|
@ -529,7 +529,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 (next = rules->file->trans.table[index]; next;
|
||||
for (next = rules->file->trans.table[index].strs; next;
|
||||
next = next_name(xtype, next)) {
|
||||
const char *lookup = (*next == '&') ? next + 1 : next;
|
||||
*name = next;
|
||||
|
|
@ -880,14 +880,16 @@ static struct aa_label *handle_onexec(const struct cred *subj_cred,
|
|||
AA_BUG(!bprm);
|
||||
AA_BUG(!buffer);
|
||||
|
||||
/* TODO: determine how much we want to loosen this */
|
||||
error = fn_for_each_in_ns(label, profile,
|
||||
/* TODO: determine how much we want to loosen this
|
||||
* only check profiles in scope for permission to change at exec
|
||||
*/
|
||||
error = fn_for_each_in_scope(label, profile,
|
||||
profile_onexec(subj_cred, profile, onexec, stack,
|
||||
bprm, buffer, cond, unsafe));
|
||||
if (error)
|
||||
return ERR_PTR(error);
|
||||
|
||||
new = fn_label_build_in_ns(label, profile, GFP_KERNEL,
|
||||
new = fn_label_build_in_scope(label, profile, GFP_KERNEL,
|
||||
stack ? aa_label_merge(&profile->label, onexec,
|
||||
GFP_KERNEL)
|
||||
: aa_get_newest_label(onexec),
|
||||
|
|
@ -897,7 +899,7 @@ static struct aa_label *handle_onexec(const struct cred *subj_cred,
|
|||
return new;
|
||||
|
||||
/* TODO: get rid of GLOBAL_ROOT_UID */
|
||||
error = fn_for_each_in_ns(label, profile,
|
||||
error = fn_for_each_in_scope(label, profile,
|
||||
aa_audit_file(subj_cred, profile, &nullperms,
|
||||
OP_CHANGE_ONEXEC,
|
||||
AA_MAY_ONEXEC, bprm->filename, NULL,
|
||||
|
|
@ -1123,7 +1125,7 @@ static struct aa_label *change_hat(const struct cred *subj_cred,
|
|||
/*find first matching hat */
|
||||
for (i = 0; i < count && !hat; i++) {
|
||||
name = hats[i];
|
||||
label_for_each_in_ns(it, labels_ns(label), label, profile) {
|
||||
label_for_each_in_scope(it, labels_ns(label), label, profile) {
|
||||
if (sibling && PROFILE_IS_HAT(profile)) {
|
||||
root = aa_get_profile_rcu(&profile->parent);
|
||||
} else if (!sibling && !PROFILE_IS_HAT(profile)) {
|
||||
|
|
@ -1159,7 +1161,7 @@ outer_continue:
|
|||
* change_hat.
|
||||
*/
|
||||
name = NULL;
|
||||
label_for_each_in_ns(it, labels_ns(label), label, profile) {
|
||||
label_for_each_in_scope(it, labels_ns(label), label, profile) {
|
||||
if (!list_empty(&profile->base.profiles)) {
|
||||
info = "hat not found";
|
||||
error = -ENOENT;
|
||||
|
|
@ -1170,7 +1172,7 @@ outer_continue:
|
|||
error = -ECHILD;
|
||||
|
||||
fail:
|
||||
label_for_each_in_ns(it, labels_ns(label), label, profile) {
|
||||
label_for_each_in_scope(it, labels_ns(label), label, profile) {
|
||||
/*
|
||||
* no target as it has failed to be found or built
|
||||
*
|
||||
|
|
@ -1188,7 +1190,7 @@ fail:
|
|||
return ERR_PTR(error);
|
||||
|
||||
build:
|
||||
new = fn_label_build_in_ns(label, profile, GFP_KERNEL,
|
||||
new = fn_label_build_in_scope(label, profile, GFP_KERNEL,
|
||||
build_change_hat(subj_cred, profile, name,
|
||||
sibling),
|
||||
aa_get_label(&profile->label));
|
||||
|
|
@ -1251,7 +1253,7 @@ int aa_change_hat(const char *hats[], int count, u64 token, int flags)
|
|||
bool empty = true;
|
||||
|
||||
rcu_read_lock();
|
||||
label_for_each_in_ns(i, labels_ns(label), label, profile) {
|
||||
label_for_each_in_scope(i, labels_ns(label), label, profile) {
|
||||
empty &= list_empty(&profile->base.profiles);
|
||||
}
|
||||
rcu_read_unlock();
|
||||
|
|
@ -1338,7 +1340,7 @@ kill:
|
|||
perms.kill = AA_MAY_CHANGEHAT;
|
||||
|
||||
fail:
|
||||
fn_for_each_in_ns(label, profile,
|
||||
fn_for_each_in_scope(label, profile,
|
||||
aa_audit_file(subj_cred, profile, &perms, OP_CHANGE_HAT,
|
||||
AA_MAY_CHANGEHAT, NULL, NULL, target,
|
||||
GLOBAL_ROOT_UID, info, error));
|
||||
|
|
@ -1446,7 +1448,7 @@ int aa_change_profile(const char *fqname, int flags)
|
|||
*/
|
||||
stack = true;
|
||||
perms.audit = request;
|
||||
(void) fn_for_each_in_ns(label, profile,
|
||||
(void) fn_for_each_in_scope(label, profile,
|
||||
aa_audit_file(subj_cred, profile, &perms, op,
|
||||
request, auditname, NULL, target,
|
||||
GLOBAL_ROOT_UID, stack_msg, 0));
|
||||
|
|
@ -1492,7 +1494,7 @@ int aa_change_profile(const char *fqname, int flags)
|
|||
*
|
||||
* if (!stack) {
|
||||
*/
|
||||
error = fn_for_each_in_ns(label, profile,
|
||||
error = fn_for_each_in_scope(label, profile,
|
||||
change_profile_perms_wrapper(op, auditname,
|
||||
subj_cred,
|
||||
profile, target, stack,
|
||||
|
|
@ -1506,7 +1508,7 @@ int aa_change_profile(const char *fqname, int flags)
|
|||
check:
|
||||
/* check if tracing task is allowed to trace target domain */
|
||||
error = may_change_ptraced_domain(subj_cred, target, &info);
|
||||
if (error && !fn_for_each_in_ns(label, profile,
|
||||
if (error && !fn_for_each_in_scope(label, profile,
|
||||
COMPLAIN_MODE(profile)))
|
||||
goto audit;
|
||||
|
||||
|
|
@ -1522,7 +1524,7 @@ check:
|
|||
|
||||
/* stacking is always a subset, so only check the nonstack case */
|
||||
if (!stack) {
|
||||
new = fn_label_build_in_ns(label, profile, GFP_KERNEL,
|
||||
new = fn_label_build_in_scope(label, profile, GFP_KERNEL,
|
||||
aa_get_label(target),
|
||||
aa_get_label(&profile->label));
|
||||
/*
|
||||
|
|
@ -1565,7 +1567,7 @@ check:
|
|||
}
|
||||
|
||||
audit:
|
||||
error = fn_for_each_in_ns(label, profile,
|
||||
error = fn_for_each_in_scope(label, profile,
|
||||
aa_audit_file(subj_cred,
|
||||
profile, &perms, op, request, auditname,
|
||||
NULL, new ? new : target,
|
||||
|
|
|
|||
|
|
@ -103,6 +103,7 @@ int aa_audit_file(const struct cred *subj_cred,
|
|||
|
||||
ad.subj_cred = subj_cred;
|
||||
ad.request = request;
|
||||
ad.tags = perms->tag;
|
||||
ad.name = name;
|
||||
ad.fs.target = target;
|
||||
ad.peer = tlabel;
|
||||
|
|
@ -154,8 +155,12 @@ static int path_name(const char *op, const struct cred *subj_cred,
|
|||
const char *info = NULL;
|
||||
int error;
|
||||
|
||||
error = aa_path_name(path, flags, buffer, name, &info,
|
||||
labels_profile(label)->disconnected);
|
||||
/* don't reaudit files closed during inheritance */
|
||||
if (unlikely(path->dentry == aa_null.dentry))
|
||||
error = -EACCES;
|
||||
else
|
||||
error = aa_path_name(path, flags, buffer, name, &info,
|
||||
labels_profile(label)->disconnected);
|
||||
if (error) {
|
||||
fn_for_each_confined(label, profile,
|
||||
aa_audit_file(subj_cred,
|
||||
|
|
@ -567,8 +572,7 @@ static bool __file_is_delegated(struct aa_label *obj_label)
|
|||
return unconfined(obj_label);
|
||||
}
|
||||
|
||||
static bool __unix_needs_revalidation(struct file *file, struct aa_label *label,
|
||||
u32 request)
|
||||
static bool __is_unix_file(struct file *file)
|
||||
{
|
||||
struct socket *sock = (struct socket *) file->private_data;
|
||||
|
||||
|
|
@ -576,20 +580,31 @@ static bool __unix_needs_revalidation(struct file *file, struct aa_label *label,
|
|||
|
||||
if (!S_ISSOCK(file_inode(file)->i_mode))
|
||||
return false;
|
||||
if (request & NET_PEER_MASK)
|
||||
/* sock and sock->sk can be NULL for sockets being set up or torn down */
|
||||
if (!sock || !sock->sk)
|
||||
return false;
|
||||
if (sock->sk->sk_family == PF_UNIX) {
|
||||
struct aa_sk_ctx *ctx = aa_sock(sock->sk);
|
||||
|
||||
if (rcu_access_pointer(ctx->peer) !=
|
||||
rcu_access_pointer(ctx->peer_lastupdate))
|
||||
return true;
|
||||
return !__aa_subj_label_is_cached(rcu_dereference(ctx->label),
|
||||
label);
|
||||
}
|
||||
if (sock->sk->sk_family == PF_UNIX)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool __unix_needs_revalidation(struct file *file, struct aa_label *label,
|
||||
u32 request)
|
||||
{
|
||||
struct socket *sock = (struct socket *) file->private_data;
|
||||
|
||||
AA_BUG(!__is_unix_file(file));
|
||||
lockdep_assert_in_rcu_read_lock();
|
||||
|
||||
struct aa_sk_ctx *skctx = aa_sock(sock->sk);
|
||||
|
||||
if (rcu_access_pointer(skctx->peer) !=
|
||||
rcu_access_pointer(skctx->peer_lastupdate))
|
||||
return true;
|
||||
|
||||
return !__aa_subj_label_is_cached(rcu_dereference(skctx->label), label);
|
||||
}
|
||||
|
||||
/**
|
||||
* aa_file_perm - do permission revalidation check & audit for @file
|
||||
* @op: operation being checked
|
||||
|
|
@ -613,6 +628,10 @@ int aa_file_perm(const char *op, const struct cred *subj_cred,
|
|||
AA_BUG(!label);
|
||||
AA_BUG(!file);
|
||||
|
||||
/* don't reaudit files closed during inheritance */
|
||||
if (unlikely(file->f_path.dentry == aa_null.dentry))
|
||||
return -EACCES;
|
||||
|
||||
fctx = file_ctx(file);
|
||||
|
||||
rcu_read_lock();
|
||||
|
|
@ -628,7 +647,7 @@ int aa_file_perm(const char *op, const struct cred *subj_cred,
|
|||
*/
|
||||
denied = request & ~fctx->allow;
|
||||
if (unconfined(label) || __file_is_delegated(flabel) ||
|
||||
__unix_needs_revalidation(file, label, request) ||
|
||||
(!denied && __is_unix_file(file) && !__unix_needs_revalidation(file, label, request)) ||
|
||||
(!denied && __aa_subj_label_is_cached(label, flabel))) {
|
||||
rcu_read_unlock();
|
||||
goto done;
|
||||
|
|
|
|||
|
|
@ -119,6 +119,8 @@ struct apparmor_audit_data {
|
|||
const char *info;
|
||||
u32 request;
|
||||
u32 denied;
|
||||
u32 tags;
|
||||
|
||||
union {
|
||||
/* these entries require a custom callback fn */
|
||||
struct {
|
||||
|
|
|
|||
|
|
@ -36,22 +36,6 @@ static inline void set_cred_label(const struct cred *cred,
|
|||
*blob = label;
|
||||
}
|
||||
|
||||
/**
|
||||
* aa_cred_raw_label - obtain cred's label
|
||||
* @cred: cred to obtain label from (NOT NULL)
|
||||
*
|
||||
* Returns: confining label
|
||||
*
|
||||
* does NOT increment reference count
|
||||
*/
|
||||
static inline struct aa_label *aa_cred_raw_label(const struct cred *cred)
|
||||
{
|
||||
struct aa_label *label = cred_label(cred);
|
||||
|
||||
AA_BUG(!label);
|
||||
return label;
|
||||
}
|
||||
|
||||
/**
|
||||
* aa_get_newest_cred_label - obtain the newest label on a cred
|
||||
* @cred: cred to obtain label from (NOT NULL)
|
||||
|
|
@ -60,13 +44,13 @@ static inline struct aa_label *aa_cred_raw_label(const struct cred *cred)
|
|||
*/
|
||||
static inline struct aa_label *aa_get_newest_cred_label(const struct cred *cred)
|
||||
{
|
||||
return aa_get_newest_label(aa_cred_raw_label(cred));
|
||||
return aa_get_newest_label(cred_label(cred));
|
||||
}
|
||||
|
||||
static inline struct aa_label *aa_get_newest_cred_label_condref(const struct cred *cred,
|
||||
bool *needput)
|
||||
{
|
||||
struct aa_label *l = aa_cred_raw_label(cred);
|
||||
struct aa_label *l = cred_label(cred);
|
||||
|
||||
if (unlikely(label_is_stale(l))) {
|
||||
*needput = true;
|
||||
|
|
@ -93,7 +77,7 @@ static inline void aa_put_label_condref(struct aa_label *l, bool needput)
|
|||
*/
|
||||
static inline struct aa_label *aa_current_raw_label(void)
|
||||
{
|
||||
return aa_cred_raw_label(current_cred());
|
||||
return cred_label(current_cred());
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -115,19 +99,81 @@ static inline struct aa_label *aa_get_current_label(void)
|
|||
}
|
||||
|
||||
/**
|
||||
* __end_current_label_crit_section - end crit section begun with __begin_...
|
||||
* @label: label obtained from __begin_current_label_crit_section
|
||||
* @needput: output: bool set by __begin_current_label_crit_section
|
||||
* __end_cred_crit_section - end crit section begun with __begin_...
|
||||
* @label: label obtained from __begin_cred_crit_section
|
||||
* @needput: output: bool set by __begin_cred_crit_section
|
||||
*
|
||||
* Returns: label to use for this crit section
|
||||
* While the cred passed to __begin is guaranteed to not change
|
||||
* and the cred and label could be passed here instead of needput
|
||||
* using needput with a local var makes it easier for the compiler
|
||||
* and processor to optimize and speculatively execute the comparison
|
||||
* than chasing a pointer in the cred struct.
|
||||
*/
|
||||
static inline void __end_current_label_crit_section(struct aa_label *label,
|
||||
static inline void __end_cred_crit_section(struct aa_label *label,
|
||||
bool needput)
|
||||
{
|
||||
if (unlikely(needput))
|
||||
aa_put_label(label);
|
||||
}
|
||||
|
||||
/**
|
||||
* __begin_cred_crit_section - @cred's confining label
|
||||
* @cred: current's cred to start a crit section on its label
|
||||
* @needput: store whether the label needs to be put when ending crit section
|
||||
*
|
||||
* Returns: up to date confining label or the ns unconfined label (NOT NULL)
|
||||
*
|
||||
* safe to call inside locks
|
||||
*
|
||||
* The returned reference must be put with __end_cred_crit_section()
|
||||
* This must NOT be used if the task cred could be updated within the
|
||||
* critical section between
|
||||
* __begin_cred_crit_section() .. __end_cred_crit_section()
|
||||
*
|
||||
* The crit section is an optimization to avoid having to get and put
|
||||
* the newest version of the label. While the cred won't change and
|
||||
* hence the label it contains won't change, the newest version of the
|
||||
* label can. During the crit section the newest versions of the label
|
||||
* will be used until the end of the crit section.
|
||||
*
|
||||
* If the label has not been updated at the start of the crit section
|
||||
* no refcount is taken, the cred's refcount is enough to hold the
|
||||
* label for the duration of the crit section.
|
||||
*
|
||||
* If the label has been updated then a refcount will be taken and the
|
||||
* newest version of the label will be returned. While the cred label
|
||||
* and the returned label could be compared at the end of the crit
|
||||
* section, needput is used because it allows better optimization by
|
||||
* the compiler and the processor's speculative execution.
|
||||
*/
|
||||
static inline struct aa_label *__begin_cred_crit_section(const struct cred *cred,
|
||||
bool *needput)
|
||||
{
|
||||
struct aa_label *label = cred_label(cred);
|
||||
|
||||
if (label_is_stale(label)) {
|
||||
*needput = true;
|
||||
return aa_get_newest_label(label);
|
||||
}
|
||||
|
||||
*needput = false;
|
||||
return label;
|
||||
}
|
||||
|
||||
/**
|
||||
* __end_current_label_crit_section - end crit section begun with __begin_...
|
||||
* @label: label obtained from __begin_current_label_crit_section
|
||||
* @needput: output: bool set by __begin_current_label_crit_section
|
||||
*
|
||||
* wrapper around __end_cred_crit_section() to pair nicely with
|
||||
* __begin_current_label_crit_section()
|
||||
*/
|
||||
static inline void __end_current_label_crit_section(struct aa_label *label,
|
||||
bool needput)
|
||||
{
|
||||
__end_cred_crit_section(label, needput);
|
||||
}
|
||||
|
||||
/**
|
||||
* end_current_label_crit_section - put a reference found with begin_current_label..
|
||||
* @label: label reference to put
|
||||
|
|
@ -157,15 +203,7 @@ static inline void end_current_label_crit_section(struct aa_label *label)
|
|||
*/
|
||||
static inline struct aa_label *__begin_current_label_crit_section(bool *needput)
|
||||
{
|
||||
struct aa_label *label = aa_current_raw_label();
|
||||
|
||||
if (label_is_stale(label)) {
|
||||
*needput = true;
|
||||
return aa_get_newest_label(label);
|
||||
}
|
||||
|
||||
*needput = false;
|
||||
return label;
|
||||
return __begin_cred_crit_section(current_cred(), needput);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -30,8 +30,10 @@ extern struct aa_dfa *stacksplitdfa;
|
|||
#define DEBUG_DOMAIN 4
|
||||
#define DEBUG_POLICY 8
|
||||
#define DEBUG_INTERFACE 0x10
|
||||
#define DEBUG_UNPACK 0x20
|
||||
#define DEBUG_TAGS 0x40
|
||||
|
||||
#define DEBUG_ALL 0x1f /* update if new DEBUG_X added */
|
||||
#define DEBUG_ALL 0x7f /* update if new DEBUG_X added */
|
||||
#define DEBUG_PARSE_ERROR (-1)
|
||||
|
||||
#define DEBUG_ON (aa_g_debug != DEBUG_NONE)
|
||||
|
|
@ -45,9 +47,11 @@ extern struct aa_dfa *stacksplitdfa;
|
|||
#define AA_DEBUG_LABEL(LAB, X, fmt, args...) \
|
||||
do { \
|
||||
if ((LAB)->flags & FLAG_DEBUG1) \
|
||||
AA_DEBUG(X, fmt, args); \
|
||||
AA_DEBUG(X, fmt, ##args); \
|
||||
} while (0)
|
||||
|
||||
#define AA_DEBUG_PROFILE(PROF, X, fmt...) AA_DEBUG_LABEL(&(PROF)->label, X, ##fmt)
|
||||
|
||||
#define AA_WARN(X) WARN((X), "APPARMOR WARN %s: %s\n", __func__, #X)
|
||||
|
||||
#define AA_BUG(X, args...) \
|
||||
|
|
@ -76,6 +80,19 @@ int aa_print_debug_params(char *buffer);
|
|||
/* Flag indicating whether initialization completed */
|
||||
extern int apparmor_initialized;
|
||||
|
||||
/* semantic split of scope and view */
|
||||
#define aa_in_scope(SUBJ, OBJ) \
|
||||
aa_ns_visible(SUBJ, OBJ, false)
|
||||
|
||||
#define aa_in_view(SUBJ, OBJ) \
|
||||
aa_ns_visible(SUBJ, OBJ, true)
|
||||
|
||||
#define label_for_each_in_scope(I, NS, L, P) \
|
||||
label_for_each_in_ns(I, NS, L, P)
|
||||
|
||||
#define fn_for_each_in_scope(L, P, FN) \
|
||||
fn_for_each_in_ns(L, P, FN)
|
||||
|
||||
/* fn's in lib */
|
||||
const char *skipn_spaces(const char *str, size_t n);
|
||||
const char *aa_splitn_fqname(const char *fqname, size_t n, const char **ns_name,
|
||||
|
|
@ -119,13 +136,19 @@ static inline bool path_mediated_fs(struct dentry *dentry)
|
|||
return !(dentry->d_sb->s_flags & SB_NOUSER);
|
||||
}
|
||||
|
||||
struct aa_str_table {
|
||||
struct aa_str_table_ent {
|
||||
int count;
|
||||
int size;
|
||||
char **table;
|
||||
char *strs;
|
||||
};
|
||||
|
||||
struct aa_str_table {
|
||||
int size;
|
||||
struct aa_str_table_ent *table;
|
||||
};
|
||||
|
||||
void aa_free_str_table(struct aa_str_table *table);
|
||||
bool aa_resize_str_table(struct aa_str_table *t, int newsize, gfp_t gfp);
|
||||
void aa_destroy_str_table(struct aa_str_table *table);
|
||||
|
||||
struct counted_str {
|
||||
struct kref count;
|
||||
|
|
@ -306,7 +329,7 @@ __done: \
|
|||
})
|
||||
|
||||
|
||||
#define __fn_build_in_ns(NS, P, NS_FN, OTHER_FN) \
|
||||
#define __fn_build_in_scope(NS, P, NS_FN, OTHER_FN) \
|
||||
({ \
|
||||
struct aa_label *__new; \
|
||||
if ((P)->ns != (NS)) \
|
||||
|
|
@ -316,10 +339,10 @@ __done: \
|
|||
(__new); \
|
||||
})
|
||||
|
||||
#define fn_label_build_in_ns(L, P, GFP, NS_FN, OTHER_FN) \
|
||||
#define fn_label_build_in_scope(L, P, GFP, NS_FN, OTHER_FN) \
|
||||
({ \
|
||||
fn_label_build((L), (P), (GFP), \
|
||||
__fn_build_in_ns(labels_ns(L), (P), (NS_FN), (OTHER_FN))); \
|
||||
__fn_build_in_scope(labels_ns(L), (P), (NS_FN), (OTHER_FN))); \
|
||||
})
|
||||
|
||||
#endif /* __AA_LIB_H */
|
||||
|
|
|
|||
|
|
@ -104,16 +104,18 @@ struct aa_dfa {
|
|||
struct table_header *tables[YYTD_ID_TSIZE];
|
||||
};
|
||||
|
||||
#define byte_to_byte(X) (X)
|
||||
|
||||
#define UNPACK_ARRAY(TABLE, BLOB, LEN, TTYPE, BTYPE, NTOHX) \
|
||||
do { \
|
||||
typeof(LEN) __i; \
|
||||
TTYPE *__t = (TTYPE *) TABLE; \
|
||||
BTYPE *__b = (BTYPE *) BLOB; \
|
||||
for (__i = 0; __i < LEN; __i++) { \
|
||||
__t[__i] = NTOHX(__b[__i]); \
|
||||
} \
|
||||
BUILD_BUG_ON(sizeof(TTYPE) != sizeof(BTYPE)); \
|
||||
if (IS_ENABLED(CONFIG_CPU_BIG_ENDIAN)) \
|
||||
memcpy(__t, __b, (LEN) * sizeof(BTYPE)); \
|
||||
else /* copy & convert from big-endian */ \
|
||||
for (__i = 0; __i < LEN; __i++) { \
|
||||
__t[__i] = NTOHX(&__b[__i]); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
static inline size_t table_size(size_t len, size_t el_size)
|
||||
|
|
|
|||
|
|
@ -79,11 +79,33 @@ enum profile_mode {
|
|||
};
|
||||
|
||||
|
||||
struct aa_tags_header {
|
||||
u32 mask; /* bit mask matching permissions */
|
||||
u32 count; /* number of strings per entry */
|
||||
u32 size; /* size of all strings covered by count */
|
||||
u32 tags; /* index into string table */
|
||||
};
|
||||
|
||||
struct aa_tags_struct {
|
||||
struct {
|
||||
u32 size; /* number of entries in tagsets */
|
||||
u32 *table; /* indexes into headers & strs */
|
||||
} sets;
|
||||
struct {
|
||||
u32 size; /* number of headers == num of strs */
|
||||
struct aa_tags_header *table;
|
||||
} hdrs;
|
||||
struct aa_str_table strs;
|
||||
};
|
||||
|
||||
/* struct aa_policydb - match engine for a policy
|
||||
* count: refcount for the pdb
|
||||
* dfa: dfa pattern match
|
||||
* perms: table of permissions
|
||||
* strs: table of strings, index by x
|
||||
* @count: refcount for the pdb
|
||||
* @dfa: dfa pattern match
|
||||
* @perms: table of permissions
|
||||
* @size: number of entries in @perms
|
||||
* @trans: table of strings, index by x
|
||||
* @tags: table of tags that perms->tag indexes
|
||||
* @start:_states to start in for each class
|
||||
* start: set of start states for the different classes of data
|
||||
*/
|
||||
struct aa_policydb {
|
||||
|
|
@ -94,11 +116,13 @@ struct aa_policydb {
|
|||
u32 size;
|
||||
};
|
||||
struct aa_str_table trans;
|
||||
struct aa_tags_struct tags;
|
||||
aa_state_t start[AA_CLASS_LAST + 1];
|
||||
};
|
||||
|
||||
extern struct aa_policydb *nullpdb;
|
||||
|
||||
void aa_destroy_tags(struct aa_tags_struct *tags);
|
||||
struct aa_policydb *aa_alloc_pdb(gfp_t gfp);
|
||||
void aa_pdb_free_kref(struct kref *kref);
|
||||
|
||||
|
|
|
|||
|
|
@ -1274,11 +1274,11 @@ static inline aa_state_t match_component(struct aa_profile *profile,
|
|||
* @rules: ruleset to search
|
||||
* @label: label to check access permissions for
|
||||
* @state: state to start match in
|
||||
* @subns: whether to do permission checks on components in a subns
|
||||
* @inview: whether to match labels in view or only in scope
|
||||
* @request: permissions to request
|
||||
* @perms: perms struct to set
|
||||
*
|
||||
* Returns: 0 on success else ERROR
|
||||
* Returns: state match stopped at or DFA_NOMATCH if aborted early
|
||||
*
|
||||
* For the label A//&B//&C this does the perm match for A//&B//&C
|
||||
* @perms should be preinitialized with allperms OR a previous permission
|
||||
|
|
@ -1287,7 +1287,7 @@ static inline aa_state_t match_component(struct aa_profile *profile,
|
|||
static int label_compound_match(struct aa_profile *profile,
|
||||
struct aa_ruleset *rules,
|
||||
struct aa_label *label,
|
||||
aa_state_t state, bool subns, u32 request,
|
||||
aa_state_t state, bool inview, u32 request,
|
||||
struct aa_perms *perms)
|
||||
{
|
||||
struct aa_profile *tp;
|
||||
|
|
@ -1295,7 +1295,7 @@ static int label_compound_match(struct aa_profile *profile,
|
|||
|
||||
/* find first subcomponent that is visible */
|
||||
label_for_each(i, label, tp) {
|
||||
if (!aa_ns_visible(profile->ns, tp->ns, subns))
|
||||
if (!aa_ns_visible(profile->ns, tp->ns, inview))
|
||||
continue;
|
||||
state = match_component(profile, rules, tp, state);
|
||||
if (!state)
|
||||
|
|
@ -1305,11 +1305,11 @@ static int label_compound_match(struct aa_profile *profile,
|
|||
|
||||
/* no component visible */
|
||||
*perms = allperms;
|
||||
return 0;
|
||||
return state;
|
||||
|
||||
next:
|
||||
label_for_each_cont(i, label, tp) {
|
||||
if (!aa_ns_visible(profile->ns, tp->ns, subns))
|
||||
if (!aa_ns_visible(profile->ns, tp->ns, inview))
|
||||
continue;
|
||||
state = aa_dfa_match(rules->policy->dfa, state, "//&");
|
||||
state = match_component(profile, rules, tp, state);
|
||||
|
|
@ -1317,15 +1317,11 @@ next:
|
|||
goto fail;
|
||||
}
|
||||
*perms = *aa_lookup_perms(rules->policy, state);
|
||||
aa_apply_modes_to_perms(profile, perms);
|
||||
if ((perms->allow & request) != request)
|
||||
return -EACCES;
|
||||
|
||||
return 0;
|
||||
return state;
|
||||
|
||||
fail:
|
||||
*perms = nullperms;
|
||||
return state;
|
||||
return DFA_NOMATCH;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -1334,11 +1330,11 @@ fail:
|
|||
* @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
|
||||
* @inview: whether to match labels in view or only in scope
|
||||
* @request: permissions to request
|
||||
* @perms: an initialized perms struct to add accumulation to
|
||||
*
|
||||
* Returns: 0 on success else ERROR
|
||||
* Returns: the state the match finished in, may be the none matching state
|
||||
*
|
||||
* For the label A//&B//&C this does the perm match for each of A and B and C
|
||||
* @perms should be preinitialized with allperms OR a previous permission
|
||||
|
|
@ -1347,7 +1343,7 @@ fail:
|
|||
static int label_components_match(struct aa_profile *profile,
|
||||
struct aa_ruleset *rules,
|
||||
struct aa_label *label, aa_state_t start,
|
||||
bool subns, u32 request,
|
||||
bool inview, u32 request,
|
||||
struct aa_perms *perms)
|
||||
{
|
||||
struct aa_profile *tp;
|
||||
|
|
@ -1357,7 +1353,7 @@ static int label_components_match(struct aa_profile *profile,
|
|||
|
||||
/* find first subcomponent to test */
|
||||
label_for_each(i, label, tp) {
|
||||
if (!aa_ns_visible(profile->ns, tp->ns, subns))
|
||||
if (!aa_ns_visible(profile->ns, tp->ns, inview))
|
||||
continue;
|
||||
state = match_component(profile, rules, tp, start);
|
||||
if (!state)
|
||||
|
|
@ -1366,31 +1362,29 @@ static int label_components_match(struct aa_profile *profile,
|
|||
}
|
||||
|
||||
/* no subcomponents visible - no change in perms */
|
||||
return 0;
|
||||
return state;
|
||||
|
||||
next:
|
||||
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))
|
||||
if (!aa_ns_visible(profile->ns, tp->ns, inview))
|
||||
continue;
|
||||
state = match_component(profile, rules, tp, start);
|
||||
if (!state)
|
||||
goto fail;
|
||||
tmp = *aa_lookup_perms(rules->policy, state);
|
||||
aa_apply_modes_to_perms(profile, &tmp);
|
||||
aa_perms_accum(perms, &tmp);
|
||||
}
|
||||
|
||||
if ((perms->allow & request) != request)
|
||||
return -EACCES;
|
||||
return DFA_NOMATCH;
|
||||
|
||||
return 0;
|
||||
return state;
|
||||
|
||||
fail:
|
||||
*perms = nullperms;
|
||||
return -EACCES;
|
||||
return DFA_NOMATCH;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -1399,23 +1393,24 @@ fail:
|
|||
* @rules: ruleset to search
|
||||
* @label: label to match (NOT NULL)
|
||||
* @state: state to start in
|
||||
* @subns: whether to match subns components
|
||||
* @inview: whether to match labels in view or only in scope
|
||||
* @request: permission request
|
||||
* @perms: Returns computed perms (NOT NULL)
|
||||
*
|
||||
* Returns: the state the match finished in, may be the none matching state
|
||||
*/
|
||||
int aa_label_match(struct aa_profile *profile, struct aa_ruleset *rules,
|
||||
struct aa_label *label, aa_state_t state, bool subns,
|
||||
struct aa_label *label, aa_state_t state, bool inview,
|
||||
u32 request, struct aa_perms *perms)
|
||||
{
|
||||
int error = label_compound_match(profile, rules, label, state, subns,
|
||||
request, perms);
|
||||
if (!error)
|
||||
return error;
|
||||
aa_state_t tmp = label_compound_match(profile, rules, label, state,
|
||||
inview, request, perms);
|
||||
if ((perms->allow & request) == request)
|
||||
return tmp;
|
||||
|
||||
/* failed compound_match try component matches */
|
||||
*perms = allperms;
|
||||
return label_components_match(profile, rules, label, state, subns,
|
||||
return label_components_match(profile, rules, label, state, inview,
|
||||
request, perms);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -44,6 +44,8 @@ static struct val_table_ent debug_values_table[] = {
|
|||
{ "domain", DEBUG_DOMAIN },
|
||||
{ "policy", DEBUG_POLICY },
|
||||
{ "interface", DEBUG_INTERFACE },
|
||||
{ "unpack", DEBUG_UNPACK },
|
||||
{ "tags", DEBUG_TAGS },
|
||||
{ NULL, 0 }
|
||||
};
|
||||
|
||||
|
|
@ -118,7 +120,7 @@ int aa_print_debug_params(char *buffer)
|
|||
|
||||
bool aa_resize_str_table(struct aa_str_table *t, int newsize, gfp_t gfp)
|
||||
{
|
||||
char **n;
|
||||
struct aa_str_table_ent *n;
|
||||
int i;
|
||||
|
||||
if (t->size == newsize)
|
||||
|
|
@ -129,7 +131,7 @@ bool aa_resize_str_table(struct aa_str_table *t, int newsize, gfp_t gfp)
|
|||
for (i = 0; i < min(t->size, newsize); i++)
|
||||
n[i] = t->table[i];
|
||||
for (; i < t->size; i++)
|
||||
kfree_sensitive(t->table[i]);
|
||||
kfree_sensitive(t->table[i].strs);
|
||||
if (newsize > t->size)
|
||||
memset(&n[t->size], 0, (newsize-t->size)*sizeof(*n));
|
||||
kfree_sensitive(t->table);
|
||||
|
|
@ -140,10 +142,10 @@ bool aa_resize_str_table(struct aa_str_table *t, int newsize, gfp_t gfp)
|
|||
}
|
||||
|
||||
/**
|
||||
* aa_free_str_table - free entries str table
|
||||
* aa_destroy_str_table - free entries str table
|
||||
* @t: the string table to free (MAYBE NULL)
|
||||
*/
|
||||
void aa_free_str_table(struct aa_str_table *t)
|
||||
void aa_destroy_str_table(struct aa_str_table *t)
|
||||
{
|
||||
int i;
|
||||
|
||||
|
|
@ -152,7 +154,7 @@ void aa_free_str_table(struct aa_str_table *t)
|
|||
return;
|
||||
|
||||
for (i = 0; i < t->size; i++)
|
||||
kfree_sensitive(t->table[i]);
|
||||
kfree_sensitive(t->table[i].strs);
|
||||
kfree_sensitive(t->table);
|
||||
t->table = NULL;
|
||||
t->size = 0;
|
||||
|
|
@ -478,19 +480,17 @@ bool aa_policy_init(struct aa_policy *policy, const char *prefix,
|
|||
const char *name, gfp_t gfp)
|
||||
{
|
||||
char *hname;
|
||||
size_t hname_sz;
|
||||
|
||||
hname_sz = (prefix ? strlen(prefix) + 2 : 0) + strlen(name) + 1;
|
||||
/* freed by policy_free */
|
||||
if (prefix) {
|
||||
hname = aa_str_alloc(strlen(prefix) + strlen(name) + 3, gfp);
|
||||
if (hname)
|
||||
sprintf(hname, "%s//%s", prefix, name);
|
||||
} else {
|
||||
hname = aa_str_alloc(strlen(name) + 1, gfp);
|
||||
if (hname)
|
||||
strcpy(hname, name);
|
||||
}
|
||||
hname = aa_str_alloc(hname_sz, gfp);
|
||||
if (!hname)
|
||||
return false;
|
||||
if (prefix)
|
||||
scnprintf(hname, hname_sz, "%s//%s", prefix, name);
|
||||
else
|
||||
strscpy(hname, name, hname_sz);
|
||||
policy->hname = hname;
|
||||
/* base.name is a substring of fqname */
|
||||
policy->name = basename(policy->hname);
|
||||
|
|
@ -512,3 +512,4 @@ void aa_policy_destroy(struct aa_policy *policy)
|
|||
/* don't free name as its a subset of hname */
|
||||
aa_put_str(policy->hname);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -520,33 +520,26 @@ static void apparmor_file_free_security(struct file *file)
|
|||
aa_put_label(rcu_access_pointer(ctx->label));
|
||||
}
|
||||
|
||||
static int common_file_perm(const char *op, struct file *file, u32 mask,
|
||||
bool in_atomic)
|
||||
static int common_file_perm(const char *op, struct file *file, u32 mask)
|
||||
{
|
||||
struct aa_label *label;
|
||||
int error = 0;
|
||||
bool needput;
|
||||
|
||||
/* don't reaudit files closed during inheritance */
|
||||
if (unlikely(file->f_path.dentry == aa_null.dentry))
|
||||
return -EACCES;
|
||||
|
||||
label = __begin_current_label_crit_section(&needput);
|
||||
error = aa_file_perm(op, current_cred(), label, file, mask, in_atomic);
|
||||
__end_current_label_crit_section(label, needput);
|
||||
label = begin_current_label_crit_section();
|
||||
error = aa_file_perm(op, current_cred(), label, file, mask, false);
|
||||
end_current_label_crit_section(label);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
static int apparmor_file_receive(struct file *file)
|
||||
{
|
||||
return common_file_perm(OP_FRECEIVE, file, aa_map_file_to_perms(file),
|
||||
false);
|
||||
return common_file_perm(OP_FRECEIVE, file, aa_map_file_to_perms(file));
|
||||
}
|
||||
|
||||
static int apparmor_file_permission(struct file *file, int mask)
|
||||
{
|
||||
return common_file_perm(OP_FPERM, file, mask, false);
|
||||
return common_file_perm(OP_FPERM, file, mask);
|
||||
}
|
||||
|
||||
static int apparmor_file_lock(struct file *file, unsigned int cmd)
|
||||
|
|
@ -556,11 +549,11 @@ static int apparmor_file_lock(struct file *file, unsigned int cmd)
|
|||
if (cmd == F_WRLCK)
|
||||
mask |= MAY_WRITE;
|
||||
|
||||
return common_file_perm(OP_FLOCK, file, mask, false);
|
||||
return common_file_perm(OP_FLOCK, file, mask);
|
||||
}
|
||||
|
||||
static int common_mmap(const char *op, struct file *file, unsigned long prot,
|
||||
unsigned long flags, bool in_atomic)
|
||||
unsigned long flags)
|
||||
{
|
||||
int mask = 0;
|
||||
|
||||
|
|
@ -578,21 +571,20 @@ static int common_mmap(const char *op, struct file *file, unsigned long prot,
|
|||
if (prot & PROT_EXEC)
|
||||
mask |= AA_EXEC_MMAP;
|
||||
|
||||
return common_file_perm(op, file, mask, in_atomic);
|
||||
return common_file_perm(op, file, mask);
|
||||
}
|
||||
|
||||
static int apparmor_mmap_file(struct file *file, unsigned long reqprot,
|
||||
unsigned long prot, unsigned long flags)
|
||||
{
|
||||
return common_mmap(OP_FMMAP, file, prot, flags, GFP_ATOMIC);
|
||||
return common_mmap(OP_FMMAP, file, prot, flags);
|
||||
}
|
||||
|
||||
static int apparmor_file_mprotect(struct vm_area_struct *vma,
|
||||
unsigned long reqprot, unsigned long prot)
|
||||
{
|
||||
return common_mmap(OP_FMPROT, vma->vm_file, prot,
|
||||
!(vma->vm_flags & VM_SHARED) ? MAP_PRIVATE : 0,
|
||||
false);
|
||||
!(vma->vm_flags & VM_SHARED) ? MAP_PRIVATE : 0);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_IO_URING
|
||||
|
|
@ -2133,6 +2125,23 @@ static int param_set_mode(const char *val, const struct kernel_param *kp)
|
|||
return 0;
|
||||
}
|
||||
|
||||
/* arbitrary cap on how long to hold buffer because contention was
|
||||
* encountered before trying to put it back into the global pool
|
||||
*/
|
||||
#define MAX_HOLD_COUNT 64
|
||||
|
||||
/* the hold count is a heuristic for lock contention, and can be
|
||||
* incremented async to actual buffer alloc/free. Because buffers
|
||||
* may be put back onto a percpu cache different than the ->hold was
|
||||
* added to the counts can be out of sync. Guard against underflow
|
||||
* and overflow
|
||||
*/
|
||||
static void cache_hold_inc(unsigned int *hold)
|
||||
{
|
||||
if (*hold > MAX_HOLD_COUNT)
|
||||
(*hold)++;
|
||||
}
|
||||
|
||||
char *aa_get_buffer(bool in_atomic)
|
||||
{
|
||||
union aa_buffer *aa_buf;
|
||||
|
|
@ -2145,21 +2154,26 @@ char *aa_get_buffer(bool in_atomic)
|
|||
if (!list_empty(&cache->head)) {
|
||||
aa_buf = list_first_entry(&cache->head, union aa_buffer, list);
|
||||
list_del(&aa_buf->list);
|
||||
cache->hold--;
|
||||
if (cache->hold)
|
||||
cache->hold--;
|
||||
cache->count--;
|
||||
put_cpu_ptr(&aa_local_buffers);
|
||||
return &aa_buf->buffer[0];
|
||||
}
|
||||
/* exit percpu as spinlocks may sleep on realtime kernels */
|
||||
put_cpu_ptr(&aa_local_buffers);
|
||||
|
||||
if (!spin_trylock(&aa_buffers_lock)) {
|
||||
/* had contention on lock so increase hold count. Doesn't
|
||||
* really matter if recorded before or after the spin lock
|
||||
* as there is no way to guarantee the buffer will be put
|
||||
* back on the same percpu cache. Instead rely on holds
|
||||
* roughly averaging out over time.
|
||||
*/
|
||||
cache = get_cpu_ptr(&aa_local_buffers);
|
||||
cache->hold += 1;
|
||||
cache_hold_inc(&cache->hold);
|
||||
put_cpu_ptr(&aa_local_buffers);
|
||||
spin_lock(&aa_buffers_lock);
|
||||
} else {
|
||||
cache = get_cpu_ptr(&aa_local_buffers);
|
||||
put_cpu_ptr(&aa_local_buffers);
|
||||
}
|
||||
retry:
|
||||
if (buffer_count > reserve_count ||
|
||||
|
|
@ -2214,13 +2228,11 @@ void aa_put_buffer(char *buf)
|
|||
list_add(&aa_buf->list, &aa_global_buffers);
|
||||
buffer_count++;
|
||||
spin_unlock(&aa_buffers_lock);
|
||||
cache = get_cpu_ptr(&aa_local_buffers);
|
||||
put_cpu_ptr(&aa_local_buffers);
|
||||
return;
|
||||
}
|
||||
/* contention on global list, fallback to percpu */
|
||||
cache = get_cpu_ptr(&aa_local_buffers);
|
||||
cache->hold += 1;
|
||||
cache_hold_inc(&cache->hold);
|
||||
}
|
||||
|
||||
/* cache in percpu list */
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@
|
|||
#include <linux/vmalloc.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/kref.h>
|
||||
#include <linux/unaligned.h>
|
||||
|
||||
#include "include/lib.h"
|
||||
#include "include/match.h"
|
||||
|
|
@ -42,11 +43,11 @@ static struct table_header *unpack_table(char *blob, size_t bsize)
|
|||
/* loaded td_id's start at 1, subtract 1 now to avoid doing
|
||||
* it every time we use td_id as an index
|
||||
*/
|
||||
th.td_id = be16_to_cpu(*(__be16 *) (blob)) - 1;
|
||||
th.td_id = get_unaligned_be16(blob) - 1;
|
||||
if (th.td_id > YYTD_ID_MAX)
|
||||
goto out;
|
||||
th.td_flags = be16_to_cpu(*(__be16 *) (blob + 2));
|
||||
th.td_lolen = be32_to_cpu(*(__be32 *) (blob + 8));
|
||||
th.td_flags = get_unaligned_be16(blob + 2);
|
||||
th.td_lolen = get_unaligned_be32(blob + 8);
|
||||
blob += sizeof(struct table_header);
|
||||
|
||||
if (!(th.td_flags == YYTD_DATA16 || th.td_flags == YYTD_DATA32 ||
|
||||
|
|
@ -66,14 +67,13 @@ static struct table_header *unpack_table(char *blob, size_t bsize)
|
|||
table->td_flags = th.td_flags;
|
||||
table->td_lolen = th.td_lolen;
|
||||
if (th.td_flags == YYTD_DATA8)
|
||||
UNPACK_ARRAY(table->td_data, blob, th.td_lolen,
|
||||
u8, u8, byte_to_byte);
|
||||
memcpy(table->td_data, blob, th.td_lolen);
|
||||
else if (th.td_flags == YYTD_DATA16)
|
||||
UNPACK_ARRAY(table->td_data, blob, th.td_lolen,
|
||||
u16, __be16, be16_to_cpu);
|
||||
u16, __be16, get_unaligned_be16);
|
||||
else if (th.td_flags == YYTD_DATA32)
|
||||
UNPACK_ARRAY(table->td_data, blob, th.td_lolen,
|
||||
u32, __be32, be32_to_cpu);
|
||||
u32, __be32, get_unaligned_be32);
|
||||
else
|
||||
goto fail;
|
||||
/* if table was vmalloced make sure the page tables are synced
|
||||
|
|
@ -313,14 +313,14 @@ struct aa_dfa *aa_dfa_unpack(void *blob, size_t size, int flags)
|
|||
if (size < sizeof(struct table_set_header))
|
||||
goto fail;
|
||||
|
||||
if (ntohl(*(__be32 *) data) != YYTH_MAGIC)
|
||||
if (get_unaligned_be32(data) != YYTH_MAGIC)
|
||||
goto fail;
|
||||
|
||||
hsize = ntohl(*(__be32 *) (data + 4));
|
||||
hsize = get_unaligned_be32(data + 4);
|
||||
if (size < hsize)
|
||||
goto fail;
|
||||
|
||||
dfa->flags = ntohs(*(__be16 *) (data + 12));
|
||||
dfa->flags = get_unaligned_be16(data + 12);
|
||||
if (dfa->flags & ~(YYTH_FLAGS))
|
||||
goto fail;
|
||||
|
||||
|
|
@ -329,7 +329,7 @@ struct aa_dfa *aa_dfa_unpack(void *blob, size_t size, int flags)
|
|||
* if (dfa->flags & YYTH_FLAGS_OOB_TRANS) {
|
||||
* if (hsize < 16 + 4)
|
||||
* goto fail;
|
||||
* dfa->max_oob = ntol(*(__be32 *) (data + 16));
|
||||
* dfa->max_oob = get_unaligned_be32(data + 16);
|
||||
* if (dfa->max <= MAX_OOB_SUPPORTED) {
|
||||
* pr_err("AppArmor DFA OOB greater than supported\n");
|
||||
* goto fail;
|
||||
|
|
|
|||
|
|
@ -326,8 +326,10 @@ int aa_sock_file_perm(const struct cred *subj_cred, struct aa_label *label,
|
|||
struct socket *sock = (struct socket *) file->private_data;
|
||||
|
||||
AA_BUG(!label);
|
||||
AA_BUG(!sock);
|
||||
AA_BUG(!sock->sk);
|
||||
|
||||
/* sock && sock->sk can be NULL for sockets being set up or torn down */
|
||||
if (!sock || !sock->sk)
|
||||
return 0;
|
||||
|
||||
if (sock->sk->sk_family == PF_UNIX)
|
||||
return aa_unix_file_perm(subj_cred, label, op, request, file);
|
||||
|
|
|
|||
|
|
@ -164,12 +164,15 @@ static int d_namespace_path(const struct path *path, char *buf, char **name,
|
|||
}
|
||||
|
||||
out:
|
||||
/*
|
||||
* Append "/" to the pathname. The root directory is a special
|
||||
* case; it already ends in slash.
|
||||
/* Append "/" to directory paths, except for root "/" which
|
||||
* already ends in a slash.
|
||||
*/
|
||||
if (!error && isdir && ((*name)[1] != '\0' || (*name)[0] != '/'))
|
||||
strcpy(&buf[aa_g_path_max - 2], "/");
|
||||
if (!error && isdir) {
|
||||
bool is_root = (*name)[0] == '/' && (*name)[1] == '\0';
|
||||
|
||||
if (!is_root)
|
||||
buf[aa_g_path_max - 2] = '/';
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -98,13 +98,21 @@ const char *const aa_profile_mode_names[] = {
|
|||
"user",
|
||||
};
|
||||
|
||||
void aa_destroy_tags(struct aa_tags_struct *tags)
|
||||
{
|
||||
kfree_sensitive(tags->hdrs.table);
|
||||
kfree_sensitive(tags->sets.table);
|
||||
aa_destroy_str_table(&tags->strs);
|
||||
memset(tags, 0, sizeof(*tags));
|
||||
}
|
||||
|
||||
static void aa_free_pdb(struct aa_policydb *pdb)
|
||||
{
|
||||
if (pdb) {
|
||||
aa_put_dfa(pdb->dfa);
|
||||
kvfree(pdb->perms);
|
||||
aa_free_str_table(&pdb->trans);
|
||||
aa_destroy_str_table(&pdb->trans);
|
||||
aa_destroy_tags(&pdb->tags);
|
||||
kfree(pdb);
|
||||
}
|
||||
}
|
||||
|
|
@ -224,6 +232,9 @@ static void aa_free_data(void *ptr, void *arg)
|
|||
{
|
||||
struct aa_data *data = ptr;
|
||||
|
||||
if (!ptr)
|
||||
return;
|
||||
|
||||
kvfree_sensitive(data->data, data->size);
|
||||
kfree_sensitive(data->key);
|
||||
kfree_sensitive(data);
|
||||
|
|
@ -233,6 +244,9 @@ static void free_attachment(struct aa_attachment *attach)
|
|||
{
|
||||
int i;
|
||||
|
||||
if (!attach)
|
||||
return;
|
||||
|
||||
for (i = 0; i < attach->xattr_count; i++)
|
||||
kfree_sensitive(attach->xattrs[i]);
|
||||
kfree_sensitive(attach->xattrs);
|
||||
|
|
@ -697,24 +711,27 @@ struct aa_profile *aa_new_learning_profile(struct aa_profile *parent, bool hat,
|
|||
struct aa_profile *p, *profile;
|
||||
const char *bname;
|
||||
char *name = NULL;
|
||||
size_t name_sz;
|
||||
|
||||
AA_BUG(!parent);
|
||||
|
||||
if (base) {
|
||||
name = kmalloc(strlen(parent->base.hname) + 8 + strlen(base),
|
||||
gfp);
|
||||
name_sz = strlen(parent->base.hname) + 8 + strlen(base);
|
||||
name = kmalloc(name_sz, gfp);
|
||||
if (name) {
|
||||
sprintf(name, "%s//null-%s", parent->base.hname, base);
|
||||
snprintf(name, name_sz, "%s//null-%s",
|
||||
parent->base.hname, base);
|
||||
goto name;
|
||||
}
|
||||
/* fall through to try shorter uniq */
|
||||
}
|
||||
|
||||
name = kmalloc(strlen(parent->base.hname) + 2 + 7 + 8, gfp);
|
||||
name_sz = strlen(parent->base.hname) + 2 + 7 + 8;
|
||||
name = kmalloc(name_sz, gfp);
|
||||
if (!name)
|
||||
return NULL;
|
||||
sprintf(name, "%s//null-%x", parent->base.hname,
|
||||
atomic_inc_return(&parent->ns->uniq_null));
|
||||
snprintf(name, name_sz, "%s//null-%x", parent->base.hname,
|
||||
atomic_inc_return(&parent->ns->uniq_null));
|
||||
|
||||
name:
|
||||
/* lookup to see if this is a dup creation */
|
||||
|
|
|
|||
|
|
@ -263,9 +263,15 @@ static struct aa_perms *compute_perms(struct aa_dfa *dfa, u32 version,
|
|||
*size = state_count;
|
||||
|
||||
/* zero init so skip the trap state (state == 0) */
|
||||
for (state = 1; state < state_count; state++)
|
||||
for (state = 1; state < state_count; state++) {
|
||||
table[state] = compute_perms_entry(dfa, state, version);
|
||||
|
||||
AA_DEBUG(DEBUG_UNPACK,
|
||||
"[%d]: (0x%x/0x%x/0x%x//0x%x/0x%x//0x%x), converted from accept1: 0x%x, accept2: 0x%x",
|
||||
state, table[state].allow, table[state].deny,
|
||||
table[state].prompt, table[state].audit,
|
||||
table[state].quiet, table[state].xindex,
|
||||
ACCEPT_TABLE(dfa)[state], ACCEPT_TABLE2(dfa)[state]);
|
||||
}
|
||||
return table;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -450,20 +450,73 @@ static struct aa_dfa *unpack_dfa(struct aa_ext *e, int flags)
|
|||
return dfa;
|
||||
}
|
||||
|
||||
static int process_strs_entry(char *str, int size, bool multi)
|
||||
{
|
||||
int c = 1;
|
||||
|
||||
if (size <= 0)
|
||||
return -1;
|
||||
if (multi) {
|
||||
if (size < 2)
|
||||
return -2;
|
||||
/* multi ends with double \0 */
|
||||
if (str[size - 2])
|
||||
return -3;
|
||||
}
|
||||
|
||||
char *save = str;
|
||||
char *pos = str;
|
||||
char *end = multi ? str + size - 2 : str + size - 1;
|
||||
/* count # of internal \0 */
|
||||
while (str < end) {
|
||||
if (str == pos) {
|
||||
/* starts with ... */
|
||||
if (!*str) {
|
||||
AA_DEBUG(DEBUG_UNPACK,
|
||||
"starting with null save=%lu size %d c=%d",
|
||||
(unsigned long)(str - save), size, c);
|
||||
return -4;
|
||||
}
|
||||
if (isspace(*str))
|
||||
return -5;
|
||||
if (*str == ':') {
|
||||
/* :ns_str\0str\0
|
||||
* first character after : must be valid
|
||||
*/
|
||||
if (!str[1])
|
||||
return -6;
|
||||
}
|
||||
} else if (!*str) {
|
||||
if (*pos == ':')
|
||||
*str = ':';
|
||||
else
|
||||
c++;
|
||||
pos = str + 1;
|
||||
}
|
||||
str++;
|
||||
} /* while */
|
||||
|
||||
return c;
|
||||
}
|
||||
|
||||
/**
|
||||
* unpack_trans_table - unpack a profile transition table
|
||||
* unpack_strs_table - unpack a profile transition table
|
||||
* @e: serialized data extent information (NOT NULL)
|
||||
* @name: name of table (MAY BE NULL)
|
||||
* @multi: allow multiple strings on a single entry
|
||||
* @strs: str table to unpack to (NOT NULL)
|
||||
*
|
||||
* Returns: true if table successfully unpacked or not present
|
||||
* Returns: 0 if table successfully unpacked or not present, else error
|
||||
*/
|
||||
static bool unpack_trans_table(struct aa_ext *e, struct aa_str_table *strs)
|
||||
static int unpack_strs_table(struct aa_ext *e, const char *name, bool multi,
|
||||
struct aa_str_table *strs)
|
||||
{
|
||||
void *saved_pos = e->pos;
|
||||
char **table = NULL;
|
||||
struct aa_str_table_ent *table = NULL;
|
||||
int error = -EPROTO;
|
||||
|
||||
/* exec table is optional */
|
||||
if (aa_unpack_nameX(e, AA_STRUCT, "xtable")) {
|
||||
if (aa_unpack_nameX(e, AA_STRUCT, name)) {
|
||||
u16 size;
|
||||
int i;
|
||||
|
||||
|
|
@ -475,61 +528,47 @@ static bool unpack_trans_table(struct aa_ext *e, struct aa_str_table *strs)
|
|||
* for size check here
|
||||
*/
|
||||
goto fail;
|
||||
table = kcalloc(size, sizeof(char *), GFP_KERNEL);
|
||||
if (!table)
|
||||
table = kcalloc(size, sizeof(struct aa_str_table_ent),
|
||||
GFP_KERNEL);
|
||||
if (!table) {
|
||||
error = -ENOMEM;
|
||||
goto fail;
|
||||
|
||||
}
|
||||
strs->table = table;
|
||||
strs->size = size;
|
||||
for (i = 0; i < size; i++) {
|
||||
char *str;
|
||||
int c, j, pos, size2 = aa_unpack_strdup(e, &str, NULL);
|
||||
int c, size2 = aa_unpack_strdup(e, &str, NULL);
|
||||
/* aa_unpack_strdup verifies that the last character is
|
||||
* null termination byte.
|
||||
*/
|
||||
if (!size2)
|
||||
c = process_strs_entry(str, size2, multi);
|
||||
if (c <= 0) {
|
||||
AA_DEBUG(DEBUG_UNPACK, "process_strs %d i %d pos %ld",
|
||||
c, i,
|
||||
(unsigned long)(e->pos - saved_pos));
|
||||
goto fail;
|
||||
table[i] = str;
|
||||
/* verify that name doesn't start with space */
|
||||
if (isspace(*str))
|
||||
goto fail;
|
||||
|
||||
/* count internal # of internal \0 */
|
||||
for (c = j = 0; j < size2 - 1; j++) {
|
||||
if (!str[j]) {
|
||||
pos = j;
|
||||
c++;
|
||||
}
|
||||
}
|
||||
if (*str == ':') {
|
||||
/* first character after : must be valid */
|
||||
if (!str[1])
|
||||
goto fail;
|
||||
/* beginning with : requires an embedded \0,
|
||||
* verify that exactly 1 internal \0 exists
|
||||
* trailing \0 already verified by aa_unpack_strdup
|
||||
*
|
||||
* convert \0 back to : for label_parse
|
||||
*/
|
||||
if (c == 1)
|
||||
str[pos] = ':';
|
||||
else if (c > 1)
|
||||
goto fail;
|
||||
} else if (c)
|
||||
if (!multi && c > 1) {
|
||||
AA_DEBUG(DEBUG_UNPACK, "!multi && c > 1");
|
||||
/* fail - all other cases with embedded \0 */
|
||||
goto fail;
|
||||
}
|
||||
table[i].strs = str;
|
||||
table[i].count = c;
|
||||
table[i].size = size2;
|
||||
}
|
||||
if (!aa_unpack_nameX(e, AA_ARRAYEND, NULL))
|
||||
goto fail;
|
||||
if (!aa_unpack_nameX(e, AA_STRUCTEND, NULL))
|
||||
goto fail;
|
||||
}
|
||||
return true;
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
aa_free_str_table(strs);
|
||||
aa_destroy_str_table(strs);
|
||||
e->pos = saved_pos;
|
||||
return false;
|
||||
return error;
|
||||
}
|
||||
|
||||
static bool unpack_xattrs(struct aa_ext *e, struct aa_profile *profile)
|
||||
|
|
@ -644,6 +683,204 @@ fail:
|
|||
return false;
|
||||
}
|
||||
|
||||
|
||||
static bool verify_tags(struct aa_tags_struct *tags, const char **info)
|
||||
{
|
||||
if ((tags->hdrs.size && !tags->hdrs.table) ||
|
||||
(!tags->hdrs.size && tags->hdrs.table)) {
|
||||
*info = "failed verification tag.hdrs disagree";
|
||||
return false;
|
||||
}
|
||||
if ((tags->sets.size && !tags->sets.table) ||
|
||||
(!tags->sets.size && tags->sets.table)) {
|
||||
*info = "failed verification tag.sets disagree";
|
||||
return false;
|
||||
}
|
||||
if ((tags->strs.size && !tags->strs.table) ||
|
||||
(!tags->strs.size && tags->strs.table)) {
|
||||
*info = "failed verification tags->strs disagree";
|
||||
return false;
|
||||
}
|
||||
/* no data present */
|
||||
if (!tags->sets.size && !tags->hdrs.size && !tags->strs.size) {
|
||||
return true;
|
||||
} else if (!(tags->sets.size && tags->hdrs.size && tags->strs.size)) {
|
||||
/* some data present but not all */
|
||||
*info = "failed verification tags partial data present";
|
||||
return false;
|
||||
}
|
||||
|
||||
u32 i;
|
||||
|
||||
for (i = 0; i < tags->sets.size; i++) {
|
||||
/* count followed by count indexes into hdrs */
|
||||
u32 cnt = tags->sets.table[i];
|
||||
|
||||
if (i+cnt >= tags->sets.size) {
|
||||
AA_DEBUG(DEBUG_UNPACK,
|
||||
"tagset too large %d+%d > sets.table[%d]",
|
||||
i, cnt, tags->sets.size);
|
||||
*info = "failed verification tagset too large";
|
||||
return false;
|
||||
}
|
||||
for (; cnt; cnt--) {
|
||||
if (tags->sets.table[++i] >= tags->hdrs.size) {
|
||||
AA_DEBUG(DEBUG_UNPACK,
|
||||
"tagsets idx out of bounds cnt %d sets.table[%d] >= %d",
|
||||
cnt, i-1, tags->hdrs.size);
|
||||
*info = "failed verification tagsets idx out of bounds";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (i = 0; i < tags->hdrs.size; i++) {
|
||||
u32 idx = tags->hdrs.table[i].tags;
|
||||
|
||||
if (idx >= tags->strs.size) {
|
||||
AA_DEBUG(DEBUG_UNPACK,
|
||||
"tag.hdrs idx oob idx %d > tags->strs.size=%d",
|
||||
idx, tags->strs.size);
|
||||
*info = "failed verification tags.hdrs idx out of bounds";
|
||||
return false;
|
||||
}
|
||||
if (tags->hdrs.table[i].count != tags->strs.table[idx].count) {
|
||||
AA_DEBUG(DEBUG_UNPACK, "hdrs.table[%d].count=%d != tags->strs.table[%d]=%d",
|
||||
i, tags->hdrs.table[i].count, idx, tags->strs.table[idx].count);
|
||||
*info = "failed verification tagd.hdrs[idx].count";
|
||||
return false;
|
||||
}
|
||||
if (tags->hdrs.table[i].size != tags->strs.table[idx].size) {
|
||||
AA_DEBUG(DEBUG_UNPACK, "hdrs.table[%d].size=%d != strs.table[%d].size=%d",
|
||||
i, tags->hdrs.table[i].size, idx, tags->strs.table[idx].size);
|
||||
*info = "failed verification tagd.hdrs[idx].size";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static int unpack_tagsets(struct aa_ext *e, struct aa_tags_struct *tags)
|
||||
{
|
||||
u32 *sets;
|
||||
u16 i, size;
|
||||
int error = -EPROTO;
|
||||
void *pos = e->pos;
|
||||
|
||||
if (!aa_unpack_array(e, "sets", &size))
|
||||
goto fail_reset;
|
||||
sets = kcalloc(size, sizeof(u32), GFP_KERNEL);
|
||||
if (!sets) {
|
||||
error = -ENOMEM;
|
||||
goto fail_reset;
|
||||
}
|
||||
for (i = 0; i < size; i++) {
|
||||
if (!aa_unpack_u32(e, &sets[i], NULL))
|
||||
goto fail;
|
||||
}
|
||||
if (!aa_unpack_nameX(e, AA_ARRAYEND, NULL))
|
||||
goto fail;
|
||||
|
||||
tags->sets.size = size;
|
||||
tags->sets.table = sets;
|
||||
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
kfree_sensitive(sets);
|
||||
fail_reset:
|
||||
e->pos = pos;
|
||||
return error;
|
||||
}
|
||||
|
||||
static bool unpack_tag_header_ent(struct aa_ext *e, struct aa_tags_header *h)
|
||||
{
|
||||
return aa_unpack_u32(e, &h->mask, NULL) &&
|
||||
aa_unpack_u32(e, &h->count, NULL) &&
|
||||
aa_unpack_u32(e, &h->size, NULL) &&
|
||||
aa_unpack_u32(e, &h->tags, NULL);
|
||||
}
|
||||
|
||||
static int unpack_tag_headers(struct aa_ext *e, struct aa_tags_struct *tags)
|
||||
{
|
||||
struct aa_tags_header *hdrs;
|
||||
u16 i, size;
|
||||
int error = -EPROTO;
|
||||
void *pos = e->pos;
|
||||
|
||||
if (!aa_unpack_array(e, "hdrs", &size))
|
||||
goto fail_reset;
|
||||
hdrs = kcalloc(size, sizeof(struct aa_tags_header), GFP_KERNEL);
|
||||
if (!hdrs) {
|
||||
error = -ENOMEM;
|
||||
goto fail_reset;
|
||||
}
|
||||
for (i = 0; i < size; i++) {
|
||||
if (!unpack_tag_header_ent(e, &hdrs[i]))
|
||||
goto fail;
|
||||
}
|
||||
if (!aa_unpack_nameX(e, AA_ARRAYEND, NULL))
|
||||
goto fail;
|
||||
|
||||
tags->hdrs.size = size;
|
||||
tags->hdrs.table = hdrs;
|
||||
AA_DEBUG(DEBUG_UNPACK, "headers %ld size %d", (long) hdrs, size);
|
||||
return true;
|
||||
|
||||
fail:
|
||||
kfree_sensitive(hdrs);
|
||||
fail_reset:
|
||||
e->pos = pos;
|
||||
return error;
|
||||
}
|
||||
|
||||
|
||||
static int unpack_tags(struct aa_ext *e, struct aa_tags_struct *tags,
|
||||
const char **info)
|
||||
{
|
||||
int error = -EPROTO;
|
||||
void *pos = e->pos;
|
||||
|
||||
AA_BUG(!tags);
|
||||
/* policy tags are optional */
|
||||
if (aa_unpack_nameX(e, AA_STRUCT, "tags")) {
|
||||
u32 version;
|
||||
|
||||
if (!aa_unpack_u32(e, &version, "version") || version != 1) {
|
||||
*info = "invalid tags version";
|
||||
goto fail_reset;
|
||||
}
|
||||
error = unpack_strs_table(e, "strs", true, &tags->strs);
|
||||
if (error) {
|
||||
*info = "failed to unpack profile tag.strs";
|
||||
goto fail;
|
||||
}
|
||||
error = unpack_tag_headers(e, tags);
|
||||
if (error) {
|
||||
*info = "failed to unpack profile tag.headers";
|
||||
goto fail;
|
||||
}
|
||||
error = unpack_tagsets(e, tags);
|
||||
if (error) {
|
||||
*info = "failed to unpack profile tag.sets";
|
||||
goto fail;
|
||||
}
|
||||
if (!aa_unpack_nameX(e, AA_STRUCTEND, NULL))
|
||||
goto fail;
|
||||
|
||||
if (!verify_tags(tags, info))
|
||||
goto fail;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
aa_destroy_tags(tags);
|
||||
fail_reset:
|
||||
e->pos = pos;
|
||||
return error;
|
||||
}
|
||||
|
||||
static bool unpack_perm(struct aa_ext *e, u32 version, struct aa_perms *perm)
|
||||
{
|
||||
u32 reserved;
|
||||
|
|
@ -687,8 +924,10 @@ static ssize_t unpack_perms_table(struct aa_ext *e, struct aa_perms **perms)
|
|||
if (!aa_unpack_array(e, NULL, &size))
|
||||
goto fail_reset;
|
||||
*perms = kcalloc(size, sizeof(struct aa_perms), GFP_KERNEL);
|
||||
if (!*perms)
|
||||
goto fail_reset;
|
||||
if (!*perms) {
|
||||
e->pos = pos;
|
||||
return -ENOMEM;
|
||||
}
|
||||
for (i = 0; i < size; i++) {
|
||||
if (!unpack_perm(e, version, &(*perms)[i]))
|
||||
goto fail;
|
||||
|
|
@ -723,6 +962,11 @@ static int unpack_pdb(struct aa_ext *e, struct aa_policydb **policy,
|
|||
if (!pdb)
|
||||
return -ENOMEM;
|
||||
|
||||
AA_DEBUG(DEBUG_UNPACK, "unpacking tags");
|
||||
if (unpack_tags(e, &pdb->tags, info) < 0)
|
||||
goto fail;
|
||||
AA_DEBUG(DEBUG_UNPACK, "done unpacking tags");
|
||||
|
||||
size = unpack_perms_table(e, &pdb->perms);
|
||||
if (size < 0) {
|
||||
error = size;
|
||||
|
|
@ -795,13 +1039,14 @@ static int unpack_pdb(struct aa_ext *e, struct aa_policydb **policy,
|
|||
* transition table may be present even when the dfa is
|
||||
* not. For compatibility reasons unpack and discard.
|
||||
*/
|
||||
if (!unpack_trans_table(e, &pdb->trans) && required_trans) {
|
||||
error = unpack_strs_table(e, "xtable", false, &pdb->trans);
|
||||
if (error && required_trans) {
|
||||
*info = "failed to unpack profile transition table";
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (!pdb->dfa && pdb->trans.table)
|
||||
aa_free_str_table(&pdb->trans);
|
||||
aa_destroy_str_table(&pdb->trans);
|
||||
|
||||
/* TODO:
|
||||
* - move compat mapping here, requires dfa merging first
|
||||
|
|
@ -1058,6 +1303,7 @@ static struct aa_profile *unpack_profile(struct aa_ext *e, char **ns_name)
|
|||
goto fail;
|
||||
} else if (rules->file->dfa) {
|
||||
if (!rules->file->perms) {
|
||||
AA_DEBUG(DEBUG_UNPACK, "compat mapping perms");
|
||||
error = aa_compat_map_file(rules->file);
|
||||
if (error) {
|
||||
info = "failed to remap file permission table";
|
||||
|
|
@ -1260,7 +1506,7 @@ static bool verify_perms(struct aa_policydb *pdb)
|
|||
if (xmax < xidx)
|
||||
xmax = xidx;
|
||||
}
|
||||
if (pdb->perms[i].tag && pdb->perms[i].tag >= pdb->trans.size)
|
||||
if (pdb->perms[i].tag && pdb->perms[i].tag >= pdb->tags.sets.size)
|
||||
return false;
|
||||
if (pdb->perms[i].label &&
|
||||
pdb->perms[i].label >= pdb->trans.size)
|
||||
|
|
@ -1268,7 +1514,7 @@ static bool verify_perms(struct aa_policydb *pdb)
|
|||
}
|
||||
/* deal with incorrectly constructed string tables */
|
||||
if (xmax == -1) {
|
||||
aa_free_str_table(&pdb->trans);
|
||||
aa_destroy_str_table(&pdb->trans);
|
||||
} else if (pdb->trans.size > xmax + 1) {
|
||||
if (!aa_resize_str_table(&pdb->trans, xmax + 1, GFP_KERNEL))
|
||||
return false;
|
||||
|
|
|
|||
|
|
@ -196,6 +196,11 @@ void __aa_transition_rlimits(struct aa_label *old_l, struct aa_label *new_l)
|
|||
rules->rlimits.limits[j].rlim_max);
|
||||
/* soft limit should not exceed hard limit */
|
||||
rlim->rlim_cur = min(rlim->rlim_cur, rlim->rlim_max);
|
||||
if (j == RLIMIT_CPU &&
|
||||
rlim->rlim_cur != RLIM_INFINITY &&
|
||||
IS_ENABLED(CONFIG_POSIX_TIMERS))
|
||||
(void) update_rlimit_cpu(current->group_leader,
|
||||
rlim->rlim_cur);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@
|
|||
#include <linux/gfp.h>
|
||||
#include <linux/ptrace.h>
|
||||
|
||||
#include "include/path.h"
|
||||
#include "include/audit.h"
|
||||
#include "include/cred.h"
|
||||
#include "include/policy.h"
|
||||
|
|
@ -300,16 +301,47 @@ int aa_may_ptrace(const struct cred *tracer_cred, struct aa_label *tracer,
|
|||
xrequest, &sa));
|
||||
}
|
||||
|
||||
static const char *get_current_exe_path(char *buffer, int buffer_size)
|
||||
{
|
||||
struct file *exe_file;
|
||||
struct path p;
|
||||
const char *path_str;
|
||||
|
||||
exe_file = get_task_exe_file(current);
|
||||
if (!exe_file)
|
||||
return ERR_PTR(-ENOENT);
|
||||
p = exe_file->f_path;
|
||||
path_get(&p);
|
||||
|
||||
if (aa_path_name(&p, FLAG_VIEW_SUBNS, buffer, &path_str, NULL, NULL))
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
fput(exe_file);
|
||||
path_put(&p);
|
||||
|
||||
return path_str;
|
||||
}
|
||||
|
||||
/* call back to audit ptrace fields */
|
||||
static void audit_ns_cb(struct audit_buffer *ab, void *va)
|
||||
{
|
||||
struct apparmor_audit_data *ad = aad_of_va(va);
|
||||
char *buffer;
|
||||
const char *path;
|
||||
|
||||
if (ad->request & AA_USERNS_CREATE)
|
||||
audit_log_format(ab, " requested=\"userns_create\"");
|
||||
|
||||
if (ad->denied & AA_USERNS_CREATE)
|
||||
audit_log_format(ab, " denied=\"userns_create\"");
|
||||
|
||||
buffer = aa_get_buffer(false);
|
||||
if (!buffer)
|
||||
return; // OOM
|
||||
path = get_current_exe_path(buffer, aa_g_path_max);
|
||||
if (!IS_ERR(path))
|
||||
audit_log_format(ab, " execpath=\"%s\"", path);
|
||||
aa_put_buffer(buffer);
|
||||
}
|
||||
|
||||
int aa_profile_ns_perm(struct aa_profile *profile,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue