mirror of
https://gitlab.com/apparmor/apparmor
synced 2025-09-05 08:45:22 +00:00
746 lines
22 KiB
Diff
746 lines
22 KiB
Diff
---
|
|
security/apparmor/apparmor.h | 14 -
|
|
security/apparmor/apparmorfs.c | 2
|
|
security/apparmor/inline.h | 14 -
|
|
security/apparmor/main.c | 490 +++++++++++++++++++----------------
|
|
security/apparmor/match.c | 9
|
|
security/apparmor/module_interface.c | 10
|
|
6 files changed, 309 insertions(+), 230 deletions(-)
|
|
|
|
--- a/security/apparmor/apparmor.h
|
|
+++ b/security/apparmor/apparmor.h
|
|
@@ -44,8 +44,7 @@
|
|
AA_EXEC_MOD_2 | AA_EXEC_MOD_3 | \
|
|
AA_EXEC_MOD_4)
|
|
|
|
-#define AA_EXEC_TYPE (MAY_EXEC | AA_EXEC_UNSAFE | \
|
|
- AA_EXEC_MODIFIERS)
|
|
+#define AA_EXEC_TYPE (AA_EXEC_UNSAFE | AA_EXEC_MODIFIERS)
|
|
|
|
#define AA_EXEC_UNCONFINED AA_EXEC_MOD_0
|
|
#define AA_EXEC_INHERIT AA_EXEC_MOD_1
|
|
@@ -85,6 +84,10 @@
|
|
AA_AUDIT_FIELD)
|
|
|
|
#define AA_VALID_PERM_MASK (AA_FILE_PERMS | AA_SHARED_PERMS)
|
|
+
|
|
+/* audit bits for the second accept field */
|
|
+#define AUDIT_FILE_MASK 0x1fc07f
|
|
+#define AUDIT_QUIET_MASK(mask) ((mask >> 7) & AUDIT_FILE_MASK)
|
|
#define AA_VALID_PERM2_MASK 0x0fffffff
|
|
|
|
#define AA_SECURE_EXEC_NEEDED 1
|
|
@@ -179,6 +182,9 @@ struct aa_profile {
|
|
int isstale;
|
|
|
|
kernel_cap_t capabilities;
|
|
+ kernel_cap_t audit_caps;
|
|
+ kernel_cap_t quiet_caps;
|
|
+
|
|
struct kref count;
|
|
struct list_head task_contexts;
|
|
spinlock_t lock;
|
|
@@ -226,7 +232,7 @@ struct aa_audit {
|
|
const char *name;
|
|
const char *name2;
|
|
const char *name3;
|
|
- int request_mask, denied_mask;
|
|
+ int request_mask, denied_mask, audit_mask;
|
|
struct iattr *iattr;
|
|
pid_t task, parent;
|
|
int error_code;
|
|
@@ -331,7 +337,7 @@ extern struct aa_dfa *aa_match_alloc(voi
|
|
extern void aa_match_free(struct aa_dfa *dfa);
|
|
extern int unpack_dfa(struct aa_dfa *dfa, void *blob, size_t size);
|
|
extern int verify_dfa(struct aa_dfa *dfa);
|
|
-extern unsigned int aa_dfa_match(struct aa_dfa *dfa, const char *str);
|
|
+extern unsigned int aa_dfa_match(struct aa_dfa *dfa, const char *str, int *);
|
|
extern unsigned int aa_dfa_next_state(struct aa_dfa *dfa, unsigned int start,
|
|
const char *str);
|
|
extern unsigned int aa_match_state(struct aa_dfa *dfa, unsigned int start,
|
|
--- a/security/apparmor/apparmorfs.c
|
|
+++ b/security/apparmor/apparmorfs.c
|
|
@@ -89,7 +89,7 @@ static struct file_operations apparmorfs
|
|
static ssize_t aa_matching_read(struct file *file, char __user *buf,
|
|
size_t size, loff_t *ppos)
|
|
{
|
|
- const char *matching = "pattern=aadfa perms=rwxamlk/ user::other";
|
|
+ const char *matching = "pattern=aadfa audit perms=rwxamlk/ user::other";
|
|
|
|
return simple_read_from_buffer(buf, size, ppos, matching,
|
|
strlen(matching));
|
|
--- a/security/apparmor/inline.h
|
|
+++ b/security/apparmor/inline.h
|
|
@@ -232,9 +232,19 @@ static inline void unlock_both_profiles(
|
|
}
|
|
}
|
|
|
|
-static inline unsigned int aa_match(struct aa_dfa *dfa, const char *pathname)
|
|
+static inline unsigned int aa_match(struct aa_dfa *dfa, const char *pathname,
|
|
+ int *audit_mask)
|
|
{
|
|
- return dfa ? aa_dfa_match(dfa, pathname) : 0;
|
|
+ if (dfa)
|
|
+ return aa_dfa_match(dfa, pathname, audit_mask);
|
|
+ if (audit_mask)
|
|
+ *audit_mask = 0;
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static inline int dfa_audit_mask(struct aa_dfa *dfa, unsigned int state)
|
|
+{
|
|
+ return ACCEPT_TABLE2(dfa)[state];
|
|
}
|
|
|
|
#endif /* __INLINE_H__ */
|
|
--- a/security/apparmor/main.c
|
|
+++ b/security/apparmor/main.c
|
|
@@ -36,204 +36,6 @@ static int aa_inode_mode(struct inode *i
|
|
return AA_OTHER_SHIFT;
|
|
}
|
|
|
|
-/**
|
|
- * aa_file_denied - check for @mask access on a file
|
|
- * @profile: profile to check against
|
|
- * @name: pathname of file
|
|
- * @mask: permission mask requested for file
|
|
- *
|
|
- * Return %0 on success, or else the permissions in @mask that the
|
|
- * profile denies.
|
|
- */
|
|
-static int aa_file_denied(struct aa_profile *profile, const char *name,
|
|
- int mask)
|
|
-{
|
|
- return (mask & ~aa_match(profile->file_rules, name));
|
|
-}
|
|
-
|
|
-/**
|
|
- * aa_link_denied - check for permission to link a file
|
|
- * @profile: profile to check against
|
|
- * @link: pathname of link being created
|
|
- * @target: pathname of target to be linked to
|
|
- * @target_mode: UGO shift for target inode
|
|
- * @request_mask: the permissions subset valid only if link succeeds
|
|
- * Return %0 on success, or else the permissions that the profile denies.
|
|
- */
|
|
-static int aa_link_denied(struct aa_profile *profile, const char *link,
|
|
- const char *target, int target_mode,
|
|
- int *request_mask)
|
|
-{
|
|
- unsigned int state;
|
|
- int l_mode, t_mode, denied_mask = 0;
|
|
- int link_mask = AA_MAY_LINK << target_mode;
|
|
-
|
|
- *request_mask = link_mask;
|
|
-
|
|
- l_mode = aa_match_state(profile->file_rules, DFA_START, link, &state);
|
|
- if (l_mode & link_mask) {
|
|
- int mode;
|
|
- /* test to see if target can be paired with link */
|
|
- state = aa_dfa_null_transition(profile->file_rules, state);
|
|
- mode = aa_match_state(profile->file_rules, state, target,
|
|
- NULL);
|
|
-
|
|
- if (!(mode & link_mask))
|
|
- denied_mask |= link_mask;
|
|
- /* return if link subset test is not required */
|
|
- if (!(mode & (AA_LINK_SUBSET_TEST << target_mode)))
|
|
- return denied_mask;
|
|
- }
|
|
-
|
|
- /* Do link perm subset test
|
|
- * If a subset test is required a permission subset test of the
|
|
- * perms for the link are done against the user::other of the
|
|
- * target's 'r', 'w', 'x', 'a', 'k', and 'm' permissions.
|
|
- *
|
|
- * If the link has 'x', an exact match of all the execute flags
|
|
- * must match.
|
|
- */
|
|
- denied_mask |= ~l_mode & link_mask;
|
|
-
|
|
- t_mode = aa_match(profile->file_rules, target);
|
|
-
|
|
- /* For actual subset test ignore valid-profile-transition flags,
|
|
- * and link bits
|
|
- */
|
|
- l_mode &= AA_FILE_PERMS & ~AA_LINK_BITS;
|
|
- t_mode &= AA_FILE_PERMS & ~AA_LINK_BITS;
|
|
-
|
|
- *request_mask = l_mode | link_mask;
|
|
-
|
|
- if (l_mode) {
|
|
- denied_mask |= l_mode & ~t_mode;
|
|
- if ((l_mode & AA_EXEC_BITS) &&
|
|
- (l_mode & ALL_AA_EXEC_TYPE) !=
|
|
- (t_mode & ALL_AA_EXEC_TYPE))
|
|
- denied_mask = (denied_mask & ~ALL_AA_EXEC_TYPE) |
|
|
- (l_mode & ALL_AA_EXEC_TYPE);
|
|
- }
|
|
-
|
|
- return denied_mask;
|
|
-}
|
|
-
|
|
-/**
|
|
- * aa_get_name - compute the pathname of a file
|
|
- * @dentry: dentry of the file
|
|
- * @mnt: vfsmount of the file
|
|
- * @buffer: buffer that aa_get_name() allocated
|
|
- * @check: AA_CHECK_DIR is set if the file is a directory
|
|
- *
|
|
- * Returns a pointer to the beginning of the pathname (which usually differs
|
|
- * from the beginning of the buffer), or an error code.
|
|
- *
|
|
- * We need @check to indicate whether the file is a directory or not because
|
|
- * the file may not yet exist, and so we cannot check the inode's file type.
|
|
- */
|
|
-static char *aa_get_name(struct dentry *dentry, struct vfsmount *mnt,
|
|
- char **buffer, int check)
|
|
-{
|
|
- char *name;
|
|
- int is_dir, size = 256;
|
|
-
|
|
- is_dir = (check & AA_CHECK_DIR) ? 1 : 0;
|
|
-
|
|
- for (;;) {
|
|
- char *buf = kmalloc(size, GFP_KERNEL);
|
|
- if (!buf)
|
|
- return ERR_PTR(-ENOMEM);
|
|
-
|
|
- name = d_namespace_path(dentry, mnt, buf, size - is_dir);
|
|
- if (!IS_ERR(name)) {
|
|
- if (name[0] != '/') {
|
|
- /*
|
|
- * This dentry is not connected to the
|
|
- * namespace root -- reject access.
|
|
- */
|
|
- kfree(buf);
|
|
- return ERR_PTR(-ENOENT);
|
|
- }
|
|
- if (is_dir && name[1] != '\0') {
|
|
- /*
|
|
- * Append "/" to the pathname. The root
|
|
- * directory is a special case; it already
|
|
- * ends in slash.
|
|
- */
|
|
- buf[size - 2] = '/';
|
|
- buf[size - 1] = '\0';
|
|
- }
|
|
-
|
|
- *buffer = buf;
|
|
- return name;
|
|
- }
|
|
- if (PTR_ERR(name) != -ENAMETOOLONG)
|
|
- return name;
|
|
-
|
|
- kfree(buf);
|
|
- size <<= 1;
|
|
- if (size > apparmor_path_max)
|
|
- return ERR_PTR(-ENAMETOOLONG);
|
|
- }
|
|
-}
|
|
-
|
|
-static inline void aa_put_name_buffer(char *buffer)
|
|
-{
|
|
- kfree(buffer);
|
|
-}
|
|
-
|
|
-/**
|
|
- * aa_perm_dentry - check if @profile allows @mask for a file
|
|
- * @profile: profile to check against
|
|
- * @dentry: dentry of the file
|
|
- * @mnt: vfsmount o the file
|
|
- * @sa: audit context
|
|
- * @mask: requested profile permissions
|
|
- * @check: kind of check to perform
|
|
- *
|
|
- * Returns 0 upon success, or else an error code.
|
|
- *
|
|
- * @check indicates the file type, and whether the file was accessed through
|
|
- * an open file descriptor (AA_CHECK_FD) or not.
|
|
- */
|
|
-static int aa_perm_dentry(struct aa_profile *profile, struct dentry *dentry,
|
|
- struct vfsmount *mnt, struct aa_audit *sa, int check)
|
|
-{
|
|
- int error;
|
|
- char *buffer = NULL;
|
|
-
|
|
- sa->name = aa_get_name(dentry, mnt, &buffer, check);
|
|
- sa->request_mask <<= aa_inode_mode(dentry->d_inode);
|
|
- if (IS_ERR(sa->name)) {
|
|
- /*
|
|
- * deleted files are given a pass on permission checks when
|
|
- * accessed through a file descriptor.
|
|
- */
|
|
- if (PTR_ERR(sa->name) == -ENOENT && (check & AA_CHECK_FD))
|
|
- sa->denied_mask = 0;
|
|
- else {
|
|
- sa->denied_mask = sa->request_mask;
|
|
- sa->error_code = PTR_ERR(sa->name);
|
|
- if (sa->error_code == -ENOENT)
|
|
- sa->info = "Failed name resolution - object not a valid entry";
|
|
- else if (sa->error_code == -ENAMETOOLONG)
|
|
- sa->info = "Failed name resolution - name too long";
|
|
- else
|
|
- sa->info = "Failed name resolution";
|
|
- }
|
|
- sa->name = NULL;
|
|
- } else
|
|
- sa->denied_mask = aa_file_denied(profile, sa->name,
|
|
- sa->request_mask);
|
|
-
|
|
- if (!sa->denied_mask)
|
|
- sa->error_code = 0;
|
|
-
|
|
- error = aa_audit(profile, sa);
|
|
- aa_put_name_buffer(buffer);
|
|
-
|
|
- return error;
|
|
-}
|
|
-
|
|
int alloc_default_namespace(void)
|
|
{
|
|
struct aa_namespace *ns;
|
|
@@ -471,20 +273,259 @@ int aa_audit(struct aa_profile *profile,
|
|
int type = AUDIT_APPARMOR_DENIED;
|
|
struct audit_context *audit_cxt;
|
|
|
|
- if (likely(!sa->error_code)) {
|
|
- if (likely(!PROFILE_AUDIT(profile)))
|
|
- /* nothing to log */
|
|
- return 0;
|
|
- else
|
|
- type = AUDIT_APPARMOR_AUDIT;
|
|
- } else if (PROFILE_COMPLAIN(profile)) {
|
|
+ if (likely(!sa->error_code))
|
|
+ type = AUDIT_APPARMOR_AUDIT;
|
|
+ else if (PROFILE_COMPLAIN(profile))
|
|
type = AUDIT_APPARMOR_ALLOWED;
|
|
- }
|
|
|
|
audit_cxt = apparmor_logsyscall ? current->audit_context : NULL;
|
|
return aa_audit_base(profile, sa, audit_cxt, type);
|
|
}
|
|
|
|
+static int aa_audit_file(struct aa_profile *profile, struct aa_audit *sa)
|
|
+{
|
|
+ if (likely(!sa->error_code)) {
|
|
+ int mask = sa->audit_mask & AUDIT_FILE_MASK;
|
|
+
|
|
+ if (unlikely(PROFILE_AUDIT(profile)))
|
|
+ mask |= AUDIT_FILE_MASK;
|
|
+
|
|
+ if (likely(!(sa->request_mask & mask)))
|
|
+ return 0;
|
|
+
|
|
+ /* mask off perms that are not being force audited */
|
|
+ sa->request_mask &= mask | ALL_AA_EXEC_TYPE;
|
|
+ } else {
|
|
+ int mask = AUDIT_QUIET_MASK(sa->audit_mask);
|
|
+
|
|
+ if (!(sa->denied_mask & ~mask))
|
|
+ return sa->error_code;
|
|
+
|
|
+ /* mask off perms whose denial is being silenced */
|
|
+ sa->denied_mask &= (~mask) | ALL_AA_EXEC_TYPE;
|
|
+ }
|
|
+
|
|
+ return aa_audit(profile, sa);
|
|
+}
|
|
+
|
|
+static int aa_audit_caps(struct aa_profile *profile, struct aa_audit *sa,
|
|
+ int cap)
|
|
+{
|
|
+ if (likely(!sa->error_code)) {
|
|
+ if (likely(!PROFILE_AUDIT(profile) &&
|
|
+ !cap_raised(profile->audit_caps, cap)))
|
|
+ return 0;
|
|
+ }
|
|
+
|
|
+ /* quieting of capabilities is handled the caps_logged cache */
|
|
+ return aa_audit(profile, sa);
|
|
+}
|
|
+
|
|
+/**
|
|
+ * aa_file_denied - check for @mask access on a file
|
|
+ * @profile: profile to check against
|
|
+ * @name: pathname of file
|
|
+ * @mask: permission mask requested for file
|
|
+ * @audit_mask: return audit mask for the match
|
|
+ *
|
|
+ * Return %0 on success, or else the permissions in @mask that the
|
|
+ * profile denies.
|
|
+ */
|
|
+static int aa_file_denied(struct aa_profile *profile, const char *name,
|
|
+ int mask, int *audit_mask)
|
|
+{
|
|
+ return (mask & ~aa_match(profile->file_rules, name, audit_mask));
|
|
+}
|
|
+
|
|
+/**
|
|
+ * aa_link_denied - check for permission to link a file
|
|
+ * @profile: profile to check against
|
|
+ * @link: pathname of link being created
|
|
+ * @target: pathname of target to be linked to
|
|
+ * @target_mode: UGO shift for target inode
|
|
+ * @request_mask: the permissions subset valid only if link succeeds
|
|
+ * @audit_mask: return the audit_mask for the link permission
|
|
+ * Return %0 on success, or else the permissions that the profile denies.
|
|
+ */
|
|
+static int aa_link_denied(struct aa_profile *profile, const char *link,
|
|
+ const char *target, int target_mode,
|
|
+ int *request_mask, int *audit_mask)
|
|
+{
|
|
+ unsigned int state;
|
|
+ int l_mode, t_mode, denied_mask = 0;
|
|
+ int link_mask = AA_MAY_LINK << target_mode;
|
|
+
|
|
+ *request_mask = link_mask;
|
|
+
|
|
+ l_mode = aa_match_state(profile->file_rules, DFA_START, link, &state);
|
|
+
|
|
+ if (l_mode & link_mask) {
|
|
+ int mode;
|
|
+ /* test to see if target can be paired with link */
|
|
+ state = aa_dfa_null_transition(profile->file_rules, state);
|
|
+ mode = aa_match_state(profile->file_rules, state, target,
|
|
+ &state);
|
|
+
|
|
+ if (!(mode & link_mask))
|
|
+ denied_mask |= link_mask;
|
|
+
|
|
+ *audit_mask = dfa_audit_mask(profile->file_rules, state);
|
|
+
|
|
+ /* return if link subset test is not required */
|
|
+ if (!(mode & (AA_LINK_SUBSET_TEST << target_mode)))
|
|
+ return denied_mask;
|
|
+ }
|
|
+
|
|
+ /* Do link perm subset test
|
|
+ * If a subset test is required a permission subset test of the
|
|
+ * perms for the link are done against the user::other of the
|
|
+ * target's 'r', 'w', 'x', 'a', 'k', and 'm' permissions.
|
|
+ *
|
|
+ * If the link has 'x', an exact match of all the execute flags
|
|
+ * must match.
|
|
+ */
|
|
+ denied_mask |= ~l_mode & link_mask;
|
|
+
|
|
+ t_mode = aa_match(profile->file_rules, target, NULL);
|
|
+
|
|
+ /* For actual subset test ignore valid-profile-transition flags,
|
|
+ * and link bits
|
|
+ */
|
|
+ l_mode &= AA_FILE_PERMS & ~AA_LINK_BITS;
|
|
+ t_mode &= AA_FILE_PERMS & ~AA_LINK_BITS;
|
|
+
|
|
+ *request_mask = l_mode | link_mask;
|
|
+
|
|
+ if (l_mode) {
|
|
+ denied_mask |= l_mode & ~t_mode;
|
|
+ if ((l_mode & AA_EXEC_BITS) &&
|
|
+ (l_mode & ALL_AA_EXEC_TYPE) !=
|
|
+ (t_mode & ALL_AA_EXEC_TYPE))
|
|
+ denied_mask = (denied_mask & ~ALL_AA_EXEC_TYPE) |
|
|
+ (l_mode & (ALL_AA_EXEC_TYPE | AA_EXEC_BITS));
|
|
+ }
|
|
+
|
|
+ return denied_mask;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * aa_get_name - compute the pathname of a file
|
|
+ * @dentry: dentry of the file
|
|
+ * @mnt: vfsmount of the file
|
|
+ * @buffer: buffer that aa_get_name() allocated
|
|
+ * @check: AA_CHECK_DIR is set if the file is a directory
|
|
+ *
|
|
+ * Returns a pointer to the beginning of the pathname (which usually differs
|
|
+ * from the beginning of the buffer), or an error code.
|
|
+ *
|
|
+ * We need @check to indicate whether the file is a directory or not because
|
|
+ * the file may not yet exist, and so we cannot check the inode's file type.
|
|
+ */
|
|
+static char *aa_get_name(struct dentry *dentry, struct vfsmount *mnt,
|
|
+ char **buffer, int check)
|
|
+{
|
|
+ char *name;
|
|
+ int is_dir, size = 256;
|
|
+
|
|
+ is_dir = (check & AA_CHECK_DIR) ? 1 : 0;
|
|
+
|
|
+ for (;;) {
|
|
+ char *buf = kmalloc(size, GFP_KERNEL);
|
|
+ if (!buf)
|
|
+ return ERR_PTR(-ENOMEM);
|
|
+
|
|
+ name = d_namespace_path(dentry, mnt, buf, size - is_dir);
|
|
+ if (!IS_ERR(name)) {
|
|
+ if (name[0] != '/') {
|
|
+ /*
|
|
+ * This dentry is not connected to the
|
|
+ * namespace root -- reject access.
|
|
+ */
|
|
+ kfree(buf);
|
|
+ return ERR_PTR(-ENOENT);
|
|
+ }
|
|
+ if (is_dir && name[1] != '\0') {
|
|
+ /*
|
|
+ * Append "/" to the pathname. The root
|
|
+ * directory is a special case; it already
|
|
+ * ends in slash.
|
|
+ */
|
|
+ buf[size - 2] = '/';
|
|
+ buf[size - 1] = '\0';
|
|
+ }
|
|
+
|
|
+ *buffer = buf;
|
|
+ return name;
|
|
+ }
|
|
+ if (PTR_ERR(name) != -ENAMETOOLONG)
|
|
+ return name;
|
|
+
|
|
+ kfree(buf);
|
|
+ size <<= 1;
|
|
+ if (size > apparmor_path_max)
|
|
+ return ERR_PTR(-ENAMETOOLONG);
|
|
+ }
|
|
+}
|
|
+
|
|
+static inline void aa_put_name_buffer(char *buffer)
|
|
+{
|
|
+ kfree(buffer);
|
|
+}
|
|
+
|
|
+/**
|
|
+ * aa_perm_dentry - check if @profile allows @mask for a file
|
|
+ * @profile: profile to check against
|
|
+ * @dentry: dentry of the file
|
|
+ * @mnt: vfsmount o the file
|
|
+ * @sa: audit context
|
|
+ * @mask: requested profile permissions
|
|
+ * @check: kind of check to perform
|
|
+ *
|
|
+ * Returns 0 upon success, or else an error code.
|
|
+ *
|
|
+ * @check indicates the file type, and whether the file was accessed through
|
|
+ * an open file descriptor (AA_CHECK_FD) or not.
|
|
+ */
|
|
+static int aa_perm_dentry(struct aa_profile *profile, struct dentry *dentry,
|
|
+ struct vfsmount *mnt, struct aa_audit *sa, int check)
|
|
+{
|
|
+ int error;
|
|
+ char *buffer = NULL;
|
|
+
|
|
+ sa->name = aa_get_name(dentry, mnt, &buffer, check);
|
|
+ sa->request_mask <<= aa_inode_mode(dentry->d_inode);
|
|
+ if (IS_ERR(sa->name)) {
|
|
+ /*
|
|
+ * deleted files are given a pass on permission checks when
|
|
+ * accessed through a file descriptor.
|
|
+ */
|
|
+ if (PTR_ERR(sa->name) == -ENOENT && (check & AA_CHECK_FD))
|
|
+ sa->denied_mask = 0;
|
|
+ else {
|
|
+ sa->denied_mask = sa->request_mask;
|
|
+ sa->error_code = PTR_ERR(sa->name);
|
|
+ if (sa->error_code == -ENOENT)
|
|
+ sa->info = "Failed name resolution - object not a valid entry";
|
|
+ else if (sa->error_code == -ENAMETOOLONG)
|
|
+ sa->info = "Failed name resolution - name too long";
|
|
+ else
|
|
+ sa->info = "Failed name resolution";
|
|
+ }
|
|
+ sa->name = NULL;
|
|
+ } else
|
|
+ sa->denied_mask = aa_file_denied(profile, sa->name,
|
|
+ sa->request_mask,
|
|
+ &sa->audit_mask);
|
|
+
|
|
+ if (!sa->denied_mask)
|
|
+ sa->error_code = 0;
|
|
+
|
|
+ error = aa_audit_file(profile, sa);
|
|
+ aa_put_name_buffer(buffer);
|
|
+
|
|
+ return error;
|
|
+}
|
|
+
|
|
/**
|
|
* aa_attr - check if attribute change is allowed
|
|
* @profile: profile to check against
|
|
@@ -621,10 +662,11 @@ int aa_perm_path(struct aa_profile *prof
|
|
else
|
|
sa.request_mask = mask << AA_OTHER_SHIFT;
|
|
|
|
- sa.denied_mask = aa_file_denied(profile, name, sa.request_mask) ;
|
|
+ sa.denied_mask = aa_file_denied(profile, name, sa.request_mask,
|
|
+ &sa.audit_mask) ;
|
|
sa.error_code = sa.denied_mask ? -EACCES : 0;
|
|
|
|
- return aa_audit(profile, &sa);
|
|
+ return aa_audit_file(profile, &sa);
|
|
}
|
|
|
|
/**
|
|
@@ -660,7 +702,7 @@ int aa_capability(struct aa_task_context
|
|
sa.name = capability_names[cap];
|
|
sa.error_code = error;
|
|
|
|
- error = aa_audit(cxt->profile, &sa);
|
|
+ error = aa_audit_caps(cxt->profile, &sa, cap);
|
|
|
|
return error;
|
|
}
|
|
@@ -709,11 +751,12 @@ int aa_link(struct aa_profile *profile,
|
|
if (sa.name && sa.name2) {
|
|
sa.denied_mask = aa_link_denied(profile, sa.name, sa.name2,
|
|
aa_inode_mode(target->d_inode),
|
|
- &sa.request_mask);
|
|
+ &sa.request_mask,
|
|
+ &sa.audit_mask);
|
|
sa.error_code = sa.denied_mask ? -EACCES : 0;
|
|
}
|
|
|
|
- error = aa_audit(profile, &sa);
|
|
+ error = aa_audit_file(profile, &sa);
|
|
|
|
aa_put_name_buffer(buffer);
|
|
aa_put_name_buffer(buffer2);
|
|
@@ -802,8 +845,8 @@ aa_register_find(struct aa_profile *prof
|
|
new_profile =
|
|
aa_dup_profile(profile->ns->null_complain_profile);
|
|
} else {
|
|
- aa_audit_reject(profile, sa);
|
|
- return ERR_PTR(-EACCES); /* was -EPERM */
|
|
+ sa->error_code = -EACCES;
|
|
+ return ERR_PTR(aa_audit_file(profile, sa));
|
|
}
|
|
} else {
|
|
/* Only way we can get into this code is if task
|
|
@@ -863,7 +906,8 @@ repeat:
|
|
/* Confined task, determine what mode inherit, unconfined or
|
|
* mandatory to load new profile
|
|
*/
|
|
- exec_mode = aa_match(profile->file_rules, filename);
|
|
+ exec_mode = aa_match(profile->file_rules, filename,
|
|
+ &sa.audit_mask);
|
|
|
|
if (exec_mode & sa.request_mask) {
|
|
switch ((exec_mode >> shift) & AA_EXEC_MODIFIERS) {
|
|
@@ -906,6 +950,9 @@ repeat:
|
|
break;
|
|
}
|
|
|
|
+ } else if (sa.request_mask & AUDIT_QUIET_MASK(sa.audit_mask)) {
|
|
+ /* quiet failed exit */
|
|
+ new_profile = ERR_PTR(-EACCES);
|
|
} else if (complain) {
|
|
/* There was no entry in calling profile
|
|
* describing mode to execute image in.
|
|
@@ -916,8 +963,8 @@ repeat:
|
|
exec_mode |= AA_EXEC_UNSAFE << shift;
|
|
} else {
|
|
sa.denied_mask = sa.request_mask;
|
|
- aa_audit_reject(profile, &sa);
|
|
- new_profile = ERR_PTR(-EPERM);
|
|
+ sa.error_code = -EACCES;
|
|
+ new_profile = ERR_PTR(aa_audit_file(profile, &sa));
|
|
}
|
|
} else {
|
|
/* Unconfined task, load profile if it exists */
|
|
@@ -973,6 +1020,7 @@ repeat:
|
|
sa.info = "set profile";
|
|
aa_audit_hint(new_profile, &sa);
|
|
}
|
|
+
|
|
cleanup:
|
|
aa_put_name_buffer(buffer);
|
|
if (IS_ERR(new_profile))
|
|
@@ -1149,7 +1197,7 @@ repeat:
|
|
|
|
if (PROFILE_COMPLAIN(profile) ||
|
|
(ns == profile->ns &&
|
|
- (aa_match(profile->file_rules, name) & AA_CHANGE_PROFILE)))
|
|
+ (aa_match(profile->file_rules, name, NULL) & AA_CHANGE_PROFILE)))
|
|
error = do_change_profile(profile, ns, name, 0, 0, &sa);
|
|
else {
|
|
/* check for a rule with a namespace prepended */
|
|
@@ -1356,9 +1404,11 @@ void aa_change_task_context(struct task_
|
|
call_rcu(&old_cxt->rcu, free_aa_task_context_rcu_callback);
|
|
}
|
|
if (new_cxt) {
|
|
- /* clear the caps_logged cache, so that new profile/hat has
|
|
- * chance to emit its own set of cap messages */
|
|
- new_cxt->caps_logged = CAP_EMPTY_SET;
|
|
+ /* set the caps_logged cache to the quiet_caps mask
|
|
+ * this has the effect of quieting caps that are not
|
|
+ * supposed to be logged
|
|
+ */
|
|
+ new_cxt->caps_logged = profile->quiet_caps;
|
|
new_cxt->cookie = cookie;
|
|
new_cxt->task = task;
|
|
new_cxt->profile = aa_dup_profile(profile);
|
|
--- a/security/apparmor/match.c
|
|
+++ b/security/apparmor/match.c
|
|
@@ -14,6 +14,7 @@
|
|
#include <linux/errno.h>
|
|
#include "apparmor.h"
|
|
#include "match.h"
|
|
+#include "inline.h"
|
|
|
|
static struct table_header *unpack_table(void *blob, size_t bsize)
|
|
{
|
|
@@ -295,13 +296,17 @@ unsigned int aa_dfa_null_transition(stru
|
|
* aa_dfa_match - find accept perm for @str in @dfa
|
|
* @dfa: the dfa to match @str against
|
|
* @str: the string to match against the dfa
|
|
+ * @audit_mask: the audit_mask for the final state
|
|
*
|
|
* aa_dfa_match will match @str and return the accept perms for the
|
|
* final state.
|
|
*/
|
|
-unsigned int aa_dfa_match(struct aa_dfa *dfa, const char *str)
|
|
+unsigned int aa_dfa_match(struct aa_dfa *dfa, const char *str, int *audit_mask)
|
|
{
|
|
- return ACCEPT_TABLE(dfa)[aa_dfa_next_state(dfa, DFA_START, str)];
|
|
+ int state = aa_dfa_next_state(dfa, DFA_START, str);
|
|
+ if (audit_mask)
|
|
+ *audit_mask = dfa_audit_mask(dfa, state);
|
|
+ return ACCEPT_TABLE(dfa)[state];
|
|
}
|
|
|
|
/**
|
|
--- a/security/apparmor/module_interface.c
|
|
+++ b/security/apparmor/module_interface.c
|
|
@@ -310,6 +310,10 @@ static struct aa_profile *aa_unpack_prof
|
|
|
|
if (!aa_is_u32(e, &(profile->capabilities), NULL))
|
|
goto fail;
|
|
+ if (!aa_is_u32(e, &(profile->audit_caps), NULL))
|
|
+ goto fail;
|
|
+ if (!aa_is_u32(e, &(profile->quiet_caps), NULL))
|
|
+ goto fail;
|
|
|
|
/* get file rules */
|
|
profile->file_rules = aa_unpack_dfa(e);
|
|
@@ -317,6 +321,10 @@ static struct aa_profile *aa_unpack_prof
|
|
error = PTR_ERR(profile->file_rules);
|
|
profile->file_rules = NULL;
|
|
goto fail;
|
|
+ if (!aa_is_u16(e, &profile->audit_network[i], NULL))
|
|
+ goto fail;
|
|
+ if (!aa_is_u16(e, &profile->quiet_network[i], NULL))
|
|
+ goto fail;
|
|
}
|
|
|
|
if (!aa_is_nameX(e, AA_STRUCTEND, NULL))
|
|
@@ -360,7 +368,7 @@ static int aa_verify_header(struct aa_ex
|
|
}
|
|
|
|
/* check that the interface version is currently supported */
|
|
- if (e->version != 3) {
|
|
+ if (e->version != 4) {
|
|
struct aa_audit sa;
|
|
memset(&sa, 0, sizeof(sa));
|
|
sa.operation = operation;
|