Skip to content

Commit 8e51f90

Browse files
mjg59jrjohansen
authored andcommitted
apparmor: Add support for attaching profiles via xattr, presence and value
Make it possible to tie Apparmor profiles to the presence of one or more extended attributes, and optionally their values. An example usecase for this is to automatically transition to a more privileged Apparmor profile if an executable has a valid IMA signature, which can then be appraised by the IMA subsystem. Signed-off-by: Matthew Garrett <[email protected]> Signed-off-by: John Johansen <[email protected]>
1 parent a078120 commit 8e51f90

File tree

4 files changed

+217
-34
lines changed

4 files changed

+217
-34
lines changed

security/apparmor/domain.c

Lines changed: 127 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
#include <linux/syscalls.h>
2020
#include <linux/tracehook.h>
2121
#include <linux/personality.h>
22+
#include <linux/xattr.h>
2223

2324
#include "include/audit.h"
2425
#include "include/apparmorfs.h"
@@ -301,8 +302,57 @@ static int change_profile_perms(struct aa_profile *profile,
301302
return label_match(profile, target, stack, start, true, request, perms);
302303
}
303304

305+
/**
306+
* aa_xattrs_match - check whether a file matches the xattrs defined in profile
307+
* @bprm: binprm struct for the process to validate
308+
* @profile: profile to match against (NOT NULL)
309+
*
310+
* Returns: number of extended attributes that matched, or < 0 on error
311+
*/
312+
static int aa_xattrs_match(const struct linux_binprm *bprm,
313+
struct aa_profile *profile)
314+
{
315+
int i;
316+
size_t size;
317+
struct dentry *d;
318+
char *value = NULL;
319+
int value_size = 0, ret = profile->xattr_count;
320+
321+
if (!bprm || !profile->xattr_count)
322+
return 0;
323+
324+
d = bprm->file->f_path.dentry;
325+
326+
for (i = 0; i < profile->xattr_count; i++) {
327+
size = vfs_getxattr_alloc(d, profile->xattrs[i], &value,
328+
value_size, GFP_KERNEL);
329+
if (size < 0) {
330+
ret = -EINVAL;
331+
goto out;
332+
}
333+
334+
/* Check the xattr value, not just presence */
335+
if (profile->xattr_lens[i]) {
336+
if (profile->xattr_lens[i] != size) {
337+
ret = -EINVAL;
338+
goto out;
339+
}
340+
341+
if (memcmp(value, profile->xattr_values[i], size)) {
342+
ret = -EINVAL;
343+
goto out;
344+
}
345+
}
346+
}
347+
348+
out:
349+
kfree(value);
350+
return ret;
351+
}
352+
304353
/**
305354
* __attach_match_ - find an attachment match
355+
* @bprm - binprm structure of transitioning task
306356
* @name - to match against (NOT NULL)
307357
* @head - profile list to walk (NOT NULL)
308358
* @info - info message if there was an error (NOT NULL)
@@ -316,11 +366,12 @@ static int change_profile_perms(struct aa_profile *profile,
316366
*
317367
* Returns: profile or NULL if no match found
318368
*/
319-
static struct aa_profile *__attach_match(const char *name,
369+
static struct aa_profile *__attach_match(const struct linux_binprm *bprm,
370+
const char *name,
320371
struct list_head *head,
321372
const char **info)
322373
{
323-
int len = 0;
374+
int len = 0, xattrs = 0;
324375
bool conflict = false;
325376
struct aa_profile *profile, *candidate = NULL;
326377

@@ -329,26 +380,56 @@ static struct aa_profile *__attach_match(const char *name,
329380
&profile->label == ns_unconfined(profile->ns))
330381
continue;
331382

383+
/* Find the "best" matching profile. Profiles must
384+
* match the path and extended attributes (if any)
385+
* associated with the file. A more specific path
386+
* match will be preferred over a less specific one,
387+
* and a match with more matching extended attributes
388+
* will be preferred over one with fewer. If the best
389+
* match has both the same level of path specificity
390+
* and the same number of matching extended attributes
391+
* as another profile, signal a conflict and refuse to
392+
* match.
393+
*/
332394
if (profile->xmatch) {
333-
if (profile->xmatch_len >= len) {
334-
unsigned int state;
335-
u32 perm;
336-
337-
state = aa_dfa_match(profile->xmatch,
338-
DFA_START, name);
339-
perm = dfa_user_allow(profile->xmatch, state);
340-
/* any accepting state means a valid match. */
341-
if (perm & MAY_EXEC) {
342-
if (profile->xmatch_len == len) {
395+
unsigned int state;
396+
u32 perm;
397+
398+
if (profile->xmatch_len < len)
399+
continue;
400+
401+
state = aa_dfa_match(profile->xmatch,
402+
DFA_START, name);
403+
perm = dfa_user_allow(profile->xmatch, state);
404+
/* any accepting state means a valid match. */
405+
if (perm & MAY_EXEC) {
406+
int ret = aa_xattrs_match(bprm, profile);
407+
408+
/* Fail matching if the xattrs don't match */
409+
if (ret < 0)
410+
continue;
411+
412+
/* The new match isn't more specific
413+
* than the current best match
414+
*/
415+
if (profile->xmatch_len == len &&
416+
ret <= xattrs) {
417+
/* Match is equivalent, so conflict */
418+
if (ret == xattrs)
343419
conflict = true;
344-
continue;
345-
}
346-
candidate = profile;
347-
len = profile->xmatch_len;
348-
conflict = false;
420+
continue;
349421
}
422+
423+
/* Either the same length with more matching
424+
* xattrs, or a longer match
425+
*/
426+
candidate = profile;
427+
len = profile->xmatch_len;
428+
xattrs = ret;
429+
conflict = false;
350430
}
351-
} else if (!strcmp(profile->base.name, name))
431+
} else if (!strcmp(profile->base.name, name) &&
432+
aa_xattrs_match(bprm, profile) >= 0)
352433
/* exact non-re match, no more searching required */
353434
return profile;
354435
}
@@ -363,20 +444,22 @@ static struct aa_profile *__attach_match(const char *name,
363444

364445
/**
365446
* find_attach - do attachment search for unconfined processes
447+
* @bprm - binprm structure of transitioning task
366448
* @ns: the current namespace (NOT NULL)
367449
* @list: list to search (NOT NULL)
368450
* @name: the executable name to match against (NOT NULL)
369451
* @info: info message if there was an error
370452
*
371453
* Returns: label or NULL if no match found
372454
*/
373-
static struct aa_label *find_attach(struct aa_ns *ns, struct list_head *list,
455+
static struct aa_label *find_attach(const struct linux_binprm *bprm,
456+
struct aa_ns *ns, struct list_head *list,
374457
const char *name, const char **info)
375458
{
376459
struct aa_profile *profile;
377460

378461
rcu_read_lock();
379-
profile = aa_get_profile(__attach_match(name, list, info));
462+
profile = aa_get_profile(__attach_match(bprm, name, list, info));
380463
rcu_read_unlock();
381464

382465
return profile ? &profile->label : NULL;
@@ -432,6 +515,7 @@ struct aa_label *x_table_lookup(struct aa_profile *profile, u32 xindex,
432515
/**
433516
* x_to_label - get target label for a given xindex
434517
* @profile: current profile (NOT NULL)
518+
* @bprm: binprm structure of transitioning task
435519
* @name: name to lookup (NOT NULL)
436520
* @xindex: index into x transition table
437521
* @lookupname: returns: name used in lookup if one was specified (NOT NULL)
@@ -441,6 +525,7 @@ struct aa_label *x_table_lookup(struct aa_profile *profile, u32 xindex,
441525
* Returns: refcounted label or NULL if not found available
442526
*/
443527
static struct aa_label *x_to_label(struct aa_profile *profile,
528+
const struct linux_binprm *bprm,
444529
const char *name, u32 xindex,
445530
const char **lookupname,
446531
const char **info)
@@ -468,11 +553,11 @@ static struct aa_label *x_to_label(struct aa_profile *profile,
468553
case AA_X_NAME:
469554
if (xindex & AA_X_CHILD)
470555
/* released by caller */
471-
new = find_attach(ns, &profile->base.profiles,
556+
new = find_attach(bprm, ns, &profile->base.profiles,
472557
name, info);
473558
else
474559
/* released by caller */
475-
new = find_attach(ns, &ns->base.profiles,
560+
new = find_attach(bprm, ns, &ns->base.profiles,
476561
name, info);
477562
*lookupname = name;
478563
break;
@@ -512,6 +597,8 @@ static struct aa_label *profile_transition(struct aa_profile *profile,
512597
bool *secure_exec)
513598
{
514599
struct aa_label *new = NULL;
600+
struct aa_profile *component;
601+
struct label_it i;
515602
const char *info = NULL, *name = NULL, *target = NULL;
516603
unsigned int state = profile->file.start;
517604
struct aa_perms perms = {};
@@ -536,8 +623,8 @@ static struct aa_label *profile_transition(struct aa_profile *profile,
536623
}
537624

538625
if (profile_unconfined(profile)) {
539-
new = find_attach(profile->ns, &profile->ns->base.profiles,
540-
name, &info);
626+
new = find_attach(bprm, profile->ns,
627+
&profile->ns->base.profiles, name, &info);
541628
if (new) {
542629
AA_DEBUG("unconfined attached to new label");
543630
return new;
@@ -550,7 +637,8 @@ static struct aa_label *profile_transition(struct aa_profile *profile,
550637
state = aa_str_perms(profile->file.dfa, state, name, cond, &perms);
551638
if (perms.allow & MAY_EXEC) {
552639
/* exec permission determine how to transition */
553-
new = x_to_label(profile, name, perms.xindex, &target, &info);
640+
new = x_to_label(profile, bprm, name, perms.xindex, &target,
641+
&info);
554642
if (new && new->proxy == profile->label.proxy && info) {
555643
/* hack ix fallback - improve how this is detected */
556644
goto audit;
@@ -559,6 +647,20 @@ static struct aa_label *profile_transition(struct aa_profile *profile,
559647
info = "profile transition not found";
560648
/* remove MAY_EXEC to audit as failure */
561649
perms.allow &= ~MAY_EXEC;
650+
} else {
651+
/* verify that each component's xattr requirements are
652+
* met, and fail execution otherwise
653+
*/
654+
label_for_each(i, new, component) {
655+
if (aa_xattrs_match(bprm, component) < 0) {
656+
error = -EACCES;
657+
info = "required xattrs not present";
658+
perms.allow &= ~MAY_EXEC;
659+
aa_put_label(new);
660+
new = NULL;
661+
goto audit;
662+
}
663+
}
562664
}
563665
} else if (COMPLAIN_MODE(profile)) {
564666
/* no exec permission - learning mode */

security/apparmor/include/policy.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,12 @@ struct aa_profile {
148148
struct aa_policydb policy;
149149
struct aa_file_rules file;
150150
struct aa_caps caps;
151+
152+
int xattr_count;
153+
char **xattrs;
154+
size_t *xattr_lens;
155+
char **xattr_values;
156+
151157
struct aa_rlimit rlimits;
152158

153159
struct aa_loaddata *rawdata;

security/apparmor/policy.c

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,7 @@ static void aa_free_data(void *ptr, void *arg)
210210
void aa_free_profile(struct aa_profile *profile)
211211
{
212212
struct rhashtable *rht;
213+
int i;
213214

214215
AA_DEBUG("%s(%p)\n", __func__, profile);
215216

@@ -227,6 +228,13 @@ void aa_free_profile(struct aa_profile *profile)
227228
aa_free_cap_rules(&profile->caps);
228229
aa_free_rlimit_rules(&profile->rlimits);
229230

231+
for (i = 0; i < profile->xattr_count; i++) {
232+
kzfree(profile->xattrs[i]);
233+
kzfree(profile->xattr_values[i]);
234+
}
235+
kzfree(profile->xattrs);
236+
kzfree(profile->xattr_lens);
237+
kzfree(profile->xattr_values);
230238
kzfree(profile->dirname);
231239
aa_put_dfa(profile->xmatch);
232240
aa_put_dfa(profile->policy.dfa);

0 commit comments

Comments
 (0)