From 3bb857e47e49f72352249c938aa8b9159d636530 Mon Sep 17 00:00:00 2001 From: Mickaël Salaün Date: Wed, 10 May 2017 22:48:48 +0200 Subject: LSM: Enable multiple calls to security_add_hooks() for the same LSM MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The commit d69dece5f5b6 ("LSM: Add /sys/kernel/security/lsm") extend security_add_hooks() with a new parameter to register the LSM name, which may be useful to make the list of currently loaded LSM available to userspace. However, there is no clean way for an LSM to split its hook declarations into multiple files, which may reduce the mess with all the included files (needed for LSM hook argument types) and make the source code easier to review and maintain. This change allows an LSM to register multiple times its hook while keeping a consistent list of LSM names as described in Documentation/security/LSM.txt . The list reflects the order in which checks are made. This patch only check for the last registered LSM. If an LSM register multiple times its hooks, interleaved with other LSM registrations (which should not happen), its name will still appear in the same order that the hooks are called, hence multiple times. To sum up, "capability,selinux,foo,foo" will be replaced with "capability,selinux,foo", however "capability,foo,selinux,foo" will remain as is. Signed-off-by: Mickaël Salaün Acked-by: Kees Cook Acked-by: Casey Schaufler Signed-off-by: James Morris --- security/security.c | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/security/security.c b/security/security.c index 549bddcc2116..54b1e395978a 100644 --- a/security/security.c +++ b/security/security.c @@ -25,6 +25,7 @@ #include #include #include +#include #include #define MAX_LSM_EVM_XATTR 2 @@ -86,6 +87,21 @@ static int __init choose_lsm(char *str) } __setup("security=", choose_lsm); +static bool match_last_lsm(const char *list, const char *lsm) +{ + const char *last; + + if (WARN_ON(!list || !lsm)) + return false; + last = strrchr(list, ','); + if (last) + /* Pass the comma, strcmp() will check for '\0' */ + last++; + else + last = list; + return !strcmp(last, lsm); +} + static int lsm_append(char *new, char **result) { char *cp; @@ -93,6 +109,9 @@ static int lsm_append(char *new, char **result) if (*result == NULL) { *result = kstrdup(new, GFP_KERNEL); } else { + /* Check if it is the last registered name */ + if (match_last_lsm(*result, new)) + return 0; cp = kasprintf(GFP_KERNEL, "%s,%s", *result, new); if (cp == NULL) return -ENOMEM; -- cgit v1.2.3 From 99c55fb18fc48508ae5bba57146a556aacc4558c Mon Sep 17 00:00:00 2001 From: Geert Uytterhoeven Date: Tue, 2 May 2017 20:27:41 +0200 Subject: security: Grammar s/allocates/allocated/ Signed-off-by: Geert Uytterhoeven Signed-off-by: James Morris --- security/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/security/Kconfig b/security/Kconfig index 3ff1bf91080e..823ca1aafd09 100644 --- a/security/Kconfig +++ b/security/Kconfig @@ -148,7 +148,7 @@ config HARDENED_USERCOPY copying memory to/from the kernel (via copy_to_user() and copy_from_user() functions) by rejecting memory ranges that are larger than the specified heap object, span multiple - separately allocates pages, are not on the process stack, + separately allocated pages, are not on the process stack, or are part of the kernel text. This kills entire classes of heap overflow exploits and similar kernel memory exposures. -- cgit v1.2.3 From a79be238600d1a0319a77b080b762d03c1d253ca Mon Sep 17 00:00:00 2001 From: Tetsuo Handa Date: Tue, 28 Mar 2017 23:08:45 +0900 Subject: selinux: Use task_alloc hook rather than task_create hook This patch is a preparation for getting rid of task_create hook because task_alloc hook which can do what task_create hook can do was revived. Creating a new thread is unlikely prohibited by security policy, for fork()/execve()/exit() is fundamental of how processes are managed in Unix. If a program is known to create a new thread, it is likely that permission to create a new thread is given to that program. Therefore, a situation where security_task_create() returns an error is likely that the program was exploited and lost control. Even if SELinux failed to check permission to create a thread at security_task_create(), SELinux can later check it at security_task_alloc(). Since the new thread is not yet visible from the rest of the system, nobody can do bad things using the new thread. What we waste will be limited to some initialization steps such as dup_task_struct(), copy_creds() and audit_alloc() in copy_process(). We can tolerate these overhead for unlikely situation. Therefore, this patch changes SELinux to use task_alloc hook rather than task_create hook so that we can remove task_create hook. Signed-off-by: Tetsuo Handa Acked-by: Stephen Smalley Signed-off-by: Paul Moore --- security/selinux/hooks.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index e67a526d1f30..735609b19e76 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -3710,7 +3710,8 @@ static int selinux_file_open(struct file *file, const struct cred *cred) /* task security operations */ -static int selinux_task_create(unsigned long clone_flags) +static int selinux_task_alloc(struct task_struct *task, + unsigned long clone_flags) { u32 sid = current_sid(); @@ -6213,7 +6214,7 @@ static struct security_hook_list selinux_hooks[] __lsm_ro_after_init = { LSM_HOOK_INIT(file_open, selinux_file_open), - LSM_HOOK_INIT(task_create, selinux_task_create), + LSM_HOOK_INIT(task_alloc, selinux_task_alloc), LSM_HOOK_INIT(cred_alloc_blank, selinux_cred_alloc_blank), LSM_HOOK_INIT(cred_free, selinux_cred_free), LSM_HOOK_INIT(cred_prepare, selinux_cred_prepare), -- cgit v1.2.3 From 62934ffb9e5f9a904c83f571590631b766d68d12 Mon Sep 17 00:00:00 2001 From: Markus Elfring Date: Tue, 4 Apr 2017 10:20:46 +0200 Subject: selinux: Return directly after a failed memory allocation in policydb_index() Replace five goto statements (and previous variable assignments) by direct returns after a memory allocation failure in this function. Signed-off-by: Markus Elfring Signed-off-by: Paul Moore --- security/selinux/ss/policydb.c | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/security/selinux/ss/policydb.c b/security/selinux/ss/policydb.c index 0080122760ad..87d645d3a39f 100644 --- a/security/selinux/ss/policydb.c +++ b/security/selinux/ss/policydb.c @@ -538,34 +538,30 @@ static int policydb_index(struct policydb *p) symtab_hash_eval(p->symtab); #endif - rc = -ENOMEM; p->class_val_to_struct = kcalloc(p->p_classes.nprim, sizeof(*p->class_val_to_struct), GFP_KERNEL); if (!p->class_val_to_struct) - goto out; + return -ENOMEM; - rc = -ENOMEM; p->role_val_to_struct = kcalloc(p->p_roles.nprim, sizeof(*p->role_val_to_struct), GFP_KERNEL); if (!p->role_val_to_struct) - goto out; + return -ENOMEM; - rc = -ENOMEM; p->user_val_to_struct = kcalloc(p->p_users.nprim, sizeof(*p->user_val_to_struct), GFP_KERNEL); if (!p->user_val_to_struct) - goto out; + return -ENOMEM; /* Yes, I want the sizeof the pointer, not the structure */ - rc = -ENOMEM; p->type_val_to_struct_array = flex_array_alloc(sizeof(struct type_datum *), p->p_types.nprim, GFP_KERNEL | __GFP_ZERO); if (!p->type_val_to_struct_array) - goto out; + return -ENOMEM; rc = flex_array_prealloc(p->type_val_to_struct_array, 0, p->p_types.nprim, GFP_KERNEL | __GFP_ZERO); @@ -577,12 +573,11 @@ static int policydb_index(struct policydb *p) goto out; for (i = 0; i < SYM_NUM; i++) { - rc = -ENOMEM; p->sym_val_to_name[i] = flex_array_alloc(sizeof(char *), p->symtab[i].nprim, GFP_KERNEL | __GFP_ZERO); if (!p->sym_val_to_name[i]) - goto out; + return -ENOMEM; rc = flex_array_prealloc(p->sym_val_to_name[i], 0, p->symtab[i].nprim, -- cgit v1.2.3 From 46be14d2b6fbc20c9e7008ec8c28b40609ef6f22 Mon Sep 17 00:00:00 2001 From: Markus Elfring Date: Tue, 4 Apr 2017 11:33:53 +0200 Subject: selinux: Return an error code only as a constant in sidtab_insert() * Return an error code without storing it in an intermediate variable. * Delete the local variable "rc" and the jump label "out" which became unnecessary with this refactoring. Signed-off-by: Markus Elfring Signed-off-by: Paul Moore --- security/selinux/ss/sidtab.c | 27 ++++++++++----------------- 1 file changed, 10 insertions(+), 17 deletions(-) diff --git a/security/selinux/ss/sidtab.c b/security/selinux/ss/sidtab.c index f6915f257486..c5f436b15d19 100644 --- a/security/selinux/ss/sidtab.c +++ b/security/selinux/ss/sidtab.c @@ -32,13 +32,11 @@ int sidtab_init(struct sidtab *s) int sidtab_insert(struct sidtab *s, u32 sid, struct context *context) { - int hvalue, rc = 0; + int hvalue; struct sidtab_node *prev, *cur, *newnode; - if (!s) { - rc = -ENOMEM; - goto out; - } + if (!s) + return -ENOMEM; hvalue = SIDTAB_HASH(sid); prev = NULL; @@ -48,21 +46,17 @@ int sidtab_insert(struct sidtab *s, u32 sid, struct context *context) cur = cur->next; } - if (cur && sid == cur->sid) { - rc = -EEXIST; - goto out; - } + if (cur && sid == cur->sid) + return -EEXIST; newnode = kmalloc(sizeof(*newnode), GFP_ATOMIC); - if (!newnode) { - rc = -ENOMEM; - goto out; - } + if (!newnode) + return -ENOMEM; + newnode->sid = sid; if (context_cpy(&newnode->context, context)) { kfree(newnode); - rc = -ENOMEM; - goto out; + return -ENOMEM; } if (prev) { @@ -78,8 +72,7 @@ int sidtab_insert(struct sidtab *s, u32 sid, struct context *context) s->nel++; if (sid >= s->next_sid) s->next_sid = sid + 1; -out: - return rc; + return 0; } static struct context *sidtab_search_core(struct sidtab *s, u32 sid, int force) -- cgit v1.2.3 From db59000ab760f8d77b07b7f2898ff61110f88607 Mon Sep 17 00:00:00 2001 From: Stephen Smalley Date: Thu, 20 Apr 2017 11:31:30 -0400 Subject: selinux: only invoke capabilities and selinux for CAP_MAC_ADMIN checks SELinux uses CAP_MAC_ADMIN to control the ability to get or set a raw, uninterpreted security context unknown to the currently loaded security policy. When performing these checks, we only want to perform a base capabilities check and a SELinux permission check. If any other modules that implement a capable hook are stacked with SELinux, we do not want to require them to also have to authorize CAP_MAC_ADMIN, since it may have different implications for their security model. Rework the CAP_MAC_ADMIN checks within SELinux to only invoke the capabilities module and the SELinux permission checking. Signed-off-by: Stephen Smalley Signed-off-by: Paul Moore --- security/selinux/hooks.c | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index 735609b19e76..dddb81e06d2d 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -3107,6 +3107,18 @@ static int selinux_inode_setotherxattr(struct dentry *dentry, const char *name) return dentry_has_perm(cred, dentry, FILE__SETATTR); } +static bool has_cap_mac_admin(bool audit) +{ + const struct cred *cred = current_cred(); + int cap_audit = audit ? SECURITY_CAP_AUDIT : SECURITY_CAP_NOAUDIT; + + if (cap_capable(cred, &init_user_ns, CAP_MAC_ADMIN, cap_audit)) + return false; + if (cred_has_capability(cred, CAP_MAC_ADMIN, cap_audit, true)) + return false; + return true; +} + static int selinux_inode_setxattr(struct dentry *dentry, const char *name, const void *value, size_t size, int flags) { @@ -3138,7 +3150,7 @@ static int selinux_inode_setxattr(struct dentry *dentry, const char *name, rc = security_context_to_sid(value, size, &newsid, GFP_KERNEL); if (rc == -EINVAL) { - if (!capable(CAP_MAC_ADMIN)) { + if (!has_cap_mac_admin(true)) { struct audit_buffer *ab; size_t audit_size; const char *str; @@ -3264,13 +3276,8 @@ static int selinux_inode_getsecurity(struct inode *inode, const char *name, void * and lack of permission just means that we fall back to the * in-core context value, not a denial. */ - error = cap_capable(current_cred(), &init_user_ns, CAP_MAC_ADMIN, - SECURITY_CAP_NOAUDIT); - if (!error) - error = cred_has_capability(current_cred(), CAP_MAC_ADMIN, - SECURITY_CAP_NOAUDIT, true); isec = inode_security(inode); - if (!error) + if (has_cap_mac_admin(false)) error = security_sid_to_context_force(isec->sid, &context, &size); else @@ -5919,7 +5926,7 @@ static int selinux_setprocattr(const char *name, void *value, size_t size) } error = security_context_to_sid(value, size, &sid, GFP_KERNEL); if (error == -EINVAL && !strcmp(name, "fscreate")) { - if (!capable(CAP_MAC_ADMIN)) { + if (!has_cap_mac_admin(true)) { struct audit_buffer *ab; size_t audit_size; -- cgit v1.2.3 From 3ba4bf5f1e2c58bddd84ba27c5aeaf8ca1d36bff Mon Sep 17 00:00:00 2001 From: Stephen Smalley Date: Fri, 5 May 2017 09:14:48 -0400 Subject: selinux: add a map permission check for mmap Add a map permission check on mmap so that we can distinguish memory mapped access (since it has different implications for revocation). When a file is opened and then read or written via syscalls like read(2)/write(2), we revalidate access on each read/write operation via selinux_file_permission() and therefore can revoke access if the process context, the file context, or the policy changes in such a manner that access is no longer allowed. When a file is opened and then memory mapped via mmap(2) and then subsequently read or written directly in memory, we presently have no way to revalidate or revoke access. The purpose of a separate map permission check on mmap(2) is to permit policy to prohibit memory mapping of specific files for which we need to ensure that every access is revalidated, particularly useful for scenarios where we expect the file to be relabeled at runtime in order to reflect state changes (e.g. cross-domain solution, assured pipeline without data copying). Signed-off-by: Stephen Smalley Signed-off-by: Paul Moore --- security/selinux/hooks.c | 12 ++++++++++++ security/selinux/include/classmap.h | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index dddb81e06d2d..e29800091e17 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -3557,6 +3557,18 @@ static int selinux_mmap_addr(unsigned long addr) static int selinux_mmap_file(struct file *file, unsigned long reqprot, unsigned long prot, unsigned long flags) { + struct common_audit_data ad; + int rc; + + if (file) { + ad.type = LSM_AUDIT_DATA_FILE; + ad.u.file = file; + rc = inode_has_perm(current_cred(), file_inode(file), + FILE__MAP, &ad); + if (rc) + return rc; + } + if (selinux_checkreqprot) prot = reqprot; diff --git a/security/selinux/include/classmap.h b/security/selinux/include/classmap.h index 1e0cc9b5de20..3e49a78f1f46 100644 --- a/security/selinux/include/classmap.h +++ b/security/selinux/include/classmap.h @@ -1,7 +1,7 @@ #include #define COMMON_FILE_SOCK_PERMS "ioctl", "read", "write", "create", \ - "getattr", "setattr", "lock", "relabelfrom", "relabelto", "append" + "getattr", "setattr", "lock", "relabelfrom", "relabelto", "append", "map" #define COMMON_FILE_PERMS COMMON_FILE_SOCK_PERMS, "unlink", "link", \ "rename", "execute", "quotaon", "mounton", "audit_access", \ -- cgit v1.2.3 From ccb544781d34afdb73a9a73ae53035d824d193bf Mon Sep 17 00:00:00 2001 From: Stephen Smalley Date: Fri, 12 May 2017 12:41:24 -0400 Subject: selinux: do not check open permission on sockets open permission is currently only defined for files in the kernel (COMMON_FILE_PERMS rather than COMMON_FILE_SOCK_PERMS). Construction of an artificial test case that tries to open a socket via /proc/pid/fd will generate a recvfrom avc denial because recvfrom and open happen to map to the same permission bit in socket vs file classes. open of a socket via /proc/pid/fd is not supported by the kernel regardless and will ultimately return ENXIO. But we hit the permission check first and can thus produce these odd/misleading denials. Omit the open check when operating on a socket. Signed-off-by: Stephen Smalley Signed-off-by: Paul Moore --- security/selinux/hooks.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index e29800091e17..627f291fb6c1 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -2063,8 +2063,9 @@ static inline u32 file_to_av(struct file *file) static inline u32 open_file_to_av(struct file *file) { u32 av = file_to_av(file); + struct inode *inode = file_inode(file); - if (selinux_policycap_openperm) + if (selinux_policycap_openperm && inode->i_sb->s_magic != SOCKFS_MAGIC) av |= FILE__OPEN; return av; @@ -3059,6 +3060,7 @@ static int selinux_inode_permission(struct inode *inode, int mask) static int selinux_inode_setattr(struct dentry *dentry, struct iattr *iattr) { const struct cred *cred = current_cred(); + struct inode *inode = d_backing_inode(dentry); unsigned int ia_valid = iattr->ia_valid; __u32 av = FILE__WRITE; @@ -3074,8 +3076,10 @@ static int selinux_inode_setattr(struct dentry *dentry, struct iattr *iattr) ATTR_ATIME_SET | ATTR_MTIME_SET | ATTR_TIMES_SET)) return dentry_has_perm(cred, dentry, FILE__SETATTR); - if (selinux_policycap_openperm && (ia_valid & ATTR_SIZE) - && !(ia_valid & ATTR_FILE)) + if (selinux_policycap_openperm && + inode->i_sb->s_magic != SOCKFS_MAGIC && + (ia_valid & ATTR_SIZE) && + !(ia_valid & ATTR_FILE)) av |= FILE__OPEN; return dentry_has_perm(cred, dentry, av); -- cgit v1.2.3 From 4dc2fce342f8e5b165e2eda29a39446bb07b2457 Mon Sep 17 00:00:00 2001 From: Stephen Smalley Date: Thu, 18 May 2017 16:58:31 -0400 Subject: selinux: log policy capability state when a policy is loaded Log the state of SELinux policy capabilities when a policy is loaded. For each policy capability known to the kernel, log the policy capability name and the value set in the policy. For policy capabilities that are set in the loaded policy but unknown to the kernel, log the policy capability index, since this is the only information presently available in the policy. Sample output with a policy created with a new capability defined that is not known to the kernel: SELinux: policy capability network_peer_controls=1 SELinux: policy capability open_perms=1 SELinux: policy capability extended_socket_class=1 SELinux: policy capability always_check_network=0 SELinux: policy capability cgroup_seclabel=0 SELinux: unknown policy capability 5 Resolves: https://github.com/SELinuxProject/selinux-kernel/issues/32 Signed-off-by: Stephen Smalley Signed-off-by: Paul Moore --- security/selinux/include/security.h | 2 ++ security/selinux/selinuxfs.c | 13 ++----------- security/selinux/ss/services.c | 23 +++++++++++++++++++++++ 3 files changed, 27 insertions(+), 11 deletions(-) diff --git a/security/selinux/include/security.h b/security/selinux/include/security.h index f979c35e037e..c4224bbf9f4e 100644 --- a/security/selinux/include/security.h +++ b/security/selinux/include/security.h @@ -76,6 +76,8 @@ enum { }; #define POLICYDB_CAPABILITY_MAX (__POLICYDB_CAPABILITY_MAX - 1) +extern char *selinux_policycap_names[__POLICYDB_CAPABILITY_MAX]; + extern int selinux_policycap_netpeer; extern int selinux_policycap_openperm; extern int selinux_policycap_extsockclass; diff --git a/security/selinux/selinuxfs.c b/security/selinux/selinuxfs.c index 50062e70140d..82adb78a58f7 100644 --- a/security/selinux/selinuxfs.c +++ b/security/selinux/selinuxfs.c @@ -41,15 +41,6 @@ #include "objsec.h" #include "conditional.h" -/* Policy capability filenames */ -static char *policycap_names[] = { - "network_peer_controls", - "open_perms", - "extended_socket_class", - "always_check_network", - "cgroup_seclabel" -}; - unsigned int selinux_checkreqprot = CONFIG_SECURITY_SELINUX_CHECKREQPROT_VALUE; static int __init checkreqprot_setup(char *str) @@ -1750,9 +1741,9 @@ static int sel_make_policycap(void) sel_remove_entries(policycap_dir); for (iter = 0; iter <= POLICYDB_CAPABILITY_MAX; iter++) { - if (iter < ARRAY_SIZE(policycap_names)) + if (iter < ARRAY_SIZE(selinux_policycap_names)) dentry = d_alloc_name(policycap_dir, - policycap_names[iter]); + selinux_policycap_names[iter]); else dentry = d_alloc_name(policycap_dir, "unknown"); diff --git a/security/selinux/ss/services.c b/security/selinux/ss/services.c index 60d9b0252321..2dccba4851f8 100644 --- a/security/selinux/ss/services.c +++ b/security/selinux/ss/services.c @@ -70,6 +70,15 @@ #include "ebitmap.h" #include "audit.h" +/* Policy capability names */ +char *selinux_policycap_names[__POLICYDB_CAPABILITY_MAX] = { + "network_peer_controls", + "open_perms", + "extended_socket_class", + "always_check_network", + "cgroup_seclabel" +}; + int selinux_policycap_netpeer; int selinux_policycap_openperm; int selinux_policycap_extsockclass; @@ -1986,6 +1995,9 @@ bad: static void security_load_policycaps(void) { + unsigned int i; + struct ebitmap_node *node; + selinux_policycap_netpeer = ebitmap_get_bit(&policydb.policycaps, POLICYDB_CAPABILITY_NETPEER); selinux_policycap_openperm = ebitmap_get_bit(&policydb.policycaps, @@ -1997,6 +2009,17 @@ static void security_load_policycaps(void) selinux_policycap_cgroupseclabel = ebitmap_get_bit(&policydb.policycaps, POLICYDB_CAPABILITY_CGROUPSECLABEL); + + for (i = 0; i < ARRAY_SIZE(selinux_policycap_names); i++) + pr_info("SELinux: policy capability %s=%d\n", + selinux_policycap_names[i], + ebitmap_get_bit(&policydb.policycaps, i)); + + ebitmap_for_each_positive_bit(&policydb.policycaps, node, i) { + if (i >= ARRAY_SIZE(selinux_policycap_names)) + pr_info("SELinux: unknown policy capability %u\n", + i); + } } static int security_preserve_bools(struct policydb *p); -- cgit v1.2.3 From 270e8573145a26de924e2dc644596332d400445b Mon Sep 17 00:00:00 2001 From: Matthias Kaehlcke Date: Fri, 19 May 2017 10:09:32 -0700 Subject: selinux: Remove redundant check for unknown labeling behavior The check is already performed in ocontext_read() when the policy is loaded. Removing the array also fixes the following warning when building with clang: security/selinux/hooks.c:338:20: error: variable 'labeling_behaviors' is not needed and will not be emitted [-Werror,-Wunneeded-internal-declaration] Signed-off-by: Matthias Kaehlcke Acked-by: Stephen Smalley Signed-off-by: Paul Moore --- security/selinux/hooks.c | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index 627f291fb6c1..cfb7ce339adc 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -398,18 +398,6 @@ static void superblock_free_security(struct super_block *sb) kfree(sbsec); } -/* The file system's label must be initialized prior to use. */ - -static const char *labeling_behaviors[7] = { - "uses xattr", - "uses transition SIDs", - "uses task SIDs", - "uses genfs_contexts", - "not configured for labeling", - "uses mountpoint labeling", - "uses native labeling", -}; - static inline int inode_doinit(struct inode *inode) { return inode_doinit_with_dentry(inode, NULL); @@ -524,10 +512,6 @@ static int sb_finish_set_opts(struct super_block *sb) } } - if (sbsec->behavior > ARRAY_SIZE(labeling_behaviors)) - printk(KERN_ERR "SELinux: initialized (dev %s, type %s), unknown behavior\n", - sb->s_id, sb->s_type->name); - sbsec->flags |= SE_SBINITIALIZED; if (selinux_is_sblabel_mnt(sb)) sbsec->flags |= SBLABEL_MNT; -- cgit v1.2.3 From 883c71feaf2e810e0331cf780c738cbb09e93b58 Mon Sep 17 00:00:00 2001 From: Daniel Jurgens Date: Fri, 19 May 2017 15:48:51 +0300 Subject: IB/core: IB cache enhancements to support Infiniband security Cache the subnet prefix and add a function to access it. Enforcing security requires frequent queries of the subnet prefix and the pkeys in the pkey table. Signed-off-by: Daniel Jurgens Reviewed-by: Eli Cohen Reviewed-by: Leon Romanovsky Reviewed-by: James Morris Acked-by: Doug Ledford Signed-off-by: Paul Moore --- drivers/infiniband/core/cache.c | 22 ++++++++++++++++++++++ drivers/infiniband/core/core_priv.h | 3 +++ include/rdma/ib_verbs.h | 1 + 3 files changed, 26 insertions(+) diff --git a/drivers/infiniband/core/cache.c b/drivers/infiniband/core/cache.c index b1371eb9f46c..b9c0066b704e 100644 --- a/drivers/infiniband/core/cache.c +++ b/drivers/infiniband/core/cache.c @@ -911,6 +911,26 @@ int ib_get_cached_pkey(struct ib_device *device, } EXPORT_SYMBOL(ib_get_cached_pkey); +int ib_get_cached_subnet_prefix(struct ib_device *device, + u8 port_num, + u64 *sn_pfx) +{ + unsigned long flags; + int p; + + if (port_num < rdma_start_port(device) || + port_num > rdma_end_port(device)) + return -EINVAL; + + p = port_num - rdma_start_port(device); + read_lock_irqsave(&device->cache.lock, flags); + *sn_pfx = device->cache.ports[p].subnet_prefix; + read_unlock_irqrestore(&device->cache.lock, flags); + + return 0; +} +EXPORT_SYMBOL(ib_get_cached_subnet_prefix); + int ib_find_cached_pkey(struct ib_device *device, u8 port_num, u16 pkey, @@ -1108,6 +1128,8 @@ static void ib_cache_update(struct ib_device *device, device->cache.ports[port - rdma_start_port(device)].port_state = tprops->state; + device->cache.ports[port - rdma_start_port(device)].subnet_prefix = + tprops->subnet_prefix; write_unlock_irq(&device->cache.lock); kfree(gid_cache); diff --git a/drivers/infiniband/core/core_priv.h b/drivers/infiniband/core/core_priv.h index cb7d372e4bdf..249e3ed90365 100644 --- a/drivers/infiniband/core/core_priv.h +++ b/drivers/infiniband/core/core_priv.h @@ -176,4 +176,7 @@ int ib_nl_handle_set_timeout(struct sk_buff *skb, int ib_nl_handle_ip_res_resp(struct sk_buff *skb, struct netlink_callback *cb); +int ib_get_cached_subnet_prefix(struct ib_device *device, + u8 port_num, + u64 *sn_pfx); #endif /* _CORE_PRIV_H */ diff --git a/include/rdma/ib_verbs.h b/include/rdma/ib_verbs.h index ba8314ec5768..9dc4e7e0aba4 100644 --- a/include/rdma/ib_verbs.h +++ b/include/rdma/ib_verbs.h @@ -1891,6 +1891,7 @@ enum ib_mad_result { }; struct ib_port_cache { + u64 subnet_prefix; struct ib_pkey_cache *pkey; struct ib_gid_table *gid; u8 lmc; -- cgit v1.2.3 From d291f1a6523292d916fe1659c67f6db061fbd1b5 Mon Sep 17 00:00:00 2001 From: Daniel Jurgens Date: Fri, 19 May 2017 15:48:52 +0300 Subject: IB/core: Enforce PKey security on QPs Add new LSM hooks to allocate and free security contexts and check for permission to access a PKey. Allocate and free a security context when creating and destroying a QP. This context is used for controlling access to PKeys. When a request is made to modify a QP that changes the port, PKey index, or alternate path, check that the QP has permission for the PKey in the PKey table index on the subnet prefix of the port. If the QP is shared make sure all handles to the QP also have access. Store which port and PKey index a QP is using. After the reset to init transition the user can modify the port, PKey index and alternate path independently. So port and PKey settings changes can be a merge of the previous settings and the new ones. In order to maintain access control if there are PKey table or subnet prefix change keep a list of all QPs are using each PKey index on each port. If a change occurs all QPs using that device and port must have access enforced for the new cache settings. These changes add a transaction to the QP modify process. Association with the old port and PKey index must be maintained if the modify fails, and must be removed if it succeeds. Association with the new port and PKey index must be established prior to the modify and removed if the modify fails. 1. When a QP is modified to a particular Port, PKey index or alternate path insert that QP into the appropriate lists. 2. Check permission to access the new settings. 3. If step 2 grants access attempt to modify the QP. 4a. If steps 2 and 3 succeed remove any prior associations. 4b. If ether fails remove the new setting associations. If a PKey table or subnet prefix changes walk the list of QPs and check that they have permission. If not send the QP to the error state and raise a fatal error event. If it's a shared QP make sure all the QPs that share the real_qp have permission as well. If the QP that owns a security structure is denied access the security structure is marked as such and the QP is added to an error_list. Once the moving the QP to error is complete the security structure mark is cleared. Maintaining the lists correctly turns QP destroy into a transaction. The hardware driver for the device frees the ib_qp structure, so while the destroy is in progress the ib_qp pointer in the ib_qp_security struct is undefined. When the destroy process begins the ib_qp_security structure is marked as destroying. This prevents any action from being taken on the QP pointer. After the QP is destroyed successfully it could still listed on an error_list wait for it to be processed by that flow before cleaning up the structure. If the destroy fails the QPs port and PKey settings are reinserted into the appropriate lists, the destroying flag is cleared, and access control is enforced, in case there were any cache changes during the destroy flow. To keep the security changes isolated a new file is used to hold security related functionality. Signed-off-by: Daniel Jurgens Acked-by: Doug Ledford [PM: merge fixup in ib_verbs.h and uverbs_cmd.c] Signed-off-by: Paul Moore --- drivers/infiniband/core/Makefile | 3 +- drivers/infiniband/core/cache.c | 21 +- drivers/infiniband/core/core_priv.h | 77 +++++ drivers/infiniband/core/device.c | 33 ++ drivers/infiniband/core/security.c | 613 +++++++++++++++++++++++++++++++++++ drivers/infiniband/core/uverbs_cmd.c | 15 +- drivers/infiniband/core/verbs.c | 27 +- include/linux/lsm_hooks.h | 27 ++ include/linux/security.h | 21 ++ include/rdma/ib_verbs.h | 48 +++ security/Kconfig | 9 + security/security.c | 22 ++ 12 files changed, 907 insertions(+), 9 deletions(-) create mode 100644 drivers/infiniband/core/security.c diff --git a/drivers/infiniband/core/Makefile b/drivers/infiniband/core/Makefile index 6ebd9ad95010..e3cdafff8ece 100644 --- a/drivers/infiniband/core/Makefile +++ b/drivers/infiniband/core/Makefile @@ -10,7 +10,8 @@ obj-$(CONFIG_INFINIBAND_USER_ACCESS) += ib_uverbs.o ib_ucm.o \ ib_core-y := packer.o ud_header.o verbs.o cq.o rw.o sysfs.o \ device.o fmr_pool.o cache.o netlink.o \ roce_gid_mgmt.o mr_pool.o addr.o sa_query.o \ - multicast.o mad.o smi.o agent.o mad_rmpp.o + multicast.o mad.o smi.o agent.o mad_rmpp.o \ + security.o ib_core-$(CONFIG_INFINIBAND_USER_MEM) += umem.o ib_core-$(CONFIG_INFINIBAND_ON_DEMAND_PAGING) += umem_odp.o umem_rbtree.o ib_core-$(CONFIG_CGROUP_RDMA) += cgroup.o diff --git a/drivers/infiniband/core/cache.c b/drivers/infiniband/core/cache.c index b9c0066b704e..efc94304dee3 100644 --- a/drivers/infiniband/core/cache.c +++ b/drivers/infiniband/core/cache.c @@ -53,6 +53,7 @@ struct ib_update_work { struct work_struct work; struct ib_device *device; u8 port_num; + bool enforce_security; }; union ib_gid zgid; @@ -1042,7 +1043,8 @@ int ib_get_cached_port_state(struct ib_device *device, EXPORT_SYMBOL(ib_get_cached_port_state); static void ib_cache_update(struct ib_device *device, - u8 port) + u8 port, + bool enforce_security) { struct ib_port_attr *tprops = NULL; struct ib_pkey_cache *pkey_cache = NULL, *old_pkey_cache; @@ -1132,6 +1134,11 @@ static void ib_cache_update(struct ib_device *device, tprops->subnet_prefix; write_unlock_irq(&device->cache.lock); + if (enforce_security) + ib_security_cache_change(device, + port, + tprops->subnet_prefix); + kfree(gid_cache); kfree(old_pkey_cache); kfree(tprops); @@ -1148,7 +1155,9 @@ static void ib_cache_task(struct work_struct *_work) struct ib_update_work *work = container_of(_work, struct ib_update_work, work); - ib_cache_update(work->device, work->port_num); + ib_cache_update(work->device, + work->port_num, + work->enforce_security); kfree(work); } @@ -1169,6 +1178,12 @@ static void ib_cache_event(struct ib_event_handler *handler, INIT_WORK(&work->work, ib_cache_task); work->device = event->device; work->port_num = event->element.port_num; + if (event->event == IB_EVENT_PKEY_CHANGE || + event->event == IB_EVENT_GID_CHANGE) + work->enforce_security = true; + else + work->enforce_security = false; + queue_work(ib_wq, &work->work); } } @@ -1194,7 +1209,7 @@ int ib_cache_setup_one(struct ib_device *device) goto out; for (p = 0; p <= rdma_end_port(device) - rdma_start_port(device); ++p) - ib_cache_update(device, p + rdma_start_port(device)); + ib_cache_update(device, p + rdma_start_port(device), true); INIT_IB_EVENT_HANDLER(&device->cache.event_handler, device, ib_cache_event); diff --git a/drivers/infiniband/core/core_priv.h b/drivers/infiniband/core/core_priv.h index 249e3ed90365..7b63215f80c2 100644 --- a/drivers/infiniband/core/core_priv.h +++ b/drivers/infiniband/core/core_priv.h @@ -39,6 +39,14 @@ #include +struct pkey_index_qp_list { + struct list_head pkey_index_list; + u16 pkey_index; + /* Lock to hold while iterating the qp_list. */ + spinlock_t qp_list_lock; + struct list_head qp_list; +}; + #if IS_ENABLED(CONFIG_INFINIBAND_ADDR_TRANS_CONFIGFS) int cma_configfs_init(void); void cma_configfs_exit(void); @@ -179,4 +187,73 @@ int ib_nl_handle_ip_res_resp(struct sk_buff *skb, int ib_get_cached_subnet_prefix(struct ib_device *device, u8 port_num, u64 *sn_pfx); + +#ifdef CONFIG_SECURITY_INFINIBAND +void ib_security_destroy_port_pkey_list(struct ib_device *device); + +void ib_security_cache_change(struct ib_device *device, + u8 port_num, + u64 subnet_prefix); + +int ib_security_modify_qp(struct ib_qp *qp, + struct ib_qp_attr *qp_attr, + int qp_attr_mask, + struct ib_udata *udata); + +int ib_create_qp_security(struct ib_qp *qp, struct ib_device *dev); +void ib_destroy_qp_security_begin(struct ib_qp_security *sec); +void ib_destroy_qp_security_abort(struct ib_qp_security *sec); +void ib_destroy_qp_security_end(struct ib_qp_security *sec); +int ib_open_shared_qp_security(struct ib_qp *qp, struct ib_device *dev); +void ib_close_shared_qp_security(struct ib_qp_security *sec); +#else +static inline void ib_security_destroy_port_pkey_list(struct ib_device *device) +{ +} + +static inline void ib_security_cache_change(struct ib_device *device, + u8 port_num, + u64 subnet_prefix) +{ +} + +static inline int ib_security_modify_qp(struct ib_qp *qp, + struct ib_qp_attr *qp_attr, + int qp_attr_mask, + struct ib_udata *udata) +{ + return qp->device->modify_qp(qp->real_qp, + qp_attr, + qp_attr_mask, + udata); +} + +static inline int ib_create_qp_security(struct ib_qp *qp, + struct ib_device *dev) +{ + return 0; +} + +static inline void ib_destroy_qp_security_begin(struct ib_qp_security *sec) +{ +} + +static inline void ib_destroy_qp_security_abort(struct ib_qp_security *sec) +{ +} + +static inline void ib_destroy_qp_security_end(struct ib_qp_security *sec) +{ +} + +static inline int ib_open_shared_qp_security(struct ib_qp *qp, + struct ib_device *dev) +{ + return 0; +} + +static inline void ib_close_shared_qp_security(struct ib_qp_security *sec) +{ +} +#endif #endif /* _CORE_PRIV_H */ diff --git a/drivers/infiniband/core/device.c b/drivers/infiniband/core/device.c index 81d447da0048..96e730cc9b81 100644 --- a/drivers/infiniband/core/device.c +++ b/drivers/infiniband/core/device.c @@ -325,6 +325,30 @@ void ib_get_device_fw_str(struct ib_device *dev, char *str, size_t str_len) } EXPORT_SYMBOL(ib_get_device_fw_str); +static int setup_port_pkey_list(struct ib_device *device) +{ + int i; + + /** + * device->port_pkey_list is indexed directly by the port number, + * Therefore it is declared as a 1 based array with potential empty + * slots at the beginning. + */ + device->port_pkey_list = kcalloc(rdma_end_port(device) + 1, + sizeof(*device->port_pkey_list), + GFP_KERNEL); + + if (!device->port_pkey_list) + return -ENOMEM; + + for (i = 0; i < (rdma_end_port(device) + 1); i++) { + spin_lock_init(&device->port_pkey_list[i].list_lock); + INIT_LIST_HEAD(&device->port_pkey_list[i].pkey_list); + } + + return 0; +} + /** * ib_register_device - Register an IB device with IB core * @device:Device to register @@ -385,6 +409,12 @@ int ib_register_device(struct ib_device *device, goto out; } + ret = setup_port_pkey_list(device); + if (ret) { + pr_warn("Couldn't create per port_pkey_list\n"); + goto out; + } + ret = ib_cache_setup_one(device); if (ret) { pr_warn("Couldn't set up InfiniBand P_Key/GID cache\n"); @@ -468,6 +498,9 @@ void ib_unregister_device(struct ib_device *device) ib_device_unregister_sysfs(device); ib_cache_cleanup_one(device); + ib_security_destroy_port_pkey_list(device); + kfree(device->port_pkey_list); + down_write(&lists_rwsem); spin_lock_irqsave(&device->client_data_lock, flags); list_for_each_entry_safe(context, tmp, &device->client_data_list, list) diff --git a/drivers/infiniband/core/security.c b/drivers/infiniband/core/security.c new file mode 100644 index 000000000000..b2f170ddc062 --- /dev/null +++ b/drivers/infiniband/core/security.c @@ -0,0 +1,613 @@ +/* + * Copyright (c) 2016 Mellanox Technologies Ltd. All rights reserved. + * + * This software is available to you under a choice of one of two + * licenses. You may choose to be licensed under the terms of the GNU + * General Public License (GPL) Version 2, available from the file + * COPYING in the main directory of this source tree, or the + * OpenIB.org BSD license below: + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above + * copyright notice, this list of conditions and the following + * disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifdef CONFIG_SECURITY_INFINIBAND + +#include +#include +#include + +#include +#include +#include "core_priv.h" + +static struct pkey_index_qp_list *get_pkey_idx_qp_list(struct ib_port_pkey *pp) +{ + struct pkey_index_qp_list *pkey = NULL; + struct pkey_index_qp_list *tmp_pkey; + struct ib_device *dev = pp->sec->dev; + + spin_lock(&dev->port_pkey_list[pp->port_num].list_lock); + list_for_each_entry(tmp_pkey, + &dev->port_pkey_list[pp->port_num].pkey_list, + pkey_index_list) { + if (tmp_pkey->pkey_index == pp->pkey_index) { + pkey = tmp_pkey; + break; + } + } + spin_unlock(&dev->port_pkey_list[pp->port_num].list_lock); + return pkey; +} + +static int get_pkey_and_subnet_prefix(struct ib_port_pkey *pp, + u16 *pkey, + u64 *subnet_prefix) +{ + struct ib_device *dev = pp->sec->dev; + int ret; + + ret = ib_get_cached_pkey(dev, pp->port_num, pp->pkey_index, pkey); + if (ret) + return ret; + + ret = ib_get_cached_subnet_prefix(dev, pp->port_num, subnet_prefix); + + return ret; +} + +static int enforce_qp_pkey_security(u16 pkey, + u64 subnet_prefix, + struct ib_qp_security *qp_sec) +{ + struct ib_qp_security *shared_qp_sec; + int ret; + + ret = security_ib_pkey_access(qp_sec->security, subnet_prefix, pkey); + if (ret) + return ret; + + if (qp_sec->qp == qp_sec->qp->real_qp) { + list_for_each_entry(shared_qp_sec, + &qp_sec->shared_qp_list, + shared_qp_list) { + ret = security_ib_pkey_access(shared_qp_sec->security, + subnet_prefix, + pkey); + if (ret) + return ret; + } + } + return 0; +} + +/* The caller of this function must hold the QP security + * mutex of the QP of the security structure in *pps. + * + * It takes separate ports_pkeys and security structure + * because in some cases the pps will be for a new settings + * or the pps will be for the real QP and security structure + * will be for a shared QP. + */ +static int check_qp_port_pkey_settings(struct ib_ports_pkeys *pps, + struct ib_qp_security *sec) +{ + u64 subnet_prefix; + u16 pkey; + int ret = 0; + + if (!pps) + return 0; + + if (pps->main.state != IB_PORT_PKEY_NOT_VALID) { + get_pkey_and_subnet_prefix(&pps->main, + &pkey, + &subnet_prefix); + + ret = enforce_qp_pkey_security(pkey, + subnet_prefix, + sec); + } + if (ret) + return ret; + + if (pps->alt.state != IB_PORT_PKEY_NOT_VALID) { + get_pkey_and_subnet_prefix(&pps->alt, + &pkey, + &subnet_prefix); + + ret = enforce_qp_pkey_security(pkey, + subnet_prefix, + sec); + } + + return ret; +} + +/* The caller of this function must hold the QP security + * mutex. + */ +static void qp_to_error(struct ib_qp_security *sec) +{ + struct ib_qp_security *shared_qp_sec; + struct ib_qp_attr attr = { + .qp_state = IB_QPS_ERR + }; + struct ib_event event = { + .event = IB_EVENT_QP_FATAL + }; + + /* If the QP is in the process of being destroyed + * the qp pointer in the security structure is + * undefined. It cannot be modified now. + */ + if (sec->destroying) + return; + + ib_modify_qp(sec->qp, + &attr, + IB_QP_STATE); + + if (sec->qp->event_handler && sec->qp->qp_context) { + event.element.qp = sec->qp; + sec->qp->event_handler(&event, + sec->qp->qp_context); + } + + list_for_each_entry(shared_qp_sec, + &sec->shared_qp_list, + shared_qp_list) { + struct ib_qp *qp = shared_qp_sec->qp; + + if (qp->event_handler && qp->qp_context) { + event.element.qp = qp; + event.device = qp->device; + qp->event_handler(&event, + qp->qp_context); + } + } +} + +static inline void check_pkey_qps(struct pkey_index_qp_list *pkey, + struct ib_device *device, + u8 port_num, + u64 subnet_prefix) +{ + struct ib_port_pkey *pp, *tmp_pp; + bool comp; + LIST_HEAD(to_error_list); + u16 pkey_val; + + if (!ib_get_cached_pkey(device, + port_num, + pkey->pkey_index, + &pkey_val)) { + spin_lock(&pkey->qp_list_lock); + list_for_each_entry(pp, &pkey->qp_list, qp_list) { + if (atomic_read(&pp->sec->error_list_count)) + continue; + + if (enforce_qp_pkey_security(pkey_val, + subnet_prefix, + pp->sec)) { + atomic_inc(&pp->sec->error_list_count); + list_add(&pp->to_error_list, + &to_error_list); + } + } + spin_unlock(&pkey->qp_list_lock); + } + + list_for_each_entry_safe(pp, + tmp_pp, + &to_error_list, + to_error_list) { + mutex_lock(&pp->sec->mutex); + qp_to_error(pp->sec); + list_del(&pp->to_error_list); + atomic_dec(&pp->sec->error_list_count); + comp = pp->sec->destroying; + mutex_unlock(&pp->sec->mutex); + + if (comp) + complete(&pp->sec->error_complete); + } +} + +/* The caller of this function must hold the QP security + * mutex. + */ +static int port_pkey_list_insert(struct ib_port_pkey *pp) +{ + struct pkey_index_qp_list *tmp_pkey; + struct pkey_index_qp_list *pkey; + struct ib_device *dev; + u8 port_num = pp->port_num; + int ret = 0; + + if (pp->state != IB_PORT_PKEY_VALID) + return 0; + + dev = pp->sec->dev; + + pkey = get_pkey_idx_qp_list(pp); + + if (!pkey) { + bool found = false; + + pkey = kzalloc(sizeof(*pkey), GFP_KERNEL); + if (!pkey) + return -ENOMEM; + + spin_lock(&dev->port_pkey_list[port_num].list_lock); + /* Check for the PKey again. A racing process may + * have created it. + */ + list_for_each_entry(tmp_pkey, + &dev->port_pkey_list[port_num].pkey_list, + pkey_index_list) { + if (tmp_pkey->pkey_index == pp->pkey_index) { + kfree(pkey); + pkey = tmp_pkey; + found = true; + break; + } + } + + if (!found) { + pkey->pkey_index = pp->pkey_index; + spin_lock_init(&pkey->qp_list_lock); + INIT_LIST_HEAD(&pkey->qp_list); + list_add(&pkey->pkey_index_list, + &dev->port_pkey_list[port_num].pkey_list); + } + spin_unlock(&dev->port_pkey_list[port_num].list_lock); + } + + spin_lock(&pkey->qp_list_lock); + list_add(&pp->qp_list, &pkey->qp_list); + spin_unlock(&pkey->qp_list_lock); + + pp->state = IB_PORT_PKEY_LISTED; + + return ret; +} + +/* The caller of this function must hold the QP security + * mutex. + */ +static void port_pkey_list_remove(struct ib_port_pkey *pp) +{ + struct pkey_index_qp_list *pkey; + + if (pp->state != IB_PORT_PKEY_LISTED) + return; + + pkey = get_pkey_idx_qp_list(pp); + + spin_lock(&pkey->qp_list_lock); + list_del(&pp->qp_list); + spin_unlock(&pkey->qp_list_lock); + + /* The setting may still be valid, i.e. after + * a destroy has failed for example. + */ + pp->state = IB_PORT_PKEY_VALID; +} + +static void destroy_qp_security(struct ib_qp_security *sec) +{ + security_ib_free_security(sec->security); + kfree(sec->ports_pkeys); + kfree(sec); +} + +/* The caller of this function must hold the QP security + * mutex. + */ +static struct ib_ports_pkeys *get_new_pps(const struct ib_qp *qp, + const struct ib_qp_attr *qp_attr, + int qp_attr_mask) +{ + struct ib_ports_pkeys *new_pps; + struct ib_ports_pkeys *qp_pps = qp->qp_sec->ports_pkeys; + + new_pps = kzalloc(sizeof(*new_pps), GFP_KERNEL); + if (!new_pps) + return NULL; + + if (qp_attr_mask & (IB_QP_PKEY_INDEX | IB_QP_PORT)) { + if (!qp_pps) { + new_pps->main.port_num = qp_attr->port_num; + new_pps->main.pkey_index = qp_attr->pkey_index; + } else { + new_pps->main.port_num = (qp_attr_mask & IB_QP_PORT) ? + qp_attr->port_num : + qp_pps->main.port_num; + + new_pps->main.pkey_index = + (qp_attr_mask & IB_QP_PKEY_INDEX) ? + qp_attr->pkey_index : + qp_pps->main.pkey_index; + } + new_pps->main.state = IB_PORT_PKEY_VALID; + } else if (qp_pps) { + new_pps->main.port_num = qp_pps->main.port_num; + new_pps->main.pkey_index = qp_pps->main.pkey_index; + if (qp_pps->main.state != IB_PORT_PKEY_NOT_VALID) + new_pps->main.state = IB_PORT_PKEY_VALID; + } + + if (qp_attr_mask & IB_QP_ALT_PATH) { + new_pps->alt.port_num = qp_attr->alt_port_num; + new_pps->alt.pkey_index = qp_attr->alt_pkey_index; + new_pps->alt.state = IB_PORT_PKEY_VALID; + } else if (qp_pps) { + new_pps->alt.port_num = qp_pps->alt.port_num; + new_pps->alt.pkey_index = qp_pps->alt.pkey_index; + if (qp_pps->alt.state != IB_PORT_PKEY_NOT_VALID) + new_pps->alt.state = IB_PORT_PKEY_VALID; + } + + new_pps->main.sec = qp->qp_sec; + new_pps->alt.sec = qp->qp_sec; + return new_pps; +} + +int ib_open_shared_qp_security(struct ib_qp *qp, struct ib_device *dev) +{ + struct ib_qp *real_qp = qp->real_qp; + int ret; + + ret = ib_create_qp_security(qp, dev); + + if (ret) + return ret; + + mutex_lock(&real_qp->qp_sec->mutex); + ret = check_qp_port_pkey_settings(real_qp->qp_sec->ports_pkeys, + qp->qp_sec); + + if (ret) + goto ret; + + if (qp != real_qp) + list_add(&qp->qp_sec->shared_qp_list, + &real_qp->qp_sec->shared_qp_list); +ret: + mutex_unlock(&real_qp->qp_sec->mutex); + if (ret) + destroy_qp_security(qp->qp_sec); + + return ret; +} + +void ib_close_shared_qp_security(struct ib_qp_security *sec) +{ + struct ib_qp *real_qp = sec->qp->real_qp; + + mutex_lock(&real_qp->qp_sec->mutex); + list_del(&sec->shared_qp_list); + mutex_unlock(&real_qp->qp_sec->mutex); + + destroy_qp_security(sec); +} + +int ib_create_qp_security(struct ib_qp *qp, struct ib_device *dev) +{ + int ret; + + qp->qp_sec = kzalloc(sizeof(*qp->qp_sec), GFP_KERNEL); + if (!qp->qp_sec) + return -ENOMEM; + + qp->qp_sec->qp = qp; + qp->qp_sec->dev = dev; + mutex_init(&qp->qp_sec->mutex); + INIT_LIST_HEAD(&qp->qp_sec->shared_qp_list); + atomic_set(&qp->qp_sec->error_list_count, 0); + init_completion(&qp->qp_sec->error_complete); + ret = security_ib_alloc_security(&qp->qp_sec->security); + if (ret) + kfree(qp->qp_sec); + + return ret; +} +EXPORT_SYMBOL(ib_create_qp_security); + +void ib_destroy_qp_security_begin(struct ib_qp_security *sec) +{ + mutex_lock(&sec->mutex); + + /* Remove the QP from the lists so it won't get added to + * a to_error_list during the destroy process. + */ + if (sec->ports_pkeys) { + port_pkey_list_remove(&sec->ports_pkeys->main); + port_pkey_list_remove(&sec->ports_pkeys->alt); + } + + /* If the QP is already in one or more of those lists + * the destroying flag will ensure the to error flow + * doesn't operate on an undefined QP. + */ + sec->destroying = true; + + /* Record the error list count to know how many completions + * to wait for. + */ + sec->error_comps_pending = atomic_read(&sec->error_list_count); + + mutex_unlock(&sec->mutex); +} + +void ib_destroy_qp_security_abort(struct ib_qp_security *sec) +{ + int ret; + int i; + + /* If a concurrent cache update is in progress this + * QP security could be marked for an error state + * transition. Wait for this to complete. + */ + for (i = 0; i < sec->error_comps_pending; i++) + wait_for_completion(&sec->error_complete); + + mutex_lock(&sec->mutex); + sec->destroying = false; + + /* Restore the position in the lists and verify + * access is still allowed in case a cache update + * occurred while attempting to destroy. + * + * Because these setting were listed already + * and removed during ib_destroy_qp_security_begin + * we know the pkey_index_qp_list for the PKey + * already exists so port_pkey_list_insert won't fail. + */ + if (sec->ports_pkeys) { + port_pkey_list_insert(&sec->ports_pkeys->main); + port_pkey_list_insert(&sec->ports_pkeys->alt); + } + + ret = check_qp_port_pkey_settings(sec->ports_pkeys, sec); + if (ret) + qp_to_error(sec); + + mutex_unlock(&sec->mutex); +} + +void ib_destroy_qp_security_end(struct ib_qp_security *sec) +{ + int i; + + /* If a concurrent cache update is occurring we must + * wait until this QP security structure is processed + * in the QP to error flow before destroying it because + * the to_error_list is in use. + */ + for (i = 0; i < sec->error_comps_pending; i++) + wait_for_completion(&sec->error_complete); + + destroy_qp_security(sec); +} + +void ib_security_cache_change(struct ib_device *device, + u8 port_num, + u64 subnet_prefix) +{ + struct pkey_index_qp_list *pkey; + + list_for_each_entry(pkey, + &device->port_pkey_list[port_num].pkey_list, + pkey_index_list) { + check_pkey_qps(pkey, + device, + port_num, + subnet_prefix); + } +} + +void ib_security_destroy_port_pkey_list(struct ib_device *device) +{ + struct pkey_index_qp_list *pkey, *tmp_pkey; + int i; + + for (i = rdma_start_port(device); i <= rdma_end_port(device); i++) { + spin_lock(&device->port_pkey_list[i].list_lock); + list_for_each_entry_safe(pkey, + tmp_pkey, + &device->port_pkey_list[i].pkey_list, + pkey_index_list) { + list_del(&pkey->pkey_index_list); + kfree(pkey); + } + spin_unlock(&device->port_pkey_list[i].list_lock); + } +} + +int ib_security_modify_qp(struct ib_qp *qp, + struct ib_qp_attr *qp_attr, + int qp_attr_mask, + struct ib_udata *udata) +{ + int ret = 0; + struct ib_ports_pkeys *tmp_pps; + struct ib_ports_pkeys *new_pps; + bool special_qp = (qp->qp_type == IB_QPT_SMI || + qp->qp_type == IB_QPT_GSI || + qp->qp_type >= IB_QPT_RESERVED1); + bool pps_change = ((qp_attr_mask & (IB_QP_PKEY_INDEX | IB_QP_PORT)) || + (qp_attr_mask & IB_QP_ALT_PATH)); + + if (pps_change && !special_qp) { + mutex_lock(&qp->qp_sec->mutex); + new_pps = get_new_pps(qp, + qp_attr, + qp_attr_mask); + + /* Add this QP to the lists for the new port + * and pkey settings before checking for permission + * in case there is a concurrent cache update + * occurring. Walking the list for a cache change + * doesn't acquire the security mutex unless it's + * sending the QP to error. + */ + ret = port_pkey_list_insert(&new_pps->main); + + if (!ret) + ret = port_pkey_list_insert(&new_pps->alt); + + if (!ret) + ret = check_qp_port_pkey_settings(new_pps, + qp->qp_sec); + } + + if (!ret) + ret = qp->device->modify_qp(qp->real_qp, + qp_attr, + qp_attr_mask, + udata); + + if (pps_change && !special_qp) { + /* Clean up the lists and free the appropriate + * ports_pkeys structure. + */ + if (ret) { + tmp_pps = new_pps; + } else { + tmp_pps = qp->qp_sec->ports_pkeys; + qp->qp_sec->ports_pkeys = new_pps; + } + + if (tmp_pps) { + port_pkey_list_remove(&tmp_pps->main); + port_pkey_list_remove(&tmp_pps->alt); + } + kfree(tmp_pps); + mutex_unlock(&qp->qp_sec->mutex); + } + return ret; +} +EXPORT_SYMBOL(ib_security_modify_qp); + +#endif /* CONFIG_SECURITY_INFINIBAND */ diff --git a/drivers/infiniband/core/uverbs_cmd.c b/drivers/infiniband/core/uverbs_cmd.c index 70b7fb156414..0ad3b05405d8 100644 --- a/drivers/infiniband/core/uverbs_cmd.c +++ b/drivers/infiniband/core/uverbs_cmd.c @@ -1508,6 +1508,10 @@ static int create_qp(struct ib_uverbs_file *file, } if (cmd->qp_type != IB_QPT_XRC_TGT) { + ret = ib_create_qp_security(qp, device); + if (ret) + goto err_cb; + qp->real_qp = qp; qp->device = device; qp->pd = pd; @@ -2002,14 +2006,17 @@ static int modify_qp(struct ib_uverbs_file *file, if (ret) goto release_qp; } - ret = qp->device->modify_qp(qp, attr, + ret = ib_security_modify_qp(qp, + attr, modify_qp_mask(qp->qp_type, cmd->base.attr_mask), udata); } else { - ret = ib_modify_qp(qp, attr, - modify_qp_mask(qp->qp_type, - cmd->base.attr_mask)); + ret = ib_security_modify_qp(qp, + attr, + modify_qp_mask(qp->qp_type, + cmd->base.attr_mask), + NULL); } release_qp: diff --git a/drivers/infiniband/core/verbs.c b/drivers/infiniband/core/verbs.c index 4792f5209ac2..c973a83c898b 100644 --- a/drivers/infiniband/core/verbs.c +++ b/drivers/infiniband/core/verbs.c @@ -44,6 +44,7 @@ #include #include #include +#include #include #include @@ -713,11 +714,19 @@ static struct ib_qp *__ib_open_qp(struct ib_qp *real_qp, { struct ib_qp *qp; unsigned long flags; + int err; qp = kzalloc(sizeof *qp, GFP_KERNEL); if (!qp) return ERR_PTR(-ENOMEM); + qp->real_qp = real_qp; + err = ib_open_shared_qp_security(qp, real_qp->device); + if (err) { + kfree(qp); + return ERR_PTR(err); + } + qp->real_qp = real_qp; atomic_inc(&real_qp->usecnt); qp->device = real_qp->device; @@ -804,6 +813,12 @@ struct ib_qp *ib_create_qp(struct ib_pd *pd, if (IS_ERR(qp)) return qp; + ret = ib_create_qp_security(qp, device); + if (ret) { + ib_destroy_qp(qp); + return ERR_PTR(ret); + } + qp->device = device; qp->real_qp = qp; qp->uobject = NULL; @@ -1266,7 +1281,7 @@ int ib_modify_qp(struct ib_qp *qp, return ret; } - return qp->device->modify_qp(qp->real_qp, qp_attr, qp_attr_mask, NULL); + return ib_security_modify_qp(qp->real_qp, qp_attr, qp_attr_mask, NULL); } EXPORT_SYMBOL(ib_modify_qp); @@ -1295,6 +1310,7 @@ int ib_close_qp(struct ib_qp *qp) spin_unlock_irqrestore(&real_qp->device->event_handler_lock, flags); atomic_dec(&real_qp->usecnt); + ib_close_shared_qp_security(qp->qp_sec); kfree(qp); return 0; @@ -1335,6 +1351,7 @@ int ib_destroy_qp(struct ib_qp *qp) struct ib_cq *scq, *rcq; struct ib_srq *srq; struct ib_rwq_ind_table *ind_tbl; + struct ib_qp_security *sec; int ret; WARN_ON_ONCE(qp->mrs_used > 0); @@ -1350,6 +1367,9 @@ int ib_destroy_qp(struct ib_qp *qp) rcq = qp->recv_cq; srq = qp->srq; ind_tbl = qp->rwq_ind_tbl; + sec = qp->qp_sec; + if (sec) + ib_destroy_qp_security_begin(sec); if (!qp->uobject) rdma_rw_cleanup_mrs(qp); @@ -1366,6 +1386,11 @@ int ib_destroy_qp(struct ib_qp *qp) atomic_dec(&srq->usecnt); if (ind_tbl) atomic_dec(&ind_tbl->usecnt); + if (sec) + ib_destroy_qp_security_end(sec); + } else { + if (sec) + ib_destroy_qp_security_abort(sec); } return ret; diff --git a/include/linux/lsm_hooks.h b/include/linux/lsm_hooks.h index 080f34e66017..6d9f41fffda7 100644 --- a/include/linux/lsm_hooks.h +++ b/include/linux/lsm_hooks.h @@ -8,6 +8,7 @@ * Copyright (C) 2001 Silicon Graphics, Inc. (Trust Technology Group) * Copyright (C) 2015 Intel Corporation. * Copyright (C) 2015 Casey Schaufler + * Copyright (C) 2016 Mellanox Techonologies * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -911,6 +912,21 @@ * associated with the TUN device's security structure. * @security pointer to the TUN devices's security structure. * + * Security hooks for Infiniband + * + * @ib_pkey_access: + * Check permission to access a pkey when modifing a QP. + * @subnet_prefix the subnet prefix of the port being used. + * @pkey the pkey to be accessed. + * @sec pointer to a security structure. + * @ib_alloc_security: + * Allocate a security structure for Infiniband objects. + * @sec pointer to a security structure pointer. + * Returns 0 on success, non-zero on failure + * @ib_free_security: + * Deallocate an Infiniband security structure. + * @sec contains the security structure to be freed. + * * Security hooks for XFRM operations. * * @xfrm_policy_alloc_security: @@ -1620,6 +1636,12 @@ union security_list_options { int (*tun_dev_open)(void *security); #endif /* CONFIG_SECURITY_NETWORK */ +#ifdef CONFIG_SECURITY_INFINIBAND + int (*ib_pkey_access)(void *sec, u64 subnet_prefix, u16 pkey); + int (*ib_alloc_security)(void **sec); + void (*ib_free_security)(void *sec); +#endif /* CONFIG_SECURITY_INFINIBAND */ + #ifdef CONFIG_SECURITY_NETWORK_XFRM int (*xfrm_policy_alloc_security)(struct xfrm_sec_ctx **ctxp, struct xfrm_user_sec_ctx *sec_ctx, @@ -1851,6 +1873,11 @@ struct security_hook_heads { struct list_head tun_dev_attach; struct list_head tun_dev_open; #endif /* CONFIG_SECURITY_NETWORK */ +#ifdef CONFIG_SECURITY_INFINIBAND + struct list_head ib_pkey_access; + struct list_head ib_alloc_security; + struct list_head ib_free_security; +#endif /* CONFIG_SECURITY_INFINIBAND */ #ifdef CONFIG_SECURITY_NETWORK_XFRM struct list_head xfrm_policy_alloc_security; struct list_head xfrm_policy_clone_security; diff --git a/include/linux/security.h b/include/linux/security.h index af675b576645..8c73ee073bab 100644 --- a/include/linux/security.h +++ b/include/linux/security.h @@ -6,6 +6,7 @@ * Copyright (C) 2001 Networks Associates Technology, Inc * Copyright (C) 2001 James Morris * Copyright (C) 2001 Silicon Graphics, Inc. (Trust Technology Group) + * Copyright (C) 2016 Mellanox Techonologies * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -1406,6 +1407,26 @@ static inline int security_tun_dev_open(void *security) } #endif /* CONFIG_SECURITY_NETWORK */ +#ifdef CONFIG_SECURITY_INFINIBAND +int security_ib_pkey_access(void *sec, u64 subnet_prefix, u16 pkey); +int security_ib_alloc_security(void **sec); +void security_ib_free_security(void *sec); +#else /* CONFIG_SECURITY_INFINIBAND */ +static inline int security_ib_pkey_access(void *sec, u64 subnet_prefix, u16 pkey) +{ + return 0; +} + +static inline int security_ib_alloc_security(void **sec) +{ + return 0; +} + +static inline void security_ib_free_security(void *sec) +{ +} +#endif /* CONFIG_SECURITY_INFINIBAND */ + #ifdef CONFIG_SECURITY_NETWORK_XFRM int security_xfrm_policy_alloc(struct xfrm_sec_ctx **ctxp, diff --git a/include/rdma/ib_verbs.h b/include/rdma/ib_verbs.h index 9dc4e7e0aba4..0e480a5630d4 100644 --- a/include/rdma/ib_verbs.h +++ b/include/rdma/ib_verbs.h @@ -1614,6 +1614,45 @@ struct ib_rwq_ind_table_init_attr { struct ib_wq **ind_tbl; }; +enum port_pkey_state { + IB_PORT_PKEY_NOT_VALID = 0, + IB_PORT_PKEY_VALID = 1, + IB_PORT_PKEY_LISTED = 2, +}; + +struct ib_qp_security; + +struct ib_port_pkey { + enum port_pkey_state state; + u16 pkey_index; + u8 port_num; + struct list_head qp_list; + struct list_head to_error_list; + struct ib_qp_security *sec; +}; + +struct ib_ports_pkeys { + struct ib_port_pkey main; + struct ib_port_pkey alt; +}; + +struct ib_qp_security { + struct ib_qp *qp; + struct ib_device *dev; + /* Hold this mutex when changing port and pkey settings. */ + struct mutex mutex; + struct ib_ports_pkeys *ports_pkeys; + /* A list of all open shared QP handles. Required to enforce security + * properly for all users of a shared QP. + */ + struct list_head shared_qp_list; + void *security; + bool destroying; + atomic_t error_list_count; + struct completion error_complete; + int error_comps_pending; +}; + /* * @max_write_sge: Maximum SGE elements per RDMA WRITE request. * @max_read_sge: Maximum SGE elements per RDMA READ request. @@ -1643,6 +1682,7 @@ struct ib_qp { u32 max_read_sge; enum ib_qp_type qp_type; struct ib_rwq_ind_table *rwq_ind_tbl; + struct ib_qp_security *qp_sec; }; struct ib_mr { @@ -1941,6 +1981,12 @@ struct rdma_netdev { union ib_gid *gid, u16 mlid); }; +struct ib_port_pkey_list { + /* Lock to hold while modifying the list. */ + spinlock_t list_lock; + struct list_head pkey_list; +}; + struct ib_device { /* Do not access @dma_device directly from ULP nor from HW drivers. */ struct device *dma_device; @@ -1964,6 +2010,8 @@ struct ib_device { int num_comp_vectors; + struct ib_port_pkey_list *port_pkey_list; + struct iw_cm_verbs *iwcm; /** diff --git a/security/Kconfig b/security/Kconfig index bdcbb92927ab..d540bfe73190 100644 --- a/security/Kconfig +++ b/security/Kconfig @@ -54,6 +54,15 @@ config SECURITY_NETWORK implement socket and networking access controls. If you are unsure how to answer this question, answer N. +config SECURITY_INFINIBAND + bool "Infiniband Security Hooks" + depends on SECURITY && INFINIBAND + help + This enables the Infiniband security hooks. + If enabled, a security module can use these hooks to + implement Infiniband access controls. + If you are unsure how to answer this question, answer N. + config SECURITY_NETWORK_XFRM bool "XFRM (IPSec) Networking Security Hooks" depends on XFRM && SECURITY_NETWORK diff --git a/security/security.c b/security/security.c index 38316bb28b16..2a9d1a7fa1f8 100644 --- a/security/security.c +++ b/security/security.c @@ -4,6 +4,7 @@ * Copyright (C) 2001 WireX Communications, Inc * Copyright (C) 2001-2002 Greg Kroah-Hartman * Copyright (C) 2001 Networks Associates Technology, Inc + * Copyright (C) 2016 Mellanox Technologies * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -1515,6 +1516,27 @@ EXPORT_SYMBOL(security_tun_dev_open); #endif /* CONFIG_SECURITY_NETWORK */ +#ifdef CONFIG_SECURITY_INFINIBAND + +int security_ib_pkey_access(void *sec, u64 subnet_prefix, u16 pkey) +{ + return call_int_hook(ib_pkey_access, 0, sec, subnet_prefix, pkey); +} +EXPORT_SYMBOL(security_ib_pkey_access); + +int security_ib_alloc_security(void **sec) +{ + return call_int_hook(ib_alloc_security, 0, sec); +} +EXPORT_SYMBOL(security_ib_alloc_security); + +void security_ib_free_security(void *sec) +{ + call_void_hook(ib_free_security, sec); +} +EXPORT_SYMBOL(security_ib_free_security); +#endif /* CONFIG_SECURITY_INFINIBAND */ + #ifdef CONFIG_SECURITY_NETWORK_XFRM int security_xfrm_policy_alloc(struct xfrm_sec_ctx **ctxp, -- cgit v1.2.3 From 8f408ab64be6319cb7736cbc6982838dcc362306 Mon Sep 17 00:00:00 2001 From: Daniel Jurgens Date: Fri, 19 May 2017 15:48:53 +0300 Subject: selinux lsm IB/core: Implement LSM notification system Add a generic notificaiton mechanism in the LSM. Interested consumers can register a callback with the LSM and security modules can produce events. Because access to Infiniband QPs are enforced in the setup phase of a connection security should be enforced again if the policy changes. Register infiniband devices for policy change notification and check all QPs on that device when the notification is received. Add a call to the notification mechanism from SELinux when the AVC cache changes or setenforce is cleared. Signed-off-by: Daniel Jurgens Acked-by: James Morris Acked-by: Doug Ledford Signed-off-by: Paul Moore --- drivers/infiniband/core/device.c | 53 ++++++++++++++++++++++++++++++++++++++++ include/linux/security.h | 23 +++++++++++++++++ security/security.c | 20 +++++++++++++++ security/selinux/hooks.c | 11 +++++++++ security/selinux/selinuxfs.c | 2 ++ 5 files changed, 109 insertions(+) diff --git a/drivers/infiniband/core/device.c b/drivers/infiniband/core/device.c index 96e730cc9b81..631eaa9daf65 100644 --- a/drivers/infiniband/core/device.c +++ b/drivers/infiniband/core/device.c @@ -39,6 +39,8 @@ #include #include #include +#include +#include #include #include #include @@ -82,6 +84,14 @@ static LIST_HEAD(client_list); static DEFINE_MUTEX(device_mutex); static DECLARE_RWSEM(lists_rwsem); +static int ib_security_change(struct notifier_block *nb, unsigned long event, + void *lsm_data); +static void ib_policy_change_task(struct work_struct *work); +static DECLARE_WORK(ib_policy_change_work, ib_policy_change_task); + +static struct notifier_block ibdev_lsm_nb = { + .notifier_call = ib_security_change, +}; static int ib_device_check_mandatory(struct ib_device *device) { @@ -349,6 +359,40 @@ static int setup_port_pkey_list(struct ib_device *device) return 0; } +static void ib_policy_change_task(struct work_struct *work) +{ + struct ib_device *dev; + + down_read(&lists_rwsem); + list_for_each_entry(dev, &device_list, core_list) { + int i; + + for (i = rdma_start_port(dev); i <= rdma_end_port(dev); i++) { + u64 sp; + int ret = ib_get_cached_subnet_prefix(dev, + i, + &sp); + + WARN_ONCE(ret, + "ib_get_cached_subnet_prefix err: %d, this should never happen here\n", + ret); + ib_security_cache_change(dev, i, sp); + } + } + up_read(&lists_rwsem); +} + +static int ib_security_change(struct notifier_block *nb, unsigned long event, + void *lsm_data) +{ + if (event != LSM_POLICY_CHANGE) + return NOTIFY_DONE; + + schedule_work(&ib_policy_change_work); + + return NOTIFY_OK; +} + /** * ib_register_device - Register an IB device with IB core * @device:Device to register @@ -1115,10 +1159,18 @@ static int __init ib_core_init(void) goto err_sa; } + ret = register_lsm_notifier(&ibdev_lsm_nb); + if (ret) { + pr_warn("Couldn't register LSM notifier. ret %d\n", ret); + goto err_ibnl_clients; + } + ib_cache_setup(); return 0; +err_ibnl_clients: + ib_remove_ibnl_clients(); err_sa: ib_sa_cleanup(); err_mad: @@ -1138,6 +1190,7 @@ err: static void __exit ib_core_cleanup(void) { + unregister_lsm_notifier(&ibdev_lsm_nb); ib_cache_cleanup(); ib_remove_ibnl_clients(); ib_sa_cleanup(); diff --git a/include/linux/security.h b/include/linux/security.h index 8c73ee073bab..f96e333f6042 100644 --- a/include/linux/security.h +++ b/include/linux/security.h @@ -69,6 +69,10 @@ struct audit_krule; struct user_namespace; struct timezone; +enum lsm_event { + LSM_POLICY_CHANGE, +}; + /* These functions are in security/commoncap.c */ extern int cap_capable(const struct cred *cred, struct user_namespace *ns, int cap, int audit); @@ -164,6 +168,10 @@ struct security_mnt_opts { int num_mnt_opts; }; +int call_lsm_notifier(enum lsm_event event, void *data); +int register_lsm_notifier(struct notifier_block *nb); +int unregister_lsm_notifier(struct notifier_block *nb); + static inline void security_init_mnt_opts(struct security_mnt_opts *opts) { opts->mnt_opts = NULL; @@ -382,6 +390,21 @@ int security_inode_getsecctx(struct inode *inode, void **ctx, u32 *ctxlen); struct security_mnt_opts { }; +static inline int call_lsm_notifier(enum lsm_event event, void *data) +{ + return 0; +} + +static inline int register_lsm_notifier(struct notifier_block *nb) +{ + return 0; +} + +static inline int unregister_lsm_notifier(struct notifier_block *nb) +{ + return 0; +} + static inline void security_init_mnt_opts(struct security_mnt_opts *opts) { } diff --git a/security/security.c b/security/security.c index 2a9d1a7fa1f8..b59be0d6535f 100644 --- a/security/security.c +++ b/security/security.c @@ -35,6 +35,8 @@ #define SECURITY_NAME_MAX 10 struct security_hook_heads security_hook_heads __lsm_ro_after_init; +static ATOMIC_NOTIFIER_HEAD(lsm_notifier_chain); + char *lsm_names; /* Boot-time LSM user choice */ static __initdata char chosen_lsm[SECURITY_NAME_MAX + 1] = @@ -166,6 +168,24 @@ void __init security_add_hooks(struct security_hook_list *hooks, int count, panic("%s - Cannot get early memory.\n", __func__); } +int call_lsm_notifier(enum lsm_event event, void *data) +{ + return atomic_notifier_call_chain(&lsm_notifier_chain, event, data); +} +EXPORT_SYMBOL(call_lsm_notifier); + +int register_lsm_notifier(struct notifier_block *nb) +{ + return atomic_notifier_chain_register(&lsm_notifier_chain, nb); +} +EXPORT_SYMBOL(register_lsm_notifier); + +int unregister_lsm_notifier(struct notifier_block *nb) +{ + return atomic_notifier_chain_unregister(&lsm_notifier_chain, nb); +} +EXPORT_SYMBOL(unregister_lsm_notifier); + /* * Hook list operation macros. * diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index cfb7ce339adc..b005acbcc6e9 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -171,6 +171,14 @@ static int selinux_netcache_avc_callback(u32 event) return 0; } +static int selinux_lsm_notifier_avc_callback(u32 event) +{ + if (event == AVC_CALLBACK_RESET) + call_lsm_notifier(LSM_POLICY_CHANGE, NULL); + + return 0; +} + /* * initialise the security for the init task */ @@ -6387,6 +6395,9 @@ static __init int selinux_init(void) if (avc_add_callback(selinux_netcache_avc_callback, AVC_CALLBACK_RESET)) panic("SELinux: Unable to register AVC netcache callback\n"); + if (avc_add_callback(selinux_lsm_notifier_avc_callback, AVC_CALLBACK_RESET)) + panic("SELinux: Unable to register AVC LSM notifier callback\n"); + if (selinux_enforcing) printk(KERN_DEBUG "SELinux: Starting in enforcing mode\n"); else diff --git a/security/selinux/selinuxfs.c b/security/selinux/selinuxfs.c index 82adb78a58f7..9010a3632d6f 100644 --- a/security/selinux/selinuxfs.c +++ b/security/selinux/selinuxfs.c @@ -154,6 +154,8 @@ static ssize_t sel_write_enforce(struct file *file, const char __user *buf, avc_ss_reset(0); selnl_notify_setenforce(selinux_enforcing); selinux_status_update_setenforce(selinux_enforcing); + if (!selinux_enforcing) + call_lsm_notifier(LSM_POLICY_CHANGE, NULL); } length = count; out: -- cgit v1.2.3 From 47a2b338fe63200d716d2e24131cdb49f17c77da Mon Sep 17 00:00:00 2001 From: Daniel Jurgens Date: Fri, 19 May 2017 15:48:54 +0300 Subject: IB/core: Enforce security on management datagrams Allocate and free a security context when creating and destroying a MAD agent. This context is used for controlling access to PKeys and sending and receiving SMPs. When sending or receiving a MAD check that the agent has permission to access the PKey for the Subnet Prefix of the port. During MAD and snoop agent registration for SMI QPs check that the calling process has permission to access the manage the subnet and register a callback with the LSM to be notified of policy changes. When notificaiton of a policy change occurs recheck permission and set a flag indicating sending and receiving SMPs is allowed. When sending and receiving MADs check that the agent has access to the SMI if it's on an SMI QP. Because security policy can change it's possible permission was allowed when creating the agent, but no longer is. Signed-off-by: Daniel Jurgens Acked-by: Doug Ledford [PM: remove the LSM hook init code] Signed-off-by: Paul Moore --- drivers/infiniband/core/core_priv.h | 35 ++++++++++++++ drivers/infiniband/core/mad.c | 52 +++++++++++++++++---- drivers/infiniband/core/security.c | 92 +++++++++++++++++++++++++++++++++++++ include/linux/lsm_hooks.h | 8 ++++ include/linux/security.h | 6 +++ include/rdma/ib_mad.h | 4 ++ security/security.c | 6 +++ 7 files changed, 195 insertions(+), 8 deletions(-) diff --git a/drivers/infiniband/core/core_priv.h b/drivers/infiniband/core/core_priv.h index 7b63215f80c2..06645272c784 100644 --- a/drivers/infiniband/core/core_priv.h +++ b/drivers/infiniband/core/core_priv.h @@ -38,6 +38,8 @@ #include #include +#include +#include "mad_priv.h" struct pkey_index_qp_list { struct list_head pkey_index_list; @@ -189,6 +191,11 @@ int ib_get_cached_subnet_prefix(struct ib_device *device, u64 *sn_pfx); #ifdef CONFIG_SECURITY_INFINIBAND +int ib_security_pkey_access(struct ib_device *dev, + u8 port_num, + u16 pkey_index, + void *sec); + void ib_security_destroy_port_pkey_list(struct ib_device *device); void ib_security_cache_change(struct ib_device *device, @@ -206,7 +213,19 @@ void ib_destroy_qp_security_abort(struct ib_qp_security *sec); void ib_destroy_qp_security_end(struct ib_qp_security *sec); int ib_open_shared_qp_security(struct ib_qp *qp, struct ib_device *dev); void ib_close_shared_qp_security(struct ib_qp_security *sec); +int ib_mad_agent_security_setup(struct ib_mad_agent *agent, + enum ib_qp_type qp_type); +void ib_mad_agent_security_cleanup(struct ib_mad_agent *agent); +int ib_mad_enforce_security(struct ib_mad_agent_private *map, u16 pkey_index); #else +static inline int ib_security_pkey_access(struct ib_device *dev, + u8 port_num, + u16 pkey_index, + void *sec) +{ + return 0; +} + static inline void ib_security_destroy_port_pkey_list(struct ib_device *device) { } @@ -255,5 +274,21 @@ static inline int ib_open_shared_qp_security(struct ib_qp *qp, static inline void ib_close_shared_qp_security(struct ib_qp_security *sec) { } + +static inline int ib_mad_agent_security_setup(struct ib_mad_agent *agent, + enum ib_qp_type qp_type) +{ + return 0; +} + +static inline void ib_mad_agent_security_cleanup(struct ib_mad_agent *agent) +{ +} + +static inline int ib_mad_enforce_security(struct ib_mad_agent_private *map, + u16 pkey_index) +{ + return 0; +} #endif #endif /* _CORE_PRIV_H */ diff --git a/drivers/infiniband/core/mad.c b/drivers/infiniband/core/mad.c index 192ee3dafb80..f8f53bb90837 100644 --- a/drivers/infiniband/core/mad.c +++ b/drivers/infiniband/core/mad.c @@ -40,9 +40,11 @@ #include #include #include +#include #include #include "mad_priv.h" +#include "core_priv.h" #include "mad_rmpp.h" #include "smi.h" #include "opa_smi.h" @@ -369,6 +371,12 @@ struct ib_mad_agent *ib_register_mad_agent(struct ib_device *device, atomic_set(&mad_agent_priv->refcount, 1); init_completion(&mad_agent_priv->comp); + ret2 = ib_mad_agent_security_setup(&mad_agent_priv->agent, qp_type); + if (ret2) { + ret = ERR_PTR(ret2); + goto error4; + } + spin_lock_irqsave(&port_priv->reg_lock, flags); mad_agent_priv->agent.hi_tid = ++ib_mad_client_id; @@ -386,7 +394,7 @@ struct ib_mad_agent *ib_register_mad_agent(struct ib_device *device, if (method) { if (method_in_use(&method, mad_reg_req)) - goto error4; + goto error5; } } ret2 = add_nonoui_reg_req(mad_reg_req, mad_agent_priv, @@ -402,14 +410,14 @@ struct ib_mad_agent *ib_register_mad_agent(struct ib_device *device, if (is_vendor_method_in_use( vendor_class, mad_reg_req)) - goto error4; + goto error5; } } ret2 = add_oui_reg_req(mad_reg_req, mad_agent_priv); } if (ret2) { ret = ERR_PTR(ret2); - goto error4; + goto error5; } } @@ -418,9 +426,10 @@ struct ib_mad_agent *ib_register_mad_agent(struct ib_device *device, spin_unlock_irqrestore(&port_priv->reg_lock, flags); return &mad_agent_priv->agent; - -error4: +error5: spin_unlock_irqrestore(&port_priv->reg_lock, flags); + ib_mad_agent_security_cleanup(&mad_agent_priv->agent); +error4: kfree(reg_req); error3: kfree(mad_agent_priv); @@ -491,6 +500,7 @@ struct ib_mad_agent *ib_register_mad_snoop(struct ib_device *device, struct ib_mad_agent *ret; struct ib_mad_snoop_private *mad_snoop_priv; int qpn; + int err; /* Validate parameters */ if ((is_snooping_sends(mad_snoop_flags) && !snoop_handler) || @@ -525,17 +535,25 @@ struct ib_mad_agent *ib_register_mad_snoop(struct ib_device *device, mad_snoop_priv->agent.port_num = port_num; mad_snoop_priv->mad_snoop_flags = mad_snoop_flags; init_completion(&mad_snoop_priv->comp); + + err = ib_mad_agent_security_setup(&mad_snoop_priv->agent, qp_type); + if (err) { + ret = ERR_PTR(err); + goto error2; + } + mad_snoop_priv->snoop_index = register_snoop_agent( &port_priv->qp_info[qpn], mad_snoop_priv); if (mad_snoop_priv->snoop_index < 0) { ret = ERR_PTR(mad_snoop_priv->snoop_index); - goto error2; + goto error3; } atomic_set(&mad_snoop_priv->refcount, 1); return &mad_snoop_priv->agent; - +error3: + ib_mad_agent_security_cleanup(&mad_snoop_priv->agent); error2: kfree(mad_snoop_priv); error1: @@ -581,6 +599,8 @@ static void unregister_mad_agent(struct ib_mad_agent_private *mad_agent_priv) deref_mad_agent(mad_agent_priv); wait_for_completion(&mad_agent_priv->comp); + ib_mad_agent_security_cleanup(&mad_agent_priv->agent); + kfree(mad_agent_priv->reg_req); kfree(mad_agent_priv); } @@ -599,6 +619,8 @@ static void unregister_mad_snoop(struct ib_mad_snoop_private *mad_snoop_priv) deref_snoop_agent(mad_snoop_priv); wait_for_completion(&mad_snoop_priv->comp); + ib_mad_agent_security_cleanup(&mad_snoop_priv->agent); + kfree(mad_snoop_priv); } @@ -1215,12 +1237,16 @@ int ib_post_send_mad(struct ib_mad_send_buf *send_buf, /* Walk list of send WRs and post each on send list */ for (; send_buf; send_buf = next_send_buf) { - mad_send_wr = container_of(send_buf, struct ib_mad_send_wr_private, send_buf); mad_agent_priv = mad_send_wr->mad_agent_priv; + ret = ib_mad_enforce_security(mad_agent_priv, + mad_send_wr->send_wr.pkey_index); + if (ret) + goto error; + if (!send_buf->mad_agent->send_handler || (send_buf->timeout_ms && !send_buf->mad_agent->recv_handler)) { @@ -1946,6 +1972,14 @@ static void ib_mad_complete_recv(struct ib_mad_agent_private *mad_agent_priv, struct ib_mad_send_wr_private *mad_send_wr; struct ib_mad_send_wc mad_send_wc; unsigned long flags; + int ret; + + ret = ib_mad_enforce_security(mad_agent_priv, + mad_recv_wc->wc->pkey_index); + if (ret) { + ib_free_recv_mad(mad_recv_wc); + deref_mad_agent(mad_agent_priv); + } INIT_LIST_HEAD(&mad_recv_wc->rmpp_list); list_add(&mad_recv_wc->recv_buf.list, &mad_recv_wc->rmpp_list); @@ -2003,6 +2037,8 @@ static void ib_mad_complete_recv(struct ib_mad_agent_private *mad_agent_priv, mad_recv_wc); deref_mad_agent(mad_agent_priv); } + + return; } static enum smi_action handle_ib_smi(const struct ib_mad_port_private *port_priv, diff --git a/drivers/infiniband/core/security.c b/drivers/infiniband/core/security.c index b2f170ddc062..3e8c38953912 100644 --- a/drivers/infiniband/core/security.c +++ b/drivers/infiniband/core/security.c @@ -39,6 +39,7 @@ #include #include #include "core_priv.h" +#include "mad_priv.h" static struct pkey_index_qp_list *get_pkey_idx_qp_list(struct ib_port_pkey *pp) { @@ -610,4 +611,95 @@ int ib_security_modify_qp(struct ib_qp *qp, } EXPORT_SYMBOL(ib_security_modify_qp); +int ib_security_pkey_access(struct ib_device *dev, + u8 port_num, + u16 pkey_index, + void *sec) +{ + u64 subnet_prefix; + u16 pkey; + int ret; + + ret = ib_get_cached_pkey(dev, port_num, pkey_index, &pkey); + if (ret) + return ret; + + ret = ib_get_cached_subnet_prefix(dev, port_num, &subnet_prefix); + + if (ret) + return ret; + + return security_ib_pkey_access(sec, subnet_prefix, pkey); +} +EXPORT_SYMBOL(ib_security_pkey_access); + +static int ib_mad_agent_security_change(struct notifier_block *nb, + unsigned long event, + void *data) +{ + struct ib_mad_agent *ag = container_of(nb, struct ib_mad_agent, lsm_nb); + + if (event != LSM_POLICY_CHANGE) + return NOTIFY_DONE; + + ag->smp_allowed = !security_ib_endport_manage_subnet(ag->security, + ag->device->name, + ag->port_num); + + return NOTIFY_OK; +} + +int ib_mad_agent_security_setup(struct ib_mad_agent *agent, + enum ib_qp_type qp_type) +{ + int ret; + + ret = security_ib_alloc_security(&agent->security); + if (ret) + return ret; + + if (qp_type != IB_QPT_SMI) + return 0; + + ret = security_ib_endport_manage_subnet(agent->security, + agent->device->name, + agent->port_num); + if (ret) + return ret; + + agent->lsm_nb.notifier_call = ib_mad_agent_security_change; + ret = register_lsm_notifier(&agent->lsm_nb); + if (ret) + return ret; + + agent->smp_allowed = true; + agent->lsm_nb_reg = true; + return 0; +} + +void ib_mad_agent_security_cleanup(struct ib_mad_agent *agent) +{ + security_ib_free_security(agent->security); + if (agent->lsm_nb_reg) + unregister_lsm_notifier(&agent->lsm_nb); +} + +int ib_mad_enforce_security(struct ib_mad_agent_private *map, u16 pkey_index) +{ + int ret; + + if (map->agent.qp->qp_type == IB_QPT_SMI && !map->agent.smp_allowed) + return -EACCES; + + ret = ib_security_pkey_access(map->agent.device, + map->agent.port_num, + pkey_index, + map->agent.security); + + if (ret) + return ret; + + return 0; +} + #endif /* CONFIG_SECURITY_INFINIBAND */ diff --git a/include/linux/lsm_hooks.h b/include/linux/lsm_hooks.h index 6d9f41fffda7..68d91e423bca 100644 --- a/include/linux/lsm_hooks.h +++ b/include/linux/lsm_hooks.h @@ -919,6 +919,11 @@ * @subnet_prefix the subnet prefix of the port being used. * @pkey the pkey to be accessed. * @sec pointer to a security structure. + * @ib_endport_manage_subnet: + * Check permissions to send and receive SMPs on a end port. + * @dev_name the IB device name (i.e. mlx4_0). + * @port_num the port number. + * @sec pointer to a security structure. * @ib_alloc_security: * Allocate a security structure for Infiniband objects. * @sec pointer to a security structure pointer. @@ -1638,6 +1643,8 @@ union security_list_options { #ifdef CONFIG_SECURITY_INFINIBAND int (*ib_pkey_access)(void *sec, u64 subnet_prefix, u16 pkey); + int (*ib_endport_manage_subnet)(void *sec, const char *dev_name, + u8 port_num); int (*ib_alloc_security)(void **sec); void (*ib_free_security)(void *sec); #endif /* CONFIG_SECURITY_INFINIBAND */ @@ -1875,6 +1882,7 @@ struct security_hook_heads { #endif /* CONFIG_SECURITY_NETWORK */ #ifdef CONFIG_SECURITY_INFINIBAND struct list_head ib_pkey_access; + struct list_head ib_endport_manage_subnet; struct list_head ib_alloc_security; struct list_head ib_free_security; #endif /* CONFIG_SECURITY_INFINIBAND */ diff --git a/include/linux/security.h b/include/linux/security.h index f96e333f6042..549cb828a888 100644 --- a/include/linux/security.h +++ b/include/linux/security.h @@ -1432,6 +1432,7 @@ static inline int security_tun_dev_open(void *security) #ifdef CONFIG_SECURITY_INFINIBAND int security_ib_pkey_access(void *sec, u64 subnet_prefix, u16 pkey); +int security_ib_endport_manage_subnet(void *sec, const char *name, u8 port_num); int security_ib_alloc_security(void **sec); void security_ib_free_security(void *sec); #else /* CONFIG_SECURITY_INFINIBAND */ @@ -1440,6 +1441,11 @@ static inline int security_ib_pkey_access(void *sec, u64 subnet_prefix, u16 pkey return 0; } +static inline int security_ib_endport_manage_subnet(void *sec, const char *dev_name, u8 port_num) +{ + return 0; +} + static inline int security_ib_alloc_security(void **sec) { return 0; diff --git a/include/rdma/ib_mad.h b/include/rdma/ib_mad.h index d67b11b72029..2f4f1768ded4 100644 --- a/include/rdma/ib_mad.h +++ b/include/rdma/ib_mad.h @@ -575,6 +575,10 @@ struct ib_mad_agent { u32 flags; u8 port_num; u8 rmpp_version; + void *security; + bool smp_allowed; + bool lsm_nb_reg; + struct notifier_block lsm_nb; }; /** diff --git a/security/security.c b/security/security.c index b59be0d6535f..714433e3e9a2 100644 --- a/security/security.c +++ b/security/security.c @@ -1544,6 +1544,12 @@ int security_ib_pkey_access(void *sec, u64 subnet_prefix, u16 pkey) } EXPORT_SYMBOL(security_ib_pkey_access); +int security_ib_endport_manage_subnet(void *sec, const char *dev_name, u8 port_num) +{ + return call_int_hook(ib_endport_manage_subnet, 0, sec, dev_name, port_num); +} +EXPORT_SYMBOL(security_ib_endport_manage_subnet); + int security_ib_alloc_security(void **sec) { return call_int_hook(ib_alloc_security, 0, sec); -- cgit v1.2.3 From a806f7a1616f29b80749d708115a643c1f4ba056 Mon Sep 17 00:00:00 2001 From: Daniel Jurgens Date: Fri, 19 May 2017 15:48:55 +0300 Subject: selinux: Create policydb version for Infiniband support Support for Infiniband requires the addition of two new object contexts, one for infiniband PKeys and another IB Ports. Added handlers to read and write the new ocontext types when reading or writing a binary policy representation. Signed-off-by: Daniel Jurgens Reviewed-by: Eli Cohen Reviewed-by: James Morris Acked-by: Doug Ledford Signed-off-by: Paul Moore --- security/selinux/include/security.h | 3 +- security/selinux/ss/policydb.c | 112 +++++++++++++++++++++++++++++++----- security/selinux/ss/policydb.h | 27 ++++++--- 3 files changed, 118 insertions(+), 24 deletions(-) diff --git a/security/selinux/include/security.h b/security/selinux/include/security.h index c4224bbf9f4e..b48a462cf446 100644 --- a/security/selinux/include/security.h +++ b/security/selinux/include/security.h @@ -36,10 +36,11 @@ #define POLICYDB_VERSION_DEFAULT_TYPE 28 #define POLICYDB_VERSION_CONSTRAINT_NAMES 29 #define POLICYDB_VERSION_XPERMS_IOCTL 30 +#define POLICYDB_VERSION_INFINIBAND 31 /* Range of policy versions we understand*/ #define POLICYDB_VERSION_MIN POLICYDB_VERSION_BASE -#define POLICYDB_VERSION_MAX POLICYDB_VERSION_XPERMS_IOCTL +#define POLICYDB_VERSION_MAX POLICYDB_VERSION_INFINIBAND /* Mask for just the mount related flags */ #define SE_MNTMASK 0x0f diff --git a/security/selinux/ss/policydb.c b/security/selinux/ss/policydb.c index 87d645d3a39f..aa6500abb178 100644 --- a/security/selinux/ss/policydb.c +++ b/security/selinux/ss/policydb.c @@ -17,6 +17,11 @@ * * Added support for the policy capability bitmap * + * Update: Mellanox Techonologies + * + * Added Infiniband support + * + * Copyright (C) 2016 Mellanox Techonologies * Copyright (C) 2007 Hewlett-Packard Development Company, L.P. * Copyright (C) 2004-2005 Trusted Computer Solutions, Inc. * Copyright (C) 2003 - 2004 Tresys Technology, LLC @@ -76,81 +81,86 @@ static struct policydb_compat_info policydb_compat[] = { { .version = POLICYDB_VERSION_BASE, .sym_num = SYM_NUM - 3, - .ocon_num = OCON_NUM - 1, + .ocon_num = OCON_NUM - 3, }, { .version = POLICYDB_VERSION_BOOL, .sym_num = SYM_NUM - 2, - .ocon_num = OCON_NUM - 1, + .ocon_num = OCON_NUM - 3, }, { .version = POLICYDB_VERSION_IPV6, .sym_num = SYM_NUM - 2, - .ocon_num = OCON_NUM, + .ocon_num = OCON_NUM - 2, }, { .version = POLICYDB_VERSION_NLCLASS, .sym_num = SYM_NUM - 2, - .ocon_num = OCON_NUM, + .ocon_num = OCON_NUM - 2, }, { .version = POLICYDB_VERSION_MLS, .sym_num = SYM_NUM, - .ocon_num = OCON_NUM, + .ocon_num = OCON_NUM - 2, }, { .version = POLICYDB_VERSION_AVTAB, .sym_num = SYM_NUM, - .ocon_num = OCON_NUM, + .ocon_num = OCON_NUM - 2, }, { .version = POLICYDB_VERSION_RANGETRANS, .sym_num = SYM_NUM, - .ocon_num = OCON_NUM, + .ocon_num = OCON_NUM - 2, }, { .version = POLICYDB_VERSION_POLCAP, .sym_num = SYM_NUM, - .ocon_num = OCON_NUM, + .ocon_num = OCON_NUM - 2, }, { .version = POLICYDB_VERSION_PERMISSIVE, .sym_num = SYM_NUM, - .ocon_num = OCON_NUM, + .ocon_num = OCON_NUM - 2, }, { .version = POLICYDB_VERSION_BOUNDARY, .sym_num = SYM_NUM, - .ocon_num = OCON_NUM, + .ocon_num = OCON_NUM - 2, }, { .version = POLICYDB_VERSION_FILENAME_TRANS, .sym_num = SYM_NUM, - .ocon_num = OCON_NUM, + .ocon_num = OCON_NUM - 2, }, { .version = POLICYDB_VERSION_ROLETRANS, .sym_num = SYM_NUM, - .ocon_num = OCON_NUM, + .ocon_num = OCON_NUM - 2, }, { .version = POLICYDB_VERSION_NEW_OBJECT_DEFAULTS, .sym_num = SYM_NUM, - .ocon_num = OCON_NUM, + .ocon_num = OCON_NUM - 2, }, { .version = POLICYDB_VERSION_DEFAULT_TYPE, .sym_num = SYM_NUM, - .ocon_num = OCON_NUM, + .ocon_num = OCON_NUM - 2, }, { .version = POLICYDB_VERSION_CONSTRAINT_NAMES, .sym_num = SYM_NUM, - .ocon_num = OCON_NUM, + .ocon_num = OCON_NUM - 2, }, { .version = POLICYDB_VERSION_XPERMS_IOCTL, .sym_num = SYM_NUM, + .ocon_num = OCON_NUM - 2, + }, + { + .version = POLICYDB_VERSION_INFINIBAND, + .sym_num = SYM_NUM, .ocon_num = OCON_NUM, }, }; @@ -2206,6 +2216,51 @@ static int ocontext_read(struct policydb *p, struct policydb_compat_info *info, goto out; break; } + case OCON_IBPKEY: + rc = next_entry(nodebuf, fp, sizeof(u32) * 4); + if (rc) + goto out; + + c->u.ibpkey.subnet_prefix = be64_to_cpu(*((__be64 *)nodebuf)); + + if (nodebuf[2] > 0xffff || + nodebuf[3] > 0xffff) { + rc = -EINVAL; + goto out; + } + + c->u.ibpkey.low_pkey = le32_to_cpu(nodebuf[2]); + c->u.ibpkey.high_pkey = le32_to_cpu(nodebuf[3]); + + rc = context_read_and_validate(&c->context[0], + p, + fp); + if (rc) + goto out; + break; + case OCON_IBENDPORT: + rc = next_entry(buf, fp, sizeof(u32) * 2); + if (rc) + goto out; + len = le32_to_cpu(buf[0]); + + rc = str_read(&c->u.ibendport.dev_name, GFP_KERNEL, fp, len); + if (rc) + goto out; + + if (buf[1] > 0xff || buf[1] == 0) { + rc = -EINVAL; + goto out; + } + + c->u.ibendport.port = le32_to_cpu(buf[1]); + + rc = context_read_and_validate(&c->context[0], + p, + fp); + if (rc) + goto out; + break; } } } @@ -3135,6 +3190,33 @@ static int ocontext_write(struct policydb *p, struct policydb_compat_info *info, if (rc) return rc; break; + case OCON_IBPKEY: + *((__be64 *)nodebuf) = cpu_to_be64(c->u.ibpkey.subnet_prefix); + + nodebuf[2] = cpu_to_le32(c->u.ibpkey.low_pkey); + nodebuf[3] = cpu_to_le32(c->u.ibpkey.high_pkey); + + rc = put_entry(nodebuf, sizeof(u32), 4, fp); + if (rc) + return rc; + rc = context_write(p, &c->context[0], fp); + if (rc) + return rc; + break; + case OCON_IBENDPORT: + len = strlen(c->u.ibendport.dev_name); + buf[0] = cpu_to_le32(len); + buf[1] = cpu_to_le32(c->u.ibendport.port); + rc = put_entry(buf, sizeof(u32), 2, fp); + if (rc) + return rc; + rc = put_entry(c->u.ibendport.dev_name, 1, len, fp); + if (rc) + return rc; + rc = context_write(p, &c->context[0], fp); + if (rc) + return rc; + break; } } } diff --git a/security/selinux/ss/policydb.h b/security/selinux/ss/policydb.h index 725d5945a97e..5d23eed35fa7 100644 --- a/security/selinux/ss/policydb.h +++ b/security/selinux/ss/policydb.h @@ -187,6 +187,15 @@ struct ocontext { u32 addr[4]; u32 mask[4]; } node6; /* IPv6 node information */ + struct { + u64 subnet_prefix; + u16 low_pkey; + u16 high_pkey; + } ibpkey; + struct { + char *dev_name; + u8 port; + } ibendport; } u; union { u32 sclass; /* security class for genfs */ @@ -215,14 +224,16 @@ struct genfs { #define SYM_NUM 8 /* object context array indices */ -#define OCON_ISID 0 /* initial SIDs */ -#define OCON_FS 1 /* unlabeled file systems */ -#define OCON_PORT 2 /* TCP and UDP port numbers */ -#define OCON_NETIF 3 /* network interfaces */ -#define OCON_NODE 4 /* nodes */ -#define OCON_FSUSE 5 /* fs_use */ -#define OCON_NODE6 6 /* IPv6 nodes */ -#define OCON_NUM 7 +#define OCON_ISID 0 /* initial SIDs */ +#define OCON_FS 1 /* unlabeled file systems */ +#define OCON_PORT 2 /* TCP and UDP port numbers */ +#define OCON_NETIF 3 /* network interfaces */ +#define OCON_NODE 4 /* nodes */ +#define OCON_FSUSE 5 /* fs_use */ +#define OCON_NODE6 6 /* IPv6 nodes */ +#define OCON_IBPKEY 7 /* Infiniband PKeys */ +#define OCON_IBENDPORT 8 /* Infiniband end ports */ +#define OCON_NUM 9 /* The policy database */ struct policydb { -- cgit v1.2.3 From 3a976fa6767f3edebbf43839b686efaf71b8dee1 Mon Sep 17 00:00:00 2001 From: Daniel Jurgens Date: Fri, 19 May 2017 15:48:56 +0300 Subject: selinux: Allocate and free infiniband security hooks Implement and attach hooks to allocate and free Infiniband object security structures. Signed-off-by: Daniel Jurgens Reviewed-by: James Morris Acked-by: Doug Ledford Signed-off-by: Paul Moore --- security/selinux/hooks.c | 25 ++++++++++++++++++++++++- security/selinux/include/objsec.h | 5 +++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index b005acbcc6e9..062b459b62bf 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -17,6 +17,7 @@ * Paul Moore * Copyright (C) 2007 Hitachi Software Engineering Co., Ltd. * Yuichi Nakamura + * Copyright (C) 2016 Mellanox Technologies * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2, @@ -6144,7 +6145,26 @@ static int selinux_key_getsecurity(struct key *key, char **_buffer) *_buffer = context; return rc; } +#endif + +#ifdef CONFIG_SECURITY_INFINIBAND +static int selinux_ib_alloc_security(void **ib_sec) +{ + struct ib_security_struct *sec; + + sec = kzalloc(sizeof(*sec), GFP_KERNEL); + if (!sec) + return -ENOMEM; + sec->sid = current_sid(); + + *ib_sec = sec; + return 0; +} +static void selinux_ib_free_security(void *ib_sec) +{ + kfree(ib_sec); +} #endif static struct security_hook_list selinux_hooks[] __lsm_ro_after_init = { @@ -6331,7 +6351,10 @@ static struct security_hook_list selinux_hooks[] __lsm_ro_after_init = { LSM_HOOK_INIT(tun_dev_attach_queue, selinux_tun_dev_attach_queue), LSM_HOOK_INIT(tun_dev_attach, selinux_tun_dev_attach), LSM_HOOK_INIT(tun_dev_open, selinux_tun_dev_open), - +#ifdef CONFIG_SECURITY_INFINIBAND + LSM_HOOK_INIT(ib_alloc_security, selinux_ib_alloc_security), + LSM_HOOK_INIT(ib_free_security, selinux_ib_free_security), +#endif #ifdef CONFIG_SECURITY_NETWORK_XFRM LSM_HOOK_INIT(xfrm_policy_alloc_security, selinux_xfrm_policy_alloc), LSM_HOOK_INIT(xfrm_policy_clone_security, selinux_xfrm_policy_clone), diff --git a/security/selinux/include/objsec.h b/security/selinux/include/objsec.h index c03cdcd12a3b..b7f15f7dc9af 100644 --- a/security/selinux/include/objsec.h +++ b/security/selinux/include/objsec.h @@ -10,6 +10,7 @@ * * Copyright (C) 2001,2002 Networks Associates Technology, Inc. * Copyright (C) 2003 Red Hat, Inc., James Morris + * Copyright (C) 2016 Mellanox Technologies * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2, @@ -139,6 +140,10 @@ struct key_security_struct { u32 sid; /* SID of key */ }; +struct ib_security_struct { + u32 sid; /* SID of the queue pair or MAD agent */ +}; + extern unsigned int selinux_checkreqprot; #endif /* _SELINUX_OBJSEC_H_ */ -- cgit v1.2.3 From cfc4d882d41780d93471066d57d4630995427b29 Mon Sep 17 00:00:00 2001 From: Daniel Jurgens Date: Fri, 19 May 2017 15:48:57 +0300 Subject: selinux: Implement Infiniband PKey "Access" access vector Add a type and access vector for PKeys. Implement the ib_pkey_access hook to check that the caller has permission to access the PKey on the given subnet prefix. Add an interface to get the PKey SID. Walk the PKey ocontexts to find an entry for the given subnet prefix and pkey. Signed-off-by: Daniel Jurgens Reviewed-by: James Morris Acked-by: Doug Ledford Signed-off-by: Paul Moore --- include/linux/lsm_audit.h | 7 +++++++ security/lsm_audit.c | 11 ++++++++++ security/selinux/hooks.c | 22 ++++++++++++++++++++ security/selinux/include/classmap.h | 2 ++ security/selinux/include/security.h | 2 ++ security/selinux/ss/services.c | 40 +++++++++++++++++++++++++++++++++++++ 6 files changed, 84 insertions(+) diff --git a/include/linux/lsm_audit.h b/include/linux/lsm_audit.h index e58e577117b6..0df5639a4ff4 100644 --- a/include/linux/lsm_audit.h +++ b/include/linux/lsm_audit.h @@ -45,6 +45,11 @@ struct lsm_ioctlop_audit { u16 cmd; }; +struct lsm_ibpkey_audit { + u64 subnet_prefix; + u16 pkey; +}; + /* Auxiliary data to use in generating the audit record. */ struct common_audit_data { char type; @@ -60,6 +65,7 @@ struct common_audit_data { #define LSM_AUDIT_DATA_DENTRY 10 #define LSM_AUDIT_DATA_IOCTL_OP 11 #define LSM_AUDIT_DATA_FILE 12 +#define LSM_AUDIT_DATA_IBPKEY 13 union { struct path path; struct dentry *dentry; @@ -77,6 +83,7 @@ struct common_audit_data { char *kmod_name; struct lsm_ioctlop_audit *op; struct file *file; + struct lsm_ibpkey_audit *ibpkey; } u; /* this union contains LSM specific data */ union { diff --git a/security/lsm_audit.c b/security/lsm_audit.c index 37f04dadc8d6..c22c99fae06a 100644 --- a/security/lsm_audit.c +++ b/security/lsm_audit.c @@ -410,6 +410,17 @@ static void dump_common_audit_data(struct audit_buffer *ab, audit_log_format(ab, " kmod="); audit_log_untrustedstring(ab, a->u.kmod_name); break; + case LSM_AUDIT_DATA_IBPKEY: { + struct in6_addr sbn_pfx; + + memset(&sbn_pfx.s6_addr, 0, + sizeof(sbn_pfx.s6_addr)); + memcpy(&sbn_pfx.s6_addr, &a->u.ibpkey->subnet_prefix, + sizeof(a->u.ibpkey->subnet_prefix)); + audit_log_format(ab, " pkey=0x%x subnet_prefix=%pI6c", + a->u.ibpkey->pkey, &sbn_pfx); + break; + } } /* switch (a->type) */ } diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index 062b459b62bf..b59255f86274 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -6148,6 +6148,27 @@ static int selinux_key_getsecurity(struct key *key, char **_buffer) #endif #ifdef CONFIG_SECURITY_INFINIBAND +static int selinux_ib_pkey_access(void *ib_sec, u64 subnet_prefix, u16 pkey_val) +{ + struct common_audit_data ad; + int err; + u32 sid = 0; + struct ib_security_struct *sec = ib_sec; + struct lsm_ibpkey_audit ibpkey; + + err = security_ib_pkey_sid(subnet_prefix, pkey_val, &sid); + if (err) + return err; + + ad.type = LSM_AUDIT_DATA_IBPKEY; + ibpkey.subnet_prefix = subnet_prefix; + ibpkey.pkey = pkey_val; + ad.u.ibpkey = &ibpkey; + return avc_has_perm(sec->sid, sid, + SECCLASS_INFINIBAND_PKEY, + INFINIBAND_PKEY__ACCESS, &ad); +} + static int selinux_ib_alloc_security(void **ib_sec) { struct ib_security_struct *sec; @@ -6352,6 +6373,7 @@ static struct security_hook_list selinux_hooks[] __lsm_ro_after_init = { LSM_HOOK_INIT(tun_dev_attach, selinux_tun_dev_attach), LSM_HOOK_INIT(tun_dev_open, selinux_tun_dev_open), #ifdef CONFIG_SECURITY_INFINIBAND + LSM_HOOK_INIT(ib_pkey_access, selinux_ib_pkey_access), LSM_HOOK_INIT(ib_alloc_security, selinux_ib_alloc_security), LSM_HOOK_INIT(ib_free_security, selinux_ib_free_security), #endif diff --git a/security/selinux/include/classmap.h b/security/selinux/include/classmap.h index 3e49a78f1f46..0fec1c505f84 100644 --- a/security/selinux/include/classmap.h +++ b/security/selinux/include/classmap.h @@ -231,6 +231,8 @@ struct security_class_mapping secclass_map[] = { { COMMON_SOCK_PERMS, NULL } }, { "smc_socket", { COMMON_SOCK_PERMS, NULL } }, + { "infiniband_pkey", + { "access", NULL } }, { NULL } }; diff --git a/security/selinux/include/security.h b/security/selinux/include/security.h index b48a462cf446..592c014e369c 100644 --- a/security/selinux/include/security.h +++ b/security/selinux/include/security.h @@ -181,6 +181,8 @@ int security_get_user_sids(u32 callsid, char *username, int security_port_sid(u8 protocol, u16 port, u32 *out_sid); +int security_ib_pkey_sid(u64 subnet_prefix, u16 pkey_num, u32 *out_sid); + int security_netif_sid(char *name, u32 *if_sid); int security_node_sid(u16 domain, void *addr, u32 addrlen, diff --git a/security/selinux/ss/services.c b/security/selinux/ss/services.c index 2dccba4851f8..02257d90adc9 100644 --- a/security/selinux/ss/services.c +++ b/security/selinux/ss/services.c @@ -2232,6 +2232,46 @@ out: return rc; } +/** + * security_pkey_sid - Obtain the SID for a pkey. + * @subnet_prefix: Subnet Prefix + * @pkey_num: pkey number + * @out_sid: security identifier + */ +int security_ib_pkey_sid(u64 subnet_prefix, u16 pkey_num, u32 *out_sid) +{ + struct ocontext *c; + int rc = 0; + + read_lock(&policy_rwlock); + + c = policydb.ocontexts[OCON_IBPKEY]; + while (c) { + if (c->u.ibpkey.low_pkey <= pkey_num && + c->u.ibpkey.high_pkey >= pkey_num && + c->u.ibpkey.subnet_prefix == subnet_prefix) + break; + + c = c->next; + } + + if (c) { + if (!c->sid[0]) { + rc = sidtab_context_to_sid(&sidtab, + &c->context[0], + &c->sid[0]); + if (rc) + goto out; + } + *out_sid = c->sid[0]; + } else + *out_sid = SECINITSID_UNLABELED; + +out: + read_unlock(&policy_rwlock); + return rc; +} + /** * security_netif_sid - Obtain the SID for a network interface. * @name: interface name -- cgit v1.2.3 From ab861dfca1652aa09b26b7aa2899feb29b33dfd9 Mon Sep 17 00:00:00 2001 From: Daniel Jurgens Date: Fri, 19 May 2017 15:48:58 +0300 Subject: selinux: Add IB Port SMP access vector Add a type for Infiniband ports and an access vector for subnet management packets. Implement the ib_port_smp hook to check that the caller has permission to send and receive SMPs on the end port specified by the device name and port. Add interface to query the SID for a IB port, which walks the IB_PORT ocontexts to find an entry for the given name and port. Signed-off-by: Daniel Jurgens Reviewed-by: James Morris Acked-by: Doug Ledford Signed-off-by: Paul Moore --- include/linux/lsm_audit.h | 8 ++++++++ security/lsm_audit.c | 5 +++++ security/selinux/hooks.c | 25 ++++++++++++++++++++++ security/selinux/include/classmap.h | 2 ++ security/selinux/include/security.h | 2 ++ security/selinux/ss/services.c | 41 +++++++++++++++++++++++++++++++++++++ 6 files changed, 83 insertions(+) diff --git a/include/linux/lsm_audit.h b/include/linux/lsm_audit.h index 0df5639a4ff4..22b5d4e687ce 100644 --- a/include/linux/lsm_audit.h +++ b/include/linux/lsm_audit.h @@ -21,6 +21,7 @@ #include #include #include +#include struct lsm_network_audit { int netif; @@ -50,6 +51,11 @@ struct lsm_ibpkey_audit { u16 pkey; }; +struct lsm_ibendport_audit { + char dev_name[IB_DEVICE_NAME_MAX]; + u8 port; +}; + /* Auxiliary data to use in generating the audit record. */ struct common_audit_data { char type; @@ -66,6 +72,7 @@ struct common_audit_data { #define LSM_AUDIT_DATA_IOCTL_OP 11 #define LSM_AUDIT_DATA_FILE 12 #define LSM_AUDIT_DATA_IBPKEY 13 +#define LSM_AUDIT_DATA_IBENDPORT 14 union { struct path path; struct dentry *dentry; @@ -84,6 +91,7 @@ struct common_audit_data { struct lsm_ioctlop_audit *op; struct file *file; struct lsm_ibpkey_audit *ibpkey; + struct lsm_ibendport_audit *ibendport; } u; /* this union contains LSM specific data */ union { diff --git a/security/lsm_audit.c b/security/lsm_audit.c index c22c99fae06a..28d4c3a528ab 100644 --- a/security/lsm_audit.c +++ b/security/lsm_audit.c @@ -421,6 +421,11 @@ static void dump_common_audit_data(struct audit_buffer *ab, a->u.ibpkey->pkey, &sbn_pfx); break; } + case LSM_AUDIT_DATA_IBENDPORT: + audit_log_format(ab, " device=%s port_num=%u", + a->u.ibendport->dev_name, + a->u.ibendport->port); + break; } /* switch (a->type) */ } diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index b59255f86274..91ec46dd34d9 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -6169,6 +6169,29 @@ static int selinux_ib_pkey_access(void *ib_sec, u64 subnet_prefix, u16 pkey_val) INFINIBAND_PKEY__ACCESS, &ad); } +static int selinux_ib_endport_manage_subnet(void *ib_sec, const char *dev_name, + u8 port_num) +{ + struct common_audit_data ad; + int err; + u32 sid = 0; + struct ib_security_struct *sec = ib_sec; + struct lsm_ibendport_audit ibendport; + + err = security_ib_endport_sid(dev_name, port_num, &sid); + + if (err) + return err; + + ad.type = LSM_AUDIT_DATA_IBENDPORT; + strncpy(ibendport.dev_name, dev_name, sizeof(ibendport.dev_name)); + ibendport.port = port_num; + ad.u.ibendport = &ibendport; + return avc_has_perm(sec->sid, sid, + SECCLASS_INFINIBAND_ENDPORT, + INFINIBAND_ENDPORT__MANAGE_SUBNET, &ad); +} + static int selinux_ib_alloc_security(void **ib_sec) { struct ib_security_struct *sec; @@ -6374,6 +6397,8 @@ static struct security_hook_list selinux_hooks[] __lsm_ro_after_init = { LSM_HOOK_INIT(tun_dev_open, selinux_tun_dev_open), #ifdef CONFIG_SECURITY_INFINIBAND LSM_HOOK_INIT(ib_pkey_access, selinux_ib_pkey_access), + LSM_HOOK_INIT(ib_endport_manage_subnet, + selinux_ib_endport_manage_subnet), LSM_HOOK_INIT(ib_alloc_security, selinux_ib_alloc_security), LSM_HOOK_INIT(ib_free_security, selinux_ib_free_security), #endif diff --git a/security/selinux/include/classmap.h b/security/selinux/include/classmap.h index 0fec1c505f84..b9fe3434b036 100644 --- a/security/selinux/include/classmap.h +++ b/security/selinux/include/classmap.h @@ -233,6 +233,8 @@ struct security_class_mapping secclass_map[] = { { COMMON_SOCK_PERMS, NULL } }, { "infiniband_pkey", { "access", NULL } }, + { "infiniband_endport", + { "manage_subnet", NULL } }, { NULL } }; diff --git a/security/selinux/include/security.h b/security/selinux/include/security.h index 592c014e369c..e91f08c16c0b 100644 --- a/security/selinux/include/security.h +++ b/security/selinux/include/security.h @@ -183,6 +183,8 @@ int security_port_sid(u8 protocol, u16 port, u32 *out_sid); int security_ib_pkey_sid(u64 subnet_prefix, u16 pkey_num, u32 *out_sid); +int security_ib_endport_sid(const char *dev_name, u8 port_num, u32 *out_sid); + int security_netif_sid(char *name, u32 *if_sid); int security_node_sid(u16 domain, void *addr, u32 addrlen, diff --git a/security/selinux/ss/services.c b/security/selinux/ss/services.c index 02257d90adc9..202166612b80 100644 --- a/security/selinux/ss/services.c +++ b/security/selinux/ss/services.c @@ -2272,6 +2272,47 @@ out: return rc; } +/** + * security_ib_endport_sid - Obtain the SID for a subnet management interface. + * @dev_name: device name + * @port: port number + * @out_sid: security identifier + */ +int security_ib_endport_sid(const char *dev_name, u8 port_num, u32 *out_sid) +{ + struct ocontext *c; + int rc = 0; + + read_lock(&policy_rwlock); + + c = policydb.ocontexts[OCON_IBENDPORT]; + while (c) { + if (c->u.ibendport.port == port_num && + !strncmp(c->u.ibendport.dev_name, + dev_name, + IB_DEVICE_NAME_MAX)) + break; + + c = c->next; + } + + if (c) { + if (!c->sid[0]) { + rc = sidtab_context_to_sid(&sidtab, + &c->context[0], + &c->sid[0]); + if (rc) + goto out; + } + *out_sid = c->sid[0]; + } else + *out_sid = SECINITSID_UNLABELED; + +out: + read_unlock(&policy_rwlock); + return rc; +} + /** * security_netif_sid - Obtain the SID for a network interface. * @name: interface name -- cgit v1.2.3 From 409dcf31538ae6ae96b3a0a1d3211e668bfefe8b Mon Sep 17 00:00:00 2001 From: Daniel Jurgens Date: Fri, 19 May 2017 15:48:59 +0300 Subject: selinux: Add a cache for quicker retreival of PKey SIDs It is likely that the SID for the same PKey will be requested many times. To reduce the time to modify QPs and process MADs use a cache to store PKey SIDs. This code is heavily based on the "netif" and "netport" concept originally developed by James Morris and Paul Moore (see security/selinux/netif.c and security/selinux/netport.c for more information) Signed-off-by: Daniel Jurgens Acked-by: Doug Ledford Signed-off-by: Paul Moore --- security/selinux/Makefile | 2 +- security/selinux/hooks.c | 7 +- security/selinux/ibpkey.c | 245 ++++++++++++++++++++++++++++++++++++++ security/selinux/include/ibpkey.h | 31 +++++ security/selinux/include/objsec.h | 6 + 5 files changed, 288 insertions(+), 3 deletions(-) create mode 100644 security/selinux/ibpkey.c create mode 100644 security/selinux/include/ibpkey.h diff --git a/security/selinux/Makefile b/security/selinux/Makefile index 3411c33e2a44..ff5895ede96f 100644 --- a/security/selinux/Makefile +++ b/security/selinux/Makefile @@ -5,7 +5,7 @@ obj-$(CONFIG_SECURITY_SELINUX) := selinux.o selinux-y := avc.o hooks.o selinuxfs.o netlink.o nlmsgtab.o netif.o \ - netnode.o netport.o exports.o \ + netnode.o netport.o ibpkey.o exports.o \ ss/ebitmap.o ss/hashtab.o ss/symtab.o ss/sidtab.o ss/avtab.o \ ss/policydb.o ss/services.o ss/conditional.o ss/mls.o ss/status.o diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index 91ec46dd34d9..158f6a005246 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -91,6 +91,7 @@ #include "netif.h" #include "netnode.h" #include "netport.h" +#include "ibpkey.h" #include "xfrm.h" #include "netlabel.h" #include "audit.h" @@ -174,8 +175,10 @@ static int selinux_netcache_avc_callback(u32 event) static int selinux_lsm_notifier_avc_callback(u32 event) { - if (event == AVC_CALLBACK_RESET) + if (event == AVC_CALLBACK_RESET) { + sel_ib_pkey_flush(); call_lsm_notifier(LSM_POLICY_CHANGE, NULL); + } return 0; } @@ -6156,7 +6159,7 @@ static int selinux_ib_pkey_access(void *ib_sec, u64 subnet_prefix, u16 pkey_val) struct ib_security_struct *sec = ib_sec; struct lsm_ibpkey_audit ibpkey; - err = security_ib_pkey_sid(subnet_prefix, pkey_val, &sid); + err = sel_ib_pkey_sid(subnet_prefix, pkey_val, &sid); if (err) return err; diff --git a/security/selinux/ibpkey.c b/security/selinux/ibpkey.c new file mode 100644 index 000000000000..e3614ee5f1c0 --- /dev/null +++ b/security/selinux/ibpkey.c @@ -0,0 +1,245 @@ +/* + * Pkey table + * + * SELinux must keep a mapping of Infinband PKEYs to labels/SIDs. This + * mapping is maintained as part of the normal policy but a fast cache is + * needed to reduce the lookup overhead. + * + * This code is heavily based on the "netif" and "netport" concept originally + * developed by + * James Morris and + * Paul Moore + * (see security/selinux/netif.c and security/selinux/netport.c for more + * information) + * + */ + +/* + * (c) Mellanox Technologies, 2016 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include + +#include "ibpkey.h" +#include "objsec.h" + +#define SEL_PKEY_HASH_SIZE 256 +#define SEL_PKEY_HASH_BKT_LIMIT 16 + +struct sel_ib_pkey_bkt { + int size; + struct list_head list; +}; + +struct sel_ib_pkey { + struct pkey_security_struct psec; + struct list_head list; + struct rcu_head rcu; +}; + +static LIST_HEAD(sel_ib_pkey_list); +static DEFINE_SPINLOCK(sel_ib_pkey_lock); +static struct sel_ib_pkey_bkt sel_ib_pkey_hash[SEL_PKEY_HASH_SIZE]; + +/** + * sel_ib_pkey_hashfn - Hashing function for the pkey table + * @pkey: pkey number + * + * Description: + * This is the hashing function for the pkey table, it returns the bucket + * number for the given pkey. + * + */ +static unsigned int sel_ib_pkey_hashfn(u16 pkey) +{ + return (pkey & (SEL_PKEY_HASH_SIZE - 1)); +} + +/** + * sel_ib_pkey_find - Search for a pkey record + * @subnet_prefix: subnet_prefix + * @pkey_num: pkey_num + * + * Description: + * Search the pkey table and return the matching record. If an entry + * can not be found in the table return NULL. + * + */ +static struct sel_ib_pkey *sel_ib_pkey_find(u64 subnet_prefix, u16 pkey_num) +{ + unsigned int idx; + struct sel_ib_pkey *pkey; + + idx = sel_ib_pkey_hashfn(pkey_num); + list_for_each_entry_rcu(pkey, &sel_ib_pkey_hash[idx].list, list) { + if (pkey->psec.pkey == pkey_num && + pkey->psec.subnet_prefix == subnet_prefix) + return pkey; + } + + return NULL; +} + +/** + * sel_ib_pkey_insert - Insert a new pkey into the table + * @pkey: the new pkey record + * + * Description: + * Add a new pkey record to the hash table. + * + */ +static void sel_ib_pkey_insert(struct sel_ib_pkey *pkey) +{ + unsigned int idx; + + /* we need to impose a limit on the growth of the hash table so check + * this bucket to make sure it is within the specified bounds + */ + idx = sel_ib_pkey_hashfn(pkey->psec.pkey); + list_add_rcu(&pkey->list, &sel_ib_pkey_hash[idx].list); + if (sel_ib_pkey_hash[idx].size == SEL_PKEY_HASH_BKT_LIMIT) { + struct sel_ib_pkey *tail; + + tail = list_entry( + rcu_dereference_protected( + sel_ib_pkey_hash[idx].list.prev, + lockdep_is_held(&sel_ib_pkey_lock)), + struct sel_ib_pkey, list); + list_del_rcu(&tail->list); + kfree_rcu(tail, rcu); + } else { + sel_ib_pkey_hash[idx].size++; + } +} + +/** + * sel_ib_pkey_sid_slow - Lookup the SID of a pkey using the policy + * @subnet_prefix: subnet prefix + * @pkey_num: pkey number + * @sid: pkey SID + * + * Description: + * This function determines the SID of a pkey by querying the security + * policy. The result is added to the pkey table to speedup future + * queries. Returns zero on success, negative values on failure. + * + */ +static int sel_ib_pkey_sid_slow(u64 subnet_prefix, u16 pkey_num, u32 *sid) +{ + int ret; + struct sel_ib_pkey *pkey; + struct sel_ib_pkey *new = NULL; + unsigned long flags; + + spin_lock_irqsave(&sel_ib_pkey_lock, flags); + pkey = sel_ib_pkey_find(subnet_prefix, pkey_num); + if (pkey) { + *sid = pkey->psec.sid; + spin_unlock_irqrestore(&sel_ib_pkey_lock, flags); + return 0; + } + + ret = security_ib_pkey_sid(subnet_prefix, pkey_num, sid); + if (ret) + goto out; + + /* If this memory allocation fails still return 0. The SID + * is valid, it just won't be added to the cache. + */ + new = kzalloc(sizeof(*new), GFP_ATOMIC); + if (!new) + goto out; + + new->psec.subnet_prefix = subnet_prefix; + new->psec.pkey = pkey_num; + new->psec.sid = *sid; + sel_ib_pkey_insert(new); + +out: + spin_unlock_irqrestore(&sel_ib_pkey_lock, flags); + return ret; +} + +/** + * sel_ib_pkey_sid - Lookup the SID of a PKEY + * @subnet_prefix: subnet_prefix + * @pkey_num: pkey number + * @sid: pkey SID + * + * Description: + * This function determines the SID of a PKEY using the fastest method + * possible. First the pkey table is queried, but if an entry can't be found + * then the policy is queried and the result is added to the table to speedup + * future queries. Returns zero on success, negative values on failure. + * + */ +int sel_ib_pkey_sid(u64 subnet_prefix, u16 pkey_num, u32 *sid) +{ + struct sel_ib_pkey *pkey; + + rcu_read_lock(); + pkey = sel_ib_pkey_find(subnet_prefix, pkey_num); + if (pkey) { + *sid = pkey->psec.sid; + rcu_read_unlock(); + return 0; + } + rcu_read_unlock(); + + return sel_ib_pkey_sid_slow(subnet_prefix, pkey_num, sid); +} + +/** + * sel_ib_pkey_flush - Flush the entire pkey table + * + * Description: + * Remove all entries from the pkey table + * + */ +void sel_ib_pkey_flush(void) +{ + unsigned int idx; + struct sel_ib_pkey *pkey, *pkey_tmp; + unsigned long flags; + + spin_lock_irqsave(&sel_ib_pkey_lock, flags); + for (idx = 0; idx < SEL_PKEY_HASH_SIZE; idx++) { + list_for_each_entry_safe(pkey, pkey_tmp, + &sel_ib_pkey_hash[idx].list, list) { + list_del_rcu(&pkey->list); + kfree_rcu(pkey, rcu); + } + sel_ib_pkey_hash[idx].size = 0; + } + spin_unlock_irqrestore(&sel_ib_pkey_lock, flags); +} + +static __init int sel_ib_pkey_init(void) +{ + int iter; + + if (!selinux_enabled) + return 0; + + for (iter = 0; iter < SEL_PKEY_HASH_SIZE; iter++) { + INIT_LIST_HEAD(&sel_ib_pkey_hash[iter].list); + sel_ib_pkey_hash[iter].size = 0; + } + + return 0; +} + +subsys_initcall(sel_ib_pkey_init); diff --git a/security/selinux/include/ibpkey.h b/security/selinux/include/ibpkey.h new file mode 100644 index 000000000000..b17a19e348e6 --- /dev/null +++ b/security/selinux/include/ibpkey.h @@ -0,0 +1,31 @@ +/* + * pkey table + * + * SELinux must keep a mapping of pkeys to labels/SIDs. This + * mapping is maintained as part of the normal policy but a fast cache is + * needed to reduce the lookup overhead. + * + */ + +/* + * (c) Mellanox Technologies, 2016 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef _SELINUX_IB_PKEY_H +#define _SELINUX_IB_PKEY_H + +void sel_ib_pkey_flush(void); + +int sel_ib_pkey_sid(u64 subnet_prefix, u16 pkey, u32 *sid); + +#endif diff --git a/security/selinux/include/objsec.h b/security/selinux/include/objsec.h index b7f15f7dc9af..6ebc61e370ff 100644 --- a/security/selinux/include/objsec.h +++ b/security/selinux/include/objsec.h @@ -144,6 +144,12 @@ struct ib_security_struct { u32 sid; /* SID of the queue pair or MAD agent */ }; +struct pkey_security_struct { + u64 subnet_prefix; /* Port subnet prefix */ + u16 pkey; /* PKey number */ + u32 sid; /* SID of pkey */ +}; + extern unsigned int selinux_checkreqprot; #endif /* _SELINUX_OBJSEC_H_ */ -- cgit v1.2.3 From e661a58279132da0127c67705e59d12f6027858d Mon Sep 17 00:00:00 2001 From: Florian Westphal Date: Fri, 21 Apr 2017 11:49:08 +0200 Subject: smack: use pernet operations for hook registration It will allow us to remove the old netfilter hook api in the near future. Signed-off-by: Florian Westphal Signed-off-by: Casey Schaufler --- security/smack/smack_netfilter.c | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/security/smack/smack_netfilter.c b/security/smack/smack_netfilter.c index 205b785fb400..cdeb0f3243dd 100644 --- a/security/smack/smack_netfilter.c +++ b/security/smack/smack_netfilter.c @@ -18,6 +18,7 @@ #include #include #include +#include #include "smack.h" #if IS_ENABLED(CONFIG_IPV6) @@ -74,20 +75,29 @@ static struct nf_hook_ops smack_nf_ops[] = { #endif /* IPV6 */ }; -static int __init smack_nf_ip_init(void) +static int __net_init smack_nf_register(struct net *net) +{ + return nf_register_net_hooks(net, smack_nf_ops, + ARRAY_SIZE(smack_nf_ops)); +} + +static void __net_exit smack_nf_unregister(struct net *net) { - int err; + nf_unregister_net_hooks(net, smack_nf_ops, ARRAY_SIZE(smack_nf_ops)); +} +static struct pernet_operations smack_net_ops = { + .init = smack_nf_register, + .exit = smack_nf_unregister, +}; + +static int __init smack_nf_ip_init(void) +{ if (smack_enabled == 0) return 0; printk(KERN_DEBUG "Smack: Registering netfilter hooks\n"); - - err = nf_register_hooks(smack_nf_ops, ARRAY_SIZE(smack_nf_ops)); - if (err) - pr_info("Smack: nf_register_hooks: error %d\n", err); - - return 0; + return register_pernet_subsys(&smack_net_ops); } __initcall(smack_nf_ip_init); -- cgit v1.2.3 From 51d59af26fe81967e0d7ec92bd9381d3b26434f3 Mon Sep 17 00:00:00 2001 From: Casey Schaufler Date: Wed, 31 May 2017 08:53:42 -0700 Subject: Smack: Safer check for a socket in file_receive The check of S_ISSOCK() in smack_file_receive() is not appropriate if the passed descriptor is a socket. Reported-by: Stephen Smalley Signed-off-by: Casey Schaufler --- security/smack/smack_lsm.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/security/smack/smack_lsm.c b/security/smack/smack_lsm.c index 658f5d8c7e76..463af86812c7 100644 --- a/security/smack/smack_lsm.c +++ b/security/smack/smack_lsm.c @@ -1915,7 +1915,7 @@ static int smack_file_receive(struct file *file) smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_PATH); smk_ad_setfield_u_fs_path(&ad, file->f_path); - if (S_ISSOCK(inode->i_mode)) { + if (inode->i_sb->s_magic == SOCKFS_MAGIC) { sock = SOCKET_I(inode); ssp = sock->sk->sk_security; tsp = current_security(); -- cgit v1.2.3 From f28e783ff668cf5757182f6b00d488be37226bff Mon Sep 17 00:00:00 2001 From: Casey Schaufler Date: Wed, 31 May 2017 13:23:41 -0700 Subject: Smack: Use cap_capable in privilege check Use cap_capable() rather than capable() in the Smack privilege check as the former does not invoke other security module privilege check, while the later does. This becomes important when stacking. It may be a problem even with minor modules. Signed-off-by: Casey Schaufler --- security/smack/smack.h | 2 +- security/smack/smack_access.c | 19 +++++++++++-------- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/security/smack/smack.h b/security/smack/smack.h index 612b810fbbc6..6a71fc7831ab 100644 --- a/security/smack/smack.h +++ b/security/smack/smack.h @@ -320,7 +320,7 @@ int smk_netlbl_mls(int, char *, struct netlbl_lsm_secattr *, int); struct smack_known *smk_import_entry(const char *, int); void smk_insert_entry(struct smack_known *skp); struct smack_known *smk_find_entry(const char *); -int smack_privileged(int cap); +bool smack_privileged(int cap); void smk_destroy_label_list(struct list_head *list); /* diff --git a/security/smack/smack_access.c b/security/smack/smack_access.c index a4b2e6b94abd..1a3004189447 100644 --- a/security/smack/smack_access.c +++ b/security/smack/smack_access.c @@ -627,35 +627,38 @@ DEFINE_MUTEX(smack_onlycap_lock); * Is the task privileged and allowed to be privileged * by the onlycap rule. * - * Returns 1 if the task is allowed to be privileged, 0 if it's not. + * Returns true if the task is allowed to be privileged, false if it's not. */ -int smack_privileged(int cap) +bool smack_privileged(int cap) { struct smack_known *skp = smk_of_current(); struct smack_known_list_elem *sklep; + int rc; /* * All kernel tasks are privileged */ if (unlikely(current->flags & PF_KTHREAD)) - return 1; + return true; - if (!capable(cap)) - return 0; + rc = cap_capable(current_cred(), &init_user_ns, cap, + SECURITY_CAP_AUDIT); + if (rc) + return false; rcu_read_lock(); if (list_empty(&smack_onlycap_list)) { rcu_read_unlock(); - return 1; + return true; } list_for_each_entry_rcu(sklep, &smack_onlycap_list, list) { if (sklep->smk_label == skp) { rcu_read_unlock(); - return 1; + return true; } } rcu_read_unlock(); - return 0; + return false; } -- cgit v1.2.3 From 8e71bf75efceff07e04e1f8a4b7c0dbff7205949 Mon Sep 17 00:00:00 2001 From: Florian Westphal Date: Fri, 21 Apr 2017 11:49:09 +0200 Subject: selinux: use pernet operations for hook registration It will allow us to remove the old netfilter hook api in the near future. Signed-off-by: Florian Westphal Signed-off-by: Paul Moore --- security/selinux/hooks.c | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index 158f6a005246..9926adbd50a9 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -6540,6 +6540,23 @@ static struct nf_hook_ops selinux_nf_ops[] = { #endif /* IPV6 */ }; +static int __net_init selinux_nf_register(struct net *net) +{ + return nf_register_net_hooks(net, selinux_nf_ops, + ARRAY_SIZE(selinux_nf_ops)); +} + +static void __net_exit selinux_nf_unregister(struct net *net) +{ + nf_unregister_net_hooks(net, selinux_nf_ops, + ARRAY_SIZE(selinux_nf_ops)); +} + +static struct pernet_operations selinux_net_ops = { + .init = selinux_nf_register, + .exit = selinux_nf_unregister, +}; + static int __init selinux_nf_ip_init(void) { int err; @@ -6549,13 +6566,12 @@ static int __init selinux_nf_ip_init(void) printk(KERN_DEBUG "SELinux: Registering netfilter hooks\n"); - err = nf_register_hooks(selinux_nf_ops, ARRAY_SIZE(selinux_nf_ops)); + err = register_pernet_subsys(&selinux_net_ops); if (err) - panic("SELinux: nf_register_hooks: error %d\n", err); + panic("SELinux: register_pernet_subsys: error %d\n", err); return 0; } - __initcall(selinux_nf_ip_init); #ifdef CONFIG_SECURITY_SELINUX_DISABLE @@ -6563,7 +6579,7 @@ static void selinux_nf_ip_exit(void) { printk(KERN_DEBUG "SELinux: Unregistering netfilter hooks\n"); - nf_unregister_hooks(selinux_nf_ops, ARRAY_SIZE(selinux_nf_ops)); + unregister_pernet_subsys(&selinux_net_ops); } #endif -- cgit v1.2.3 From 0ff3d97f7676d9f513288a2d30582dcd2b34d238 Mon Sep 17 00:00:00 2001 From: Markus Elfring Date: Sun, 7 May 2017 13:43:50 +0200 Subject: apparmorfs: Combine two function calls into one in aa_fs_seq_raw_abi_show() A bit of data was put into a sequence by two separate function calls. Print the same data by a single function call instead. Signed-off-by: Markus Elfring Signed-off-by: John Johansen --- security/apparmor/apparmorfs.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c index 4f6ac9dbc65d..b4d83e0bc651 100644 --- a/security/apparmor/apparmorfs.c +++ b/security/apparmor/apparmorfs.c @@ -572,10 +572,9 @@ static int aa_fs_seq_raw_abi_show(struct seq_file *seq, void *v) struct aa_proxy *proxy = seq->private; struct aa_profile *profile = aa_get_profile_rcu(&proxy->profile); - if (profile->rawdata->abi) { - seq_printf(seq, "v%d", profile->rawdata->abi); - seq_puts(seq, "\n"); - } + if (profile->rawdata->abi) + seq_printf(seq, "v%d\n", profile->rawdata->abi); + aa_put_profile(profile); return 0; -- cgit v1.2.3 From 47dbd1cdbb4e74d656e444deb6675ee38ca1b1f3 Mon Sep 17 00:00:00 2001 From: Markus Elfring Date: Sun, 7 May 2017 13:50:28 +0200 Subject: apparmorfs: Use seq_putc() in two functions Two single characters (line breaks) should be put into a sequence. Thus use the corresponding function "seq_putc". This issue was detected by using the Coccinelle software. Signed-off-by: Markus Elfring Signed-off-by: John Johansen --- security/apparmor/apparmorfs.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c index b4d83e0bc651..41e427a4f051 100644 --- a/security/apparmor/apparmorfs.c +++ b/security/apparmor/apparmorfs.c @@ -494,7 +494,7 @@ static int aa_fs_seq_hash_show(struct seq_file *seq, void *v) if (profile->hash) { for (i = 0; i < size; i++) seq_printf(seq, "%.2x", profile->hash[i]); - seq_puts(seq, "\n"); + seq_putc(seq, '\n'); } aa_put_profile(profile); @@ -602,7 +602,7 @@ static int aa_fs_seq_raw_hash_show(struct seq_file *seq, void *v) if (profile->rawdata->hash) { for (i = 0; i < size; i++) seq_printf(seq, "%.2x", profile->rawdata->hash[i]); - seq_puts(seq, "\n"); + seq_putc(seq, '\n'); } aa_put_profile(profile); -- cgit v1.2.3 From ffac1de6cf6f84e47cdb6d6de0629bc534f60961 Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Tue, 23 May 2017 17:33:46 +0300 Subject: apparmor: Fix error cod in __aa_fs_profile_mkdir() We can either return PTR_ERR(NULL) or a PTR_ERR(a valid pointer) here. Returning NULL is probably not good, but since this happens at boot then we are probably already toasted if we were to hit this bug in real life. In other words, it seems like a very low severity bug to me. Signed-off-by: Dan Carpenter Signed-off-by: John Johansen --- security/apparmor/apparmorfs.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c index 41e427a4f051..26ad1a370632 100644 --- a/security/apparmor/apparmorfs.c +++ b/security/apparmor/apparmorfs.c @@ -727,8 +727,10 @@ int __aa_fs_profile_mkdir(struct aa_profile *profile, struct dentry *parent) id_len = snprintf(NULL, 0, ".%ld", profile->ns->uniq_id); profile->dirname = kmalloc(len + id_len + 1, GFP_KERNEL); - if (!profile->dirname) - goto fail; + if (!profile->dirname) { + error = -ENOMEM; + goto fail2; + } mangle_name(profile->base.name, profile->dirname); sprintf(profile->dirname + len, ".%ld", profile->ns->uniq_id++); -- cgit v1.2.3 From 651e54953b5d4ad103f0efa54fc6b380807fca3a Mon Sep 17 00:00:00 2001 From: Thomas Schneider Date: Fri, 14 Oct 2016 21:29:49 +0200 Subject: security/apparmor: Use POSIX-compatible "printf '%s'" When using a strictly POSIX-compliant shell, "-n #define ..." gets written into the file. Use "printf '%s'" to avoid this. Signed-off-by: Thomas Schneider Signed-off-by: John Johansen --- security/apparmor/Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/security/apparmor/Makefile b/security/apparmor/Makefile index ad369a7aac24..2ded2f1be98b 100644 --- a/security/apparmor/Makefile +++ b/security/apparmor/Makefile @@ -20,7 +20,7 @@ cmd_make-caps = echo "static const char *const capability_names[] = {" > $@ ;\ sed $< >>$@ -r -n -e '/CAP_FS_MASK/d' \ -e 's/^\#define[ \t]+CAP_([A-Z0-9_]+)[ \t]+([0-9]+)/[\2] = "\L\1",/p';\ echo "};" >> $@ ;\ - echo -n '\#define AA_FS_CAPS_MASK "' >> $@ ;\ + printf '%s' '\#define AA_FS_CAPS_MASK "' >> $@ ;\ sed $< -r -n -e '/CAP_FS_MASK/d' \ -e 's/^\#define[ \t]+CAP_([A-Z0-9_]+)[ \t]+([0-9]+)/\L\1/p' | \ tr '\n' ' ' | sed -e 's/ $$/"\n/' >> $@ @@ -56,7 +56,7 @@ cmd_make-rlim = echo "static const char *const rlim_names[RLIM_NLIMITS] = {" \ echo "static const int rlim_map[RLIM_NLIMITS] = {" >> $@ ;\ sed -r -n "s/^\# ?define[ \t]+(RLIMIT_[A-Z0-9_]+).*/\1,/p" $< >> $@ ;\ echo "};" >> $@ ; \ - echo -n '\#define AA_FS_RLIMIT_MASK "' >> $@ ;\ + printf '%s' '\#define AA_FS_RLIMIT_MASK "' >> $@ ;\ sed -r -n 's/^\# ?define[ \t]+RLIMIT_([A-Z0-9_]+).*/\L\1/p' $< | \ tr '\n' ' ' | sed -e 's/ $$/"\n/' >> $@ -- cgit v1.2.3 From af7caa8f8dd1b45e38a3653a69ed4d708286bc83 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Sun, 21 May 2017 17:15:28 -0700 Subject: apparmor: move file context into file.h Signed-off-by: John Johansen --- security/apparmor/include/context.h | 32 -------------------------------- security/apparmor/include/file.h | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 32 deletions(-) diff --git a/security/apparmor/include/context.h b/security/apparmor/include/context.h index 5b18fedab4c8..420cfd041218 100644 --- a/security/apparmor/include/context.h +++ b/security/apparmor/include/context.h @@ -25,38 +25,6 @@ #define cred_ctx(X) ((X)->security) #define current_ctx() cred_ctx(current_cred()) -/* struct aa_file_ctx - the AppArmor context the file was opened in - * @perms: the permission the file was opened with - * - * The file_ctx could currently be directly stored in file->f_security - * as the profile reference is now stored in the f_cred. However the - * ctx struct will expand in the future so we keep the struct. - */ -struct aa_file_ctx { - u16 allow; -}; - -/** - * aa_alloc_file_context - allocate file_ctx - * @gfp: gfp flags for allocation - * - * Returns: file_ctx or NULL on failure - */ -static inline struct aa_file_ctx *aa_alloc_file_context(gfp_t gfp) -{ - return kzalloc(sizeof(struct aa_file_ctx), gfp); -} - -/** - * aa_free_file_context - free a file_ctx - * @ctx: file_ctx to free (MAYBE_NULL) - */ -static inline void aa_free_file_context(struct aa_file_ctx *ctx) -{ - if (ctx) - kzfree(ctx); -} - /** * struct aa_task_ctx - primary label for confined tasks * @profile: the current profile (NOT NULL) diff --git a/security/apparmor/include/file.h b/security/apparmor/include/file.h index 38f821bf49b6..eba39cb25f02 100644 --- a/security/apparmor/include/file.h +++ b/security/apparmor/include/file.h @@ -47,6 +47,38 @@ struct path; AA_MAY_CHMOD | AA_MAY_CHOWN | AA_MAY_LOCK | \ AA_EXEC_MMAP | AA_MAY_LINK) +/* struct aa_file_ctx - the AppArmor context the file was opened in + * @perms: the permission the file was opened with + * + * The file_ctx could currently be directly stored in file->f_security + * as the profile reference is now stored in the f_cred. However the + * ctx struct will expand in the future so we keep the struct. + */ +struct aa_file_ctx { + u16 allow; +}; + +/** + * aa_alloc_file_context - allocate file_ctx + * @gfp: gfp flags for allocation + * + * Returns: file_ctx or NULL on failure + */ +static inline struct aa_file_ctx *aa_alloc_file_context(gfp_t gfp) +{ + return kzalloc(sizeof(struct aa_file_ctx), gfp); +} + +/** + * aa_free_file_context - free a file_ctx + * @ctx: file_ctx to free (MAYBE_NULL) + */ +static inline void aa_free_file_context(struct aa_file_ctx *ctx) +{ + if (ctx) + kzfree(ctx); +} + /* * The xindex is broken into 3 parts * - index - an index into either the exec name table or the variable table -- cgit v1.2.3 From b91deb9db12851c18ccb55719f1cd55c2400aca1 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Mon, 22 May 2017 02:47:22 -0700 Subject: apparmor: make internal lib fn skipn_spaces available to the rest of apparmor Signed-off-by: John Johansen --- security/apparmor/include/lib.h | 1 + security/apparmor/lib.c | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/security/apparmor/include/lib.h b/security/apparmor/include/lib.h index 550a700563b4..89524aade657 100644 --- a/security/apparmor/include/lib.h +++ b/security/apparmor/include/lib.h @@ -60,6 +60,7 @@ extern int apparmor_initialized; /* fn's in lib */ +const char *skipn_spaces(const char *str, size_t n); char *aa_split_fqname(char *args, char **ns_name); const char *aa_splitn_fqname(const char *fqname, size_t n, const char **ns_name, size_t *ns_len); diff --git a/security/apparmor/lib.c b/security/apparmor/lib.c index 7cd788a9445b..864b2fa45852 100644 --- a/security/apparmor/lib.c +++ b/security/apparmor/lib.c @@ -69,7 +69,7 @@ char *aa_split_fqname(char *fqname, char **ns_name) * if all whitespace will return NULL */ -static const char *skipn_spaces(const char *str, size_t n) +const char *skipn_spaces(const char *str, size_t n) { for (; n && isspace(*str); --n) ++str; -- cgit v1.2.3 From 72c8a768641dc6ee8d1d9dcebd51bbec2817459b Mon Sep 17 00:00:00 2001 From: John Johansen Date: Mon, 22 May 2017 03:06:52 -0700 Subject: apparmor: allow profiles to provide info to disconnected paths Signed-off-by: John Johansen --- security/apparmor/domain.c | 2 +- security/apparmor/file.c | 7 ++++--- security/apparmor/include/path.h | 3 ++- security/apparmor/include/policy.h | 2 ++ security/apparmor/path.c | 34 ++++++++++++++++++++++------------ security/apparmor/policy_unpack.c | 3 +++ 6 files changed, 34 insertions(+), 17 deletions(-) diff --git a/security/apparmor/domain.c b/security/apparmor/domain.c index 001e133a3c8c..c92fd0e7b33c 100644 --- a/security/apparmor/domain.c +++ b/security/apparmor/domain.c @@ -366,7 +366,7 @@ int apparmor_bprm_set_creds(struct linux_binprm *bprm) /* buffer freed below, name is pointer into buffer */ error = aa_path_name(&bprm->file->f_path, profile->path_flags, &buffer, - &name, &info); + &name, &info, profile->disconnected); if (error) { if (unconfined(profile) || (profile->flags & PFLAG_IX_ON_NAME_ERROR)) diff --git a/security/apparmor/file.c b/security/apparmor/file.c index 750564c3ab71..83d43ac72134 100644 --- a/security/apparmor/file.c +++ b/security/apparmor/file.c @@ -285,7 +285,8 @@ int aa_path_perm(const char *op, struct aa_profile *profile, int error; flags |= profile->path_flags | (S_ISDIR(cond->mode) ? PATH_IS_DIR : 0); - error = aa_path_name(path, flags, &buffer, &name, &info); + error = aa_path_name(path, flags, &buffer, &name, &info, + profile->disconnected); if (error) { if (error == -ENOENT && is_deleted(path->dentry)) { /* Access to open files that are deleted are @@ -366,13 +367,13 @@ int aa_path_link(struct aa_profile *profile, struct dentry *old_dentry, /* buffer freed below, lname is pointer in buffer */ error = aa_path_name(&link, profile->path_flags, &buffer, &lname, - &info); + &info, profile->disconnected); if (error) goto audit; /* buffer2 freed below, tname is pointer in buffer2 */ error = aa_path_name(&target, profile->path_flags, &buffer2, &tname, - &info); + &info, profile->disconnected); if (error) goto audit; diff --git a/security/apparmor/include/path.h b/security/apparmor/include/path.h index 0444fdde3918..78e4909dcc6a 100644 --- a/security/apparmor/include/path.h +++ b/security/apparmor/include/path.h @@ -27,7 +27,8 @@ enum path_flags { }; int aa_path_name(const struct path *path, int flags, char **buffer, - const char **name, const char **info); + const char **name, const char **info, + const char *disconnected); #define MAX_PATH_BUFFERS 2 diff --git a/security/apparmor/include/policy.h b/security/apparmor/include/policy.h index 67bc96afe541..dffa01c018c8 100644 --- a/security/apparmor/include/policy.h +++ b/security/apparmor/include/policy.h @@ -128,6 +128,7 @@ struct aa_data { * @mode: the enforcement mode of the profile * @flags: flags controlling profile behavior * @path_flags: flags controlling path generation behavior + * @disconnected: what to prepend if attach_disconnected is specified * @size: the memory consumed by this profiles rules * @policy: general match rules governing policy * @file: The set of rules governing basic file access and domain transitions @@ -169,6 +170,7 @@ struct aa_profile { long mode; long flags; u32 path_flags; + const char *disconnected; int size; struct aa_policydb policy; diff --git a/security/apparmor/path.c b/security/apparmor/path.c index a8fc7d08c144..9490f8e89630 100644 --- a/security/apparmor/path.c +++ b/security/apparmor/path.c @@ -50,7 +50,7 @@ static int prepend(char **buffer, int buflen, const char *str, int namelen) * namespace root. */ static int disconnect(const struct path *path, char *buf, char **name, - int flags) + int flags, const char *disconnected) { int error = 0; @@ -63,9 +63,14 @@ static int disconnect(const struct path *path, char *buf, char **name, error = -EACCES; if (**name == '/') *name = *name + 1; - } else if (**name != '/') - /* CONNECT_PATH with missing root */ - error = prepend(name, *name - buf, "/", 1); + } else { + if (**name != '/') + /* CONNECT_PATH with missing root */ + error = prepend(name, *name - buf, "/", 1); + if (!error && disconnected) + error = prepend(name, *name - buf, disconnected, + strlen(disconnected)); + } return error; } @@ -77,6 +82,7 @@ static int disconnect(const struct path *path, char *buf, char **name, * @buflen: length of @buf * @name: Returns - pointer for start of path name with in @buf (NOT NULL) * @flags: flags controlling path lookup + * @disconnected: string to prefix to disconnected paths * * Handle path name lookup. * @@ -85,7 +91,7 @@ static int disconnect(const struct path *path, char *buf, char **name, * to a position in @buf */ static int d_namespace_path(const struct path *path, char *buf, int buflen, - char **name, int flags) + char **name, int flags, const char *disconnected) { char *res; int error = 0; @@ -106,8 +112,8 @@ static int d_namespace_path(const struct path *path, char *buf, int buflen, */ return prepend(name, *name - buf, "/proc", 5); } else - return disconnect(path, buf, name, flags); - return 0; + return disconnect(path, buf, name, flags, + disconnected); } /* resolve paths relative to chroot?*/ @@ -153,7 +159,7 @@ static int d_namespace_path(const struct path *path, char *buf, int buflen, } if (!connected) - error = disconnect(path, buf, name, flags); + error = disconnect(path, buf, name, flags, disconnected); out: return error; @@ -170,10 +176,12 @@ out: * Returns: %0 else error on failure */ static int get_name_to_buffer(const struct path *path, int flags, char *buffer, - int size, char **name, const char **info) + int size, char **name, const char **info, + const char *disconnected) { int adjust = (flags & PATH_IS_DIR) ? 1 : 0; - int error = d_namespace_path(path, buffer, size - adjust, name, flags); + int error = d_namespace_path(path, buffer, size - adjust, name, flags, + disconnected); if (!error && (flags & PATH_IS_DIR) && (*name)[1] != '\0') /* @@ -203,6 +211,7 @@ static int get_name_to_buffer(const struct path *path, int flags, char *buffer, * @buffer: buffer that aa_get_name() allocated (NOT NULL) * @name: Returns - the generated path name if !error (NOT NULL) * @info: Returns - information on why the path lookup failed (MAYBE NULL) + * @disconnected: string to prepend to disconnected paths * * @name is a pointer to the beginning of the pathname (which usually differs * from the beginning of the buffer), or NULL. If there is an error @name @@ -216,7 +225,7 @@ static int get_name_to_buffer(const struct path *path, int flags, char *buffer, * Returns: %0 else error code if could retrieve name */ int aa_path_name(const struct path *path, int flags, char **buffer, - const char **name, const char **info) + const char **name, const char **info, const char *disconnected) { char *buf, *str = NULL; int size = 256; @@ -230,7 +239,8 @@ int aa_path_name(const struct path *path, int flags, char **buffer, if (!buf) return -ENOMEM; - error = get_name_to_buffer(path, flags, buf, size, &str, info); + error = get_name_to_buffer(path, flags, buf, size, &str, info, + disconnected); if (error != -ENAMETOOLONG) break; diff --git a/security/apparmor/policy_unpack.c b/security/apparmor/policy_unpack.c index f3422a91353c..6bc1b10191fa 100644 --- a/security/apparmor/policy_unpack.c +++ b/security/apparmor/policy_unpack.c @@ -569,6 +569,9 @@ static struct aa_profile *unpack_profile(struct aa_ext *e, char **ns_name) profile->xmatch_len = tmp; } + /* disconnected attachment string is optional */ + (void) unpack_str(e, &profile->disconnected, "disconnected"); + /* per profile debug flags (complain, audit) */ if (!unpack_nameX(e, AA_STRUCT, "flags")) goto fail; -- cgit v1.2.3 From 4227c333f65cddc6c2f048e5b67cfe796b9df9a6 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Tue, 23 May 2017 03:25:14 -0700 Subject: apparmor: Move path lookup to using preallocated buffers Dynamically allocating buffers is problematic and is an extra layer that is a potntial point of failure and can slow down mediation. Change path lookup to use the preallocated per cpu buffers. Signed-off-by: John Johansen --- security/apparmor/domain.c | 8 +-- security/apparmor/file.c | 13 ++--- security/apparmor/include/path.h | 4 +- security/apparmor/path.c | 114 ++++++++++++++------------------------- 4 files changed, 53 insertions(+), 86 deletions(-) diff --git a/security/apparmor/domain.c b/security/apparmor/domain.c index c92fd0e7b33c..ab8f23cdccff 100644 --- a/security/apparmor/domain.c +++ b/security/apparmor/domain.c @@ -357,6 +357,9 @@ int apparmor_bprm_set_creds(struct linux_binprm *bprm) AA_BUG(!ctx); profile = aa_get_newest_profile(ctx->profile); + + /* buffer freed below, name is pointer into buffer */ + get_buffers(buffer); /* * get the namespace from the replacement profile as replacement * can change the namespace @@ -364,8 +367,7 @@ int apparmor_bprm_set_creds(struct linux_binprm *bprm) ns = profile->ns; state = profile->file.start; - /* buffer freed below, name is pointer into buffer */ - error = aa_path_name(&bprm->file->f_path, profile->path_flags, &buffer, + error = aa_path_name(&bprm->file->f_path, profile->path_flags, buffer, &name, &info, profile->disconnected); if (error) { if (unconfined(profile) || @@ -515,7 +517,7 @@ audit: cleanup: aa_put_profile(new_profile); aa_put_profile(profile); - kfree(buffer); + put_buffers(buffer); return error; } diff --git a/security/apparmor/file.c b/security/apparmor/file.c index 83d43ac72134..22be62f0fc73 100644 --- a/security/apparmor/file.c +++ b/security/apparmor/file.c @@ -285,7 +285,8 @@ int aa_path_perm(const char *op, struct aa_profile *profile, int error; flags |= profile->path_flags | (S_ISDIR(cond->mode) ? PATH_IS_DIR : 0); - error = aa_path_name(path, flags, &buffer, &name, &info, + get_buffers(buffer); + error = aa_path_name(path, flags, buffer, &name, &info, profile->disconnected); if (error) { if (error == -ENOENT && is_deleted(path->dentry)) { @@ -304,7 +305,7 @@ int aa_path_perm(const char *op, struct aa_profile *profile, } error = aa_audit_file(profile, &perms, op, request, name, NULL, cond->uid, info, error); - kfree(buffer); + put_buffers(buffer); return error; } @@ -363,16 +364,17 @@ int aa_path_link(struct aa_profile *profile, struct dentry *old_dentry, unsigned int state; int error; + get_buffers(buffer, buffer2); lperms = nullperms; /* buffer freed below, lname is pointer in buffer */ - error = aa_path_name(&link, profile->path_flags, &buffer, &lname, + error = aa_path_name(&link, profile->path_flags, buffer, &lname, &info, profile->disconnected); if (error) goto audit; /* buffer2 freed below, tname is pointer in buffer2 */ - error = aa_path_name(&target, profile->path_flags, &buffer2, &tname, + error = aa_path_name(&target, profile->path_flags, buffer2, &tname, &info, profile->disconnected); if (error) goto audit; @@ -432,8 +434,7 @@ done_tests: audit: error = aa_audit_file(profile, &lperms, OP_LINK, request, lname, tname, cond.uid, info, error); - kfree(buffer); - kfree(buffer2); + put_buffers(buffer, buffer2); return error; } diff --git a/security/apparmor/include/path.h b/security/apparmor/include/path.h index 78e4909dcc6a..05fb3305671e 100644 --- a/security/apparmor/include/path.h +++ b/security/apparmor/include/path.h @@ -23,10 +23,10 @@ enum path_flags { PATH_CHROOT_NSCONNECT = 0x10, /* connect paths that are at ns root */ PATH_DELEGATE_DELETED = 0x08000, /* delegate deleted files */ - PATH_MEDIATE_DELETED = 0x10000, /* mediate deleted paths */ + PATH_MEDIATE_DELETED = 0x10000, /* mediate deleted paths */ }; -int aa_path_name(const struct path *path, int flags, char **buffer, +int aa_path_name(const struct path *path, int flags, char *buffer, const char **name, const char **info, const char *disconnected); diff --git a/security/apparmor/path.c b/security/apparmor/path.c index 9490f8e89630..9d5de1d05be4 100644 --- a/security/apparmor/path.c +++ b/security/apparmor/path.c @@ -79,7 +79,6 @@ static int disconnect(const struct path *path, char *buf, char **name, * d_namespace_path - lookup a name associated with a given path * @path: path to lookup (NOT NULL) * @buf: buffer to store path to (NOT NULL) - * @buflen: length of @buf * @name: Returns - pointer for start of path name with in @buf (NOT NULL) * @flags: flags controlling path lookup * @disconnected: string to prefix to disconnected paths @@ -90,12 +89,14 @@ static int disconnect(const struct path *path, char *buf, char **name, * When no error the path name is returned in @name which points to * to a position in @buf */ -static int d_namespace_path(const struct path *path, char *buf, int buflen, - char **name, int flags, const char *disconnected) +static int d_namespace_path(const struct path *path, char *buf, char **name, + int flags, const char *disconnected) { char *res; int error = 0; int connected = 1; + int isdir = (flags & PATH_IS_DIR) ? 1 : 0; + int buflen = aa_g_path_max - isdir; if (path->mnt->mnt_flags & MNT_INTERNAL) { /* it's not mounted anywhere */ @@ -110,10 +111,12 @@ static int d_namespace_path(const struct path *path, char *buf, int buflen, /* TODO: convert over to using a per namespace * control instead of hard coded /proc */ - return prepend(name, *name - buf, "/proc", 5); + error = prepend(name, *name - buf, "/proc", 5); + goto out; } else - return disconnect(path, buf, name, flags, - disconnected); + error = disconnect(path, buf, name, flags, + disconnected); + goto out; } /* resolve paths relative to chroot?*/ @@ -132,8 +135,11 @@ static int d_namespace_path(const struct path *path, char *buf, int buflen, * be returned. */ if (!res || IS_ERR(res)) { - if (PTR_ERR(res) == -ENAMETOOLONG) - return -ENAMETOOLONG; + if (PTR_ERR(res) == -ENAMETOOLONG) { + error = -ENAMETOOLONG; + *name = buf; + goto out; + } connected = 0; res = dentry_path_raw(path->dentry, buf, buflen); if (IS_ERR(res)) { @@ -146,6 +152,9 @@ static int d_namespace_path(const struct path *path, char *buf, int buflen, *name = res; + if (!connected) + error = disconnect(path, buf, name, flags, disconnected); + /* Handle two cases: * 1. A deleted dentry && profile is not allowing mediation of deleted * 2. On some filesystems, newly allocated dentries appear to the @@ -153,62 +162,27 @@ static int d_namespace_path(const struct path *path, char *buf, int buflen, * allocated. */ if (d_unlinked(path->dentry) && d_is_positive(path->dentry) && - !(flags & PATH_MEDIATE_DELETED)) { + !(flags & (PATH_MEDIATE_DELETED | PATH_DELEGATE_DELETED))) { error = -ENOENT; goto out; } - if (!connected) - error = disconnect(path, buf, name, flags, disconnected); - out: - return error; -} - -/** - * get_name_to_buffer - get the pathname to a buffer ensure dir / is appended - * @path: path to get name for (NOT NULL) - * @flags: flags controlling path lookup - * @buffer: buffer to put name in (NOT NULL) - * @size: size of buffer - * @name: Returns - contains position of path name in @buffer (NOT NULL) - * - * Returns: %0 else error on failure - */ -static int get_name_to_buffer(const struct path *path, int flags, char *buffer, - int size, char **name, const char **info, - const char *disconnected) -{ - int adjust = (flags & PATH_IS_DIR) ? 1 : 0; - int error = d_namespace_path(path, buffer, size - adjust, name, flags, - disconnected); - - if (!error && (flags & PATH_IS_DIR) && (*name)[1] != '\0') - /* - * Append "/" to the pathname. The root directory is a special - * case; it already ends in slash. - */ - strcpy(&buffer[size - 2], "/"); - - if (info && error) { - if (error == -ENOENT) - *info = "Failed name lookup - deleted entry"; - else if (error == -EACCES) - *info = "Failed name lookup - disconnected path"; - else if (error == -ENAMETOOLONG) - *info = "Failed name lookup - name too long"; - else - *info = "Failed name lookup"; - } + /* + * Append "/" to the pathname. The root directory is a special + * case; it already ends in slash. + */ + if (!error && isdir && ((*name)[1] != '\0' || (*name)[0] != '/')) + strcpy(&buf[aa_g_path_max - 2], "/"); return error; } /** - * aa_path_name - compute the pathname of a file + * aa_path_name - get the pathname to a buffer ensure dir / is appended * @path: path the file (NOT NULL) * @flags: flags controlling path name generation - * @buffer: buffer that aa_get_name() allocated (NOT NULL) + * @buffer: buffer to put name in (NOT NULL) * @name: Returns - the generated path name if !error (NOT NULL) * @info: Returns - information on why the path lookup failed (MAYBE NULL) * @disconnected: string to prepend to disconnected paths @@ -224,33 +198,23 @@ static int get_name_to_buffer(const struct path *path, int flags, char *buffer, * * Returns: %0 else error code if could retrieve name */ -int aa_path_name(const struct path *path, int flags, char **buffer, +int aa_path_name(const struct path *path, int flags, char *buffer, const char **name, const char **info, const char *disconnected) { - char *buf, *str = NULL; - int size = 256; - int error; - - *name = NULL; - *buffer = NULL; - for (;;) { - /* freed by caller */ - buf = kmalloc(size, GFP_KERNEL); - if (!buf) - return -ENOMEM; + char *str = NULL; + int error = d_namespace_path(path, buffer, &str, flags, disconnected); - error = get_name_to_buffer(path, flags, buf, size, &str, info, - disconnected); - if (error != -ENAMETOOLONG) - break; - - kfree(buf); - size <<= 1; - if (size > aa_g_path_max) - return -ENAMETOOLONG; - *info = NULL; + if (info && error) { + if (error == -ENOENT) + *info = "Failed name lookup - deleted entry"; + else if (error == -EACCES) + *info = "Failed name lookup - disconnected path"; + else if (error == -ENAMETOOLONG) + *info = "Failed name lookup - name too long"; + else + *info = "Failed name lookup"; } - *buffer = buf; + *name = str; return error; -- cgit v1.2.3 From 6623ec7c4dbe18a5a2878e2d888be70d08a91826 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Sun, 7 May 2017 05:53:37 -0700 Subject: securityfs: add the ability to support symlinks Signed-off-by: John Johansen Reviewed-by: Seth Arnold Acked-by: Kees Cook --- include/linux/security.h | 12 ++++ security/inode.c | 144 ++++++++++++++++++++++++++++++++++++++++------- 2 files changed, 135 insertions(+), 21 deletions(-) diff --git a/include/linux/security.h b/include/linux/security.h index af675b576645..caf8b64d8b5c 100644 --- a/include/linux/security.h +++ b/include/linux/security.h @@ -1651,6 +1651,10 @@ extern struct dentry *securityfs_create_file(const char *name, umode_t mode, struct dentry *parent, void *data, const struct file_operations *fops); extern struct dentry *securityfs_create_dir(const char *name, struct dentry *parent); +struct dentry *securityfs_create_symlink(const char *name, + struct dentry *parent, + const char *target, + const struct inode_operations *iops); extern void securityfs_remove(struct dentry *dentry); #else /* CONFIG_SECURITYFS */ @@ -1670,6 +1674,14 @@ static inline struct dentry *securityfs_create_file(const char *name, return ERR_PTR(-ENODEV); } +static inline struct dentry *securityfs_create_symlink(const char *name, + struct dentry *parent, + const char *target, + const struct inode_operations *iops) +{ + return ERR_PTR(-ENODEV); +} + static inline void securityfs_remove(struct dentry *dentry) {} diff --git a/security/inode.c b/security/inode.c index eccd58ef2ae8..8dd9ca8848e4 100644 --- a/security/inode.c +++ b/security/inode.c @@ -26,11 +26,31 @@ static struct vfsmount *mount; static int mount_count; +static void securityfs_evict_inode(struct inode *inode) +{ + truncate_inode_pages_final(&inode->i_data); + clear_inode(inode); + if (S_ISLNK(inode->i_mode)) + kfree(inode->i_link); +} + +static const struct super_operations securityfs_super_operations = { + .statfs = simple_statfs, + .evict_inode = securityfs_evict_inode, +}; + static int fill_super(struct super_block *sb, void *data, int silent) { static const struct tree_descr files[] = {{""}}; + int error; + + error = simple_fill_super(sb, SECURITYFS_MAGIC, files); + if (error) + return error; + + sb->s_op = &securityfs_super_operations; - return simple_fill_super(sb, SECURITYFS_MAGIC, files); + return 0; } static struct dentry *get_sb(struct file_system_type *fs_type, @@ -48,7 +68,7 @@ static struct file_system_type fs_type = { }; /** - * securityfs_create_file - create a file in the securityfs filesystem + * securityfs_create_dentry - create a dentry in the securityfs filesystem * * @name: a pointer to a string containing the name of the file to create. * @mode: the permission that the file should have @@ -60,34 +80,35 @@ static struct file_system_type fs_type = { * the open() call. * @fops: a pointer to a struct file_operations that should be used for * this file. + * @iops: a point to a struct of inode_operations that should be used for + * this file/dir * - * This is the basic "create a file" function for securityfs. It allows for a - * wide range of flexibility in creating a file, or a directory (if you - * want to create a directory, the securityfs_create_dir() function is - * recommended to be used instead). + * This is the basic "create a file/dir/symlink" function for + * securityfs. It allows for a wide range of flexibility in creating + * a file, or a directory (if you want to create a directory, the + * securityfs_create_dir() function is recommended to be used + * instead). * * This function returns a pointer to a dentry if it succeeds. This - * pointer must be passed to the securityfs_remove() function when the file is - * to be removed (no automatic cleanup happens if your module is unloaded, - * you are responsible here). If an error occurs, the function will return - * the error value (via ERR_PTR). + * pointer must be passed to the securityfs_remove() function when the + * file is to be removed (no automatic cleanup happens if your module + * is unloaded, you are responsible here). If an error occurs, the + * function will return the error value (via ERR_PTR). * * If securityfs is not enabled in the kernel, the value %-ENODEV is * returned. */ -struct dentry *securityfs_create_file(const char *name, umode_t mode, - struct dentry *parent, void *data, - const struct file_operations *fops) +static struct dentry *securityfs_create_dentry(const char *name, umode_t mode, + struct dentry *parent, void *data, + const struct file_operations *fops, + const struct inode_operations *iops) { struct dentry *dentry; - int is_dir = S_ISDIR(mode); struct inode *dir, *inode; int error; - if (!is_dir) { - BUG_ON(!fops); + if (!(mode & S_IFMT)) mode = (mode & S_IALLUGO) | S_IFREG; - } pr_debug("securityfs: creating file '%s'\n",name); @@ -120,11 +141,14 @@ struct dentry *securityfs_create_file(const char *name, umode_t mode, inode->i_mode = mode; inode->i_atime = inode->i_mtime = inode->i_ctime = current_time(inode); inode->i_private = data; - if (is_dir) { + if (S_ISDIR(mode)) { inode->i_op = &simple_dir_inode_operations; inode->i_fop = &simple_dir_operations; inc_nlink(inode); inc_nlink(dir); + } else if (S_ISLNK(mode)) { + inode->i_op = iops ? iops : &simple_symlink_inode_operations; + inode->i_link = data; } else { inode->i_fop = fops; } @@ -141,6 +165,38 @@ out: simple_release_fs(&mount, &mount_count); return dentry; } + +/** + * securityfs_create_file - create a file in the securityfs filesystem + * + * @name: a pointer to a string containing the name of the file to create. + * @mode: the permission that the file should have + * @parent: a pointer to the parent dentry for this file. This should be a + * directory dentry if set. If this parameter is %NULL, then the + * file will be created in the root of the securityfs filesystem. + * @data: a pointer to something that the caller will want to get to later + * on. The inode.i_private pointer will point to this value on + * the open() call. + * @fops: a pointer to a struct file_operations that should be used for + * this file. + * + * This function creates a file in securityfs with the given @name. + * + * This function returns a pointer to a dentry if it succeeds. This + * pointer must be passed to the securityfs_remove() function when the file is + * to be removed (no automatic cleanup happens if your module is unloaded, + * you are responsible here). If an error occurs, the function will return + * the error value (via ERR_PTR). + * + * If securityfs is not enabled in the kernel, the value %-ENODEV is + * returned. + */ +struct dentry *securityfs_create_file(const char *name, umode_t mode, + struct dentry *parent, void *data, + const struct file_operations *fops) +{ + return securityfs_create_dentry(name, mode, parent, data, fops, NULL); +} EXPORT_SYMBOL_GPL(securityfs_create_file); /** @@ -165,12 +221,58 @@ EXPORT_SYMBOL_GPL(securityfs_create_file); */ struct dentry *securityfs_create_dir(const char *name, struct dentry *parent) { - return securityfs_create_file(name, - S_IFDIR | S_IRWXU | S_IRUGO | S_IXUGO, - parent, NULL, NULL); + return securityfs_create_file(name, S_IFDIR | 0755, parent, NULL, NULL); } EXPORT_SYMBOL_GPL(securityfs_create_dir); +/** + * securityfs_create_symlink - create a symlink in the securityfs filesystem + * + * @name: a pointer to a string containing the name of the symlink to + * create. + * @parent: a pointer to the parent dentry for the symlink. This should be a + * directory dentry if set. If this parameter is %NULL, then the + * directory will be created in the root of the securityfs filesystem. + * @target: a pointer to a string containing the name of the symlink's target. + * If this parameter is %NULL, then the @iops parameter needs to be + * setup to handle .readlink and .get_link inode_operations. + * @iops: a pointer to the struct inode_operations to use for the symlink. If + * this parameter is %NULL, then the default simple_symlink_inode + * operations will be used. + * + * This function creates a symlink in securityfs with the given @name. + * + * This function returns a pointer to a dentry if it succeeds. This + * pointer must be passed to the securityfs_remove() function when the file is + * to be removed (no automatic cleanup happens if your module is unloaded, + * you are responsible here). If an error occurs, the function will return + * the error value (via ERR_PTR). + * + * If securityfs is not enabled in the kernel, the value %-ENODEV is + * returned. + */ +struct dentry *securityfs_create_symlink(const char *name, + struct dentry *parent, + const char *target, + const struct inode_operations *iops) +{ + struct dentry *dent; + char *link = NULL; + + if (target) { + link = kstrdup(target, GFP_KERNEL); + if (!link) + return ERR_PTR(-ENOMEM); + } + dent = securityfs_create_dentry(name, S_IFLNK | 0444, parent, + link, NULL, iops); + if (IS_ERR(dent)) + kfree(link); + + return dent; +} +EXPORT_SYMBOL_GPL(securityfs_create_symlink); + /** * securityfs_remove - removes a file or directory from the securityfs filesystem * -- cgit v1.2.3 From 5d5182cae40115c03933989473288e54afb39c7c Mon Sep 17 00:00:00 2001 From: John Johansen Date: Tue, 9 May 2017 00:08:41 -0700 Subject: apparmor: move to per loaddata files, instead of replicating in profiles The loaddata sets cover more than just a single profile and should be tracked at the ns level. Move the load data files under the namespace and reference the files from the profiles via a symlink. Signed-off-by: John Johansen Reviewed-by: Seth Arnold Reviewed-by: Kees Cook --- security/apparmor/apparmorfs.c | 294 +++++++++++++++++++++++------- security/apparmor/include/apparmorfs.h | 5 + security/apparmor/include/policy_ns.h | 3 + security/apparmor/include/policy_unpack.h | 68 ++++++- security/apparmor/policy.c | 46 ++++- security/apparmor/policy_ns.c | 1 + security/apparmor/policy_unpack.c | 61 ++++++- 7 files changed, 409 insertions(+), 69 deletions(-) diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c index 26ad1a370632..d3dafd4ed144 100644 --- a/security/apparmor/apparmorfs.c +++ b/security/apparmor/apparmorfs.c @@ -98,14 +98,11 @@ static struct aa_loaddata *aa_simple_write_to_buffer(const char __user *userbuf, return ERR_PTR(-ESPIPE); /* freed by caller to simple_write_to_buffer */ - data = kvmalloc(sizeof(*data) + alloc_size, GFP_KERNEL); - if (data == NULL) - return ERR_PTR(-ENOMEM); - kref_init(&data->count); - data->size = copy_size; - data->hash = NULL; - data->abi = 0; + data = aa_loaddata_alloc(alloc_size); + if (IS_ERR(data)) + return data; + data->size = copy_size; if (copy_from_user(data->data, userbuf, copy_size)) { kvfree(data); return ERR_PTR(-EFAULT); @@ -213,6 +210,11 @@ static const struct file_operations aa_fs_profile_remove = { .llseek = default_llseek, }; +void __aa_bump_ns_revision(struct aa_ns *ns) +{ + ns->revision++; +} + /** * query_data - queries a policy and writes its data to buf * @buf: the resulting data is stored here (NOT NULL) @@ -559,68 +561,88 @@ static const struct file_operations aa_fs_ns_name = { .release = single_release, }; -static int rawdata_release(struct inode *inode, struct file *file) + +/* policy/raw_data/ * file ops */ + +#define SEQ_RAWDATA_FOPS(NAME) \ +static int seq_rawdata_ ##NAME ##_open(struct inode *inode, struct file *file)\ +{ \ + return seq_rawdata_open(inode, file, seq_rawdata_ ##NAME ##_show); \ +} \ + \ +static const struct file_operations seq_rawdata_ ##NAME ##_fops = { \ + .owner = THIS_MODULE, \ + .open = seq_rawdata_ ##NAME ##_open, \ + .read = seq_read, \ + .llseek = seq_lseek, \ + .release = seq_rawdata_release, \ +} \ + +static int seq_rawdata_open(struct inode *inode, struct file *file, + int (*show)(struct seq_file *, void *)) { - /* TODO: switch to loaddata when profile switched to symlink */ - aa_put_loaddata(file->private_data); + struct aa_loaddata *data = __aa_get_loaddata(inode->i_private); + int error; - return 0; + if (!data) + /* lost race this ent is being reaped */ + return -ENOENT; + + error = single_open(file, show, data); + if (error) { + AA_BUG(file->private_data && + ((struct seq_file *)file->private_data)->private); + aa_put_loaddata(data); + } + + return error; } -static int aa_fs_seq_raw_abi_show(struct seq_file *seq, void *v) +static int seq_rawdata_release(struct inode *inode, struct file *file) { - struct aa_proxy *proxy = seq->private; - struct aa_profile *profile = aa_get_profile_rcu(&proxy->profile); + struct seq_file *seq = (struct seq_file *) file->private_data; + + if (seq) + aa_put_loaddata(seq->private); - if (profile->rawdata->abi) - seq_printf(seq, "v%d\n", profile->rawdata->abi); + return single_release(inode, file); +} - aa_put_profile(profile); +static int seq_rawdata_abi_show(struct seq_file *seq, void *v) +{ + struct aa_loaddata *data = seq->private; + + seq_printf(seq, "v%d\n", data->abi); return 0; } -static int aa_fs_seq_raw_abi_open(struct inode *inode, struct file *file) +static int seq_rawdata_revision_show(struct seq_file *seq, void *v) { - return aa_fs_seq_profile_open(inode, file, aa_fs_seq_raw_abi_show); -} + struct aa_loaddata *data = seq->private; -static const struct file_operations aa_fs_seq_raw_abi_fops = { - .owner = THIS_MODULE, - .open = aa_fs_seq_raw_abi_open, - .read = seq_read, - .llseek = seq_lseek, - .release = aa_fs_seq_profile_release, -}; + seq_printf(seq, "%ld\n", data->revision); -static int aa_fs_seq_raw_hash_show(struct seq_file *seq, void *v) + return 0; +} + +static int seq_rawdata_hash_show(struct seq_file *seq, void *v) { - struct aa_proxy *proxy = seq->private; - struct aa_profile *profile = aa_get_profile_rcu(&proxy->profile); + struct aa_loaddata *data = seq->private; unsigned int i, size = aa_hash_size(); - if (profile->rawdata->hash) { + if (data->hash) { for (i = 0; i < size; i++) - seq_printf(seq, "%.2x", profile->rawdata->hash[i]); + seq_printf(seq, "%.2x", data->hash[i]); seq_putc(seq, '\n'); } - aa_put_profile(profile); return 0; } -static int aa_fs_seq_raw_hash_open(struct inode *inode, struct file *file) -{ - return aa_fs_seq_profile_open(inode, file, aa_fs_seq_raw_hash_show); -} - -static const struct file_operations aa_fs_seq_raw_hash_fops = { - .owner = THIS_MODULE, - .open = aa_fs_seq_raw_hash_open, - .read = seq_read, - .llseek = seq_lseek, - .release = aa_fs_seq_profile_release, -}; +SEQ_RAWDATA_FOPS(abi); +SEQ_RAWDATA_FOPS(revision); +SEQ_RAWDATA_FOPS(hash); static ssize_t rawdata_read(struct file *file, char __user *buf, size_t size, loff_t *ppos) @@ -631,27 +653,120 @@ static ssize_t rawdata_read(struct file *file, char __user *buf, size_t size, rawdata->size); } -static int rawdata_open(struct inode *inode, struct file *file) +static int rawdata_release(struct inode *inode, struct file *file) { - struct aa_proxy *proxy = inode->i_private; - struct aa_profile *profile; + aa_put_loaddata(file->private_data); + + return 0; +} +static int rawdata_open(struct inode *inode, struct file *file) +{ if (!policy_view_capable(NULL)) return -EACCES; - profile = aa_get_profile_rcu(&proxy->profile); - file->private_data = aa_get_loaddata(profile->rawdata); - aa_put_profile(profile); + file->private_data = __aa_get_loaddata(inode->i_private); + if (!file->private_data) + /* lost race: this entry is being reaped */ + return -ENOENT; return 0; } -static const struct file_operations aa_fs_rawdata_fops = { +static const struct file_operations rawdata_fops = { .open = rawdata_open, .read = rawdata_read, .llseek = generic_file_llseek, .release = rawdata_release, }; +static void remove_rawdata_dents(struct aa_loaddata *rawdata) +{ + int i; + + for (i = 0; i < AAFS_LOADDATA_NDENTS; i++) { + if (!IS_ERR_OR_NULL(rawdata->dents[i])) { + /* no refcounts on i_private */ + securityfs_remove(rawdata->dents[i]); + rawdata->dents[i] = NULL; + } + } +} + +void __aa_fs_remove_rawdata(struct aa_loaddata *rawdata) +{ + AA_BUG(rawdata->ns && !mutex_is_locked(&rawdata->ns->lock)); + + if (rawdata->ns) { + remove_rawdata_dents(rawdata); + list_del_init(&rawdata->list); + aa_put_ns(rawdata->ns); + rawdata->ns = NULL; + } +} + +int __aa_fs_create_rawdata(struct aa_ns *ns, struct aa_loaddata *rawdata) +{ + struct dentry *dent, *dir; + + AA_BUG(!ns); + AA_BUG(!rawdata); + AA_BUG(!mutex_is_locked(&ns->lock)); + AA_BUG(!ns_subdata_dir(ns)); + + /* + * just use ns revision dir was originally created at. This is + * under ns->lock and if load is successful revision will be + * bumped and is guaranteed to be unique + */ + rawdata->name = kasprintf(GFP_KERNEL, "%ld", ns->revision); + if (!rawdata->name) + return -ENOMEM; + + dir = securityfs_create_dir(rawdata->name, ns_subdata_dir(ns)); + if (IS_ERR(dir)) + /* ->name freed when rawdata freed */ + return PTR_ERR(dir); + rawdata->dents[AAFS_LOADDATA_DIR] = dir; + + dent = securityfs_create_file("abi", S_IFREG | 0444, dir, rawdata, + &seq_rawdata_abi_fops); + if (IS_ERR(dent)) + goto fail; + rawdata->dents[AAFS_LOADDATA_ABI] = dent; + + dent = securityfs_create_file("revision", S_IFREG | 0444, dir, rawdata, + &seq_rawdata_revision_fops); + if (IS_ERR(dent)) + goto fail; + rawdata->dents[AAFS_LOADDATA_REVISION] = dent; + + if (aa_g_hash_policy) { + dent = securityfs_create_file("sha1", S_IFREG | 0444, dir, + rawdata, &seq_rawdata_hash_fops); + if (IS_ERR(dent)) + goto fail; + rawdata->dents[AAFS_LOADDATA_HASH] = dent; + } + + dent = securityfs_create_file("raw_data", S_IFREG | 0444, + dir, rawdata, &rawdata_fops); + if (IS_ERR(dent)) + goto fail; + rawdata->dents[AAFS_LOADDATA_DATA] = dent; + d_inode(dent)->i_size = rawdata->size; + + rawdata->ns = aa_get_ns(ns); + list_add(&rawdata->list, &ns->rawdata_list); + /* no refcount on inode rawdata */ + + return 0; + +fail: + remove_rawdata_dents(rawdata); + + return PTR_ERR(dent); +} + /** fns to setup dynamic per profile/namespace files **/ void __aa_fs_profile_rmdir(struct aa_profile *profile) { @@ -703,7 +818,41 @@ static struct dentry *create_profile_file(struct dentry *dir, const char *name, return dent; } -/* requires lock be held */ +static int profile_depth(struct aa_profile *profile) +{ + int depth = 0; + + rcu_read_lock(); + for (depth = 0; profile; profile = rcu_access_pointer(profile->parent)) + depth++; + rcu_read_unlock(); + + return depth; +} + +static int gen_symlink_name(char *buffer, size_t bsize, int depth, + const char *dirname, const char *fname) +{ + int error; + + for (; depth > 0; depth--) { + if (bsize < 7) + return -ENAMETOOLONG; + strcpy(buffer, "../../"); + buffer += 6; + bsize -= 6; + } + + error = snprintf(buffer, bsize, "raw_data/%s/%s", dirname, fname); + if (error >= bsize || error < 0) + return -ENAMETOOLONG; + + return 0; +} + +/* + * Requires: @profile->ns->lock held + */ int __aa_fs_profile_mkdir(struct aa_profile *profile, struct dentry *parent) { struct aa_profile *child; @@ -766,26 +915,35 @@ int __aa_fs_profile_mkdir(struct aa_profile *profile, struct dentry *parent) } if (profile->rawdata) { - dent = create_profile_file(dir, "raw_sha1", profile, - &aa_fs_seq_raw_hash_fops); + char target[64]; + int depth = profile_depth(profile); + + error = gen_symlink_name(target, sizeof(target), depth, + profile->rawdata->name, "sha1"); + if (error < 0) + goto fail2; + dent = securityfs_create_symlink("raw_sha1", dir, target, NULL); if (IS_ERR(dent)) goto fail; profile->dents[AAFS_PROF_RAW_HASH] = dent; - dent = create_profile_file(dir, "raw_abi", profile, - &aa_fs_seq_raw_abi_fops); + error = gen_symlink_name(target, sizeof(target), depth, + profile->rawdata->name, "abi"); + if (error < 0) + goto fail2; + dent = securityfs_create_symlink("raw_abi", dir, target, NULL); if (IS_ERR(dent)) goto fail; profile->dents[AAFS_PROF_RAW_ABI] = dent; - dent = securityfs_create_file("raw_data", S_IFREG | 0444, dir, - profile->proxy, - &aa_fs_rawdata_fops); + error = gen_symlink_name(target, sizeof(target), depth, + profile->rawdata->name, "raw_data"); + if (error < 0) + goto fail2; + dent = securityfs_create_symlink("raw_data", dir, target, NULL); if (IS_ERR(dent)) goto fail; profile->dents[AAFS_PROF_RAW_DATA] = dent; - d_inode(dent)->i_size = profile->rawdata->size; - aa_get_proxy(profile->proxy); } list_for_each_entry(child, &profile->base.profiles, base.list) { @@ -805,6 +963,16 @@ fail2: return error; } +static void __aa_fs_list_remove_rawdata(struct aa_ns *ns) +{ + struct aa_loaddata *ent, *tmp; + + AA_BUG(!mutex_is_locked(&ns->lock)); + + list_for_each_entry_safe(ent, tmp, &ns->rawdata_list, list) + __aa_fs_remove_rawdata(ent); +} + void __aa_fs_ns_rmdir(struct aa_ns *ns) { struct aa_ns *sub; @@ -823,6 +991,8 @@ void __aa_fs_ns_rmdir(struct aa_ns *ns) mutex_unlock(&sub->lock); } + __aa_fs_list_remove_rawdata(ns); + if (ns_subns_dir(ns)) { sub = d_inode(ns_subns_dir(ns))->i_private; aa_put_ns(sub); diff --git a/security/apparmor/include/apparmorfs.h b/security/apparmor/include/apparmorfs.h index 120a798b5bb0..0b6d32b3f05e 100644 --- a/security/apparmor/include/apparmorfs.h +++ b/security/apparmor/include/apparmorfs.h @@ -106,6 +106,7 @@ enum aafs_prof_type { #define prof_dir(X) ((X)->dents[AAFS_PROF_DIR]) #define prof_child_dir(X) ((X)->dents[AAFS_PROF_PROFS]) +void __aa_bump_ns_revision(struct aa_ns *ns); void __aa_fs_profile_rmdir(struct aa_profile *profile); void __aa_fs_profile_migrate_dents(struct aa_profile *old, struct aa_profile *new); @@ -114,4 +115,8 @@ void __aa_fs_ns_rmdir(struct aa_ns *ns); int __aa_fs_ns_mkdir(struct aa_ns *ns, struct dentry *parent, const char *name); +struct aa_loaddata; +void __aa_fs_remove_rawdata(struct aa_loaddata *rawdata); +int __aa_fs_create_rawdata(struct aa_ns *ns, struct aa_loaddata *rawdata); + #endif /* __AA_APPARMORFS_H */ diff --git a/security/apparmor/include/policy_ns.h b/security/apparmor/include/policy_ns.h index 89cffddd7e75..d7a07ac96168 100644 --- a/security/apparmor/include/policy_ns.h +++ b/security/apparmor/include/policy_ns.h @@ -68,6 +68,9 @@ struct aa_ns { atomic_t uniq_null; long uniq_id; int level; + long revision; + + struct list_head rawdata_list; struct dentry *dents[AAFS_NS_SIZEOF]; }; diff --git a/security/apparmor/include/policy_unpack.h b/security/apparmor/include/policy_unpack.h index 4c1319eebc42..be6cd69ac319 100644 --- a/security/apparmor/include/policy_unpack.h +++ b/security/apparmor/include/policy_unpack.h @@ -17,6 +17,8 @@ #include #include +#include +#include struct aa_load_ent { struct list_head list; @@ -36,26 +38,84 @@ struct aa_load_ent *aa_load_ent_alloc(void); #define PACKED_MODE_KILL 2 #define PACKED_MODE_UNCONFINED 3 -/* struct aa_loaddata - buffer of policy load data set */ +struct aa_ns; + +enum { + AAFS_LOADDATA_ABI = 0, + AAFS_LOADDATA_REVISION, + AAFS_LOADDATA_HASH, + AAFS_LOADDATA_DATA, + AAFS_LOADDATA_DIR, /* must be last actual entry */ + AAFS_LOADDATA_NDENTS /* count of entries */ +}; + +/* + * struct aa_loaddata - buffer of policy raw_data set + * + * there is no loaddata ref for being on ns list, nor a ref from + * d_inode(@dentry) when grab a ref from these, @ns->lock must be held + * && __aa_get_loaddata() needs to be used, and the return value + * checked, if NULL the loaddata is already being reaped and should be + * considered dead. + */ struct aa_loaddata { struct kref count; + struct list_head list; + struct work_struct work; + struct dentry *dents[AAFS_LOADDATA_NDENTS]; + struct aa_ns *ns; + char *name; size_t size; + long revision; /* the ns policy revision this caused */ int abi; unsigned char *hash; + char data[]; }; int aa_unpack(struct aa_loaddata *udata, struct list_head *lh, const char **ns); +/** + * __aa_get_loaddata - get a reference count to uncounted data reference + * @data: reference to get a count on + * + * Returns: pointer to reference OR NULL if race is lost and reference is + * being repeated. + * Requires: @data->ns->lock held, and the return code MUST be checked + * + * Use only from inode->i_private and @data->list found references + */ +static inline struct aa_loaddata * +__aa_get_loaddata(struct aa_loaddata *data) +{ + if (data && kref_get_unless_zero(&(data->count))) + return data; + + return NULL; +} + +/** + * aa_get_loaddata - get a reference count from a counted data reference + * @data: reference to get a count on + * + * Returns: point to reference + * Requires: @data to have a valid reference count on it. It is a bug + * if the race to reap can be encountered when it is used. + */ static inline struct aa_loaddata * aa_get_loaddata(struct aa_loaddata *data) { - if (data) - kref_get(&(data->count)); - return data; + struct aa_loaddata *tmp = __aa_get_loaddata(data); + + AA_BUG(data && !tmp); + + return tmp; } +void __aa_loaddata_update(struct aa_loaddata *data, long revision); +bool aa_rawdata_eq(struct aa_loaddata *l, struct aa_loaddata *r); void aa_loaddata_kref(struct kref *kref); +struct aa_loaddata *aa_loaddata_alloc(size_t size); static inline void aa_put_loaddata(struct aa_loaddata *data) { if (data) diff --git a/security/apparmor/policy.c b/security/apparmor/policy.c index cf9d670dca94..5968914247a4 100644 --- a/security/apparmor/policy.c +++ b/security/apparmor/policy.c @@ -840,10 +840,13 @@ ssize_t aa_replace_profiles(struct aa_ns *view, struct aa_profile *profile, const char *ns_name, *info = NULL; struct aa_ns *ns = NULL; struct aa_load_ent *ent, *tmp; + struct aa_loaddata *rawdata_ent; const char *op = OP_PROF_REPL; ssize_t count, error; + LIST_HEAD(lh); + aa_get_loaddata(udata); /* released below */ error = aa_unpack(udata, &lh, &ns_name); if (error) @@ -887,9 +890,24 @@ ssize_t aa_replace_profiles(struct aa_ns *view, struct aa_profile *profile, ns = aa_get_ns(view); mutex_lock(&ns->lock); + /* check for duplicate rawdata blobs: space and file dedup */ + list_for_each_entry(rawdata_ent, &ns->rawdata_list, list) { + if (aa_rawdata_eq(rawdata_ent, udata)) { + struct aa_loaddata *tmp; + + tmp = __aa_get_loaddata(rawdata_ent); + /* check we didn't fail the race */ + if (tmp) { + aa_put_loaddata(udata); + udata = tmp; + break; + } + } + } /* setup parent and ns info */ list_for_each_entry(ent, &lh, list) { struct aa_policy *policy; + ent->new->rawdata = aa_get_loaddata(udata); error = __lookup_replace(ns, ent->new->base.hname, noreplace, &ent->old, &info); @@ -929,6 +947,14 @@ ssize_t aa_replace_profiles(struct aa_ns *view, struct aa_profile *profile, } /* create new fs entries for introspection if needed */ + if (!udata->dents[AAFS_LOADDATA_DIR]) { + error = __aa_fs_create_rawdata(ns, udata); + if (error) { + info = "failed to create raw_data dir and files"; + ent = NULL; + goto fail_lock; + } + } list_for_each_entry(ent, &lh, list) { if (ent->old) { /* inherit old interface files */ @@ -955,10 +981,24 @@ ssize_t aa_replace_profiles(struct aa_ns *view, struct aa_profile *profile, } /* Done with checks that may fail - do actual replacement */ + __aa_bump_ns_revision(ns); + __aa_loaddata_update(udata, ns->revision); list_for_each_entry_safe(ent, tmp, &lh, list) { list_del_init(&ent->list); op = (!ent->old && !ent->rename) ? OP_PROF_LOAD : OP_PROF_REPL; + if (ent->old && ent->old->rawdata == ent->new->rawdata) { + /* dedup actual profile replacement */ + audit_policy(profile, op, ns_name, ent->new->base.hname, + "same as current profile, skipping", + error); + goto skip; + } + + /* + * TODO: finer dedup based on profile range in data. Load set + * can differ but profile may remain unchanged + */ audit_policy(profile, op, NULL, ent->new->base.hname, NULL, error); @@ -998,12 +1038,14 @@ ssize_t aa_replace_profiles(struct aa_ns *view, struct aa_profile *profile, aa_get_profile(ent->new)); __list_add_profile(&ns->base.profiles, ent->new); } + skip: aa_load_ent_free(ent); } mutex_unlock(&ns->lock); out: aa_put_ns(ns); + aa_put_loaddata(udata); if (error) return error; @@ -1013,7 +1055,7 @@ fail_lock: mutex_unlock(&ns->lock); /* audit cause of failure */ - op = (!ent->old) ? OP_PROF_LOAD : OP_PROF_REPL; + op = (ent && !ent->old) ? OP_PROF_LOAD : OP_PROF_REPL; fail: audit_policy(profile, op, ns_name, ent ? ent->new->base.hname : NULL, info, error); @@ -1085,6 +1127,7 @@ ssize_t aa_remove_profiles(struct aa_ns *view, struct aa_profile *subj, /* remove namespace - can only happen if fqname[0] == ':' */ mutex_lock(&ns->parent->lock); __aa_remove_ns(ns); + __aa_bump_ns_revision(ns); mutex_unlock(&ns->parent->lock); } else { /* remove profile */ @@ -1097,6 +1140,7 @@ ssize_t aa_remove_profiles(struct aa_ns *view, struct aa_profile *subj, } name = profile->base.hname; __remove_profile(profile); + __aa_bump_ns_revision(ns); mutex_unlock(&ns->lock); } diff --git a/security/apparmor/policy_ns.c b/security/apparmor/policy_ns.c index 93d1826c4b09..c94ec6ef9e35 100644 --- a/security/apparmor/policy_ns.c +++ b/security/apparmor/policy_ns.c @@ -99,6 +99,7 @@ static struct aa_ns *alloc_ns(const char *prefix, const char *name) goto fail_ns; INIT_LIST_HEAD(&ns->sub_ns); + INIT_LIST_HEAD(&ns->rawdata_list); mutex_init(&ns->lock); /* released by aa_free_ns() */ diff --git a/security/apparmor/policy_unpack.c b/security/apparmor/policy_unpack.c index 6bc1b10191fa..e521df1bd1fb 100644 --- a/security/apparmor/policy_unpack.c +++ b/security/apparmor/policy_unpack.c @@ -122,16 +122,73 @@ static int audit_iface(struct aa_profile *new, const char *ns_name, return aa_audit(AUDIT_APPARMOR_STATUS, profile, &sa, audit_cb); } +void __aa_loaddata_update(struct aa_loaddata *data, long revision) +{ + AA_BUG(!data); + AA_BUG(!data->ns); + AA_BUG(!data->dents[AAFS_LOADDATA_REVISION]); + AA_BUG(!mutex_is_locked(&data->ns->lock)); + AA_BUG(data->revision > revision); + + data->revision = revision; + d_inode(data->dents[AAFS_LOADDATA_DIR])->i_mtime = + current_time(d_inode(data->dents[AAFS_LOADDATA_DIR])); + d_inode(data->dents[AAFS_LOADDATA_REVISION])->i_mtime = + current_time(d_inode(data->dents[AAFS_LOADDATA_REVISION])); +} + +bool aa_rawdata_eq(struct aa_loaddata *l, struct aa_loaddata *r) +{ + if (l->size != r->size) + return false; + if (aa_g_hash_policy && memcmp(l->hash, r->hash, aa_hash_size()) != 0) + return false; + return memcmp(l->data, r->data, r->size) == 0; +} + +/* + * need to take the ns mutex lock which is NOT safe most places that + * put_loaddata is called, so we have to delay freeing it + */ +static void do_loaddata_free(struct work_struct *work) +{ + struct aa_loaddata *d = container_of(work, struct aa_loaddata, work); + struct aa_ns *ns = aa_get_ns(d->ns); + + if (ns) { + mutex_lock(&ns->lock); + __aa_fs_remove_rawdata(d); + mutex_unlock(&ns->lock); + aa_put_ns(ns); + } + + kzfree(d->hash); + kfree(d->name); + kvfree(d); +} + void aa_loaddata_kref(struct kref *kref) { struct aa_loaddata *d = container_of(kref, struct aa_loaddata, count); if (d) { - kzfree(d->hash); - kvfree(d); + INIT_WORK(&d->work, do_loaddata_free); + schedule_work(&d->work); } } +struct aa_loaddata *aa_loaddata_alloc(size_t size) +{ + struct aa_loaddata *d = kvzalloc(sizeof(*d) + size, GFP_KERNEL); + + if (d == NULL) + return ERR_PTR(-ENOMEM); + kref_init(&d->count); + INIT_LIST_HEAD(&d->list); + + return d; +} + /* test if read will be in packed data bounds */ static bool inbounds(struct aa_ext *e, size_t size) { -- cgit v1.2.3 From 52b97de32236d4cc9b302ae9259e70bb2f1fa2a5 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Thu, 25 May 2017 04:35:09 -0700 Subject: apparmor: use macro template to simplify profile seq_files Signed-off-by: John Johansen Reviewed-by: Seth Arnold Reviewed-by: Kees Cook --- security/apparmor/apparmorfs.c | 97 ++++++++++++++++-------------------------- 1 file changed, 36 insertions(+), 61 deletions(-) diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c index d3dafd4ed144..750431b0ec4e 100644 --- a/security/apparmor/apparmorfs.c +++ b/security/apparmor/apparmorfs.c @@ -391,8 +391,27 @@ const struct file_operations aa_fs_seq_file_ops = { .release = single_release, }; -static int aa_fs_seq_profile_open(struct inode *inode, struct file *file, - int (*show)(struct seq_file *, void *)) +/* + * profile based file operations + * policy/profiles/XXXX/profiles/ * + */ + +#define SEQ_PROFILE_FOPS(NAME) \ +static int seq_profile_ ##NAME ##_open(struct inode *inode, struct file *file)\ +{ \ + return seq_profile_open(inode, file, seq_profile_ ##NAME ##_show); \ +} \ + \ +static const struct file_operations seq_profile_ ##NAME ##_fops = { \ + .owner = THIS_MODULE, \ + .open = seq_profile_ ##NAME ##_open, \ + .read = seq_read, \ + .llseek = seq_lseek, \ + .release = seq_profile_release, \ +} \ + +static int seq_profile_open(struct inode *inode, struct file *file, + int (*show)(struct seq_file *, void *)) { struct aa_proxy *proxy = aa_get_proxy(inode->i_private); int error = single_open(file, show, proxy); @@ -405,7 +424,7 @@ static int aa_fs_seq_profile_open(struct inode *inode, struct file *file, return error; } -static int aa_fs_seq_profile_release(struct inode *inode, struct file *file) +static int seq_profile_release(struct inode *inode, struct file *file) { struct seq_file *seq = (struct seq_file *) file->private_data; if (seq) @@ -413,7 +432,7 @@ static int aa_fs_seq_profile_release(struct inode *inode, struct file *file) return single_release(inode, file); } -static int aa_fs_seq_profname_show(struct seq_file *seq, void *v) +static int seq_profile_name_show(struct seq_file *seq, void *v) { struct aa_proxy *proxy = seq->private; struct aa_profile *profile = aa_get_profile_rcu(&proxy->profile); @@ -423,20 +442,7 @@ static int aa_fs_seq_profname_show(struct seq_file *seq, void *v) return 0; } -static int aa_fs_seq_profname_open(struct inode *inode, struct file *file) -{ - return aa_fs_seq_profile_open(inode, file, aa_fs_seq_profname_show); -} - -static const struct file_operations aa_fs_profname_fops = { - .owner = THIS_MODULE, - .open = aa_fs_seq_profname_open, - .read = seq_read, - .llseek = seq_lseek, - .release = aa_fs_seq_profile_release, -}; - -static int aa_fs_seq_profmode_show(struct seq_file *seq, void *v) +static int seq_profile_mode_show(struct seq_file *seq, void *v) { struct aa_proxy *proxy = seq->private; struct aa_profile *profile = aa_get_profile_rcu(&proxy->profile); @@ -446,20 +452,7 @@ static int aa_fs_seq_profmode_show(struct seq_file *seq, void *v) return 0; } -static int aa_fs_seq_profmode_open(struct inode *inode, struct file *file) -{ - return aa_fs_seq_profile_open(inode, file, aa_fs_seq_profmode_show); -} - -static const struct file_operations aa_fs_profmode_fops = { - .owner = THIS_MODULE, - .open = aa_fs_seq_profmode_open, - .read = seq_read, - .llseek = seq_lseek, - .release = aa_fs_seq_profile_release, -}; - -static int aa_fs_seq_profattach_show(struct seq_file *seq, void *v) +static int seq_profile_attach_show(struct seq_file *seq, void *v) { struct aa_proxy *proxy = seq->private; struct aa_profile *profile = aa_get_profile_rcu(&proxy->profile); @@ -474,20 +467,7 @@ static int aa_fs_seq_profattach_show(struct seq_file *seq, void *v) return 0; } -static int aa_fs_seq_profattach_open(struct inode *inode, struct file *file) -{ - return aa_fs_seq_profile_open(inode, file, aa_fs_seq_profattach_show); -} - -static const struct file_operations aa_fs_profattach_fops = { - .owner = THIS_MODULE, - .open = aa_fs_seq_profattach_open, - .read = seq_read, - .llseek = seq_lseek, - .release = aa_fs_seq_profile_release, -}; - -static int aa_fs_seq_hash_show(struct seq_file *seq, void *v) +static int seq_profile_hash_show(struct seq_file *seq, void *v) { struct aa_proxy *proxy = seq->private; struct aa_profile *profile = aa_get_profile_rcu(&proxy->profile); @@ -503,18 +483,11 @@ static int aa_fs_seq_hash_show(struct seq_file *seq, void *v) return 0; } -static int aa_fs_seq_hash_open(struct inode *inode, struct file *file) -{ - return single_open(file, aa_fs_seq_hash_show, inode->i_private); -} +SEQ_PROFILE_FOPS(name); +SEQ_PROFILE_FOPS(mode); +SEQ_PROFILE_FOPS(attach); +SEQ_PROFILE_FOPS(hash); -static const struct file_operations aa_fs_seq_hash_fops = { - .owner = THIS_MODULE, - .open = aa_fs_seq_hash_open, - .read = seq_read, - .llseek = seq_lseek, - .release = single_release, -}; static int aa_fs_seq_show_ns_level(struct seq_file *seq, void *v) @@ -890,25 +863,27 @@ int __aa_fs_profile_mkdir(struct aa_profile *profile, struct dentry *parent) goto fail; prof_dir(profile) = dir = dent; - dent = create_profile_file(dir, "name", profile, &aa_fs_profname_fops); + dent = create_profile_file(dir, "name", profile, + &seq_profile_name_fops); if (IS_ERR(dent)) goto fail; profile->dents[AAFS_PROF_NAME] = dent; - dent = create_profile_file(dir, "mode", profile, &aa_fs_profmode_fops); + dent = create_profile_file(dir, "mode", profile, + &seq_profile_mode_fops); if (IS_ERR(dent)) goto fail; profile->dents[AAFS_PROF_MODE] = dent; dent = create_profile_file(dir, "attach", profile, - &aa_fs_profattach_fops); + &seq_profile_attach_fops); if (IS_ERR(dent)) goto fail; profile->dents[AAFS_PROF_ATTACH] = dent; if (profile->hash) { dent = create_profile_file(dir, "sha1", profile, - &aa_fs_seq_hash_fops); + &seq_profile_hash_fops); if (IS_ERR(dent)) goto fail; profile->dents[AAFS_PROF_HASH] = dent; -- cgit v1.2.3 From 64c8697045f87713f0648e8429fcc3a0c4c61ffd Mon Sep 17 00:00:00 2001 From: John Johansen Date: Thu, 25 May 2017 07:27:35 -0700 Subject: apparmor: use macro template to simplify namespace seq_files Signed-off-by: John Johansen Reviewed-by: Seth Arnold Reviewed-by: Kees Cook --- security/apparmor/apparmorfs.c | 53 +++++++++++++++++++----------------------- 1 file changed, 24 insertions(+), 29 deletions(-) diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c index 750431b0ec4e..16680d15d43e 100644 --- a/security/apparmor/apparmorfs.c +++ b/security/apparmor/apparmorfs.c @@ -488,9 +488,27 @@ SEQ_PROFILE_FOPS(mode); SEQ_PROFILE_FOPS(attach); SEQ_PROFILE_FOPS(hash); +/* + * namespace based files + * several root files and + * policy/ * + */ +#define SEQ_NS_FOPS(NAME) \ +static int seq_ns_ ##NAME ##_open(struct inode *inode, struct file *file) \ +{ \ + return single_open(file, seq_ns_ ##NAME ##_show, inode->i_private); \ +} \ + \ +static const struct file_operations seq_ns_ ##NAME ##_fops = { \ + .owner = THIS_MODULE, \ + .open = seq_ns_ ##NAME ##_open, \ + .read = seq_read, \ + .llseek = seq_lseek, \ + .release = single_release, \ +} \ -static int aa_fs_seq_show_ns_level(struct seq_file *seq, void *v) +static int seq_ns_level_show(struct seq_file *seq, void *v) { struct aa_ns *ns = aa_current_profile()->ns; @@ -499,20 +517,7 @@ static int aa_fs_seq_show_ns_level(struct seq_file *seq, void *v) return 0; } -static int aa_fs_seq_open_ns_level(struct inode *inode, struct file *file) -{ - return single_open(file, aa_fs_seq_show_ns_level, inode->i_private); -} - -static const struct file_operations aa_fs_ns_level = { - .owner = THIS_MODULE, - .open = aa_fs_seq_open_ns_level, - .read = seq_read, - .llseek = seq_lseek, - .release = single_release, -}; - -static int aa_fs_seq_show_ns_name(struct seq_file *seq, void *v) +static int seq_ns_name_show(struct seq_file *seq, void *v) { struct aa_ns *ns = aa_current_profile()->ns; @@ -521,18 +526,8 @@ static int aa_fs_seq_show_ns_name(struct seq_file *seq, void *v) return 0; } -static int aa_fs_seq_open_ns_name(struct inode *inode, struct file *file) -{ - return single_open(file, aa_fs_seq_show_ns_name, inode->i_private); -} - -static const struct file_operations aa_fs_ns_name = { - .owner = THIS_MODULE, - .open = aa_fs_seq_open_ns_name, - .read = seq_read, - .llseek = seq_lseek, - .release = single_release, -}; +SEQ_NS_FOPS(level); +SEQ_NS_FOPS(name); /* policy/raw_data/ * file ops */ @@ -1363,8 +1358,8 @@ static struct aa_fs_entry aa_fs_entry_features[] = { static struct aa_fs_entry aa_fs_entry_apparmor[] = { AA_FS_FILE_FOPS(".access", 0640, &aa_fs_access), - AA_FS_FILE_FOPS(".ns_level", 0666, &aa_fs_ns_level), - AA_FS_FILE_FOPS(".ns_name", 0640, &aa_fs_ns_name), + AA_FS_FILE_FOPS(".ns_level", 0666, &seq_ns_level_fops), + AA_FS_FILE_FOPS(".ns_name", 0640, &seq_ns_name_fops), AA_FS_FILE_FOPS("profiles", 0440, &aa_fs_profiles_fops), AA_FS_DIR("features", aa_fs_entry_features), { } -- cgit v1.2.3 From a481f4d917835cad86701fc0d1e620c74bb5cd5f Mon Sep 17 00:00:00 2001 From: John Johansen Date: Thu, 25 May 2017 05:52:56 -0700 Subject: apparmor: add custom apparmorfs that will be used by policy namespace files AppArmor policy needs to be able to be resolved based on the policy namespace a task is confined by. Add a base apparmorfs filesystem that (like nsfs) will exist as a kern mount and be accessed via jump_link through a securityfs file. Setup the base apparmorfs fns and data, but don't use it yet. Signed-off-by: John Johansen Reviewed-by: Seth Arnold Reviewed-by: Kees Cook --- include/uapi/linux/magic.h | 2 + security/apparmor/apparmorfs.c | 353 +++++++++++++++++++++++++++++++++++++++-- 2 files changed, 338 insertions(+), 17 deletions(-) diff --git a/include/uapi/linux/magic.h b/include/uapi/linux/magic.h index e230af2e6855..a0908f1d2760 100644 --- a/include/uapi/linux/magic.h +++ b/include/uapi/linux/magic.h @@ -80,6 +80,8 @@ #define BTRFS_TEST_MAGIC 0x73727279 #define NSFS_MAGIC 0x6e736673 #define BPF_FS_MAGIC 0xcafe4a11 +#define AAFS_MAGIC 0x5a3c69f0 + /* Since UDF 2.01 is ISO 13346 based... */ #define UDF_SUPER_MAGIC 0x15013346 #define BALLOON_KVM_MAGIC 0x13661366 diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c index 16680d15d43e..7e4b7f28ee20 100644 --- a/security/apparmor/apparmorfs.c +++ b/security/apparmor/apparmorfs.c @@ -22,8 +22,9 @@ #include #include #include -#include #include +#include +#include #include "include/apparmor.h" #include "include/apparmorfs.h" @@ -74,6 +75,265 @@ static int mangle_name(const char *name, char *target) return t - target; } + +/* + * aafs - core fns and data for the policy tree + */ + +#define AAFS_NAME "apparmorfs" +static struct vfsmount *aafs_mnt; +static int aafs_count; + + +static int aafs_show_path(struct seq_file *seq, struct dentry *dentry) +{ + struct inode *inode = d_inode(dentry); + + seq_printf(seq, "%s:[%lu]", AAFS_NAME, inode->i_ino); + return 0; +} + +static void aafs_evict_inode(struct inode *inode) +{ + truncate_inode_pages_final(&inode->i_data); + clear_inode(inode); + if (S_ISLNK(inode->i_mode)) + kfree(inode->i_link); +} + +static const struct super_operations aafs_super_ops = { + .statfs = simple_statfs, + .evict_inode = aafs_evict_inode, + .show_path = aafs_show_path, +}; + +static int fill_super(struct super_block *sb, void *data, int silent) +{ + static struct tree_descr files[] = { {""} }; + int error; + + error = simple_fill_super(sb, AAFS_MAGIC, files); + if (error) + return error; + sb->s_op = &aafs_super_ops; + + return 0; +} + +static struct dentry *aafs_mount(struct file_system_type *fs_type, + int flags, const char *dev_name, void *data) +{ + return mount_single(fs_type, flags, data, fill_super); +} + +static struct file_system_type aafs_ops = { + .owner = THIS_MODULE, + .name = AAFS_NAME, + .mount = aafs_mount, + .kill_sb = kill_anon_super, +}; + +/** + * __aafs_setup_d_inode - basic inode setup for apparmorfs + * @dir: parent directory for the dentry + * @dentry: dentry we are seting the inode up for + * @mode: permissions the file should have + * @data: data to store on inode.i_private, available in open() + * @link: if symlink, symlink target string + * @fops: struct file_operations that should be used + * @iops: struct of inode_operations that should be used + */ +static int __aafs_setup_d_inode(struct inode *dir, struct dentry *dentry, + umode_t mode, void *data, char *link, + const struct file_operations *fops, + const struct inode_operations *iops) +{ + struct inode *inode = new_inode(dir->i_sb); + + AA_BUG(!dir); + AA_BUG(!dentry); + + if (!inode) + return -ENOMEM; + + inode->i_ino = get_next_ino(); + inode->i_mode = mode; + inode->i_atime = inode->i_mtime = inode->i_ctime = current_time(inode); + inode->i_private = data; + if (S_ISDIR(mode)) { + inode->i_op = iops ? iops : &simple_dir_inode_operations; + inode->i_fop = &simple_dir_operations; + inc_nlink(inode); + inc_nlink(dir); + } else if (S_ISLNK(mode)) { + inode->i_op = iops ? iops : &simple_symlink_inode_operations; + inode->i_link = link; + } else { + inode->i_fop = fops; + } + d_instantiate(dentry, inode); + dget(dentry); + + return 0; +} + +/** + * aafs_create - create a dentry in the apparmorfs filesystem + * + * @name: name of dentry to create + * @mode: permissions the file should have + * @parent: parent directory for this dentry + * @data: data to store on inode.i_private, available in open() + * @link: if symlink, symlink target string + * @fops: struct file_operations that should be used for + * @iops: struct of inode_operations that should be used + * + * This is the basic "create a xxx" function for apparmorfs. + * + * Returns a pointer to a dentry if it succeeds, that must be free with + * aafs_remove(). Will return ERR_PTR on failure. + */ +static struct dentry *aafs_create(const char *name, umode_t mode, + struct dentry *parent, void *data, void *link, + const struct file_operations *fops, + const struct inode_operations *iops) +{ + struct dentry *dentry; + struct inode *dir; + int error; + + AA_BUG(!name); + AA_BUG(!parent); + + if (!(mode & S_IFMT)) + mode = (mode & S_IALLUGO) | S_IFREG; + + error = simple_pin_fs(&aafs_ops, &aafs_mnt, &aafs_count); + if (error) + return ERR_PTR(error); + + dir = d_inode(parent); + + inode_lock(dir); + dentry = lookup_one_len(name, parent, strlen(name)); + if (IS_ERR(dentry)) + goto fail_lock; + + if (d_really_is_positive(dentry)) { + error = -EEXIST; + goto fail_dentry; + } + + error = __aafs_setup_d_inode(dir, dentry, mode, data, link, fops, iops); + if (error) + goto fail_dentry; + inode_unlock(dir); + + return dentry; + +fail_dentry: + dput(dentry); + +fail_lock: + inode_unlock(dir); + simple_release_fs(&aafs_mnt, &aafs_count); + + return ERR_PTR(error); +} + +/** + * aafs_create_file - create a file in the apparmorfs filesystem + * + * @name: name of dentry to create + * @mode: permissions the file should have + * @parent: parent directory for this dentry + * @data: data to store on inode.i_private, available in open() + * @fops: struct file_operations that should be used for + * + * see aafs_create + */ +static struct dentry *aafs_create_file(const char *name, umode_t mode, + struct dentry *parent, void *data, + const struct file_operations *fops) +{ + return aafs_create(name, mode, parent, data, NULL, fops, NULL); +} + +/** + * aafs_create_dir - create a directory in the apparmorfs filesystem + * + * @name: name of dentry to create + * @parent: parent directory for this dentry + * + * see aafs_create + */ +static struct dentry *aafs_create_dir(const char *name, struct dentry *parent) +{ + return aafs_create(name, S_IFDIR | 0755, parent, NULL, NULL, NULL, + NULL); +} + +/** + * aafs_create_symlink - create a symlink in the apparmorfs filesystem + * @name: name of dentry to create + * @parent: parent directory for this dentry + * @target: if symlink, symlink target string + * @iops: struct of inode_operations that should be used + * + * If @target parameter is %NULL, then the @iops parameter needs to be + * setup to handle .readlink and .get_link inode_operations. + */ +static struct dentry *aafs_create_symlink(const char *name, + struct dentry *parent, + const char *target, + const struct inode_operations *iops) +{ + struct dentry *dent; + char *link = NULL; + + if (target) { + link = kstrdup(target, GFP_KERNEL); + if (!link) + return ERR_PTR(-ENOMEM); + } + dent = aafs_create(name, S_IFLNK | 0444, parent, NULL, link, NULL, + iops); + if (IS_ERR(dent)) + kfree(link); + + return dent; +} + +/** + * aafs_remove - removes a file or directory from the apparmorfs filesystem + * + * @dentry: dentry of the file/directory/symlink to removed. + */ +static void aafs_remove(struct dentry *dentry) +{ + struct inode *dir; + + if (!dentry || IS_ERR(dentry)) + return; + + dir = d_inode(dentry->d_parent); + inode_lock(dir); + if (simple_positive(dentry)) { + if (d_is_dir(dentry)) + simple_rmdir(dir, dentry); + else + simple_unlink(dir, dentry); + dput(dentry); + } + inode_unlock(dir); + simple_release_fs(&aafs_mnt, &aafs_count); +} + + +/* + * aa_fs - policy load/replace/remove + */ + /** * aa_simple_write_to_buffer - common routine for getting policy from user * @userbuf: user buffer to copy data from (NOT NULL) @@ -1369,14 +1629,14 @@ static struct aa_fs_entry aa_fs_entry = AA_FS_DIR("apparmor", aa_fs_entry_apparmor); /** - * aafs_create_file - create a file entry in the apparmor securityfs + * entry_create_file - create a file entry in the apparmor securityfs * @fs_file: aa_fs_entry to build an entry for (NOT NULL) * @parent: the parent dentry in the securityfs * - * Use aafs_remove_file to remove entries created with this fn. + * Use entry_remove_file to remove entries created with this fn. */ -static int __init aafs_create_file(struct aa_fs_entry *fs_file, - struct dentry *parent) +static int __init entry_create_file(struct aa_fs_entry *fs_file, + struct dentry *parent) { int error = 0; @@ -1391,15 +1651,15 @@ static int __init aafs_create_file(struct aa_fs_entry *fs_file, return error; } -static void __init aafs_remove_dir(struct aa_fs_entry *fs_dir); +static void __init entry_remove_dir(struct aa_fs_entry *fs_dir); /** - * aafs_create_dir - recursively create a directory entry in the securityfs + * entry_create_dir - recursively create a directory entry in the securityfs * @fs_dir: aa_fs_entry (and all child entries) to build (NOT NULL) * @parent: the parent dentry in the securityfs * - * Use aafs_remove_dir to remove entries created with this fn. + * Use entry_remove_dir to remove entries created with this fn. */ -static int __init aafs_create_dir(struct aa_fs_entry *fs_dir, +static int __init entry_create_dir(struct aa_fs_entry *fs_dir, struct dentry *parent) { struct aa_fs_entry *fs_file; @@ -1413,9 +1673,9 @@ static int __init aafs_create_dir(struct aa_fs_entry *fs_dir, for (fs_file = fs_dir->v.files; fs_file && fs_file->name; ++fs_file) { if (fs_file->v_type == AA_FS_TYPE_DIR) - error = aafs_create_dir(fs_file, fs_dir->dentry); + error = entry_create_dir(fs_file, fs_dir->dentry); else - error = aafs_create_file(fs_file, fs_dir->dentry); + error = entry_create_file(fs_file, fs_dir->dentry); if (error) goto failed; } @@ -1423,7 +1683,7 @@ static int __init aafs_create_dir(struct aa_fs_entry *fs_dir, return 0; failed: - aafs_remove_dir(fs_dir); + entry_remove_dir(fs_dir); return error; } @@ -1442,16 +1702,16 @@ static void __init aafs_remove_file(struct aa_fs_entry *fs_file) } /** - * aafs_remove_dir - recursively drop a directory entry from the securityfs + * entry_remove_dir - recursively drop a directory entry from the securityfs * @fs_dir: aa_fs_entry (and all child entries) to detach (NOT NULL) */ -static void __init aafs_remove_dir(struct aa_fs_entry *fs_dir) +static void __init entry_remove_dir(struct aa_fs_entry *fs_dir) { struct aa_fs_entry *fs_file; for (fs_file = fs_dir->v.files; fs_file && fs_file->name; ++fs_file) { if (fs_file->v_type == AA_FS_TYPE_DIR) - aafs_remove_dir(fs_file); + entry_remove_dir(fs_file); else aafs_remove_file(fs_file); } @@ -1466,7 +1726,7 @@ static void __init aafs_remove_dir(struct aa_fs_entry *fs_dir) */ void __init aa_destroy_aafs(void) { - aafs_remove_dir(&aa_fs_entry); + entry_remove_dir(&aa_fs_entry); } @@ -1515,6 +1775,59 @@ out: return error; } + + +static const char *policy_get_link(struct dentry *dentry, + struct inode *inode, + struct delayed_call *done) +{ + struct aa_ns *ns; + struct path path; + + if (!dentry) + return ERR_PTR(-ECHILD); + ns = aa_get_current_ns(); + path.mnt = mntget(aafs_mnt); + path.dentry = dget(ns_dir(ns)); + nd_jump_link(&path); + aa_put_ns(ns); + + return NULL; +} + +static int ns_get_name(char *buf, size_t size, struct aa_ns *ns, + struct inode *inode) +{ + int res = snprintf(buf, size, "%s:[%lu]", AAFS_NAME, inode->i_ino); + + if (res < 0 || res >= size) + res = -ENOENT; + + return res; +} + +static int policy_readlink(struct dentry *dentry, char __user *buffer, + int buflen) +{ + struct aa_ns *ns; + char name[32]; + int res; + + ns = aa_get_current_ns(); + res = ns_get_name(name, sizeof(name), ns, d_inode(dentry)); + if (res >= 0) + res = readlink_copy(buffer, buflen, name); + aa_put_ns(ns); + + return res; +} + +static const struct inode_operations policy_link_iops = { + .readlink = policy_readlink, + .get_link = policy_get_link, +}; + + /** * aa_create_aafs - create the apparmor security filesystem * @@ -1535,8 +1848,14 @@ static int __init aa_create_aafs(void) return -EEXIST; } + /* setup apparmorfs used to virtualize policy/ */ + aafs_mnt = kern_mount(&aafs_ops); + if (IS_ERR(aafs_mnt)) + panic("can't set apparmorfs up\n"); + aafs_mnt->mnt_sb->s_flags &= ~MS_NOUSER; + /* Populate fs tree. */ - error = aafs_create_dir(&aa_fs_entry, NULL); + error = entry_create_dir(&aa_fs_entry, NULL); if (error) goto error; -- cgit v1.2.3 From c97204baf840bf850e14ef4f5f43251239ca43b6 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Thu, 25 May 2017 06:23:42 -0700 Subject: apparmor: rename apparmor file fns and data to indicate use prefixes are used for fns/data that are not static to apparmorfs.c with the prefixes being aafs - special magic apparmorfs for policy namespace data aa_sfs - for fns/data that go into securityfs aa_fs - for fns/data that may be used in the either of aafs or securityfs Signed-off-by: John Johansen Reviewed-by: Seth Arnold Reviewed-by: Kees Cook --- security/apparmor/Makefile | 6 +- security/apparmor/apparmorfs.c | 213 ++++++++++++++++++++------------- security/apparmor/capability.c | 4 +- security/apparmor/include/apparmorfs.h | 58 ++++----- security/apparmor/include/capability.h | 2 +- security/apparmor/include/resource.h | 2 +- security/apparmor/policy.c | 6 +- security/apparmor/policy_ns.c | 4 +- security/apparmor/resource.c | 4 +- 9 files changed, 172 insertions(+), 127 deletions(-) diff --git a/security/apparmor/Makefile b/security/apparmor/Makefile index 2ded2f1be98b..b3e7c04b7e7b 100644 --- a/security/apparmor/Makefile +++ b/security/apparmor/Makefile @@ -20,7 +20,7 @@ cmd_make-caps = echo "static const char *const capability_names[] = {" > $@ ;\ sed $< >>$@ -r -n -e '/CAP_FS_MASK/d' \ -e 's/^\#define[ \t]+CAP_([A-Z0-9_]+)[ \t]+([0-9]+)/[\2] = "\L\1",/p';\ echo "};" >> $@ ;\ - printf '%s' '\#define AA_FS_CAPS_MASK "' >> $@ ;\ + printf '%s' '\#define AA_SFS_CAPS_MASK "' >> $@ ;\ sed $< -r -n -e '/CAP_FS_MASK/d' \ -e 's/^\#define[ \t]+CAP_([A-Z0-9_]+)[ \t]+([0-9]+)/\L\1/p' | \ tr '\n' ' ' | sed -e 's/ $$/"\n/' >> $@ @@ -46,7 +46,7 @@ cmd_make-caps = echo "static const char *const capability_names[] = {" > $@ ;\ # #define RLIMIT_FSIZE 1 /* Maximum filesize */ # #define RLIMIT_STACK 3 /* max stack size */ # to -# #define AA_FS_RLIMIT_MASK "fsize stack" +# #define AA_SFS_RLIMIT_MASK "fsize stack" quiet_cmd_make-rlim = GEN $@ cmd_make-rlim = echo "static const char *const rlim_names[RLIM_NLIMITS] = {" \ > $@ ;\ @@ -56,7 +56,7 @@ cmd_make-rlim = echo "static const char *const rlim_names[RLIM_NLIMITS] = {" \ echo "static const int rlim_map[RLIM_NLIMITS] = {" >> $@ ;\ sed -r -n "s/^\# ?define[ \t]+(RLIMIT_[A-Z0-9_]+).*/\1,/p" $< >> $@ ;\ echo "};" >> $@ ; \ - printf '%s' '\#define AA_FS_RLIMIT_MASK "' >> $@ ;\ + printf '%s' '\#define AA_SFS_RLIMIT_MASK "' >> $@ ;\ sed -r -n 's/^\# ?define[ \t]+RLIMIT_([A-Z0-9_]+).*/\L\1/p' $< | \ tr '\n' ' ' | sed -e 's/ $$/"\n/' >> $@ diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c index 7e4b7f28ee20..35b822c4a079 100644 --- a/security/apparmor/apparmorfs.c +++ b/security/apparmor/apparmorfs.c @@ -36,6 +36,35 @@ #include "include/resource.h" #include "include/policy_unpack.h" +/* + * The apparmor filesystem interface used for policy load and introspection + * The interface is split into two main components based on their function + * a securityfs component: + * used for static files that are always available, and which allows + * userspace to specificy the location of the security filesystem. + * + * fns and data are prefixed with + * aa_sfs_ + * + * an apparmorfs component: + * used loaded policy content and introspection. It is not part of a + * regular mounted filesystem and is available only through the magic + * policy symlink in the root of the securityfs apparmor/ directory. + * Tasks queries will be magically redirected to the correct portion + * of the policy tree based on their confinement. + * + * fns and data are prefixed with + * aafs_ + * + * The aa_fs_ prefix is used to indicate the fn is used by both the + * securityfs and apparmorfs filesystems. + */ + + +/* + * support fns + */ + /** * aa_mangle_name - mangle a profile name to std profile layout form * @name: profile name to mangle (NOT NULL) @@ -606,28 +635,28 @@ static ssize_t aa_write_access(struct file *file, const char __user *ubuf, return count; } -static const struct file_operations aa_fs_access = { +static const struct file_operations aa_sfs_access = { .write = aa_write_access, .read = simple_transaction_read, .release = simple_transaction_release, .llseek = generic_file_llseek, }; -static int aa_fs_seq_show(struct seq_file *seq, void *v) +static int aa_sfs_seq_show(struct seq_file *seq, void *v) { - struct aa_fs_entry *fs_file = seq->private; + struct aa_sfs_entry *fs_file = seq->private; if (!fs_file) return 0; switch (fs_file->v_type) { - case AA_FS_TYPE_BOOLEAN: + case AA_SFS_TYPE_BOOLEAN: seq_printf(seq, "%s\n", fs_file->v.boolean ? "yes" : "no"); break; - case AA_FS_TYPE_STRING: + case AA_SFS_TYPE_STRING: seq_printf(seq, "%s\n", fs_file->v.string); break; - case AA_FS_TYPE_U64: + case AA_SFS_TYPE_U64: seq_printf(seq, "%#08lx\n", fs_file->v.u64); break; default: @@ -638,14 +667,14 @@ static int aa_fs_seq_show(struct seq_file *seq, void *v) return 0; } -static int aa_fs_seq_open(struct inode *inode, struct file *file) +static int aa_sfs_seq_open(struct inode *inode, struct file *file) { - return single_open(file, aa_fs_seq_show, inode->i_private); + return single_open(file, aa_sfs_seq_show, inode->i_private); } -const struct file_operations aa_fs_seq_file_ops = { +const struct file_operations aa_sfs_seq_file_ops = { .owner = THIS_MODULE, - .open = aa_fs_seq_open, + .open = aa_sfs_seq_open, .read = seq_read, .llseek = seq_lseek, .release = single_release, @@ -996,7 +1025,12 @@ fail: } /** fns to setup dynamic per profile/namespace files **/ -void __aa_fs_profile_rmdir(struct aa_profile *profile) + +/** + * + * Requires: @profile->ns->lock held + */ +void __aafs_profile_rmdir(struct aa_profile *profile) { struct aa_profile *child; int i; @@ -1005,7 +1039,7 @@ void __aa_fs_profile_rmdir(struct aa_profile *profile) return; list_for_each_entry(child, &profile->base.profiles, base.list) - __aa_fs_profile_rmdir(child); + __aafs_profile_rmdir(child); for (i = AAFS_PROF_SIZEOF - 1; i >= 0; --i) { struct aa_proxy *proxy; @@ -1019,8 +1053,12 @@ void __aa_fs_profile_rmdir(struct aa_profile *profile) } } -void __aa_fs_profile_migrate_dents(struct aa_profile *old, - struct aa_profile *new) +/** + * + * Requires: @old->ns->lock held + */ +void __aafs_profile_migrate_dents(struct aa_profile *old, + struct aa_profile *new) { int i; @@ -1081,7 +1119,7 @@ static int gen_symlink_name(char *buffer, size_t bsize, int depth, /* * Requires: @profile->ns->lock held */ -int __aa_fs_profile_mkdir(struct aa_profile *profile, struct dentry *parent) +int __aafs_profile_mkdir(struct aa_profile *profile, struct dentry *parent) { struct aa_profile *child; struct dentry *dent = NULL, *dir; @@ -1177,7 +1215,7 @@ int __aa_fs_profile_mkdir(struct aa_profile *profile, struct dentry *parent) } list_for_each_entry(child, &profile->base.profiles, base.list) { - error = __aa_fs_profile_mkdir(child, prof_child_dir(profile)); + error = __aafs_profile_mkdir(child, prof_child_dir(profile)); if (error) goto fail2; } @@ -1188,7 +1226,7 @@ fail: error = PTR_ERR(dent); fail2: - __aa_fs_profile_rmdir(profile); + __aafs_profile_rmdir(profile); return error; } @@ -1203,7 +1241,11 @@ static void __aa_fs_list_remove_rawdata(struct aa_ns *ns) __aa_fs_remove_rawdata(ent); } -void __aa_fs_ns_rmdir(struct aa_ns *ns) +/** + * + * Requires: @ns->lock held + */ +void __aafs_ns_rmdir(struct aa_ns *ns) { struct aa_ns *sub; struct aa_profile *child; @@ -1213,11 +1255,11 @@ void __aa_fs_ns_rmdir(struct aa_ns *ns) return; list_for_each_entry(child, &ns->base.profiles, base.list) - __aa_fs_profile_rmdir(child); + __aafs_profile_rmdir(child); list_for_each_entry(sub, &ns->sub_ns, base.list) { mutex_lock(&sub->lock); - __aa_fs_ns_rmdir(sub); + __aafs_ns_rmdir(sub); mutex_unlock(&sub->lock); } @@ -1247,7 +1289,7 @@ void __aa_fs_ns_rmdir(struct aa_ns *ns) } /* assumes cleanup in caller */ -static int __aa_fs_ns_mkdir_entries(struct aa_ns *ns, struct dentry *dir) +static int __aafs_ns_mkdir_entries(struct aa_ns *ns, struct dentry *dir) { struct dentry *dent; @@ -1294,7 +1336,10 @@ static int __aa_fs_ns_mkdir_entries(struct aa_ns *ns, struct dentry *dir) return 0; } -int __aa_fs_ns_mkdir(struct aa_ns *ns, struct dentry *parent, const char *name) +/* + * Requires: @ns->lock held + */ +int __aafs_ns_mkdir(struct aa_ns *ns, struct dentry *parent, const char *name) { struct aa_ns *sub; struct aa_profile *child; @@ -1314,13 +1359,13 @@ int __aa_fs_ns_mkdir(struct aa_ns *ns, struct dentry *parent, const char *name) goto fail; ns_dir(ns) = dir = dent; - error = __aa_fs_ns_mkdir_entries(ns, dir); + error = __aafs_ns_mkdir_entries(ns, dir); if (error) goto fail2; /* profiles */ list_for_each_entry(child, &ns->base.profiles, base.list) { - error = __aa_fs_profile_mkdir(child, ns_subprofs_dir(ns)); + error = __aafs_profile_mkdir(child, ns_subprofs_dir(ns)); if (error) goto fail2; } @@ -1328,7 +1373,7 @@ int __aa_fs_ns_mkdir(struct aa_ns *ns, struct dentry *parent, const char *name) /* subnamespaces */ list_for_each_entry(sub, &ns->sub_ns, base.list) { mutex_lock(&sub->lock); - error = __aa_fs_ns_mkdir(sub, ns_subns_dir(ns), NULL); + error = __aafs_ns_mkdir(sub, ns_subns_dir(ns), NULL); mutex_unlock(&sub->lock); if (error) goto fail2; @@ -1340,7 +1385,7 @@ fail: error = PTR_ERR(dent); fail2: - __aa_fs_ns_rmdir(ns); + __aafs_ns_rmdir(ns); return error; } @@ -1550,7 +1595,7 @@ static int seq_show_profile(struct seq_file *f, void *p) return 0; } -static const struct seq_operations aa_fs_profiles_op = { +static const struct seq_operations aa_sfs_profiles_op = { .start = p_start, .next = p_next, .stop = p_stop, @@ -1562,7 +1607,7 @@ static int profiles_open(struct inode *inode, struct file *file) if (!policy_view_capable(NULL)) return -EACCES; - return seq_open(file, &aa_fs_profiles_op); + return seq_open(file, &aa_sfs_profiles_op); } static int profiles_release(struct inode *inode, struct file *file) @@ -1570,7 +1615,7 @@ static int profiles_release(struct inode *inode, struct file *file) return seq_release(inode, file); } -static const struct file_operations aa_fs_profiles_fops = { +static const struct file_operations aa_sfs_profiles_fops = { .open = profiles_open, .read = seq_read, .llseek = seq_lseek, @@ -1579,63 +1624,63 @@ static const struct file_operations aa_fs_profiles_fops = { /** Base file system setup **/ -static struct aa_fs_entry aa_fs_entry_file[] = { - AA_FS_FILE_STRING("mask", "create read write exec append mmap_exec " \ - "link lock"), +static struct aa_sfs_entry aa_sfs_entry_file[] = { + AA_SFS_FILE_STRING("mask", + "create read write exec append mmap_exec link lock"), { } }; -static struct aa_fs_entry aa_fs_entry_domain[] = { - AA_FS_FILE_BOOLEAN("change_hat", 1), - AA_FS_FILE_BOOLEAN("change_hatv", 1), - AA_FS_FILE_BOOLEAN("change_onexec", 1), - AA_FS_FILE_BOOLEAN("change_profile", 1), - AA_FS_FILE_BOOLEAN("fix_binfmt_elf_mmap", 1), - AA_FS_FILE_STRING("version", "1.2"), +static struct aa_sfs_entry aa_sfs_entry_domain[] = { + AA_SFS_FILE_BOOLEAN("change_hat", 1), + AA_SFS_FILE_BOOLEAN("change_hatv", 1), + AA_SFS_FILE_BOOLEAN("change_onexec", 1), + AA_SFS_FILE_BOOLEAN("change_profile", 1), + AA_SFS_FILE_BOOLEAN("fix_binfmt_elf_mmap", 1), + AA_SFS_FILE_STRING("version", "1.2"), { } }; -static struct aa_fs_entry aa_fs_entry_versions[] = { - AA_FS_FILE_BOOLEAN("v5", 1), +static struct aa_sfs_entry aa_sfs_entry_versions[] = { + AA_SFS_FILE_BOOLEAN("v5", 1), { } }; -static struct aa_fs_entry aa_fs_entry_policy[] = { - AA_FS_DIR("versions", aa_fs_entry_versions), - AA_FS_FILE_BOOLEAN("set_load", 1), +static struct aa_sfs_entry aa_sfs_entry_policy[] = { + AA_SFS_DIR("versions", aa_sfs_entry_versions), + AA_SFS_FILE_BOOLEAN("set_load", 1), { } }; -static struct aa_fs_entry aa_fs_entry_features[] = { - AA_FS_DIR("policy", aa_fs_entry_policy), - AA_FS_DIR("domain", aa_fs_entry_domain), - AA_FS_DIR("file", aa_fs_entry_file), - AA_FS_FILE_U64("capability", VFS_CAP_FLAGS_MASK), - AA_FS_DIR("rlimit", aa_fs_entry_rlimit), - AA_FS_DIR("caps", aa_fs_entry_caps), +static struct aa_sfs_entry aa_sfs_entry_features[] = { + AA_SFS_DIR("policy", aa_sfs_entry_policy), + AA_SFS_DIR("domain", aa_sfs_entry_domain), + AA_SFS_DIR("file", aa_sfs_entry_file), + AA_SFS_FILE_U64("capability", VFS_CAP_FLAGS_MASK), + AA_SFS_DIR("rlimit", aa_sfs_entry_rlimit), + AA_SFS_DIR("caps", aa_sfs_entry_caps), { } }; -static struct aa_fs_entry aa_fs_entry_apparmor[] = { - AA_FS_FILE_FOPS(".access", 0640, &aa_fs_access), - AA_FS_FILE_FOPS(".ns_level", 0666, &seq_ns_level_fops), - AA_FS_FILE_FOPS(".ns_name", 0640, &seq_ns_name_fops), - AA_FS_FILE_FOPS("profiles", 0440, &aa_fs_profiles_fops), - AA_FS_DIR("features", aa_fs_entry_features), +static struct aa_sfs_entry aa_sfs_entry_apparmor[] = { + AA_SFS_FILE_FOPS(".access", 0640, &aa_sfs_access), + AA_SFS_FILE_FOPS(".ns_level", 0666, &seq_ns_level_fops), + AA_SFS_FILE_FOPS(".ns_name", 0640, &seq_ns_name_fops), + AA_SFS_FILE_FOPS("profiles", 0440, &aa_sfs_profiles_fops), + AA_SFS_DIR("features", aa_sfs_entry_features), { } }; -static struct aa_fs_entry aa_fs_entry = - AA_FS_DIR("apparmor", aa_fs_entry_apparmor); +static struct aa_sfs_entry aa_sfs_entry = + AA_SFS_DIR("apparmor", aa_sfs_entry_apparmor); /** * entry_create_file - create a file entry in the apparmor securityfs - * @fs_file: aa_fs_entry to build an entry for (NOT NULL) + * @fs_file: aa_sfs_entry to build an entry for (NOT NULL) * @parent: the parent dentry in the securityfs * * Use entry_remove_file to remove entries created with this fn. */ -static int __init entry_create_file(struct aa_fs_entry *fs_file, +static int __init entry_create_file(struct aa_sfs_entry *fs_file, struct dentry *parent) { int error = 0; @@ -1651,18 +1696,18 @@ static int __init entry_create_file(struct aa_fs_entry *fs_file, return error; } -static void __init entry_remove_dir(struct aa_fs_entry *fs_dir); +static void __init entry_remove_dir(struct aa_sfs_entry *fs_dir); /** * entry_create_dir - recursively create a directory entry in the securityfs - * @fs_dir: aa_fs_entry (and all child entries) to build (NOT NULL) + * @fs_dir: aa_sfs_entry (and all child entries) to build (NOT NULL) * @parent: the parent dentry in the securityfs * * Use entry_remove_dir to remove entries created with this fn. */ -static int __init entry_create_dir(struct aa_fs_entry *fs_dir, - struct dentry *parent) +static int __init entry_create_dir(struct aa_sfs_entry *fs_dir, + struct dentry *parent) { - struct aa_fs_entry *fs_file; + struct aa_sfs_entry *fs_file; struct dentry *dir; int error; @@ -1672,7 +1717,7 @@ static int __init entry_create_dir(struct aa_fs_entry *fs_dir, fs_dir->dentry = dir; for (fs_file = fs_dir->v.files; fs_file && fs_file->name; ++fs_file) { - if (fs_file->v_type == AA_FS_TYPE_DIR) + if (fs_file->v_type == AA_SFS_TYPE_DIR) error = entry_create_dir(fs_file, fs_dir->dentry); else error = entry_create_file(fs_file, fs_dir->dentry); @@ -1689,10 +1734,10 @@ failed: } /** - * aafs_remove_file - drop a single file entry in the apparmor securityfs - * @fs_file: aa_fs_entry to detach from the securityfs (NOT NULL) + * entry_remove_file - drop a single file entry in the apparmor securityfs + * @fs_file: aa_sfs_entry to detach from the securityfs (NOT NULL) */ -static void __init aafs_remove_file(struct aa_fs_entry *fs_file) +static void __init entry_remove_file(struct aa_sfs_entry *fs_file) { if (!fs_file->dentry) return; @@ -1703,20 +1748,20 @@ static void __init aafs_remove_file(struct aa_fs_entry *fs_file) /** * entry_remove_dir - recursively drop a directory entry from the securityfs - * @fs_dir: aa_fs_entry (and all child entries) to detach (NOT NULL) + * @fs_dir: aa_sfs_entry (and all child entries) to detach (NOT NULL) */ -static void __init entry_remove_dir(struct aa_fs_entry *fs_dir) +static void __init entry_remove_dir(struct aa_sfs_entry *fs_dir) { - struct aa_fs_entry *fs_file; + struct aa_sfs_entry *fs_file; for (fs_file = fs_dir->v.files; fs_file && fs_file->name; ++fs_file) { - if (fs_file->v_type == AA_FS_TYPE_DIR) + if (fs_file->v_type == AA_SFS_TYPE_DIR) entry_remove_dir(fs_file); else - aafs_remove_file(fs_file); + entry_remove_file(fs_file); } - aafs_remove_file(fs_dir); + entry_remove_file(fs_dir); } /** @@ -1726,7 +1771,7 @@ static void __init entry_remove_dir(struct aa_fs_entry *fs_dir) */ void __init aa_destroy_aafs(void) { - entry_remove_dir(&aa_fs_entry); + entry_remove_dir(&aa_sfs_entry); } @@ -1843,7 +1888,7 @@ static int __init aa_create_aafs(void) if (!apparmor_initialized) return 0; - if (aa_fs_entry.dentry) { + if (aa_sfs_entry.dentry) { AA_ERROR("%s: AppArmor securityfs already exists\n", __func__); return -EEXIST; } @@ -1855,11 +1900,11 @@ static int __init aa_create_aafs(void) aafs_mnt->mnt_sb->s_flags &= ~MS_NOUSER; /* Populate fs tree. */ - error = entry_create_dir(&aa_fs_entry, NULL); + error = entry_create_dir(&aa_sfs_entry, NULL); if (error) goto error; - dent = securityfs_create_file(".load", 0666, aa_fs_entry.dentry, + dent = securityfs_create_file(".load", 0666, aa_sfs_entry.dentry, NULL, &aa_fs_profile_load); if (IS_ERR(dent)) { error = PTR_ERR(dent); @@ -1867,7 +1912,7 @@ static int __init aa_create_aafs(void) } ns_subload(root_ns) = dent; - dent = securityfs_create_file(".replace", 0666, aa_fs_entry.dentry, + dent = securityfs_create_file(".replace", 0666, aa_sfs_entry.dentry, NULL, &aa_fs_profile_replace); if (IS_ERR(dent)) { error = PTR_ERR(dent); @@ -1875,7 +1920,7 @@ static int __init aa_create_aafs(void) } ns_subreplace(root_ns) = dent; - dent = securityfs_create_file(".remove", 0666, aa_fs_entry.dentry, + dent = securityfs_create_file(".remove", 0666, aa_sfs_entry.dentry, NULL, &aa_fs_profile_remove); if (IS_ERR(dent)) { error = PTR_ERR(dent); @@ -1884,13 +1929,13 @@ static int __init aa_create_aafs(void) ns_subremove(root_ns) = dent; mutex_lock(&root_ns->lock); - error = __aa_fs_ns_mkdir(root_ns, aa_fs_entry.dentry, "policy"); + error = __aafs_ns_mkdir(root_ns, aa_sfs_entry.dentry, "policy"); mutex_unlock(&root_ns->lock); if (error) goto error; - error = aa_mk_null_file(aa_fs_entry.dentry); + error = aa_mk_null_file(aa_sfs_entry.dentry); if (error) goto error; diff --git a/security/apparmor/capability.c b/security/apparmor/capability.c index ed0a3e6b8022..3bc19843d8df 100644 --- a/security/apparmor/capability.c +++ b/security/apparmor/capability.c @@ -28,8 +28,8 @@ */ #include "capability_names.h" -struct aa_fs_entry aa_fs_entry_caps[] = { - AA_FS_FILE_STRING("mask", AA_FS_CAPS_MASK), +struct aa_sfs_entry aa_sfs_entry_caps[] = { + AA_SFS_FILE_STRING("mask", AA_SFS_CAPS_MASK), { } }; diff --git a/security/apparmor/include/apparmorfs.h b/security/apparmor/include/apparmorfs.h index 0b6d32b3f05e..bcad87740cb6 100644 --- a/security/apparmor/include/apparmorfs.h +++ b/security/apparmor/include/apparmorfs.h @@ -17,49 +17,49 @@ extern struct path aa_null; -enum aa_fs_type { - AA_FS_TYPE_BOOLEAN, - AA_FS_TYPE_STRING, - AA_FS_TYPE_U64, - AA_FS_TYPE_FOPS, - AA_FS_TYPE_DIR, +enum aa_sfs_type { + AA_SFS_TYPE_BOOLEAN, + AA_SFS_TYPE_STRING, + AA_SFS_TYPE_U64, + AA_SFS_TYPE_FOPS, + AA_SFS_TYPE_DIR, }; -struct aa_fs_entry; +struct aa_sfs_entry; -struct aa_fs_entry { +struct aa_sfs_entry { const char *name; struct dentry *dentry; umode_t mode; - enum aa_fs_type v_type; + enum aa_sfs_type v_type; union { bool boolean; char *string; unsigned long u64; - struct aa_fs_entry *files; + struct aa_sfs_entry *files; } v; const struct file_operations *file_ops; }; -extern const struct file_operations aa_fs_seq_file_ops; +extern const struct file_operations aa_sfs_seq_file_ops; -#define AA_FS_FILE_BOOLEAN(_name, _value) \ +#define AA_SFS_FILE_BOOLEAN(_name, _value) \ { .name = (_name), .mode = 0444, \ - .v_type = AA_FS_TYPE_BOOLEAN, .v.boolean = (_value), \ - .file_ops = &aa_fs_seq_file_ops } -#define AA_FS_FILE_STRING(_name, _value) \ + .v_type = AA_SFS_TYPE_BOOLEAN, .v.boolean = (_value), \ + .file_ops = &aa_sfs_seq_file_ops } +#define AA_SFS_FILE_STRING(_name, _value) \ { .name = (_name), .mode = 0444, \ - .v_type = AA_FS_TYPE_STRING, .v.string = (_value), \ - .file_ops = &aa_fs_seq_file_ops } -#define AA_FS_FILE_U64(_name, _value) \ + .v_type = AA_SFS_TYPE_STRING, .v.string = (_value), \ + .file_ops = &aa_sfs_seq_file_ops } +#define AA_SFS_FILE_U64(_name, _value) \ { .name = (_name), .mode = 0444, \ - .v_type = AA_FS_TYPE_U64, .v.u64 = (_value), \ - .file_ops = &aa_fs_seq_file_ops } -#define AA_FS_FILE_FOPS(_name, _mode, _fops) \ - { .name = (_name), .v_type = AA_FS_TYPE_FOPS, \ + .v_type = AA_SFS_TYPE_U64, .v.u64 = (_value), \ + .file_ops = &aa_sfs_seq_file_ops } +#define AA_SFS_FILE_FOPS(_name, _mode, _fops) \ + { .name = (_name), .v_type = AA_SFS_TYPE_FOPS, \ .mode = (_mode), .file_ops = (_fops) } -#define AA_FS_DIR(_name, _value) \ - { .name = (_name), .v_type = AA_FS_TYPE_DIR, .v.files = (_value) } +#define AA_SFS_DIR(_name, _value) \ + { .name = (_name), .v_type = AA_SFS_TYPE_DIR, .v.files = (_value) } extern void __init aa_destroy_aafs(void); @@ -107,12 +107,12 @@ enum aafs_prof_type { #define prof_child_dir(X) ((X)->dents[AAFS_PROF_PROFS]) void __aa_bump_ns_revision(struct aa_ns *ns); -void __aa_fs_profile_rmdir(struct aa_profile *profile); -void __aa_fs_profile_migrate_dents(struct aa_profile *old, +void __aafs_profile_rmdir(struct aa_profile *profile); +void __aafs_profile_migrate_dents(struct aa_profile *old, struct aa_profile *new); -int __aa_fs_profile_mkdir(struct aa_profile *profile, struct dentry *parent); -void __aa_fs_ns_rmdir(struct aa_ns *ns); -int __aa_fs_ns_mkdir(struct aa_ns *ns, struct dentry *parent, +int __aafs_profile_mkdir(struct aa_profile *profile, struct dentry *parent); +void __aafs_ns_rmdir(struct aa_ns *ns); +int __aafs_ns_mkdir(struct aa_ns *ns, struct dentry *parent, const char *name); struct aa_loaddata; diff --git a/security/apparmor/include/capability.h b/security/apparmor/include/capability.h index fc3fa381d850..1218e95ebe49 100644 --- a/security/apparmor/include/capability.h +++ b/security/apparmor/include/capability.h @@ -36,7 +36,7 @@ struct aa_caps { kernel_cap_t extended; }; -extern struct aa_fs_entry aa_fs_entry_caps[]; +extern struct aa_sfs_entry aa_sfs_entry_caps[]; int aa_capable(struct aa_profile *profile, int cap, int audit); diff --git a/security/apparmor/include/resource.h b/security/apparmor/include/resource.h index d3f4cf027957..f6289f335c4d 100644 --- a/security/apparmor/include/resource.h +++ b/security/apparmor/include/resource.h @@ -34,7 +34,7 @@ struct aa_rlimit { struct rlimit limits[RLIM_NLIMITS]; }; -extern struct aa_fs_entry aa_fs_entry_rlimit[]; +extern struct aa_sfs_entry aa_sfs_entry_rlimit[]; int aa_map_resource(int resource); int aa_task_setrlimit(struct aa_profile *profile, struct task_struct *, diff --git a/security/apparmor/policy.c b/security/apparmor/policy.c index 5968914247a4..a5e8559e4dec 100644 --- a/security/apparmor/policy.c +++ b/security/apparmor/policy.c @@ -160,7 +160,7 @@ static void __remove_profile(struct aa_profile *profile) __aa_profile_list_release(&profile->base.profiles); /* released by free_profile */ __aa_update_proxy(profile, profile->ns->unconfined); - __aa_fs_profile_rmdir(profile); + __aafs_profile_rmdir(profile); __list_remove_profile(profile); } @@ -784,7 +784,7 @@ static void __replace_profile(struct aa_profile *old, struct aa_profile *new, /* aafs interface uses proxy */ rcu_assign_pointer(new->proxy->profile, aa_get_profile(new)); - __aa_fs_profile_migrate_dents(old, new); + __aafs_profile_migrate_dents(old, new); if (list_empty(&new->base.list)) { /* new is not on a list already */ @@ -971,7 +971,7 @@ ssize_t aa_replace_profiles(struct aa_ns *view, struct aa_profile *profile, parent = prof_child_dir(p); } else parent = ns_subprofs_dir(ent->new->ns); - error = __aa_fs_profile_mkdir(ent->new, parent); + error = __aafs_profile_mkdir(ent->new, parent); } if (error) { diff --git a/security/apparmor/policy_ns.c b/security/apparmor/policy_ns.c index c94ec6ef9e35..0a8bc4e887ef 100644 --- a/security/apparmor/policy_ns.c +++ b/security/apparmor/policy_ns.c @@ -196,7 +196,7 @@ static struct aa_ns *__aa_create_ns(struct aa_ns *parent, const char *name, if (!ns) return NULL; mutex_lock(&ns->lock); - error = __aa_fs_ns_mkdir(ns, ns_subns_dir(parent), name); + error = __aafs_ns_mkdir(ns, ns_subns_dir(parent), name); if (error) { AA_ERROR("Failed to create interface for ns %s\n", ns->base.name); @@ -284,7 +284,7 @@ static void destroy_ns(struct aa_ns *ns) if (ns->parent) __aa_update_proxy(ns->unconfined, ns->parent->unconfined); - __aa_fs_ns_rmdir(ns); + __aafs_ns_rmdir(ns); mutex_unlock(&ns->lock); } diff --git a/security/apparmor/resource.c b/security/apparmor/resource.c index 86a941afd956..ae66151fdc38 100644 --- a/security/apparmor/resource.c +++ b/security/apparmor/resource.c @@ -24,8 +24,8 @@ */ #include "rlim_names.h" -struct aa_fs_entry aa_fs_entry_rlimit[] = { - AA_FS_FILE_STRING("mask", AA_FS_RLIMIT_MASK), +struct aa_sfs_entry aa_sfs_entry_rlimit[] = { + AA_SFS_FILE_STRING("mask", AA_SFS_RLIMIT_MASK), { } }; -- cgit v1.2.3 From 98407f0a0d378df27bfea79301a3aba42d7cea1c Mon Sep 17 00:00:00 2001 From: John Johansen Date: Thu, 25 May 2017 06:31:46 -0700 Subject: apparmor: allow specifying an already created dir to create ns entries in Signed-off-by: John Johansen Reviewed-by: Seth Arnold Reviewed-by: Kees Cook --- security/apparmor/apparmorfs.c | 9 +++++---- security/apparmor/include/apparmorfs.h | 4 ++-- security/apparmor/policy_ns.c | 2 +- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c index 35b822c4a079..a18f14ab7a8b 100644 --- a/security/apparmor/apparmorfs.c +++ b/security/apparmor/apparmorfs.c @@ -1339,11 +1339,12 @@ static int __aafs_ns_mkdir_entries(struct aa_ns *ns, struct dentry *dir) /* * Requires: @ns->lock held */ -int __aafs_ns_mkdir(struct aa_ns *ns, struct dentry *parent, const char *name) +int __aafs_ns_mkdir(struct aa_ns *ns, struct dentry *parent, const char *name, + struct dentry *dent) { struct aa_ns *sub; struct aa_profile *child; - struct dentry *dent, *dir; + struct dentry *dir; int error; AA_BUG(!ns); @@ -1373,7 +1374,7 @@ int __aafs_ns_mkdir(struct aa_ns *ns, struct dentry *parent, const char *name) /* subnamespaces */ list_for_each_entry(sub, &ns->sub_ns, base.list) { mutex_lock(&sub->lock); - error = __aafs_ns_mkdir(sub, ns_subns_dir(ns), NULL); + error = __aafs_ns_mkdir(sub, ns_subns_dir(ns), NULL, NULL); mutex_unlock(&sub->lock); if (error) goto fail2; @@ -1929,7 +1930,7 @@ static int __init aa_create_aafs(void) ns_subremove(root_ns) = dent; mutex_lock(&root_ns->lock); - error = __aafs_ns_mkdir(root_ns, aa_sfs_entry.dentry, "policy"); + error = __aafs_ns_mkdir(root_ns, aa_sfs_entry.dentry, "policy", NULL); mutex_unlock(&root_ns->lock); if (error) diff --git a/security/apparmor/include/apparmorfs.h b/security/apparmor/include/apparmorfs.h index bcad87740cb6..071a59a1f056 100644 --- a/security/apparmor/include/apparmorfs.h +++ b/security/apparmor/include/apparmorfs.h @@ -112,8 +112,8 @@ void __aafs_profile_migrate_dents(struct aa_profile *old, struct aa_profile *new); int __aafs_profile_mkdir(struct aa_profile *profile, struct dentry *parent); void __aafs_ns_rmdir(struct aa_ns *ns); -int __aafs_ns_mkdir(struct aa_ns *ns, struct dentry *parent, - const char *name); +int __aafs_ns_mkdir(struct aa_ns *ns, struct dentry *parent, const char *name, + struct dentry *dent); struct aa_loaddata; void __aa_fs_remove_rawdata(struct aa_loaddata *rawdata); diff --git a/security/apparmor/policy_ns.c b/security/apparmor/policy_ns.c index 0a8bc4e887ef..7d7c23705be2 100644 --- a/security/apparmor/policy_ns.c +++ b/security/apparmor/policy_ns.c @@ -196,7 +196,7 @@ static struct aa_ns *__aa_create_ns(struct aa_ns *parent, const char *name, if (!ns) return NULL; mutex_lock(&ns->lock); - error = __aafs_ns_mkdir(ns, ns_subns_dir(parent), name); + error = __aafs_ns_mkdir(ns, ns_subns_dir(parent), name, dir); if (error) { AA_ERROR("Failed to create interface for ns %s\n", ns->base.name); -- cgit v1.2.3 From c961ee5f21b202dea60b63eeef945730d92e46a6 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Thu, 25 May 2017 06:35:38 -0700 Subject: apparmor: convert from securityfs to apparmorfs for policy ns files Virtualize the apparmor policy/ directory so that the current namespace affects what part of policy is seen. To do this convert to using apparmorfs for policy namespace files and setup a magic symlink in the securityfs apparmor dir to access those files. Signed-off-by: John Johansen Reviewed-by: Seth Arnold Reviewed-by: Kees Cook --- security/apparmor/apparmorfs.c | 63 +++++++++++++++++++++++++----------------- 1 file changed, 37 insertions(+), 26 deletions(-) diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c index a18f14ab7a8b..c847f601371d 100644 --- a/security/apparmor/apparmorfs.c +++ b/security/apparmor/apparmorfs.c @@ -943,7 +943,7 @@ static void remove_rawdata_dents(struct aa_loaddata *rawdata) for (i = 0; i < AAFS_LOADDATA_NDENTS; i++) { if (!IS_ERR_OR_NULL(rawdata->dents[i])) { /* no refcounts on i_private */ - securityfs_remove(rawdata->dents[i]); + aafs_remove(rawdata->dents[i]); rawdata->dents[i] = NULL; } } @@ -979,33 +979,33 @@ int __aa_fs_create_rawdata(struct aa_ns *ns, struct aa_loaddata *rawdata) if (!rawdata->name) return -ENOMEM; - dir = securityfs_create_dir(rawdata->name, ns_subdata_dir(ns)); + dir = aafs_create_dir(rawdata->name, ns_subdata_dir(ns)); if (IS_ERR(dir)) /* ->name freed when rawdata freed */ return PTR_ERR(dir); rawdata->dents[AAFS_LOADDATA_DIR] = dir; - dent = securityfs_create_file("abi", S_IFREG | 0444, dir, rawdata, + dent = aafs_create_file("abi", S_IFREG | 0444, dir, rawdata, &seq_rawdata_abi_fops); if (IS_ERR(dent)) goto fail; rawdata->dents[AAFS_LOADDATA_ABI] = dent; - dent = securityfs_create_file("revision", S_IFREG | 0444, dir, rawdata, + dent = aafs_create_file("revision", S_IFREG | 0444, dir, rawdata, &seq_rawdata_revision_fops); if (IS_ERR(dent)) goto fail; rawdata->dents[AAFS_LOADDATA_REVISION] = dent; if (aa_g_hash_policy) { - dent = securityfs_create_file("sha1", S_IFREG | 0444, dir, + dent = aafs_create_file("sha1", S_IFREG | 0444, dir, rawdata, &seq_rawdata_hash_fops); if (IS_ERR(dent)) goto fail; rawdata->dents[AAFS_LOADDATA_HASH] = dent; } - dent = securityfs_create_file("raw_data", S_IFREG | 0444, + dent = aafs_create_file("raw_data", S_IFREG | 0444, dir, rawdata, &rawdata_fops); if (IS_ERR(dent)) goto fail; @@ -1047,7 +1047,7 @@ void __aafs_profile_rmdir(struct aa_profile *profile) continue; proxy = d_inode(profile->dents[i])->i_private; - securityfs_remove(profile->dents[i]); + aafs_remove(profile->dents[i]); aa_put_proxy(proxy); profile->dents[i] = NULL; } @@ -1077,7 +1077,7 @@ static struct dentry *create_profile_file(struct dentry *dir, const char *name, struct aa_proxy *proxy = aa_get_proxy(profile->proxy); struct dentry *dent; - dent = securityfs_create_file(name, S_IFREG | 0444, dir, proxy, fops); + dent = aafs_create_file(name, S_IFREG | 0444, dir, proxy, fops); if (IS_ERR(dent)) aa_put_proxy(proxy); @@ -1130,7 +1130,7 @@ int __aafs_profile_mkdir(struct aa_profile *profile, struct dentry *parent) p = aa_deref_parent(profile); dent = prof_dir(p); /* adding to parent that previously didn't have children */ - dent = securityfs_create_dir("profiles", dent); + dent = aafs_create_dir("profiles", dent); if (IS_ERR(dent)) goto fail; prof_child_dir(p) = parent = dent; @@ -1151,7 +1151,7 @@ int __aafs_profile_mkdir(struct aa_profile *profile, struct dentry *parent) sprintf(profile->dirname + len, ".%ld", profile->ns->uniq_id++); } - dent = securityfs_create_dir(profile->dirname, parent); + dent = aafs_create_dir(profile->dirname, parent); if (IS_ERR(dent)) goto fail; prof_dir(profile) = dir = dent; @@ -1190,7 +1190,7 @@ int __aafs_profile_mkdir(struct aa_profile *profile, struct dentry *parent) profile->rawdata->name, "sha1"); if (error < 0) goto fail2; - dent = securityfs_create_symlink("raw_sha1", dir, target, NULL); + dent = aafs_create_symlink("raw_sha1", dir, target, NULL); if (IS_ERR(dent)) goto fail; profile->dents[AAFS_PROF_RAW_HASH] = dent; @@ -1199,7 +1199,7 @@ int __aafs_profile_mkdir(struct aa_profile *profile, struct dentry *parent) profile->rawdata->name, "abi"); if (error < 0) goto fail2; - dent = securityfs_create_symlink("raw_abi", dir, target, NULL); + dent = aafs_create_symlink("raw_abi", dir, target, NULL); if (IS_ERR(dent)) goto fail; profile->dents[AAFS_PROF_RAW_ABI] = dent; @@ -1208,7 +1208,7 @@ int __aafs_profile_mkdir(struct aa_profile *profile, struct dentry *parent) profile->rawdata->name, "raw_data"); if (error < 0) goto fail2; - dent = securityfs_create_symlink("raw_data", dir, target, NULL); + dent = aafs_create_symlink("raw_data", dir, target, NULL); if (IS_ERR(dent)) goto fail; profile->dents[AAFS_PROF_RAW_DATA] = dent; @@ -1283,7 +1283,7 @@ void __aafs_ns_rmdir(struct aa_ns *ns) } for (i = AAFS_NS_SIZEOF - 1; i >= 0; --i) { - securityfs_remove(ns->dents[i]); + aafs_remove(ns->dents[i]); ns->dents[i] = NULL; } } @@ -1296,38 +1296,38 @@ static int __aafs_ns_mkdir_entries(struct aa_ns *ns, struct dentry *dir) AA_BUG(!ns); AA_BUG(!dir); - dent = securityfs_create_dir("profiles", dir); + dent = aafs_create_dir("profiles", dir); if (IS_ERR(dent)) return PTR_ERR(dent); ns_subprofs_dir(ns) = dent; - dent = securityfs_create_dir("raw_data", dir); + dent = aafs_create_dir("raw_data", dir); if (IS_ERR(dent)) return PTR_ERR(dent); ns_subdata_dir(ns) = dent; - dent = securityfs_create_file(".load", 0640, dir, ns, + dent = aafs_create_file(".load", 0640, dir, ns, &aa_fs_profile_load); if (IS_ERR(dent)) return PTR_ERR(dent); aa_get_ns(ns); ns_subload(ns) = dent; - dent = securityfs_create_file(".replace", 0640, dir, ns, + dent = aafs_create_file(".replace", 0640, dir, ns, &aa_fs_profile_replace); if (IS_ERR(dent)) return PTR_ERR(dent); aa_get_ns(ns); ns_subreplace(ns) = dent; - dent = securityfs_create_file(".remove", 0640, dir, ns, + dent = aafs_create_file(".remove", 0640, dir, ns, &aa_fs_profile_remove); if (IS_ERR(dent)) return PTR_ERR(dent); aa_get_ns(ns); ns_subremove(ns) = dent; - dent = securityfs_create_dir("namespaces", dir); + dent = aafs_create_dir("namespaces", dir); if (IS_ERR(dent)) return PTR_ERR(dent); aa_get_ns(ns); @@ -1354,11 +1354,13 @@ int __aafs_ns_mkdir(struct aa_ns *ns, struct dentry *parent, const char *name, if (!name) name = ns->base.name; - /* create ns dir if it doesn't already exist */ - dent = securityfs_create_dir(name, parent); - if (IS_ERR(dent)) - goto fail; - + if (!dent) { + /* create ns dir if it doesn't already exist */ + dent = aafs_create_dir(name, parent); + if (IS_ERR(dent)) + goto fail; + } else + dget(dent); ns_dir(ns) = dir = dent; error = __aafs_ns_mkdir_entries(ns, dir); if (error) @@ -1930,12 +1932,21 @@ static int __init aa_create_aafs(void) ns_subremove(root_ns) = dent; mutex_lock(&root_ns->lock); - error = __aafs_ns_mkdir(root_ns, aa_sfs_entry.dentry, "policy", NULL); + error = __aafs_ns_mkdir(root_ns, aafs_mnt->mnt_root, ".policy", + aafs_mnt->mnt_root); mutex_unlock(&root_ns->lock); if (error) goto error; + /* magic symlink similar to nsfs redirects based on task policy */ + dent = securityfs_create_symlink("policy", aa_sfs_entry.dentry, + NULL, &policy_link_iops); + if (IS_ERR(dent)) { + error = PTR_ERR(dent); + goto error; + } + error = aa_mk_null_file(aa_sfs_entry.dentry); if (error) goto error; -- cgit v1.2.3 From fc7e0b26b8d26e680bb2f252e9521385e0092e4c Mon Sep 17 00:00:00 2001 From: John Johansen Date: Fri, 26 May 2017 01:57:09 -0700 Subject: apparmor: move permissions into their own file to be more easily shared Signed-off-by: John Johansen --- security/apparmor/include/file.h | 20 +------------------ security/apparmor/include/perms.h | 40 ++++++++++++++++++++++++++++++++++++++ security/apparmor/include/policy.h | 1 + security/apparmor/lib.c | 1 + 4 files changed, 43 insertions(+), 19 deletions(-) create mode 100644 security/apparmor/include/perms.h diff --git a/security/apparmor/include/file.h b/security/apparmor/include/file.h index eba39cb25f02..a75e4872053a 100644 --- a/security/apparmor/include/file.h +++ b/security/apparmor/include/file.h @@ -17,29 +17,11 @@ #include "domain.h" #include "match.h" +#include "perms.h" struct aa_profile; struct path; -/* - * We use MAY_EXEC, MAY_WRITE, MAY_READ, MAY_APPEND and the following flags - * for profile permissions - */ -#define AA_MAY_CREATE 0x0010 -#define AA_MAY_DELETE 0x0020 -#define AA_MAY_META_WRITE 0x0040 -#define AA_MAY_META_READ 0x0080 - -#define AA_MAY_CHMOD 0x0100 -#define AA_MAY_CHOWN 0x0200 -#define AA_MAY_LOCK 0x0400 -#define AA_EXEC_MMAP 0x0800 - -#define AA_MAY_LINK 0x1000 -#define AA_LINK_SUBSET AA_MAY_LOCK /* overlaid */ -#define AA_MAY_ONEXEC 0x40000000 /* exec allows onexec */ -#define AA_MAY_CHANGE_PROFILE 0x80000000 -#define AA_MAY_CHANGEHAT 0x80000000 /* ctrl auditing only */ #define AA_AUDIT_FILE_MASK (MAY_READ | MAY_WRITE | MAY_EXEC | MAY_APPEND |\ AA_MAY_CREATE | AA_MAY_DELETE | \ diff --git a/security/apparmor/include/perms.h b/security/apparmor/include/perms.h new file mode 100644 index 000000000000..4a65755a2dc0 --- /dev/null +++ b/security/apparmor/include/perms.h @@ -0,0 +1,40 @@ +/* + * AppArmor security module + * + * This file contains AppArmor basic permission sets definitions. + * + * Copyright 2017 Canonical Ltd. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2 of the + * License. + */ + +#ifndef __AA_PERM_H +#define __AA_PERM_H + +#include + +/* + * We use MAY_EXEC, MAY_WRITE, MAY_READ, MAY_APPEND and the following flags + * for profile permissions + */ +#define AA_MAY_CREATE 0x0010 +#define AA_MAY_DELETE 0x0020 +#define AA_MAY_META_WRITE 0x0040 +#define AA_MAY_META_READ 0x0080 + +#define AA_MAY_CHMOD 0x0100 +#define AA_MAY_CHOWN 0x0200 +#define AA_MAY_LOCK 0x0400 +#define AA_EXEC_MMAP 0x0800 + +#define AA_MAY_LINK 0x1000 +#define AA_LINK_SUBSET AA_MAY_LOCK /* overlaid */ +#define AA_MAY_ONEXEC 0x40000000 /* exec allows onexec */ +#define AA_MAY_CHANGE_PROFILE 0x80000000 +#define AA_MAY_CHANGEHAT 0x80000000 /* ctrl auditing only */ + + +#endif /* __AA_PERM_H */ diff --git a/security/apparmor/include/policy.h b/security/apparmor/include/policy.h index dffa01c018c8..0f87f70287ad 100644 --- a/security/apparmor/include/policy.h +++ b/security/apparmor/include/policy.h @@ -29,6 +29,7 @@ #include "domain.h" #include "file.h" #include "lib.h" +#include "perms.h" #include "resource.h" diff --git a/security/apparmor/lib.c b/security/apparmor/lib.c index 864b2fa45852..90eb14c9e0cf 100644 --- a/security/apparmor/lib.c +++ b/security/apparmor/lib.c @@ -21,6 +21,7 @@ #include "include/audit.h" #include "include/apparmor.h" #include "include/lib.h" +#include "include/perms.h" #include "include/policy.h" /** -- cgit v1.2.3 From e53cfe6c7caa79ccdccce53e600dae522acb1c84 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Fri, 26 May 2017 15:07:22 -0700 Subject: apparmor: rework perm mapping to a slightly broader set Signed-off-by: John Johansen --- security/apparmor/file.c | 43 +++++++++--------------- security/apparmor/include/file.h | 5 +-- security/apparmor/include/perms.h | 69 ++++++++++++++++++++++++++++----------- security/apparmor/lib.c | 59 +++++++++++++++++++++++++++++++++ security/apparmor/lsm.c | 10 +++--- 5 files changed, 133 insertions(+), 53 deletions(-) diff --git a/security/apparmor/file.c b/security/apparmor/file.c index 22be62f0fc73..44549db904b3 100644 --- a/security/apparmor/file.c +++ b/security/apparmor/file.c @@ -21,6 +21,17 @@ struct file_perms nullperms; +static u32 map_mask_to_chr_mask(u32 mask) +{ + u32 m = mask & PERMS_CHRS_MASK; + + if (mask & AA_MAY_GETATTR) + m |= MAY_READ; + if (mask & (AA_MAY_SETATTR | AA_MAY_CHMOD | AA_MAY_CHOWN)) + m |= MAY_WRITE; + + return m; +} /** * audit_file_mask - convert mask to permission string @@ -31,29 +42,7 @@ static void audit_file_mask(struct audit_buffer *ab, u32 mask) { char str[10]; - char *m = str; - - if (mask & AA_EXEC_MMAP) - *m++ = 'm'; - if (mask & (MAY_READ | AA_MAY_META_READ)) - *m++ = 'r'; - if (mask & (MAY_WRITE | AA_MAY_META_WRITE | AA_MAY_CHMOD | - AA_MAY_CHOWN)) - *m++ = 'w'; - else if (mask & MAY_APPEND) - *m++ = 'a'; - if (mask & AA_MAY_CREATE) - *m++ = 'c'; - if (mask & AA_MAY_DELETE) - *m++ = 'd'; - if (mask & AA_MAY_LINK) - *m++ = 'l'; - if (mask & AA_MAY_LOCK) - *m++ = 'k'; - if (mask & MAY_EXEC) - *m++ = 'x'; - *m = '\0'; - + aa_perm_mask_to_str(str, aa_file_perm_chrs, map_mask_to_chr_mask(mask)); audit_log_string(ab, str); } @@ -163,10 +152,10 @@ static u32 map_old_perms(u32 old) { u32 new = old & 0xf; if (old & MAY_READ) - new |= AA_MAY_META_READ; + new |= AA_MAY_GETATTR | AA_MAY_OPEN; if (old & MAY_WRITE) - new |= AA_MAY_META_WRITE | AA_MAY_CREATE | AA_MAY_DELETE | - AA_MAY_CHMOD | AA_MAY_CHOWN; + new |= AA_MAY_SETATTR | AA_MAY_CREATE | AA_MAY_DELETE | + AA_MAY_CHMOD | AA_MAY_CHOWN | AA_MAY_OPEN; if (old & 0x10) new |= AA_MAY_LINK; /* the old mapping lock and link_subset flags where overlaid @@ -214,7 +203,7 @@ static struct file_perms compute_perms(struct aa_dfa *dfa, unsigned int state, perms.quiet = map_old_perms(dfa_other_quiet(dfa, state)); perms.xindex = dfa_other_xindex(dfa, state); } - perms.allow |= AA_MAY_META_READ; + perms.allow |= AA_MAY_GETATTR; /* change_profile wasn't determined by ownership in old mapping */ if (ACCEPT_TABLE(dfa)[state] & 0x80000000) diff --git a/security/apparmor/include/file.h b/security/apparmor/include/file.h index a75e4872053a..fb3642a94e3d 100644 --- a/security/apparmor/include/file.h +++ b/security/apparmor/include/file.h @@ -22,10 +22,11 @@ struct aa_profile; struct path; +#define mask_mode_t(X) (X & (MAY_EXEC | MAY_WRITE | MAY_READ | MAY_APPEND)) #define AA_AUDIT_FILE_MASK (MAY_READ | MAY_WRITE | MAY_EXEC | MAY_APPEND |\ AA_MAY_CREATE | AA_MAY_DELETE | \ - AA_MAY_META_READ | AA_MAY_META_WRITE | \ + AA_MAY_GETATTR | AA_MAY_SETATTR | \ AA_MAY_CHMOD | AA_MAY_CHOWN | AA_MAY_LOCK | \ AA_EXEC_MMAP | AA_MAY_LINK) @@ -37,7 +38,7 @@ struct path; * ctx struct will expand in the future so we keep the struct. */ struct aa_file_ctx { - u16 allow; + u32 allow; }; /** diff --git a/security/apparmor/include/perms.h b/security/apparmor/include/perms.h index 4a65755a2dc0..35e365e7aa75 100644 --- a/security/apparmor/include/perms.h +++ b/security/apparmor/include/perms.h @@ -16,25 +16,56 @@ #include -/* - * We use MAY_EXEC, MAY_WRITE, MAY_READ, MAY_APPEND and the following flags - * for profile permissions - */ -#define AA_MAY_CREATE 0x0010 -#define AA_MAY_DELETE 0x0020 -#define AA_MAY_META_WRITE 0x0040 -#define AA_MAY_META_READ 0x0080 - -#define AA_MAY_CHMOD 0x0100 -#define AA_MAY_CHOWN 0x0200 -#define AA_MAY_LOCK 0x0400 -#define AA_EXEC_MMAP 0x0800 - -#define AA_MAY_LINK 0x1000 -#define AA_LINK_SUBSET AA_MAY_LOCK /* overlaid */ -#define AA_MAY_ONEXEC 0x40000000 /* exec allows onexec */ -#define AA_MAY_CHANGE_PROFILE 0x80000000 -#define AA_MAY_CHANGEHAT 0x80000000 /* ctrl auditing only */ +#define AA_MAY_EXEC MAY_EXEC +#define AA_MAY_WRITE MAY_WRITE +#define AA_MAY_READ MAY_READ +#define AA_MAY_APPEND MAY_APPEND + +#define AA_MAY_CREATE 0x0010 +#define AA_MAY_DELETE 0x0020 +#define AA_MAY_OPEN 0x0040 +#define AA_MAY_RENAME 0x0080 /* pair */ + +#define AA_MAY_SETATTR 0x0100 /* meta write */ +#define AA_MAY_GETATTR 0x0200 /* meta read */ +#define AA_MAY_SETCRED 0x0400 /* security cred/attr */ +#define AA_MAY_GETCRED 0x0800 + +#define AA_MAY_CHMOD 0x1000 /* pair */ +#define AA_MAY_CHOWN 0x2000 /* pair */ +#define AA_MAY_CHGRP 0x4000 /* pair */ +#define AA_MAY_LOCK 0x8000 /* LINK_SUBSET overlaid */ + +#define AA_EXEC_MMAP 0x00010000 +#define AA_MAY_MPROT 0x00020000 /* extend conditions */ +#define AA_MAY_LINK 0x00040000 /* pair */ +#define AA_MAY_SNAPSHOT 0x00080000 /* pair */ + +#define AA_MAY_DELEGATE +#define AA_CONT_MATCH 0x08000000 + +#define AA_MAY_STACK 0x10000000 +#define AA_MAY_ONEXEC 0x20000000 /* either stack or change_profile */ +#define AA_MAY_CHANGE_PROFILE 0x40000000 +#define AA_MAY_CHANGEHAT 0x80000000 + +#define AA_LINK_SUBSET AA_MAY_LOCK /* overlaid */ + + +#define PERMS_CHRS_MASK (MAY_READ | MAY_WRITE | AA_MAY_CREATE | \ + AA_MAY_DELETE | AA_MAY_LINK | AA_MAY_LOCK | \ + AA_MAY_EXEC | AA_EXEC_MMAP | AA_MAY_APPEND) + +#define PERMS_NAMES_MASK (PERMS_CHRS_MASK | AA_MAY_OPEN | AA_MAY_RENAME | \ + AA_MAY_SETATTR | AA_MAY_GETATTR | AA_MAY_SETCRED | \ + AA_MAY_GETCRED | AA_MAY_CHMOD | AA_MAY_CHOWN | \ + AA_MAY_CHGRP | AA_MAY_MPROT | AA_MAY_SNAPSHOT | \ + AA_MAY_STACK | AA_MAY_ONEXEC | \ + AA_MAY_CHANGE_PROFILE | AA_MAY_CHANGEHAT) + +extern const char aa_file_perm_chrs[]; +extern const char *aa_file_perm_names[]; +void aa_perm_mask_to_str(char *str, const char *chrs, u32 mask); #endif /* __AA_PERM_H */ diff --git a/security/apparmor/lib.c b/security/apparmor/lib.c index 90eb14c9e0cf..90d4631ddafe 100644 --- a/security/apparmor/lib.c +++ b/security/apparmor/lib.c @@ -129,6 +129,65 @@ void aa_info_message(const char *str) printk(KERN_INFO "AppArmor: %s\n", str); } +const char aa_file_perm_chrs[] = "xwracd km l "; +const char *aa_file_perm_names[] = { + "exec", + "write", + "read", + "append", + + "create", + "delete", + "open", + "rename", + + "setattr", + "getattr", + "setcred", + "getcred", + + "chmod", + "chown", + "chgrp", + "lock", + + "mmap", + "mprot", + "link", + "snapshot", + + "unknown", + "unknown", + "unknown", + "unknown", + + "unknown", + "unknown", + "unknown", + "unknown", + + "stack", + "change_onexec", + "change_profile", + "change_hat", +}; + +/** + * aa_perm_mask_to_str - convert a perm mask to its short string + * @str: character buffer to store string in (at least 10 characters) + * @mask: permission mask to convert + */ +void aa_perm_mask_to_str(char *str, const char *chrs, u32 mask) +{ + unsigned int i, perm = 1; + + for (i = 0; i < 32; perm <<= 1, i++) { + if (mask & perm) + *str++ = chrs[i]; + } + *str = '\0'; +} + /** * aa_policy_init - initialize a policy structure * @policy: policy to initialize (NOT NULL) diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c index 8f3c0f7aca5a..a128f1772135 100644 --- a/security/apparmor/lsm.c +++ b/security/apparmor/lsm.c @@ -278,7 +278,7 @@ static int apparmor_path_mknod(const struct path *dir, struct dentry *dentry, static int apparmor_path_truncate(const struct path *path) { - return common_perm_cond(OP_TRUNC, path, MAY_WRITE | AA_MAY_META_WRITE); + return common_perm_cond(OP_TRUNC, path, MAY_WRITE | AA_MAY_SETATTR); } static int apparmor_path_symlink(const struct path *dir, struct dentry *dentry, @@ -323,12 +323,12 @@ static int apparmor_path_rename(const struct path *old_dir, struct dentry *old_d }; error = aa_path_perm(OP_RENAME_SRC, profile, &old_path, 0, - MAY_READ | AA_MAY_META_READ | MAY_WRITE | - AA_MAY_META_WRITE | AA_MAY_DELETE, + MAY_READ | AA_MAY_GETATTR | MAY_WRITE | + AA_MAY_SETATTR | AA_MAY_DELETE, &cond); if (!error) error = aa_path_perm(OP_RENAME_DEST, profile, &new_path, - 0, MAY_WRITE | AA_MAY_META_WRITE | + 0, MAY_WRITE | AA_MAY_SETATTR | AA_MAY_CREATE, &cond); } @@ -347,7 +347,7 @@ static int apparmor_path_chown(const struct path *path, kuid_t uid, kgid_t gid) static int apparmor_inode_getattr(const struct path *path) { - return common_perm_cond(OP_GETATTR, path, AA_MAY_META_READ); + return common_perm_cond(OP_GETATTR, path, AA_MAY_GETATTR); } static int apparmor_file_open(struct file *file, const struct cred *cred) -- cgit v1.2.3 From b4958c892e02241b9bd121f3397b76225ff6f4a3 Mon Sep 17 00:00:00 2001 From: Junil Lee Date: Thu, 8 Jun 2017 13:18:09 +0900 Subject: selinux: use kmem_cache for ebitmap The allocated size for each ebitmap_node is 192byte by kzalloc(). Then, ebitmap_node size is fixed, so it's possible to use only 144byte for each object by kmem_cache_zalloc(). It can reduce some dynamic allocation size. Signed-off-by: Junil Lee Signed-off-by: Paul Moore --- security/selinux/ss/ebitmap.c | 26 ++++++++++++++++++++------ security/selinux/ss/ebitmap.h | 3 +++ security/selinux/ss/services.c | 4 ++++ 3 files changed, 27 insertions(+), 6 deletions(-) diff --git a/security/selinux/ss/ebitmap.c b/security/selinux/ss/ebitmap.c index 9db4709a6877..ad38299164c3 100644 --- a/security/selinux/ss/ebitmap.c +++ b/security/selinux/ss/ebitmap.c @@ -24,6 +24,8 @@ #define BITS_PER_U64 (sizeof(u64) * 8) +static struct kmem_cache *ebitmap_node_cachep; + int ebitmap_cmp(struct ebitmap *e1, struct ebitmap *e2) { struct ebitmap_node *n1, *n2; @@ -54,7 +56,7 @@ int ebitmap_cpy(struct ebitmap *dst, struct ebitmap *src) n = src->node; prev = NULL; while (n) { - new = kzalloc(sizeof(*new), GFP_ATOMIC); + new = kmem_cache_zalloc(ebitmap_node_cachep, GFP_ATOMIC); if (!new) { ebitmap_destroy(dst); return -ENOMEM; @@ -162,7 +164,7 @@ int ebitmap_netlbl_import(struct ebitmap *ebmap, if (e_iter == NULL || offset >= e_iter->startbit + EBITMAP_SIZE) { e_prev = e_iter; - e_iter = kzalloc(sizeof(*e_iter), GFP_ATOMIC); + e_iter = kmem_cache_zalloc(ebitmap_node_cachep, GFP_ATOMIC); if (e_iter == NULL) goto netlbl_import_failure; e_iter->startbit = offset - (offset % EBITMAP_SIZE); @@ -288,7 +290,7 @@ int ebitmap_set_bit(struct ebitmap *e, unsigned long bit, int value) prev->next = n->next; else e->node = n->next; - kfree(n); + kmem_cache_free(ebitmap_node_cachep, n); } return 0; } @@ -299,7 +301,7 @@ int ebitmap_set_bit(struct ebitmap *e, unsigned long bit, int value) if (!value) return 0; - new = kzalloc(sizeof(*new), GFP_ATOMIC); + new = kmem_cache_zalloc(ebitmap_node_cachep, GFP_ATOMIC); if (!new) return -ENOMEM; @@ -332,7 +334,7 @@ void ebitmap_destroy(struct ebitmap *e) while (n) { temp = n; n = n->next; - kfree(temp); + kmem_cache_free(ebitmap_node_cachep, temp); } e->highbit = 0; @@ -400,7 +402,7 @@ int ebitmap_read(struct ebitmap *e, void *fp) if (!n || startbit >= n->startbit + EBITMAP_SIZE) { struct ebitmap_node *tmp; - tmp = kzalloc(sizeof(*tmp), GFP_KERNEL); + tmp = kmem_cache_zalloc(ebitmap_node_cachep, GFP_KERNEL); if (!tmp) { printk(KERN_ERR "SELinux: ebitmap: out of memory\n"); @@ -519,3 +521,15 @@ int ebitmap_write(struct ebitmap *e, void *fp) } return 0; } + +void ebitmap_cache_init(void) +{ + ebitmap_node_cachep = kmem_cache_create("ebitmap_node", + sizeof(struct ebitmap_node), + 0, SLAB_PANIC, NULL); +} + +void ebitmap_cache_destroy(void) +{ + kmem_cache_destroy(ebitmap_node_cachep); +} diff --git a/security/selinux/ss/ebitmap.h b/security/selinux/ss/ebitmap.h index 9637b8c71085..6d5a9ac4251f 100644 --- a/security/selinux/ss/ebitmap.h +++ b/security/selinux/ss/ebitmap.h @@ -130,6 +130,9 @@ void ebitmap_destroy(struct ebitmap *e); int ebitmap_read(struct ebitmap *e, void *fp); int ebitmap_write(struct ebitmap *e, void *fp); +void ebitmap_cache_init(void); +void ebitmap_cache_destroy(void); + #ifdef CONFIG_NETLABEL int ebitmap_netlbl_export(struct ebitmap *ebmap, struct netlbl_lsm_catmap **catmap); diff --git a/security/selinux/ss/services.c b/security/selinux/ss/services.c index 202166612b80..2f02fa67ec2e 100644 --- a/security/selinux/ss/services.c +++ b/security/selinux/ss/services.c @@ -2054,9 +2054,11 @@ int security_load_policy(void *data, size_t len) if (!ss_initialized) { avtab_cache_init(); + ebitmap_cache_init(); rc = policydb_read(&policydb, fp); if (rc) { avtab_cache_destroy(); + ebitmap_cache_destroy(); goto out; } @@ -2067,6 +2069,7 @@ int security_load_policy(void *data, size_t len) if (rc) { policydb_destroy(&policydb); avtab_cache_destroy(); + ebitmap_cache_destroy(); goto out; } @@ -2074,6 +2077,7 @@ int security_load_policy(void *data, size_t len) if (rc) { policydb_destroy(&policydb); avtab_cache_destroy(); + ebitmap_cache_destroy(); goto out; } -- cgit v1.2.3 From 0b4d3452b8b4a5309b4445b900e3cec022cca95a Mon Sep 17 00:00:00 2001 From: Scott Mayhew Date: Mon, 5 Jun 2017 11:45:04 -0400 Subject: security/selinux: allow security_sb_clone_mnt_opts to enable/disable native labeling behavior When an NFSv4 client performs a mount operation, it first mounts the NFSv4 root and then does path walk to the exported path and performs a submount on that, cloning the security mount options from the root's superblock to the submount's superblock in the process. Unless the NFS server has an explicit fsid=0 export with the "security_label" option, the NFSv4 root superblock will not have SBLABEL_MNT set, and neither will the submount superblock after cloning the security mount options. As a result, setxattr's of security labels over NFSv4.2 will fail. In a similar fashion, NFSv4.2 mounts mounted with the context= mount option will not show the correct labels because the nfs_server->caps flags of the cloned superblock will still have NFS_CAP_SECURITY_LABEL set. Allowing the NFSv4 client to enable or disable SECURITY_LSM_NATIVE_LABELS behavior will ensure that the SBLABEL_MNT flag has the correct value when the client traverses from an exported path without the "security_label" option to one with the "security_label" option and vice versa. Similarly, checking to see if SECURITY_LSM_NATIVE_LABELS is set upon return from security_sb_clone_mnt_opts() and clearing NFS_CAP_SECURITY_LABEL if necessary will allow the correct labels to be displayed for NFSv4.2 mounts mounted with the context= mount option. Resolves: https://github.com/SELinuxProject/selinux-kernel/issues/35 Signed-off-by: Scott Mayhew Reviewed-by: Stephen Smalley Tested-by: Stephen Smalley Signed-off-by: Paul Moore --- fs/nfs/super.c | 17 ++++++++++++++++- include/linux/lsm_hooks.h | 4 +++- include/linux/security.h | 8 ++++++-- security/security.c | 7 +++++-- security/selinux/hooks.c | 35 +++++++++++++++++++++++++++++++++-- 5 files changed, 63 insertions(+), 8 deletions(-) diff --git a/fs/nfs/super.c b/fs/nfs/super.c index 2f3822a4a7d5..b8e073546507 100644 --- a/fs/nfs/super.c +++ b/fs/nfs/super.c @@ -2544,10 +2544,25 @@ EXPORT_SYMBOL_GPL(nfs_set_sb_security); int nfs_clone_sb_security(struct super_block *s, struct dentry *mntroot, struct nfs_mount_info *mount_info) { + int error; + unsigned long kflags = 0, kflags_out = 0; + /* clone any lsm security options from the parent to the new sb */ if (d_inode(mntroot)->i_op != NFS_SB(s)->nfs_client->rpc_ops->dir_inode_ops) return -ESTALE; - return security_sb_clone_mnt_opts(mount_info->cloned->sb, s); + + if (NFS_SB(s)->caps & NFS_CAP_SECURITY_LABEL) + kflags |= SECURITY_LSM_NATIVE_LABELS; + + error = security_sb_clone_mnt_opts(mount_info->cloned->sb, s, kflags, + &kflags_out); + if (error) + return error; + + if (NFS_SB(s)->caps & NFS_CAP_SECURITY_LABEL && + !(kflags_out & SECURITY_LSM_NATIVE_LABELS)) + NFS_SB(s)->caps &= ~NFS_CAP_SECURITY_LABEL; + return 0; } EXPORT_SYMBOL_GPL(nfs_clone_sb_security); diff --git a/include/linux/lsm_hooks.h b/include/linux/lsm_hooks.h index 68d91e423bca..3cc9d77c7527 100644 --- a/include/linux/lsm_hooks.h +++ b/include/linux/lsm_hooks.h @@ -1409,7 +1409,9 @@ union security_list_options { unsigned long kern_flags, unsigned long *set_kern_flags); int (*sb_clone_mnt_opts)(const struct super_block *oldsb, - struct super_block *newsb); + struct super_block *newsb, + unsigned long kern_flags, + unsigned long *set_kern_flags); int (*sb_parse_opts_str)(char *options, struct security_mnt_opts *opts); int (*dentry_init_security)(struct dentry *dentry, int mode, const struct qstr *name, void **ctx, diff --git a/include/linux/security.h b/include/linux/security.h index 549cb828a888..b44e954815ce 100644 --- a/include/linux/security.h +++ b/include/linux/security.h @@ -249,7 +249,9 @@ int security_sb_set_mnt_opts(struct super_block *sb, unsigned long kern_flags, unsigned long *set_kern_flags); int security_sb_clone_mnt_opts(const struct super_block *oldsb, - struct super_block *newsb); + struct super_block *newsb, + unsigned long kern_flags, + unsigned long *set_kern_flags); int security_sb_parse_opts_str(char *options, struct security_mnt_opts *opts); int security_dentry_init_security(struct dentry *dentry, int mode, const struct qstr *name, void **ctx, @@ -605,7 +607,9 @@ static inline int security_sb_set_mnt_opts(struct super_block *sb, } static inline int security_sb_clone_mnt_opts(const struct super_block *oldsb, - struct super_block *newsb) + struct super_block *newsb, + unsigned long kern_flags, + unsigned long *set_kern_flags) { return 0; } diff --git a/security/security.c b/security/security.c index 714433e3e9a2..30132378d103 100644 --- a/security/security.c +++ b/security/security.c @@ -420,9 +420,12 @@ int security_sb_set_mnt_opts(struct super_block *sb, EXPORT_SYMBOL(security_sb_set_mnt_opts); int security_sb_clone_mnt_opts(const struct super_block *oldsb, - struct super_block *newsb) + struct super_block *newsb, + unsigned long kern_flags, + unsigned long *set_kern_flags) { - return call_int_hook(sb_clone_mnt_opts, 0, oldsb, newsb); + return call_int_hook(sb_clone_mnt_opts, 0, oldsb, newsb, + kern_flags, set_kern_flags); } EXPORT_SYMBOL(security_sb_clone_mnt_opts); diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index 9926adbd50a9..9cc042df10d1 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -525,8 +525,16 @@ static int sb_finish_set_opts(struct super_block *sb) } sbsec->flags |= SE_SBINITIALIZED; + + /* + * Explicitly set or clear SBLABEL_MNT. It's not sufficient to simply + * leave the flag untouched because sb_clone_mnt_opts might be handing + * us a superblock that needs the flag to be cleared. + */ if (selinux_is_sblabel_mnt(sb)) sbsec->flags |= SBLABEL_MNT; + else + sbsec->flags &= ~SBLABEL_MNT; /* Initialize the root inode. */ rc = inode_doinit_with_dentry(root_inode, root); @@ -959,8 +967,11 @@ mismatch: } static int selinux_sb_clone_mnt_opts(const struct super_block *oldsb, - struct super_block *newsb) + struct super_block *newsb, + unsigned long kern_flags, + unsigned long *set_kern_flags) { + int rc = 0; const struct superblock_security_struct *oldsbsec = oldsb->s_security; struct superblock_security_struct *newsbsec = newsb->s_security; @@ -975,6 +986,13 @@ static int selinux_sb_clone_mnt_opts(const struct super_block *oldsb, if (!ss_initialized) return 0; + /* + * Specifying internal flags without providing a place to + * place the results is not allowed. + */ + if (kern_flags && !set_kern_flags) + return -EINVAL; + /* how can we clone if the old one wasn't set up?? */ BUG_ON(!(oldsbsec->flags & SE_SBINITIALIZED)); @@ -990,6 +1008,18 @@ static int selinux_sb_clone_mnt_opts(const struct super_block *oldsb, newsbsec->def_sid = oldsbsec->def_sid; newsbsec->behavior = oldsbsec->behavior; + if (newsbsec->behavior == SECURITY_FS_USE_NATIVE && + !(kern_flags & SECURITY_LSM_NATIVE_LABELS) && !set_context) { + rc = security_fs_use(newsb); + if (rc) + goto out; + } + + if (kern_flags & SECURITY_LSM_NATIVE_LABELS && !set_context) { + newsbsec->behavior = SECURITY_FS_USE_NATIVE; + *set_kern_flags |= SECURITY_LSM_NATIVE_LABELS; + } + if (set_context) { u32 sid = oldsbsec->mntpoint_sid; @@ -1009,8 +1039,9 @@ static int selinux_sb_clone_mnt_opts(const struct super_block *oldsb, } sb_finish_set_opts(newsb); +out: mutex_unlock(&newsbsec->lock); - return 0; + return rc; } static int selinux_parse_opts_str(char *options, -- cgit v1.2.3 From 18e99f191a8e66ec8fd06e4820de44bd9faa296a Mon Sep 17 00:00:00 2001 From: John Johansen Date: Fri, 26 May 2017 01:45:08 -0700 Subject: apparmor: provide finer control over policy management Signed-off-by: John Johansen --- security/apparmor/apparmorfs.c | 15 +++++++-------- security/apparmor/include/policy.h | 8 ++++++-- security/apparmor/policy.c | 35 ++++++++++++++++++++++------------- 3 files changed, 35 insertions(+), 23 deletions(-) diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c index c847f601371d..570d6b58b159 100644 --- a/security/apparmor/apparmorfs.c +++ b/security/apparmor/apparmorfs.c @@ -400,17 +400,16 @@ static struct aa_loaddata *aa_simple_write_to_buffer(const char __user *userbuf, return data; } -static ssize_t policy_update(int binop, const char __user *buf, size_t size, +static ssize_t policy_update(u32 mask, const char __user *buf, size_t size, loff_t *pos, struct aa_ns *ns) { ssize_t error; struct aa_loaddata *data; struct aa_profile *profile = aa_current_profile(); - const char *op = binop == PROF_ADD ? OP_PROF_LOAD : OP_PROF_REPL; /* high level check about policy management - fine grained in * below after unpack */ - error = aa_may_manage_policy(profile, ns, op); + error = aa_may_manage_policy(profile, ns, mask); if (error) return error; @@ -418,7 +417,7 @@ static ssize_t policy_update(int binop, const char __user *buf, size_t size, error = PTR_ERR(data); if (!IS_ERR(data)) { error = aa_replace_profiles(ns ? ns : profile->ns, profile, - binop, data); + mask, data); aa_put_loaddata(data); } @@ -430,7 +429,7 @@ static ssize_t profile_load(struct file *f, const char __user *buf, size_t size, loff_t *pos) { struct aa_ns *ns = aa_get_ns(f->f_inode->i_private); - int error = policy_update(PROF_ADD, buf, size, pos, ns); + int error = policy_update(AA_MAY_LOAD_POLICY, buf, size, pos, ns); aa_put_ns(ns); @@ -447,8 +446,8 @@ static ssize_t profile_replace(struct file *f, const char __user *buf, size_t size, loff_t *pos) { struct aa_ns *ns = aa_get_ns(f->f_inode->i_private); - int error = policy_update(PROF_REPLACE, buf, size, pos, ns); - + int error = policy_update(AA_MAY_LOAD_POLICY | AA_MAY_REPLACE_POLICY, + buf, size, pos, ns); aa_put_ns(ns); return error; @@ -472,7 +471,7 @@ static ssize_t profile_remove(struct file *f, const char __user *buf, /* high level check about policy management - fine grained in * below after unpack */ - error = aa_may_manage_policy(profile, ns, OP_PROF_RM); + error = aa_may_manage_policy(profile, ns, AA_MAY_REMOVE_POLICY); if (error) goto out; diff --git a/security/apparmor/include/policy.h b/security/apparmor/include/policy.h index 0f87f70287ad..97bfbddef7b2 100644 --- a/security/apparmor/include/policy.h +++ b/security/apparmor/include/policy.h @@ -188,6 +188,10 @@ struct aa_profile { extern enum profile_mode aa_g_profile_mode; +#define AA_MAY_LOAD_POLICY AA_MAY_APPEND +#define AA_MAY_REPLACE_POLICY AA_MAY_WRITE +#define AA_MAY_REMOVE_POLICY AA_MAY_DELETE + void __aa_update_proxy(struct aa_profile *orig, struct aa_profile *new); void aa_add_profile(struct aa_policy *common, struct aa_profile *profile); @@ -208,7 +212,7 @@ struct aa_profile *aa_fqlookupn_profile(struct aa_profile *base, struct aa_profile *aa_match_profile(struct aa_ns *ns, const char *name); ssize_t aa_replace_profiles(struct aa_ns *view, struct aa_profile *profile, - bool noreplace, struct aa_loaddata *udata); + u32 mask, struct aa_loaddata *udata); ssize_t aa_remove_profiles(struct aa_ns *view, struct aa_profile *profile, char *name, size_t size); void __aa_profile_list_release(struct list_head *head); @@ -323,6 +327,6 @@ static inline int AUDIT_MODE(struct aa_profile *profile) bool policy_view_capable(struct aa_ns *ns); bool policy_admin_capable(struct aa_ns *ns); int aa_may_manage_policy(struct aa_profile *profile, struct aa_ns *ns, - const char *op); + u32 mask); #endif /* __AA_POLICY_H */ diff --git a/security/apparmor/policy.c b/security/apparmor/policy.c index a5e8559e4dec..65e98d0491f4 100644 --- a/security/apparmor/policy.c +++ b/security/apparmor/policy.c @@ -690,17 +690,25 @@ bool policy_admin_capable(struct aa_ns *ns) * * Returns: 0 if the task is allowed to manipulate policy else error */ -int aa_may_manage_policy(struct aa_profile *profile, struct aa_ns *ns, - const char *op) +int aa_may_manage_policy(struct aa_profile *profile, struct aa_ns *ns, u32 mask) { + const char *op; + + if (mask & AA_MAY_REMOVE_POLICY) + op = OP_PROF_RM; + else if (mask & AA_MAY_REPLACE_POLICY) + op = OP_PROF_REPL; + else + op = OP_PROF_LOAD; + /* check if loading policy is locked out */ if (aa_g_lock_policy) - return audit_policy(profile, op, NULL, NULL, - "policy_locked", -EACCES); + return audit_policy(profile, op, NULL, NULL, "policy_locked", + -EACCES); if (!policy_admin_capable(ns)) - return audit_policy(profile, op, NULL, NULL, - "not policy admin", -EACCES); + return audit_policy(profile, op, NULL, NULL, "not policy admin", + -EACCES); /* TODO: add fine grained mediation of policy loads */ return 0; @@ -825,7 +833,7 @@ static int __lookup_replace(struct aa_ns *ns, const char *hname, * aa_replace_profiles - replace profile(s) on the profile list * @view: namespace load is viewed from * @label: label that is attempting to load/replace policy - * @noreplace: true if only doing addition, no replacement allowed + * @mask: permission mask * @udata: serialized data stream (NOT NULL) * * unpack and replace a profile on the profile list and uses of that profile @@ -835,17 +843,17 @@ static int __lookup_replace(struct aa_ns *ns, const char *hname, * Returns: size of data consumed else error code on failure. */ ssize_t aa_replace_profiles(struct aa_ns *view, struct aa_profile *profile, - bool noreplace, struct aa_loaddata *udata) + u32 mask, struct aa_loaddata *udata) { const char *ns_name, *info = NULL; struct aa_ns *ns = NULL; struct aa_load_ent *ent, *tmp; struct aa_loaddata *rawdata_ent; - const char *op = OP_PROF_REPL; + const char *op; ssize_t count, error; - LIST_HEAD(lh); + op = mask & AA_MAY_REPLACE_POLICY ? OP_PROF_REPL : OP_PROF_LOAD; aa_get_loaddata(udata); /* released below */ error = aa_unpack(udata, &lh, &ns_name); @@ -909,15 +917,16 @@ ssize_t aa_replace_profiles(struct aa_ns *view, struct aa_profile *profile, struct aa_policy *policy; ent->new->rawdata = aa_get_loaddata(udata); - error = __lookup_replace(ns, ent->new->base.hname, noreplace, + error = __lookup_replace(ns, ent->new->base.hname, + !(mask & AA_MAY_REPLACE_POLICY), &ent->old, &info); if (error) goto fail_lock; if (ent->new->rename) { error = __lookup_replace(ns, ent->new->rename, - noreplace, &ent->rename, - &info); + !(mask & AA_MAY_REPLACE_POLICY), + &ent->rename, &info); if (error) goto fail_lock; } -- cgit v1.2.3 From d9bf2c268be6064ae0c9980e4c37fdd262c7effc Mon Sep 17 00:00:00 2001 From: John Johansen Date: Fri, 26 May 2017 16:27:58 -0700 Subject: apparmor: add policy revision file interface Add a policy revision file to find the current revision of a ns's policy. There is a revision file per ns, as well as a virtualized global revision file in the base apparmor fs directory. The global revision file when opened will provide the revision of the opening task namespace. The revision file can be waited on via select/poll to detect apparmor policy changes from the last read revision of the opened file. This means that the revision file must be read after the select/poll other wise update data will remain ready for reading. Signed-off-by: John Johansen --- security/apparmor/apparmorfs.c | 113 ++++++++++++++++++++++++++++++++- security/apparmor/include/apparmorfs.h | 2 + security/apparmor/include/policy_ns.h | 1 + security/apparmor/policy_ns.c | 1 + 4 files changed, 116 insertions(+), 1 deletion(-) diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c index 570d6b58b159..8c413333726b 100644 --- a/security/apparmor/apparmorfs.c +++ b/security/apparmor/apparmorfs.c @@ -23,6 +23,7 @@ #include #include #include +#include #include #include @@ -31,6 +32,7 @@ #include "include/audit.h" #include "include/context.h" #include "include/crypto.h" +#include "include/policy_ns.h" #include "include/policy.h" #include "include/policy_ns.h" #include "include/resource.h" @@ -498,11 +500,101 @@ static const struct file_operations aa_fs_profile_remove = { .llseek = default_llseek, }; +struct aa_revision { + struct aa_ns *ns; + long last_read; +}; + +/* revision file hook fn for policy loads */ +static int ns_revision_release(struct inode *inode, struct file *file) +{ + struct aa_revision *rev = file->private_data; + + if (rev) { + aa_put_ns(rev->ns); + kfree(rev); + } + + return 0; +} + +static ssize_t ns_revision_read(struct file *file, char __user *buf, + size_t size, loff_t *ppos) +{ + struct aa_revision *rev = file->private_data; + char buffer[32]; + long last_read; + int avail; + + mutex_lock(&rev->ns->lock); + last_read = rev->last_read; + if (last_read == rev->ns->revision) { + mutex_unlock(&rev->ns->lock); + if (file->f_flags & O_NONBLOCK) + return -EAGAIN; + if (wait_event_interruptible(rev->ns->wait, + last_read != + READ_ONCE(rev->ns->revision))) + return -ERESTARTSYS; + mutex_lock(&rev->ns->lock); + } + + avail = sprintf(buffer, "%ld\n", rev->ns->revision); + if (*ppos + size > avail) { + rev->last_read = rev->ns->revision; + *ppos = 0; + } + mutex_unlock(&rev->ns->lock); + + return simple_read_from_buffer(buf, size, ppos, buffer, avail); +} + +static int ns_revision_open(struct inode *inode, struct file *file) +{ + struct aa_revision *rev = kzalloc(sizeof(*rev), GFP_KERNEL); + + if (!rev) + return -ENOMEM; + + rev->ns = aa_get_ns(inode->i_private); + if (!rev->ns) + rev->ns = aa_get_current_ns(); + file->private_data = rev; + + return 0; +} + +static unsigned int ns_revision_poll(struct file *file, poll_table *pt) +{ + struct aa_revision *rev = file->private_data; + unsigned int mask = 0; + + if (rev) { + mutex_lock(&rev->ns->lock); + poll_wait(file, &rev->ns->wait, pt); + if (rev->last_read < rev->ns->revision) + mask |= POLLIN | POLLRDNORM; + mutex_unlock(&rev->ns->lock); + } + + return mask; +} + void __aa_bump_ns_revision(struct aa_ns *ns) { ns->revision++; + wake_up_interruptible(&ns->wait); } +static const struct file_operations aa_fs_ns_revision_fops = { + .owner = THIS_MODULE, + .open = ns_revision_open, + .poll = ns_revision_poll, + .read = ns_revision_read, + .llseek = generic_file_llseek, + .release = ns_revision_release, +}; + /** * query_data - queries a policy and writes its data to buf * @buf: the resulting data is stored here (NOT NULL) @@ -1280,6 +1372,10 @@ void __aafs_ns_rmdir(struct aa_ns *ns) sub = d_inode(ns_subremove(ns))->i_private; aa_put_ns(sub); } + if (ns_subrevision(ns)) { + sub = d_inode(ns_subrevision(ns))->i_private; + aa_put_ns(sub); + } for (i = AAFS_NS_SIZEOF - 1; i >= 0; --i) { aafs_remove(ns->dents[i]); @@ -1305,6 +1401,13 @@ static int __aafs_ns_mkdir_entries(struct aa_ns *ns, struct dentry *dir) return PTR_ERR(dent); ns_subdata_dir(ns) = dent; + dent = aafs_create_file("revision", 0444, dir, ns, + &aa_fs_ns_revision_fops); + if (IS_ERR(dent)) + return PTR_ERR(dent); + aa_get_ns(ns); + ns_subrevision(ns) = dent; + dent = aafs_create_file(".load", 0640, dir, ns, &aa_fs_profile_load); if (IS_ERR(dent)) @@ -1930,11 +2033,19 @@ static int __init aa_create_aafs(void) } ns_subremove(root_ns) = dent; + dent = securityfs_create_file("revision", 0444, aa_sfs_entry.dentry, + NULL, &aa_fs_ns_revision_fops); + if (IS_ERR(dent)) { + error = PTR_ERR(dent); + goto error; + } + ns_subrevision(root_ns) = dent; + + /* policy tree referenced by magic policy symlink */ mutex_lock(&root_ns->lock); error = __aafs_ns_mkdir(root_ns, aafs_mnt->mnt_root, ".policy", aafs_mnt->mnt_root); mutex_unlock(&root_ns->lock); - if (error) goto error; diff --git a/security/apparmor/include/apparmorfs.h b/security/apparmor/include/apparmorfs.h index 071a59a1f056..bd689114bf93 100644 --- a/security/apparmor/include/apparmorfs.h +++ b/security/apparmor/include/apparmorfs.h @@ -74,6 +74,7 @@ enum aafs_ns_type { AAFS_NS_LOAD, AAFS_NS_REPLACE, AAFS_NS_REMOVE, + AAFS_NS_REVISION, AAFS_NS_COUNT, AAFS_NS_MAX_COUNT, AAFS_NS_SIZE, @@ -102,6 +103,7 @@ enum aafs_prof_type { #define ns_subload(X) ((X)->dents[AAFS_NS_LOAD]) #define ns_subreplace(X) ((X)->dents[AAFS_NS_REPLACE]) #define ns_subremove(X) ((X)->dents[AAFS_NS_REMOVE]) +#define ns_subrevision(X) ((X)->dents[AAFS_NS_REVISION]) #define prof_dir(X) ((X)->dents[AAFS_PROF_DIR]) #define prof_child_dir(X) ((X)->dents[AAFS_PROF_PROFS]) diff --git a/security/apparmor/include/policy_ns.h b/security/apparmor/include/policy_ns.h index d7a07ac96168..23e7cb770226 100644 --- a/security/apparmor/include/policy_ns.h +++ b/security/apparmor/include/policy_ns.h @@ -69,6 +69,7 @@ struct aa_ns { long uniq_id; int level; long revision; + wait_queue_head_t wait; struct list_head rawdata_list; diff --git a/security/apparmor/policy_ns.c b/security/apparmor/policy_ns.c index 7d7c23705be2..f3418a9e59b1 100644 --- a/security/apparmor/policy_ns.c +++ b/security/apparmor/policy_ns.c @@ -101,6 +101,7 @@ static struct aa_ns *alloc_ns(const char *prefix, const char *name) INIT_LIST_HEAD(&ns->sub_ns); INIT_LIST_HEAD(&ns->rawdata_list); mutex_init(&ns->lock); + init_waitqueue_head(&ns->wait); /* released by aa_free_ns() */ ns->unconfined = aa_alloc_profile("unconfined", GFP_KERNEL); -- cgit v1.2.3 From 4ae47f33354a96efb4e4231dec0d72a586b3921c Mon Sep 17 00:00:00 2001 From: John Johansen Date: Fri, 26 May 2017 16:45:48 -0700 Subject: apparmor: add mkdir/rmdir interface to manage policy namespaces When setting up namespaces for containers its easier for them to use an fs interface to create the namespace for the containers policy. Allow mkdir/rmdir under the policy/namespaces/ dir to be used to create and remove namespaces. BugLink: http://bugs.launchpad.net/bugs/1611078 Signed-off-by: John Johansen --- security/apparmor/apparmorfs.c | 95 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 94 insertions(+), 1 deletion(-) diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c index 8c413333726b..7f3049300ce3 100644 --- a/security/apparmor/apparmorfs.c +++ b/security/apparmor/apparmorfs.c @@ -1322,6 +1322,97 @@ fail2: return error; } +static int ns_mkdir_op(struct inode *dir, struct dentry *dentry, umode_t mode) +{ + struct aa_ns *ns, *parent; + /* TODO: improve permission check */ + struct aa_profile *profile = aa_current_profile(); + int error = aa_may_manage_policy(profile, NULL, AA_MAY_LOAD_POLICY); + + if (error) + return error; + + parent = aa_get_ns(dir->i_private); + AA_BUG(d_inode(ns_subns_dir(parent)) != dir); + + /* we have to unlock and then relock to get locking order right + * for pin_fs + */ + inode_unlock(dir); + error = simple_pin_fs(&aafs_ops, &aafs_mnt, &aafs_count); + mutex_lock(&parent->lock); + inode_lock_nested(dir, I_MUTEX_PARENT); + if (error) + goto out; + + error = __aafs_setup_d_inode(dir, dentry, mode | S_IFDIR, NULL, + NULL, NULL, NULL); + if (error) + goto out_pin; + + ns = __aa_find_or_create_ns(parent, READ_ONCE(dentry->d_name.name), + dentry); + if (IS_ERR(ns)) { + error = PTR_ERR(ns); + ns = NULL; + } + + aa_put_ns(ns); /* list ref remains */ +out_pin: + if (error) + simple_release_fs(&aafs_mnt, &aafs_count); +out: + mutex_unlock(&parent->lock); + aa_put_ns(parent); + + return error; +} + +static int ns_rmdir_op(struct inode *dir, struct dentry *dentry) +{ + struct aa_ns *ns, *parent; + /* TODO: improve permission check */ + struct aa_profile *profile = aa_current_profile(); + int error = aa_may_manage_policy(profile, NULL, AA_MAY_LOAD_POLICY); + + if (error) + return error; + + parent = aa_get_ns(dir->i_private); + /* rmdir calls the generic securityfs functions to remove files + * from the apparmor dir. It is up to the apparmor ns locking + * to avoid races. + */ + inode_unlock(dir); + inode_unlock(dentry->d_inode); + + mutex_lock(&parent->lock); + ns = aa_get_ns(__aa_findn_ns(&parent->sub_ns, dentry->d_name.name, + dentry->d_name.len)); + if (!ns) { + error = -ENOENT; + goto out; + } + AA_BUG(ns_dir(ns) != dentry); + + __aa_remove_ns(ns); + aa_put_ns(ns); + +out: + mutex_unlock(&parent->lock); + inode_lock_nested(dir, I_MUTEX_PARENT); + inode_lock(dentry->d_inode); + aa_put_ns(parent); + + return error; +} + +static const struct inode_operations ns_dir_inode_operations = { + .lookup = simple_lookup, + .mkdir = ns_mkdir_op, + .rmdir = ns_rmdir_op, +}; + static void __aa_fs_list_remove_rawdata(struct aa_ns *ns) { struct aa_loaddata *ent, *tmp; @@ -1429,7 +1520,9 @@ static int __aafs_ns_mkdir_entries(struct aa_ns *ns, struct dentry *dir) aa_get_ns(ns); ns_subremove(ns) = dent; - dent = aafs_create_dir("namespaces", dir); + /* use create_dentry so we can supply private data */ + dent = aafs_create("namespaces", S_IFDIR | 0755, dir, ns, NULL, NULL, + &ns_dir_inode_operations); if (IS_ERR(dent)) return PTR_ERR(dent); aa_get_ns(ns); -- cgit v1.2.3 From a83bd86e833a5842ad033527ea9af589efa6dc84 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Fri, 26 May 2017 18:49:04 -0700 Subject: apparmor: add label data availability to the feature set gsettings mediation needs to be able to determine if apparmor supports label data queries. A label data query can be done to test for support but its failure is indistinguishable from other failures, making it an unreliable indicator. Fix by making support of label data queries available as a flag in the apparmorfs features dir tree. Signed-off-by: John Johansen --- security/apparmor/apparmorfs.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c index 7f3049300ce3..a447c00a452c 100644 --- a/security/apparmor/apparmorfs.c +++ b/security/apparmor/apparmorfs.c @@ -1849,6 +1849,15 @@ static struct aa_sfs_entry aa_sfs_entry_policy[] = { { } }; +static struct aa_sfs_entry aa_sfs_entry_query_label[] = { + AA_SFS_FILE_BOOLEAN("data", 1), + { } +}; + +static struct aa_sfs_entry aa_sfs_entry_query[] = { + AA_SFS_DIR("label", aa_sfs_entry_query_label), + { } +}; static struct aa_sfs_entry aa_sfs_entry_features[] = { AA_SFS_DIR("policy", aa_sfs_entry_policy), AA_SFS_DIR("domain", aa_sfs_entry_domain), @@ -1856,6 +1865,7 @@ static struct aa_sfs_entry aa_sfs_entry_features[] = { AA_SFS_FILE_U64("capability", VFS_CAP_FLAGS_MASK), AA_SFS_DIR("rlimit", aa_sfs_entry_rlimit), AA_SFS_DIR("caps", aa_sfs_entry_caps), + AA_SFS_DIR("query", aa_sfs_entry_query), { } }; -- cgit v1.2.3 From 1dea3b41e84c5923173fe654dcb758a5cb4a46e5 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Fri, 26 May 2017 17:23:23 -0700 Subject: apparmor: speed up transactional queries The simple_transaction interface is slow. It requires 4 syscalls (open, write, read, close) per query and shares a single lock for each queries. So replace its use with a compatible in multi_transaction interface. It allows for a faster 2 syscall pattern per query. After an initial open, an arbitrary number of writes and reads can be issued. Each write will reset the query with new data that can be read. Reads do not clear the data, and can be issued multiple times, and used with seek, until a new write is performed which will reset the data available and the seek position. Note: this keeps the single lock design, if needed moving to a per file lock will have to come later. Signed-off-by: John Johansen --- security/apparmor/apparmorfs.c | 125 +++++++++++++++++++++++++++++++++++++---- 1 file changed, 114 insertions(+), 11 deletions(-) diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c index a447c00a452c..e553de58f801 100644 --- a/security/apparmor/apparmorfs.c +++ b/security/apparmor/apparmorfs.c @@ -673,6 +673,106 @@ static ssize_t query_data(char *buf, size_t buf_len, return out - buf; } +/* + * Transaction based IO. + * The file expects a write which triggers the transaction, and then + * possibly a read(s) which collects the result - which is stored in a + * file-local buffer. Once a new write is performed, a new set of results + * are stored in the file-local buffer. + */ +struct multi_transaction { + struct kref count; + ssize_t size; + char data[0]; +}; + +#define MULTI_TRANSACTION_LIMIT (PAGE_SIZE - sizeof(struct multi_transaction)) +/* TODO: replace with per file lock */ +static DEFINE_SPINLOCK(multi_transaction_lock); + +static void multi_transaction_kref(struct kref *kref) +{ + struct multi_transaction *t; + + t = container_of(kref, struct multi_transaction, count); + free_page((unsigned long) t); +} + +static struct multi_transaction * +get_multi_transaction(struct multi_transaction *t) +{ + if (t) + kref_get(&(t->count)); + + return t; +} + +static void put_multi_transaction(struct multi_transaction *t) +{ + if (t) + kref_put(&(t->count), multi_transaction_kref); +} + +/* does not increment @new's count */ +static void multi_transaction_set(struct file *file, + struct multi_transaction *new, size_t n) +{ + struct multi_transaction *old; + + AA_BUG(n > MULTI_TRANSACTION_LIMIT); + + new->size = n; + spin_lock(&multi_transaction_lock); + old = (struct multi_transaction *) file->private_data; + file->private_data = new; + spin_unlock(&multi_transaction_lock); + put_multi_transaction(old); +} + +static struct multi_transaction *multi_transaction_new(struct file *file, + const char __user *buf, + size_t size) +{ + struct multi_transaction *t; + + if (size > MULTI_TRANSACTION_LIMIT - 1) + return ERR_PTR(-EFBIG); + + t = (struct multi_transaction *)get_zeroed_page(GFP_KERNEL); + if (!t) + return ERR_PTR(-ENOMEM); + kref_init(&t->count); + if (copy_from_user(t->data, buf, size)) + return ERR_PTR(-EFAULT); + + return t; +} + +static ssize_t multi_transaction_read(struct file *file, char __user *buf, + size_t size, loff_t *pos) +{ + struct multi_transaction *t; + ssize_t ret; + + spin_lock(&multi_transaction_lock); + t = get_multi_transaction(file->private_data); + spin_unlock(&multi_transaction_lock); + if (!t) + return 0; + + ret = simple_read_from_buffer(buf, size, pos, t->data, t->size); + put_multi_transaction(t); + + return ret; +} + +static int multi_transaction_release(struct inode *inode, struct file *file) +{ + put_multi_transaction(file->private_data); + + return 0; +} + #define QUERY_CMD_DATA "data\0" #define QUERY_CMD_DATA_LEN 5 @@ -700,36 +800,38 @@ static ssize_t query_data(char *buf, size_t buf_len, static ssize_t aa_write_access(struct file *file, const char __user *ubuf, size_t count, loff_t *ppos) { - char *buf; + struct multi_transaction *t; ssize_t len; if (*ppos) return -ESPIPE; - buf = simple_transaction_get(file, ubuf, count); - if (IS_ERR(buf)) - return PTR_ERR(buf); + t = multi_transaction_new(file, ubuf, count); + if (IS_ERR(t)) + return PTR_ERR(t); if (count > QUERY_CMD_DATA_LEN && - !memcmp(buf, QUERY_CMD_DATA, QUERY_CMD_DATA_LEN)) { - len = query_data(buf, SIMPLE_TRANSACTION_LIMIT, - buf + QUERY_CMD_DATA_LEN, + !memcmp(t->data, QUERY_CMD_DATA, QUERY_CMD_DATA_LEN)) { + len = query_data(t->data, MULTI_TRANSACTION_LIMIT, + t->data + QUERY_CMD_DATA_LEN, count - QUERY_CMD_DATA_LEN); } else len = -EINVAL; - if (len < 0) + if (len < 0) { + put_multi_transaction(t); return len; + } - simple_transaction_set(file, len); + multi_transaction_set(file, t, len); return count; } static const struct file_operations aa_sfs_access = { .write = aa_write_access, - .read = simple_transaction_read, - .release = simple_transaction_release, + .read = multi_transaction_read, + .release = multi_transaction_release, .llseek = generic_file_llseek, }; @@ -1851,6 +1953,7 @@ static struct aa_sfs_entry aa_sfs_entry_policy[] = { static struct aa_sfs_entry aa_sfs_entry_query_label[] = { AA_SFS_FILE_BOOLEAN("data", 1), + AA_SFS_FILE_BOOLEAN("multi_transaction", 1), { } }; -- cgit v1.2.3 From b5b2557c0aeca35b34c558fd09ad6da67b9f3557 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Mon, 29 May 2017 11:45:29 -0700 Subject: apparmor: add fn to test if profile supports a given mediation class Signed-off-by: John Johansen --- security/apparmor/include/policy.h | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/security/apparmor/include/policy.h b/security/apparmor/include/policy.h index 97bfbddef7b2..d93f475bfd8b 100644 --- a/security/apparmor/include/policy.h +++ b/security/apparmor/include/policy.h @@ -222,6 +222,16 @@ void __aa_profile_list_release(struct list_head *head); #define unconfined(X) ((X)->mode == APPARMOR_UNCONFINED) +#define PROFILE_MEDIATES(P, T) ((P)->policy.start[(T)]) +/* safe version of POLICY_MEDIATES for full range input */ +static inline unsigned int PROFILE_MEDIATES_SAFE(struct aa_profile *profile, + unsigned char class) +{ + if (profile->policy.dfa) + return aa_dfa_match_len(profile->policy.dfa, + profile->policy.start[0], &class, 1); + return 0; +} /** * aa_get_profile - increment refcount on profile @p -- cgit v1.2.3 From aa9aeea8d4c3dfb9297723c4340671ef88e372d3 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Mon, 29 May 2017 12:16:04 -0700 Subject: apparmor: add gerneric permissions struct and support fns Signed-off-by: John Johansen --- security/apparmor/file.c | 30 +++++------ security/apparmor/include/audit.h | 4 +- security/apparmor/include/perms.h | 34 +++++++++++++ security/apparmor/lib.c | 102 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 153 insertions(+), 17 deletions(-) diff --git a/security/apparmor/file.c b/security/apparmor/file.c index 44549db904b3..1ee656f66aa4 100644 --- a/security/apparmor/file.c +++ b/security/apparmor/file.c @@ -56,15 +56,15 @@ static void file_audit_cb(struct audit_buffer *ab, void *va) struct common_audit_data *sa = va; kuid_t fsuid = current_fsuid(); - if (aad(sa)->fs.request & AA_AUDIT_FILE_MASK) { + if (aad(sa)->request & AA_AUDIT_FILE_MASK) { audit_log_format(ab, " requested_mask="); - audit_file_mask(ab, aad(sa)->fs.request); + audit_file_mask(ab, aad(sa)->request); } - if (aad(sa)->fs.denied & AA_AUDIT_FILE_MASK) { + if (aad(sa)->denied & AA_AUDIT_FILE_MASK) { audit_log_format(ab, " denied_mask="); - audit_file_mask(ab, aad(sa)->fs.denied); + audit_file_mask(ab, aad(sa)->denied); } - if (aad(sa)->fs.request & AA_AUDIT_FILE_MASK) { + if (aad(sa)->request & AA_AUDIT_FILE_MASK) { audit_log_format(ab, " fsuid=%d", from_kuid(&init_user_ns, fsuid)); audit_log_format(ab, " ouid=%d", @@ -100,7 +100,7 @@ int aa_audit_file(struct aa_profile *profile, struct file_perms *perms, DEFINE_AUDIT_DATA(sa, LSM_AUDIT_DATA_TASK, op); sa.u.tsk = NULL; - aad(&sa)->fs.request = request; + aad(&sa)->request = request; aad(&sa)->name = name; aad(&sa)->fs.target = target; aad(&sa)->fs.ouid = ouid; @@ -115,30 +115,30 @@ int aa_audit_file(struct aa_profile *profile, struct file_perms *perms, mask = 0xffff; /* mask off perms that are not being force audited */ - aad(&sa)->fs.request &= mask; + aad(&sa)->request &= mask; - if (likely(!aad(&sa)->fs.request)) + if (likely(!aad(&sa)->request)) return 0; type = AUDIT_APPARMOR_AUDIT; } else { /* only report permissions that were denied */ - aad(&sa)->fs.request = aad(&sa)->fs.request & ~perms->allow; - AA_BUG(!aad(&sa)->fs.request); + aad(&sa)->request = aad(&sa)->request & ~perms->allow; + AA_BUG(!aad(&sa)->request); - if (aad(&sa)->fs.request & perms->kill) + if (aad(&sa)->request & perms->kill) type = AUDIT_APPARMOR_KILL; /* quiet known rejects, assumes quiet and kill do not overlap */ - if ((aad(&sa)->fs.request & perms->quiet) && + if ((aad(&sa)->request & perms->quiet) && AUDIT_MODE(profile) != AUDIT_NOQUIET && AUDIT_MODE(profile) != AUDIT_ALL) - aad(&sa)->fs.request &= ~perms->quiet; + aad(&sa)->request &= ~perms->quiet; - if (!aad(&sa)->fs.request) + if (!aad(&sa)->request) return COMPLAIN_MODE(profile) ? 0 : aad(&sa)->error; } - aad(&sa)->fs.denied = aad(&sa)->fs.request & ~perms->allow; + aad(&sa)->denied = aad(&sa)->request & ~perms->allow; return aa_audit(type, profile, &sa, file_audit_cb); } diff --git a/security/apparmor/include/audit.h b/security/apparmor/include/audit.h index fdc4774318ba..1aeb8550fb82 100644 --- a/security/apparmor/include/audit.h +++ b/security/apparmor/include/audit.h @@ -107,14 +107,14 @@ struct apparmor_audit_data { void *profile; const char *name; const char *info; + u32 request; + u32 denied; union { /* these entries require a custom callback fn */ struct { struct aa_profile *peer; struct { const char *target; - u32 request; - u32 denied; kuid_t ouid; } fs; }; diff --git a/security/apparmor/include/perms.h b/security/apparmor/include/perms.h index 35e365e7aa75..6ef23212bd66 100644 --- a/security/apparmor/include/perms.h +++ b/security/apparmor/include/perms.h @@ -66,6 +66,40 @@ extern const char aa_file_perm_chrs[]; extern const char *aa_file_perm_names[]; +struct aa_perms { + u32 allow; + u32 audit; /* set only when allow is set */ + + u32 deny; /* explicit deny, or conflict if allow also set */ + u32 quiet; /* set only when ~allow | deny */ + u32 kill; /* set only when ~allow | deny */ + u32 stop; /* set only when ~allow | deny */ + + u32 complain; /* accumulates only used when ~allow & ~deny */ + u32 cond; /* set only when ~allow and ~deny */ + + u32 hide; /* set only when ~allow | deny */ + u32 prompt; /* accumulates only used when ~allow & ~deny */ + + /* Reserved: + * u32 subtree; / * set only when allow is set * / + */ + u16 xindex; +}; + +#define ALL_PERMS_MASK 0xffffffff + +extern struct aa_perms allperms; + +struct aa_profile; + void aa_perm_mask_to_str(char *str, const char *chrs, u32 mask); +void aa_audit_perm_names(struct audit_buffer *ab, const char **names, u32 mask); +void aa_audit_perm_mask(struct audit_buffer *ab, u32 mask, const char *chrs, + u32 chrsmask, const char **names, u32 namesmask); +void aa_apply_modes_to_perms(struct aa_profile *profile, + struct aa_perms *perms); +void aa_compute_perms(struct aa_dfa *dfa, unsigned int state, + struct aa_perms *perms); #endif /* __AA_PERM_H */ diff --git a/security/apparmor/lib.c b/security/apparmor/lib.c index 90d4631ddafe..a50913744823 100644 --- a/security/apparmor/lib.c +++ b/security/apparmor/lib.c @@ -24,6 +24,10 @@ #include "include/perms.h" #include "include/policy.h" +struct aa_perms allperms = { .allow = ALL_PERMS_MASK, + .quiet = ALL_PERMS_MASK, + .hide = ALL_PERMS_MASK }; + /** * aa_split_fqname - split a fqname into a profile and namespace name * @fqname: a full qualified name in namespace profile format (NOT NULL) @@ -188,6 +192,104 @@ void aa_perm_mask_to_str(char *str, const char *chrs, u32 mask) *str = '\0'; } +void aa_audit_perm_names(struct audit_buffer *ab, const char **names, u32 mask) +{ + const char *fmt = "%s"; + unsigned int i, perm = 1; + bool prev = false; + + for (i = 0; i < 32; perm <<= 1, i++) { + if (mask & perm) { + audit_log_format(ab, fmt, names[i]); + if (!prev) { + prev = true; + fmt = " %s"; + } + } + } +} + +void aa_audit_perm_mask(struct audit_buffer *ab, u32 mask, const char *chrs, + u32 chrsmask, const char **names, u32 namesmask) +{ + char str[33]; + + audit_log_format(ab, "\""); + if ((mask & chrsmask) && chrs) { + aa_perm_mask_to_str(str, chrs, mask & chrsmask); + mask &= ~chrsmask; + audit_log_format(ab, "%s", str); + if (mask & namesmask) + audit_log_format(ab, " "); + } + if ((mask & namesmask) && names) + aa_audit_perm_names(ab, names, mask & namesmask); + audit_log_format(ab, "\""); +} + +/** + * aa_apply_modes_to_perms - apply namespace and profile flags to perms + * @profile: that perms where computed from + * @perms: perms to apply mode modifiers to + * + * TODO: split into profile and ns based flags for when accumulating perms + */ +void aa_apply_modes_to_perms(struct aa_profile *profile, struct aa_perms *perms) +{ + switch (AUDIT_MODE(profile)) { + case AUDIT_ALL: + perms->audit = ALL_PERMS_MASK; + /* fall through */ + case AUDIT_NOQUIET: + perms->quiet = 0; + break; + case AUDIT_QUIET: + perms->audit = 0; + /* fall through */ + case AUDIT_QUIET_DENIED: + perms->quiet = ALL_PERMS_MASK; + break; + } + + if (KILL_MODE(profile)) + perms->kill = ALL_PERMS_MASK; + else if (COMPLAIN_MODE(profile)) + perms->complain = ALL_PERMS_MASK; +/* + * TODO: + * else if (PROMPT_MODE(profile)) + * perms->prompt = ALL_PERMS_MASK; + */ +} + +static u32 map_other(u32 x) +{ + return ((x & 0x3) << 8) | /* SETATTR/GETATTR */ + ((x & 0x1c) << 18) | /* ACCEPT/BIND/LISTEN */ + ((x & 0x60) << 19); /* SETOPT/GETOPT */ +} + +void aa_compute_perms(struct aa_dfa *dfa, unsigned int state, + struct aa_perms *perms) +{ + perms->deny = 0; + perms->kill = perms->stop = 0; + perms->complain = perms->cond = 0; + perms->hide = 0; + perms->prompt = 0; + perms->allow = dfa_user_allow(dfa, state); + perms->audit = dfa_user_audit(dfa, state); + perms->quiet = dfa_user_quiet(dfa, state); + + /* for v5 perm mapping in the policydb, the other set is used + * to extend the general perm set + */ + perms->allow |= map_other(dfa_other_allow(dfa, state)); + perms->audit |= map_other(dfa_other_audit(dfa, state)); + perms->quiet |= map_other(dfa_other_quiet(dfa, state)); +// perms->xindex = dfa_user_xindex(dfa, state); +} + /** * aa_policy_init - initialize a policy structure * @policy: policy to initialize (NOT NULL) -- cgit v1.2.3 From 2d679f3cb0eaa6afa0dc97fe6ad3b797e1c1899a Mon Sep 17 00:00:00 2001 From: John Johansen Date: Mon, 29 May 2017 12:19:39 -0700 Subject: apparmor: switch from file_perms to aa_perms Signed-off-by: John Johansen --- security/apparmor/domain.c | 18 +++++++++--------- security/apparmor/file.c | 31 ++++++++++++++----------------- security/apparmor/include/file.h | 25 ++++--------------------- security/apparmor/include/perms.h | 2 +- security/apparmor/lib.c | 1 + 5 files changed, 29 insertions(+), 48 deletions(-) diff --git a/security/apparmor/domain.c b/security/apparmor/domain.c index ab8f23cdccff..a0ba33454b8c 100644 --- a/security/apparmor/domain.c +++ b/security/apparmor/domain.c @@ -93,12 +93,12 @@ out: * * Returns: permission set */ -static struct file_perms change_profile_perms(struct aa_profile *profile, - struct aa_ns *ns, - const char *name, u32 request, - unsigned int start) +static struct aa_perms change_profile_perms(struct aa_profile *profile, + struct aa_ns *ns, + const char *name, u32 request, + unsigned int start) { - struct file_perms perms; + struct aa_perms perms; struct path_cond cond = { }; unsigned int state; @@ -342,7 +342,7 @@ int apparmor_bprm_set_creds(struct linux_binprm *bprm) struct aa_ns *ns; char *buffer = NULL; unsigned int state; - struct file_perms perms = {}; + struct aa_perms perms = {}; struct path_cond cond = { file_inode(bprm->file)->i_uid, file_inode(bprm->file)->i_mode @@ -400,7 +400,7 @@ int apparmor_bprm_set_creds(struct linux_binprm *bprm) /* find exec permissions for name */ state = aa_str_perms(profile->file.dfa, state, name, &cond, &perms); if (ctx->onexec) { - struct file_perms cp; + struct aa_perms cp; info = "change_profile onexec"; new_profile = aa_get_newest_profile(ctx->onexec); if (!(perms.allow & AA_MAY_ONEXEC)) @@ -609,7 +609,7 @@ int aa_change_hat(const char *hats[], int count, u64 token, bool permtest) struct aa_profile *profile, *previous_profile, *hat = NULL; char *name = NULL; int i; - struct file_perms perms = {}; + struct aa_perms perms = {}; const char *target = NULL, *info = NULL; int error = 0; @@ -748,7 +748,7 @@ int aa_change_profile(const char *fqname, bool onexec, { const struct cred *cred; struct aa_profile *profile, *target = NULL; - struct file_perms perms = {}; + struct aa_perms perms = {}; const char *info = NULL, *op; int error = 0; u32 request; diff --git a/security/apparmor/file.c b/security/apparmor/file.c index 1ee656f66aa4..2e128c2aa4dc 100644 --- a/security/apparmor/file.c +++ b/security/apparmor/file.c @@ -19,8 +19,6 @@ #include "include/path.h" #include "include/policy.h" -struct file_perms nullperms; - static u32 map_mask_to_chr_mask(u32 mask) { u32 m = mask & PERMS_CHRS_MASK; @@ -92,7 +90,7 @@ static void file_audit_cb(struct audit_buffer *ab, void *va) * * Returns: %0 or error on failure */ -int aa_audit_file(struct aa_profile *profile, struct file_perms *perms, +int aa_audit_file(struct aa_profile *profile, struct aa_perms *perms, const char *op, u32 request, const char *name, const char *target, kuid_t ouid, const char *info, int error) { @@ -170,7 +168,7 @@ static u32 map_old_perms(u32 old) } /** - * compute_perms - convert dfa compressed perms to internal perms + * aa_compute_fperms - convert dfa compressed perms to internal perms * @dfa: dfa to compute perms for (NOT NULL) * @state: state in dfa * @cond: conditions to consider (NOT NULL) @@ -180,17 +178,21 @@ static u32 map_old_perms(u32 old) * * Returns: computed permission set */ -static struct file_perms compute_perms(struct aa_dfa *dfa, unsigned int state, - struct path_cond *cond) +struct aa_perms aa_compute_fperms(struct aa_dfa *dfa, unsigned int state, + struct path_cond *cond) { - struct file_perms perms; + struct aa_perms perms; /* FIXME: change over to new dfa format * currently file perms are encoded in the dfa, new format * splits the permissions from the dfa. This mapping can be * done at profile load */ - perms.kill = 0; + perms.deny = 0; + perms.kill = perms.stop = 0; + perms.complain = perms.cond = 0; + perms.hide = 0; + perms.prompt = 0; if (uid_eq(current_fsuid(), cond->uid)) { perms.allow = map_old_perms(dfa_user_allow(dfa, state)); @@ -226,16 +228,11 @@ static struct file_perms compute_perms(struct aa_dfa *dfa, unsigned int state, */ unsigned int aa_str_perms(struct aa_dfa *dfa, unsigned int start, const char *name, struct path_cond *cond, - struct file_perms *perms) + struct aa_perms *perms) { unsigned int state; - if (!dfa) { - *perms = nullperms; - return DFA_NOMATCH; - } - state = aa_dfa_match(dfa, start, name); - *perms = compute_perms(dfa, state, cond); + *perms = aa_compute_fperms(dfa, state, cond); return state; } @@ -269,7 +266,7 @@ int aa_path_perm(const char *op, struct aa_profile *profile, struct path_cond *cond) { char *buffer = NULL; - struct file_perms perms = {}; + struct aa_perms perms = {}; const char *name, *info = NULL; int error; @@ -348,7 +345,7 @@ int aa_path_link(struct aa_profile *profile, struct dentry *old_dentry, }; char *buffer = NULL, *buffer2 = NULL; const char *lname, *tname = NULL, *info = NULL; - struct file_perms lperms, perms; + struct aa_perms lperms, perms; u32 request = AA_MAY_LINK; unsigned int state; int error; diff --git a/security/apparmor/include/file.h b/security/apparmor/include/file.h index fb3642a94e3d..365ca7ead133 100644 --- a/security/apparmor/include/file.h +++ b/security/apparmor/include/file.h @@ -90,25 +90,6 @@ struct path_cond { umode_t mode; }; -/* struct file_perms - file permission - * @allow: mask of permissions that are allowed - * @audit: mask of permissions to force an audit message for - * @quiet: mask of permissions to quiet audit messages for - * @kill: mask of permissions that when matched will kill the task - * @xindex: exec transition index if @allow contains MAY_EXEC - * - * The @audit and @queit mask should be mutually exclusive. - */ -struct file_perms { - u32 allow; - u32 audit; - u32 quiet; - u32 kill; - u16 xindex; -}; - -extern struct file_perms nullperms; - #define COMBINED_PERM_MASK(X) ((X).allow | (X).audit | (X).quiet | (X).kill) /* FIXME: split perms from dfa and match this to description @@ -159,7 +140,7 @@ static inline u16 dfa_map_xindex(u16 mask) #define dfa_other_xindex(dfa, state) \ dfa_map_xindex((ACCEPT_TABLE(dfa)[state] >> 14) & 0x3fff) -int aa_audit_file(struct aa_profile *profile, struct file_perms *perms, +int aa_audit_file(struct aa_profile *profile, struct aa_perms *perms, const char *op, u32 request, const char *name, const char *target, kuid_t ouid, const char *info, int error); @@ -182,9 +163,11 @@ struct aa_file_rules { /* TODO: add delegate table */ }; +struct aa_perms aa_compute_fperms(struct aa_dfa *dfa, unsigned int state, + struct path_cond *cond); unsigned int aa_str_perms(struct aa_dfa *dfa, unsigned int start, const char *name, struct path_cond *cond, - struct file_perms *perms); + struct aa_perms *perms); int aa_path_perm(const char *op, struct aa_profile *profile, const struct path *path, int flags, u32 request, diff --git a/security/apparmor/include/perms.h b/security/apparmor/include/perms.h index 6ef23212bd66..82946fb81f91 100644 --- a/security/apparmor/include/perms.h +++ b/security/apparmor/include/perms.h @@ -88,7 +88,7 @@ struct aa_perms { }; #define ALL_PERMS_MASK 0xffffffff - +extern struct aa_perms nullperms; extern struct aa_perms allperms; struct aa_profile; diff --git a/security/apparmor/lib.c b/security/apparmor/lib.c index a50913744823..3e9146e68c4a 100644 --- a/security/apparmor/lib.c +++ b/security/apparmor/lib.c @@ -24,6 +24,7 @@ #include "include/perms.h" #include "include/policy.h" +struct aa_perms nullperms; struct aa_perms allperms = { .allow = ALL_PERMS_MASK, .quiet = ALL_PERMS_MASK, .hide = ALL_PERMS_MASK }; -- cgit v1.2.3 From 4f3b3f2d79a42e5094f55eca4f29d8f60f1190bd Mon Sep 17 00:00:00 2001 From: John Johansen Date: Fri, 26 May 2017 18:35:29 -0700 Subject: apparmor: add profile permission query ability Allow userspace to query a profile about permissions, through the transaction interface that is already used to allow userspace to query about key,value data. Signed-off-by: John Johansen --- security/apparmor/apparmorfs.c | 103 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 102 insertions(+), 1 deletion(-) diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c index e553de58f801..105a1da57b8f 100644 --- a/security/apparmor/apparmorfs.c +++ b/security/apparmor/apparmorfs.c @@ -595,6 +595,40 @@ static const struct file_operations aa_fs_ns_revision_fops = { .release = ns_revision_release, }; +static void profile_query_cb(struct aa_profile *profile, struct aa_perms *perms, + const char *match_str, size_t match_len) +{ + struct aa_perms tmp; + struct aa_dfa *dfa; + unsigned int state = 0; + + if (unconfined(profile)) + return; + if (profile->file.dfa && *match_str == AA_CLASS_FILE) { + dfa = profile->file.dfa; + state = aa_dfa_match_len(dfa, profile->file.start, + match_str + 1, match_len - 1); + tmp = nullperms; + if (state) { + struct path_cond cond = { }; + + tmp = aa_compute_fperms(dfa, state, &cond); + } + } else if (profile->policy.dfa) { + if (!PROFILE_MEDIATES_SAFE(profile, *match_str)) + return; /* no change to current perms */ + dfa = profile->policy.dfa; + state = aa_dfa_match_len(dfa, profile->policy.start[0], + match_str, match_len); + if (state) + aa_compute_perms(dfa, state, &tmp); + else + tmp = nullperms; + } + aa_apply_modes_to_perms(profile, &tmp); +} + + /** * query_data - queries a policy and writes its data to buf * @buf: the resulting data is stored here (NOT NULL) @@ -673,6 +707,64 @@ static ssize_t query_data(char *buf, size_t buf_len, return out - buf; } +/** + * query_label - queries a label and writes permissions to buf + * @buf: the resulting permissions string is stored here (NOT NULL) + * @buf_len: size of buf + * @query: binary query string to match against the dfa + * @query_len: size of query + * @view_only: only compute for querier's view + * + * The buffers pointed to by buf and query may overlap. The query buffer is + * parsed before buf is written to. + * + * The query should look like "LABEL_NAME\0DFA_STRING" where LABEL_NAME is + * the name of the label, in the current namespace, that is to be queried and + * DFA_STRING is a binary string to match against the label(s)'s DFA. + * + * LABEL_NAME must be NUL terminated. DFA_STRING may contain NUL characters + * but must *not* be NUL terminated. + * + * Returns: number of characters written to buf or -errno on failure + */ +static ssize_t query_label(char *buf, size_t buf_len, + char *query, size_t query_len, bool view_only) +{ + struct aa_profile *profile, *curr; + char *label_name, *match_str; + size_t label_name_len, match_len; + struct aa_perms perms; + + if (!query_len) + return -EINVAL; + + label_name = query; + label_name_len = strnlen(query, query_len); + if (!label_name_len || label_name_len == query_len) + return -EINVAL; + + /** + * The extra byte is to account for the null byte between the + * profile name and dfa string. profile_name_len is greater + * than zero and less than query_len, so a byte can be safely + * added or subtracted. + */ + match_str = label_name + label_name_len + 1; + match_len = query_len - label_name_len - 1; + + curr = aa_current_profile(); + profile = aa_fqlookupn_profile(curr, label_name, label_name_len); + if (!profile) + return -ENOENT; + + perms = allperms; + profile_query_cb(profile, &perms, match_str, match_len); + + return scnprintf(buf, buf_len, + "allow 0x%08x\ndeny 0x%08x\naudit 0x%08x\nquiet 0x%08x\n", + perms.allow, perms.deny, perms.audit, perms.quiet); +} + /* * Transaction based IO. * The file expects a write which triggers the transaction, and then @@ -773,6 +865,9 @@ static int multi_transaction_release(struct inode *inode, struct file *file) return 0; } +#define QUERY_CMD_PROFILE "profile\0" +#define QUERY_CMD_PROFILE_LEN 8 + #define QUERY_CMD_DATA "data\0" #define QUERY_CMD_DATA_LEN 5 @@ -810,7 +905,12 @@ static ssize_t aa_write_access(struct file *file, const char __user *ubuf, if (IS_ERR(t)) return PTR_ERR(t); - if (count > QUERY_CMD_DATA_LEN && + if (count > QUERY_CMD_PROFILE_LEN && + !memcmp(t->data, QUERY_CMD_PROFILE, QUERY_CMD_PROFILE_LEN)) { + len = query_label(t->data, MULTI_TRANSACTION_LIMIT, + t->data + QUERY_CMD_PROFILE_LEN, + count - QUERY_CMD_PROFILE_LEN, true); + } else if (count > QUERY_CMD_DATA_LEN && !memcmp(t->data, QUERY_CMD_DATA, QUERY_CMD_DATA_LEN)) { len = query_data(t->data, MULTI_TRANSACTION_LIMIT, t->data + QUERY_CMD_DATA_LEN, @@ -1952,6 +2052,7 @@ static struct aa_sfs_entry aa_sfs_entry_policy[] = { }; static struct aa_sfs_entry aa_sfs_entry_query_label[] = { + AA_SFS_FILE_STRING("perms", "allow deny audit quiet"), AA_SFS_FILE_BOOLEAN("data", 1), AA_SFS_FILE_BOOLEAN("multi_transaction", 1), { } -- cgit v1.2.3 From 39d84824eae3b1348408237173c710473e726ca9 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Thu, 30 Mar 2017 05:25:23 -0700 Subject: apparmor: provide information about path buffer size at boot Signed-off-by: John Johansen --- security/apparmor/lsm.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c index a128f1772135..8ab00c98613f 100644 --- a/security/apparmor/lsm.c +++ b/security/apparmor/lsm.c @@ -774,11 +774,18 @@ static int param_get_aabool(char *buffer, const struct kernel_param *kp) static int param_set_aauint(const char *val, const struct kernel_param *kp) { + int error; + if (!apparmor_enabled) return -EINVAL; - if (apparmor_initialized && !policy_admin_capable(NULL)) + /* file is ro but enforce 2nd line check */ + if (apparmor_initialized) return -EPERM; - return param_set_uint(val, kp); + + error = param_set_uint(val, kp); + pr_info("AppArmor: buffer size set to %d bytes\n", aa_g_path_max); + + return error; } static int param_get_aauint(char *buffer, const struct kernel_param *kp) -- cgit v1.2.3 From ae3b31653691b9c5b572b99596de3dfcc8f05006 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Fri, 2 Jun 2017 13:50:22 -0700 Subject: apparmor: cleanup __find_child() Signed-off-by: John Johansen --- security/apparmor/policy.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/security/apparmor/policy.c b/security/apparmor/policy.c index 65e98d0491f4..0a99e5324da0 100644 --- a/security/apparmor/policy.c +++ b/security/apparmor/policy.c @@ -397,33 +397,33 @@ fail: /* TODO: profile accounting - setup in remove */ /** - * __find_child - find a profile on @head list with a name matching @name + * __strn_find_child - find a profile on @head list using substring of @name * @head: list to search (NOT NULL) * @name: name of profile (NOT NULL) + * @len: length of @name substring to match * * Requires: rcu_read_lock be held * * Returns: unrefcounted profile ptr, or NULL if not found */ -static struct aa_profile *__find_child(struct list_head *head, const char *name) +static struct aa_profile *__strn_find_child(struct list_head *head, + const char *name, int len) { - return (struct aa_profile *)__policy_find(head, name); + return (struct aa_profile *)__policy_strn_find(head, name, len); } /** - * __strn_find_child - find a profile on @head list using substring of @name + * __find_child - find a profile on @head list with a name matching @name * @head: list to search (NOT NULL) * @name: name of profile (NOT NULL) - * @len: length of @name substring to match * * Requires: rcu_read_lock be held * * Returns: unrefcounted profile ptr, or NULL if not found */ -static struct aa_profile *__strn_find_child(struct list_head *head, - const char *name, int len) +static struct aa_profile *__find_child(struct list_head *head, const char *name) { - return (struct aa_profile *)__policy_strn_find(head, name, len); + return __strn_find_child(head, name, strlen(name)); } /** -- cgit v1.2.3 From 3664268f19ea07bec55df92fe53ff9ed28968bcc Mon Sep 17 00:00:00 2001 From: John Johansen Date: Fri, 2 Jun 2017 17:44:27 -0700 Subject: apparmor: add namespace lookup fns() Currently lookups are restricted to a single ns component in the path. However when namespaces are allowed to have separate views, and scopes this will not be sufficient, as it will be possible to have a multiple component ns path in scope. Add some ns lookup fns() to allow this and use them. Signed-off-by: John Johansen --- security/apparmor/include/policy_ns.h | 13 +++++++++ security/apparmor/policy.c | 10 ++++--- security/apparmor/policy_ns.c | 54 +++++++++++++++++++++++++++++++++++ 3 files changed, 73 insertions(+), 4 deletions(-) diff --git a/security/apparmor/include/policy_ns.h b/security/apparmor/include/policy_ns.h index 23e7cb770226..2f7e480a34e0 100644 --- a/security/apparmor/include/policy_ns.h +++ b/security/apparmor/include/policy_ns.h @@ -89,6 +89,8 @@ void aa_free_ns_kref(struct kref *kref); struct aa_ns *aa_find_ns(struct aa_ns *root, const char *name); struct aa_ns *aa_findn_ns(struct aa_ns *root, const char *name, size_t n); +struct aa_ns *__aa_lookupn_ns(struct aa_ns *view, const char *hname, size_t n); +struct aa_ns *aa_lookupn_ns(struct aa_ns *view, const char *name, size_t n); struct aa_ns *__aa_find_or_create_ns(struct aa_ns *parent, const char *name, struct dentry *dir); struct aa_ns *aa_prepare_ns(struct aa_ns *root, const char *name); @@ -148,4 +150,15 @@ static inline struct aa_ns *__aa_find_ns(struct list_head *head, return __aa_findn_ns(head, name, strlen(name)); } +static inline struct aa_ns *__aa_lookup_ns(struct aa_ns *base, + const char *hname) +{ + return __aa_lookupn_ns(base, hname, strlen(hname)); +} + +static inline struct aa_ns *aa_lookup_ns(struct aa_ns *view, const char *name) +{ + return aa_lookupn_ns(view, name, strlen(name)); +} + #endif /* AA_NAMESPACE_H */ diff --git a/security/apparmor/policy.c b/security/apparmor/policy.c index 0a99e5324da0..d95aae6bf710 100644 --- a/security/apparmor/policy.c +++ b/security/apparmor/policy.c @@ -566,7 +566,7 @@ struct aa_profile *aa_fqlookupn_profile(struct aa_profile *base, name = aa_splitn_fqname(fqname, n, &ns_name, &ns_len); if (ns_name) { - ns = aa_findn_ns(base->ns, ns_name, ns_len); + ns = aa_lookupn_ns(base->ns, ns_name, ns_len); if (!ns) return NULL; } else @@ -1108,7 +1108,7 @@ ssize_t aa_remove_profiles(struct aa_ns *view, struct aa_profile *subj, struct aa_ns *root = NULL, *ns = NULL; struct aa_profile *profile = NULL; const char *name = fqname, *info = NULL; - char *ns_name = NULL; + const char *ns_name = NULL; ssize_t error = 0; if (*fqname == 0) { @@ -1120,9 +1120,11 @@ ssize_t aa_remove_profiles(struct aa_ns *view, struct aa_profile *subj, root = view; if (fqname[0] == ':') { - name = aa_split_fqname(fqname, &ns_name); + size_t ns_len; + + name = aa_splitn_fqname(fqname, size, &ns_name, &ns_len); /* released below */ - ns = aa_find_ns(root, ns_name); + ns = aa_lookupn_ns(root, ns_name, ns_len); if (!ns) { info = "namespace does not exist"; error = -ENOENT; diff --git a/security/apparmor/policy_ns.c b/security/apparmor/policy_ns.c index f3418a9e59b1..c05316809a5e 100644 --- a/security/apparmor/policy_ns.c +++ b/security/apparmor/policy_ns.c @@ -183,6 +183,60 @@ struct aa_ns *aa_find_ns(struct aa_ns *root, const char *name) return aa_findn_ns(root, name, strlen(name)); } +/** + * __aa_lookupn_ns - lookup the namespace matching @hname + * @base: base list to start looking up profile name from (NOT NULL) + * @hname: hierarchical ns name (NOT NULL) + * @n: length of @hname + * + * Requires: rcu_read_lock be held + * + * Returns: unrefcounted ns pointer or NULL if not found + * + * Do a relative name lookup, recursing through profile tree. + */ +struct aa_ns *__aa_lookupn_ns(struct aa_ns *view, const char *hname, size_t n) +{ + struct aa_ns *ns = view; + const char *split; + + for (split = strnstr(hname, "//", n); split; + split = strnstr(hname, "//", n)) { + ns = __aa_findn_ns(&ns->sub_ns, hname, split - hname); + if (!ns) + return NULL; + + n -= split + 2 - hname; + hname = split + 2; + } + + if (n) + return __aa_findn_ns(&ns->sub_ns, hname, n); + return NULL; +} + +/** + * aa_lookupn_ns - look up a policy namespace relative to @view + * @view: namespace to search in (NOT NULL) + * @name: name of namespace to find (NOT NULL) + * @n: length of @name + * + * Returns: a refcounted namespace on the list, or NULL if no namespace + * called @name exists. + * + * refcount released by caller + */ +struct aa_ns *aa_lookupn_ns(struct aa_ns *view, const char *name, size_t n) +{ + struct aa_ns *ns = NULL; + + rcu_read_lock(); + ns = aa_get_ns(__aa_lookupn_ns(view, name, n)); + rcu_read_unlock(); + + return ns; +} + static struct aa_ns *__aa_create_ns(struct aa_ns *parent, const char *name, struct dentry *dir) { -- cgit v1.2.3 From 60285eb3e7c8827e00e2f2b54561a8cca07d802f Mon Sep 17 00:00:00 2001 From: John Johansen Date: Sun, 4 Jun 2017 12:22:22 -0700 Subject: apparmor: fix policy load/remove semantics The namespace being passed into the replace/remove profiles fns() is not the view, but the namespace specified by the inode from the file hook (if present) or the loading tasks ns, if accessing the top level virtualized load/replace file interface. Signed-off-by: John Johansen --- security/apparmor/apparmorfs.c | 6 ++---- security/apparmor/policy.c | 22 +++++++++++----------- 2 files changed, 13 insertions(+), 15 deletions(-) diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c index 105a1da57b8f..4f4cd98d2b3b 100644 --- a/security/apparmor/apparmorfs.c +++ b/security/apparmor/apparmorfs.c @@ -418,8 +418,7 @@ static ssize_t policy_update(u32 mask, const char __user *buf, size_t size, data = aa_simple_write_to_buffer(buf, size, size, pos); error = PTR_ERR(data); if (!IS_ERR(data)) { - error = aa_replace_profiles(ns ? ns : profile->ns, profile, - mask, data); + error = aa_replace_profiles(ns, profile, mask, data); aa_put_loaddata(data); } @@ -486,8 +485,7 @@ static ssize_t profile_remove(struct file *f, const char __user *buf, error = PTR_ERR(data); if (!IS_ERR(data)) { data->data[size] = 0; - error = aa_remove_profiles(ns ? ns : profile->ns, profile, - data->data, size); + error = aa_remove_profiles(ns, profile, data->data, size); aa_put_loaddata(data); } out: diff --git a/security/apparmor/policy.c b/security/apparmor/policy.c index d95aae6bf710..29e04638790f 100644 --- a/security/apparmor/policy.c +++ b/security/apparmor/policy.c @@ -831,7 +831,7 @@ static int __lookup_replace(struct aa_ns *ns, const char *hname, /** * aa_replace_profiles - replace profile(s) on the profile list - * @view: namespace load is viewed from + * @policy_ns: namespace load is occurring on * @label: label that is attempting to load/replace policy * @mask: permission mask * @udata: serialized data stream (NOT NULL) @@ -842,7 +842,7 @@ static int __lookup_replace(struct aa_ns *ns, const char *hname, * * Returns: size of data consumed else error code on failure. */ -ssize_t aa_replace_profiles(struct aa_ns *view, struct aa_profile *profile, +ssize_t aa_replace_profiles(struct aa_ns *policy_ns, struct aa_profile *profile, u32 mask, struct aa_loaddata *udata) { const char *ns_name, *info = NULL; @@ -885,7 +885,8 @@ ssize_t aa_replace_profiles(struct aa_ns *view, struct aa_profile *profile, count++; } if (ns_name) { - ns = aa_prepare_ns(view, ns_name); + ns = aa_prepare_ns(policy_ns ? policy_ns : profile->ns, + ns_name); if (IS_ERR(ns)) { op = OP_PROF_LOAD; info = "failed to prepare namespace"; @@ -895,7 +896,7 @@ ssize_t aa_replace_profiles(struct aa_ns *view, struct aa_profile *profile, goto fail; } } else - ns = aa_get_ns(view); + ns = aa_get_ns(policy_ns ? policy_ns : profile->ns); mutex_lock(&ns->lock); /* check for duplicate rawdata blobs: space and file dedup */ @@ -1090,7 +1091,7 @@ fail: /** * aa_remove_profiles - remove profile(s) from the system - * @view: namespace the remove is being done from + * @policy_ns: namespace the remove is being done from * @subj: profile attempting to remove policy * @fqname: name of the profile or namespace to remove (NOT NULL) * @size: size of the name @@ -1102,10 +1103,10 @@ fail: * * Returns: size of data consume else error code if fails */ -ssize_t aa_remove_profiles(struct aa_ns *view, struct aa_profile *subj, +ssize_t aa_remove_profiles(struct aa_ns *policy_ns, struct aa_profile *subj, char *fqname, size_t size) { - struct aa_ns *root = NULL, *ns = NULL; + struct aa_ns *ns = NULL; struct aa_profile *profile = NULL; const char *name = fqname, *info = NULL; const char *ns_name = NULL; @@ -1117,14 +1118,13 @@ ssize_t aa_remove_profiles(struct aa_ns *view, struct aa_profile *subj, goto fail; } - root = view; - if (fqname[0] == ':') { size_t ns_len; name = aa_splitn_fqname(fqname, size, &ns_name, &ns_len); /* released below */ - ns = aa_lookupn_ns(root, ns_name, ns_len); + ns = aa_lookupn_ns(policy_ns ? policy_ns : subj->ns, ns_name, + ns_len); if (!ns) { info = "namespace does not exist"; error = -ENOENT; @@ -1132,7 +1132,7 @@ ssize_t aa_remove_profiles(struct aa_ns *view, struct aa_profile *subj, } } else /* released below */ - ns = aa_get_ns(root); + ns = aa_get_ns(policy_ns ? policy_ns : subj->ns); if (!name) { /* remove namespace - can only happen if fqname[0] == ':' */ -- cgit v1.2.3 From 5262ef60b1bcc40e17476fda53284621af9b0bab Mon Sep 17 00:00:00 2001 From: John Johansen Date: Fri, 9 Jun 2017 02:28:19 -0700 Subject: apparmor: fix apparmor_query data The data being queried isn't always the current profile and a lookup relative to the current profile should be done. Signed-off-by: John Johansen --- security/apparmor/apparmorfs.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c index 4f4cd98d2b3b..818b70130bae 100644 --- a/security/apparmor/apparmorfs.c +++ b/security/apparmor/apparmorfs.c @@ -650,7 +650,7 @@ static ssize_t query_data(char *buf, size_t buf_len, { char *out; const char *key; - struct aa_profile *profile; + struct aa_profile *profile, *curr; struct aa_data *data; u32 bytes, blocks; __le32 outle32; @@ -667,7 +667,10 @@ static ssize_t query_data(char *buf, size_t buf_len, if (buf_len < sizeof(bytes) + sizeof(blocks)) return -EINVAL; /* not enough space */ - profile = aa_current_profile(); + curr = aa_current_profile(); + profile = aa_fqlookupn_profile(curr, query, strnlen(query, query_len)); + if (!profile) + return -ENOENT; /* We are going to leave space for two numbers. The first is the total * number of bytes we are writing after the first number. This is so @@ -696,6 +699,7 @@ static ssize_t query_data(char *buf, size_t buf_len, blocks++; } } + aa_put_profile(profile); outle32 = __cpu_to_le32(out - buf - sizeof(bytes)); memcpy(buf, &outle32, sizeof(outle32)); -- cgit v1.2.3 From d9f02d9c237aa603d781fe5165ebe383c554376d Mon Sep 17 00:00:00 2001 From: John Johansen Date: Fri, 9 Jun 2017 02:11:29 -0700 Subject: apparmor: fix display of ns name The ns name being displayed should go through an ns view lookup. Signed-off-by: John Johansen --- security/apparmor/apparmorfs.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c index 818b70130bae..b64ea21a42ad 100644 --- a/security/apparmor/apparmorfs.c +++ b/security/apparmor/apparmorfs.c @@ -1105,7 +1105,7 @@ static int seq_ns_name_show(struct seq_file *seq, void *v) { struct aa_ns *ns = aa_current_profile()->ns; - seq_printf(seq, "%s\n", ns->base.name); + seq_printf(seq, "%s\n", aa_ns_name(ns, ns, true)); return 0; } -- cgit v1.2.3 From fe864821d504f33f22b3ce2d5599ae95598db721 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Fri, 9 Jun 2017 05:27:50 -0700 Subject: apparmor: move bprm_committing_creds/committed_creds to lsm.c There is no reason to have the small stubs that don't use domain private functions in domain.c, instead move them to lsm.c and make them static. Signed-off-by: John Johansen --- security/apparmor/domain.c | 30 ------------------------------ security/apparmor/include/domain.h | 2 -- security/apparmor/lsm.c | 30 ++++++++++++++++++++++++++++++ 3 files changed, 30 insertions(+), 32 deletions(-) diff --git a/security/apparmor/domain.c b/security/apparmor/domain.c index a0ba33454b8c..2b1524c79fb8 100644 --- a/security/apparmor/domain.c +++ b/security/apparmor/domain.c @@ -539,36 +539,6 @@ int apparmor_bprm_secureexec(struct linux_binprm *bprm) return 0; } -/** - * apparmor_bprm_committing_creds - do task cleanup on committing new creds - * @bprm: binprm for the exec (NOT NULL) - */ -void apparmor_bprm_committing_creds(struct linux_binprm *bprm) -{ - struct aa_profile *profile = __aa_current_profile(); - struct aa_task_ctx *new_ctx = cred_ctx(bprm->cred); - - /* bail out if unconfined or not changing profile */ - if ((new_ctx->profile == profile) || - (unconfined(new_ctx->profile))) - return; - - current->pdeath_signal = 0; - - /* reset soft limits and set hard limits for the new profile */ - __aa_transition_rlimits(profile, new_ctx->profile); -} - -/** - * apparmor_bprm_commited_cred - do cleanup after new creds committed - * @bprm: binprm for the exec (NOT NULL) - */ -void apparmor_bprm_committed_creds(struct linux_binprm *bprm) -{ - /* TODO: cleanup signals - ipc mediation */ - return; -} - /* * Functions for self directed profile change */ diff --git a/security/apparmor/include/domain.h b/security/apparmor/include/domain.h index 30544729878a..6587c4abb7e8 100644 --- a/security/apparmor/include/domain.h +++ b/security/apparmor/include/domain.h @@ -25,8 +25,6 @@ struct aa_domain { int apparmor_bprm_set_creds(struct linux_binprm *bprm); int apparmor_bprm_secureexec(struct linux_binprm *bprm); -void apparmor_bprm_committing_creds(struct linux_binprm *bprm); -void apparmor_bprm_committed_creds(struct linux_binprm *bprm); void aa_free_domain_entries(struct aa_domain *domain); int aa_change_hat(const char *hats[], int count, u64 token, bool permtest); diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c index 8ab00c98613f..35492008658f 100644 --- a/security/apparmor/lsm.c +++ b/security/apparmor/lsm.c @@ -575,6 +575,36 @@ fail: goto out; } +/** + * apparmor_bprm_committing_creds - do task cleanup on committing new creds + * @bprm: binprm for the exec (NOT NULL) + */ +static void apparmor_bprm_committing_creds(struct linux_binprm *bprm) +{ + struct aa_profile *profile = __aa_current_profile(); + struct aa_task_ctx *new_ctx = cred_ctx(bprm->cred); + + /* bail out if unconfined or not changing profile */ + if ((new_ctx->profile == profile) || + (unconfined(new_ctx->profile))) + return; + + current->pdeath_signal = 0; + + /* reset soft limits and set hard limits for the new profile */ + __aa_transition_rlimits(profile, new_ctx->profile); +} + +/** + * apparmor_bprm_committed_cred - do cleanup after new creds committed + * @bprm: binprm for the exec (NOT NULL) + */ +static void apparmor_bprm_committed_creds(struct linux_binprm *bprm) +{ + /* TODO: cleanup signals - ipc mediation */ + return; +} + static int apparmor_task_setrlimit(struct task_struct *task, unsigned int resource, struct rlimit *new_rlim) { -- cgit v1.2.3 From cf797c0e5e312520b0b9f0367039fc0279a07a76 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Fri, 9 Jun 2017 02:08:28 -0700 Subject: apparmor: convert to profile block critical sections There are still a few places where profile replacement fails to update and a stale profile is used for mediation. Fix this by moving to accessing the current label through a critical section that will always ensure mediation is using the current label regardless of whether the tasks cred has been updated or not. Signed-off-by: John Johansen --- security/apparmor/apparmorfs.c | 40 +++++++----- security/apparmor/context.c | 2 +- security/apparmor/domain.c | 5 +- security/apparmor/include/context.h | 123 +++++++++++++++++++++++++++++------- security/apparmor/lsm.c | 41 ++++++++---- security/apparmor/policy_unpack.c | 2 +- security/apparmor/procattr.c | 3 +- security/apparmor/resource.c | 2 +- 8 files changed, 162 insertions(+), 56 deletions(-) diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c index b64ea21a42ad..e2919a0766b0 100644 --- a/security/apparmor/apparmorfs.c +++ b/security/apparmor/apparmorfs.c @@ -407,7 +407,10 @@ static ssize_t policy_update(u32 mask, const char __user *buf, size_t size, { ssize_t error; struct aa_loaddata *data; - struct aa_profile *profile = aa_current_profile(); + struct aa_profile *profile; + + profile = begin_current_profile_crit_section(); + /* high level check about policy management - fine grained in * below after unpack */ @@ -421,6 +424,7 @@ static ssize_t policy_update(u32 mask, const char __user *buf, size_t size, error = aa_replace_profiles(ns, profile, mask, data); aa_put_loaddata(data); } + end_current_profile_crit_section(profile); return error; } @@ -468,7 +472,7 @@ static ssize_t profile_remove(struct file *f, const char __user *buf, ssize_t error; struct aa_ns *ns = aa_get_ns(f->f_inode->i_private); - profile = aa_current_profile(); + profile = begin_current_profile_crit_section(); /* high level check about policy management - fine grained in * below after unpack */ @@ -489,6 +493,7 @@ static ssize_t profile_remove(struct file *f, const char __user *buf, aa_put_loaddata(data); } out: + end_current_profile_crit_section(profile); aa_put_ns(ns); return error; } @@ -667,8 +672,9 @@ static ssize_t query_data(char *buf, size_t buf_len, if (buf_len < sizeof(bytes) + sizeof(blocks)) return -EINVAL; /* not enough space */ - curr = aa_current_profile(); + curr = begin_current_profile_crit_section(); profile = aa_fqlookupn_profile(curr, query, strnlen(query, query_len)); + end_current_profile_crit_section(curr); if (!profile) return -ENOENT; @@ -754,8 +760,9 @@ static ssize_t query_label(char *buf, size_t buf_len, match_str = label_name + label_name_len + 1; match_len = query_len - label_name_len - 1; - curr = aa_current_profile(); + curr = begin_current_profile_crit_section(); profile = aa_fqlookupn_profile(curr, label_name, label_name_len); + end_current_profile_crit_section(curr); if (!profile) return -ENOENT; @@ -1094,18 +1101,22 @@ static const struct file_operations seq_ns_ ##NAME ##_fops = { \ static int seq_ns_level_show(struct seq_file *seq, void *v) { - struct aa_ns *ns = aa_current_profile()->ns; + struct aa_profile *profile; - seq_printf(seq, "%d\n", ns->level); + profile = begin_current_profile_crit_section(); + seq_printf(seq, "%d\n", profile->ns->level); + end_current_profile_crit_section(profile); return 0; } static int seq_ns_name_show(struct seq_file *seq, void *v) { - struct aa_ns *ns = aa_current_profile()->ns; + struct aa_profile *profile; - seq_printf(seq, "%s\n", aa_ns_name(ns, ns, true)); + profile = begin_current_profile_crit_section(); + seq_printf(seq, "%s\n", aa_ns_name(profile->ns, profile->ns, true)); + end_current_profile_crit_section(profile); return 0; } @@ -1530,9 +1541,9 @@ static int ns_mkdir_op(struct inode *dir, struct dentry *dentry, umode_t mode) { struct aa_ns *ns, *parent; /* TODO: improve permission check */ - struct aa_profile *profile = aa_current_profile(); + struct aa_profile *profile = begin_current_profile_crit_section(); int error = aa_may_manage_policy(profile, NULL, AA_MAY_LOAD_POLICY); - + end_current_profile_crit_section(profile); if (error) return error; @@ -1576,9 +1587,9 @@ static int ns_rmdir_op(struct inode *dir, struct dentry *dentry) { struct aa_ns *ns, *parent; /* TODO: improve permission check */ - struct aa_profile *profile = aa_current_profile(); + struct aa_profile *profile = begin_current_profile_crit_section(); int error = aa_may_manage_policy(profile, NULL, AA_MAY_LOAD_POLICY); - + end_current_profile_crit_section(profile); if (error) return error; @@ -1922,10 +1933,9 @@ static struct aa_profile *next_profile(struct aa_ns *root, static void *p_start(struct seq_file *f, loff_t *pos) { struct aa_profile *profile = NULL; - struct aa_ns *root = aa_current_profile()->ns; + struct aa_ns *root = aa_get_current_ns(); loff_t l = *pos; - f->private = aa_get_ns(root); - + f->private = root; /* find the first profile */ mutex_lock(&root->lock); diff --git a/security/apparmor/context.c b/security/apparmor/context.c index 1fc16b88efbf..410b9f7f68a1 100644 --- a/security/apparmor/context.c +++ b/security/apparmor/context.c @@ -79,7 +79,7 @@ struct aa_profile *aa_get_task_profile(struct task_struct *task) struct aa_profile *p; rcu_read_lock(); - p = aa_get_profile(__aa_task_profile(task)); + p = aa_get_newest_profile(__aa_task_raw_profile(task)); rcu_read_unlock(); return p; diff --git a/security/apparmor/domain.c b/security/apparmor/domain.c index 2b1524c79fb8..0c02eac33a45 100644 --- a/security/apparmor/domain.c +++ b/security/apparmor/domain.c @@ -594,7 +594,7 @@ int aa_change_hat(const char *hats[], int count, u64 token, bool permtest) /* released below */ cred = get_current_cred(); ctx = cred_ctx(cred); - profile = aa_get_newest_profile(aa_cred_profile(cred)); + profile = aa_get_newest_cred_profile(cred); previous_profile = aa_get_newest_profile(ctx->previous); if (unconfined(profile)) { @@ -737,7 +737,7 @@ int aa_change_profile(const char *fqname, bool onexec, } cred = get_current_cred(); - profile = aa_cred_profile(cred); + profile = aa_get_newest_cred_profile(cred); /* * Fail explicitly requested domain transitions if no_new_privs @@ -795,6 +795,7 @@ audit: fqname, GLOBAL_ROOT_UID, info, error); aa_put_profile(target); + aa_put_profile(profile); put_cred(cred); return error; diff --git a/security/apparmor/include/context.h b/security/apparmor/include/context.h index 420cfd041218..7665fae7131f 100644 --- a/security/apparmor/include/context.h +++ b/security/apparmor/include/context.h @@ -56,14 +56,14 @@ struct aa_profile *aa_get_task_profile(struct task_struct *task); /** - * aa_cred_profile - obtain cred's profiles + * aa_cred_raw_profile - obtain cred's profiles * @cred: cred to obtain profiles from (NOT NULL) * * Returns: confining profile * * does NOT increment reference count */ -static inline struct aa_profile *aa_cred_profile(const struct cred *cred) +static inline struct aa_profile *aa_cred_raw_profile(const struct cred *cred) { struct aa_task_ctx *ctx = cred_ctx(cred); @@ -72,16 +72,28 @@ static inline struct aa_profile *aa_cred_profile(const struct cred *cred) } /** - * __aa_task_profile - retrieve another task's profile + * aa_get_newest_cred_profile - obtain the newest profile on a cred + * @cred: cred to obtain profile from (NOT NULL) + * + * Returns: newest version of confining profile + */ +static inline +struct aa_profile *aa_get_newest_cred_profile(const struct cred *cred) +{ + return aa_get_newest_profile(aa_cred_raw_profile(cred)); +} + +/** + * __aa_task_raw_profile - retrieve another task's profile * @task: task to query (NOT NULL) * * Returns: @task's profile without incrementing its ref count * * If @task != current needs to be called in RCU safe critical section */ -static inline struct aa_profile *__aa_task_profile(struct task_struct *task) +static inline struct aa_profile *__aa_task_raw_profile(struct task_struct *task) { - return aa_cred_profile(__task_cred(task)); + return aa_cred_raw_profile(__task_cred(task)); } /** @@ -92,50 +104,115 @@ static inline struct aa_profile *__aa_task_profile(struct task_struct *task) */ static inline bool __aa_task_is_confined(struct task_struct *task) { - return !unconfined(__aa_task_profile(task)); + return !unconfined(__aa_task_raw_profile(task)); } /** - * __aa_current_profile - find the current tasks confining profile + * aa_current_raw_profile - find the current tasks confining profile * * Returns: up to date confining profile or the ns unconfined profile (NOT NULL) * * This fn will not update the tasks cred to the most up to date version * of the profile so it is safe to call when inside of locks. */ -static inline struct aa_profile *__aa_current_profile(void) +static inline struct aa_profile *aa_current_raw_profile(void) { - return aa_cred_profile(current_cred()); + return aa_cred_raw_profile(current_cred()); } /** - * aa_current_profile - find the current tasks confining profile and do updates + * aa_get_current_profile - get the newest version of the current tasks profile * - * Returns: up to date confining profile or the ns unconfined profile (NOT NULL) + * Returns: newest version of confining profile (NOT NULL) + * + * This fn will not update the tasks cred, so it is safe inside of locks * - * This fn will update the tasks cred structure if the profile has been - * replaced. Not safe to call inside locks + * The returned reference must be put with aa_put_profile() */ -static inline struct aa_profile *aa_current_profile(void) +static inline struct aa_profile *aa_get_current_profile(void) { - const struct aa_task_ctx *ctx = current_ctx(); - struct aa_profile *profile; + struct aa_profile *p = aa_current_raw_profile(); - AA_BUG(!ctx || !ctx->profile); + if (profile_is_stale(p)) + return aa_get_newest_profile(p); + return aa_get_profile(p); +} - if (profile_is_stale(ctx->profile)) { - profile = aa_get_newest_profile(ctx->profile); - aa_replace_current_profile(profile); +#define __end_current_profile_crit_section(X) \ + end_current_profile_crit_section(X) + +/** + * end_profile_crit_section - put a reference found with begin_current_profile.. + * @profile: profile reference to put + * + * Should only be used with a reference obtained with + * begin_current_profile_crit_section and never used in situations where the + * task cred may be updated + */ +static inline void end_current_profile_crit_section(struct aa_profile *profile) +{ + if (profile != aa_current_raw_profile()) aa_put_profile(profile); - ctx = current_ctx(); +} + +/** + * __begin_current_profile_crit_section - current's confining profile + * + * Returns: up to date confining profile or the ns unconfined profile (NOT NULL) + * + * safe to call inside locks + * + * The returned reference must be put with __end_current_profile_crit_section() + * This must NOT be used if the task cred could be updated within the + * critical section between __begin_current_profile_crit_section() .. + * __end_current_profile_crit_section() + */ +static inline struct aa_profile *__begin_current_profile_crit_section(void) +{ + struct aa_profile *profile = aa_current_raw_profile(); + + if (profile_is_stale(profile)) + profile = aa_get_newest_profile(profile); + + return profile; +} + +/** + * begin_current_profile_crit_section - current's profile and update if needed + * + * Returns: up to date confining profile or the ns unconfined profile (NOT NULL) + * + * Not safe to call inside locks + * + * The returned reference must be put with end_current_profile_crit_section() + * This must NOT be used if the task cred could be updated within the + * critical section between begin_current_profile_crit_section() .. + * end_current_profile_crit_section() + */ +static inline struct aa_profile *begin_current_profile_crit_section(void) +{ + struct aa_profile *profile = aa_current_raw_profile(); + + if (profile_is_stale(profile)) { + profile = aa_get_newest_profile(profile); + if (aa_replace_current_profile(profile) == 0) + /* task cred will keep the reference */ + aa_put_profile(profile); } - return ctx->profile; + return profile; } static inline struct aa_ns *aa_get_current_ns(void) { - return aa_get_ns(__aa_current_profile()->ns); + struct aa_profile *profile; + struct aa_ns *ns; + + profile = __begin_current_profile_crit_section(); + ns = aa_get_ns(profile->ns); + __end_current_profile_crit_section(profile); + + return ns; } /** diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c index 35492008658f..49b780b4c53b 100644 --- a/security/apparmor/lsm.c +++ b/security/apparmor/lsm.c @@ -120,7 +120,7 @@ static int apparmor_capget(struct task_struct *target, kernel_cap_t *effective, rcu_read_lock(); cred = __task_cred(target); - profile = aa_cred_profile(cred); + profile = aa_get_newest_cred_profile(cred); /* * cap_capget is stacked ahead of this and will @@ -131,6 +131,7 @@ static int apparmor_capget(struct task_struct *target, kernel_cap_t *effective, *permitted = cap_intersect(*permitted, profile->caps.allow); } rcu_read_unlock(); + aa_put_profile(profile); return 0; } @@ -141,9 +142,11 @@ static int apparmor_capable(const struct cred *cred, struct user_namespace *ns, struct aa_profile *profile; int error = 0; - profile = aa_cred_profile(cred); + profile = aa_get_newest_cred_profile(cred); if (!unconfined(profile)) error = aa_capable(profile, cap, audit); + aa_put_profile(profile); + return error; } @@ -162,9 +165,10 @@ static int common_perm(const char *op, const struct path *path, u32 mask, struct aa_profile *profile; int error = 0; - profile = __aa_current_profile(); + profile = __begin_current_profile_crit_section(); if (!unconfined(profile)) error = aa_path_perm(op, profile, path, 0, mask, cond); + __end_current_profile_crit_section(profile); return error; } @@ -297,9 +301,11 @@ static int apparmor_path_link(struct dentry *old_dentry, const struct path *new_ if (!path_mediated_fs(old_dentry)) return 0; - profile = aa_current_profile(); + profile = begin_current_profile_crit_section(); if (!unconfined(profile)) error = aa_path_link(profile, old_dentry, new_dir, new_dentry); + end_current_profile_crit_section(profile); + return error; } @@ -312,7 +318,7 @@ static int apparmor_path_rename(const struct path *old_dir, struct dentry *old_d if (!path_mediated_fs(old_dentry)) return 0; - profile = aa_current_profile(); + profile = begin_current_profile_crit_section(); if (!unconfined(profile)) { struct path old_path = { .mnt = old_dir->mnt, .dentry = old_dentry }; @@ -332,6 +338,8 @@ static int apparmor_path_rename(const struct path *old_dir, struct dentry *old_d AA_MAY_CREATE, &cond); } + end_current_profile_crit_section(profile); + return error; } @@ -369,7 +377,7 @@ static int apparmor_file_open(struct file *file, const struct cred *cred) return 0; } - profile = aa_cred_profile(cred); + profile = aa_get_newest_cred_profile(cred); if (!unconfined(profile)) { struct inode *inode = file_inode(file); struct path_cond cond = { inode->i_uid, inode->i_mode }; @@ -379,18 +387,23 @@ static int apparmor_file_open(struct file *file, const struct cred *cred) /* todo cache full allowed permissions set and state */ fctx->allow = aa_map_file_to_perms(file); } + aa_put_profile(profile); return error; } static int apparmor_file_alloc_security(struct file *file) { + int error = 0; + /* freed by apparmor_file_free_security */ + struct aa_profile *profile = begin_current_profile_crit_section(); file->f_security = aa_alloc_file_context(GFP_KERNEL); if (!file->f_security) return -ENOMEM; - return 0; + end_current_profile_crit_section(profile); + return error; } static void apparmor_file_free_security(struct file *file) @@ -403,16 +416,17 @@ static void apparmor_file_free_security(struct file *file) static int common_file_perm(const char *op, struct file *file, u32 mask) { struct aa_file_ctx *fctx = file->f_security; - struct aa_profile *profile, *fprofile = aa_cred_profile(file->f_cred); + struct aa_profile *profile, *fprofile; int error = 0; + fprofile = aa_cred_raw_profile(file->f_cred); AA_BUG(!fprofile); if (!file->f_path.mnt || !path_mediated_fs(file->f_path.dentry)) return 0; - profile = __aa_current_profile(); + profile = __begin_current_profile_crit_section(); /* revalidate access, if task is unconfined, or the cached cred * doesn't match or if the request is for more permissions than @@ -424,6 +438,7 @@ static int common_file_perm(const char *op, struct file *file, u32 mask) if (!unconfined(profile) && !unconfined(fprofile) && ((fprofile != profile) || (mask & ~fctx->allow))) error = aa_file_perm(op, profile, file, mask); + __end_current_profile_crit_section(profile); return error; } @@ -568,10 +583,11 @@ out: return error; fail: - aad(&sa)->profile = aa_current_profile(); + aad(&sa)->profile = begin_current_profile_crit_section(); aad(&sa)->info = name; aad(&sa)->error = error = -EINVAL; aa_audit_msg(AUDIT_APPARMOR_DENIED, &sa, NULL); + end_current_profile_crit_section(aad(&sa)->profile); goto out; } @@ -581,7 +597,7 @@ fail: */ static void apparmor_bprm_committing_creds(struct linux_binprm *bprm) { - struct aa_profile *profile = __aa_current_profile(); + struct aa_profile *profile = aa_current_raw_profile(); struct aa_task_ctx *new_ctx = cred_ctx(bprm->cred); /* bail out if unconfined or not changing profile */ @@ -608,11 +624,12 @@ static void apparmor_bprm_committed_creds(struct linux_binprm *bprm) static int apparmor_task_setrlimit(struct task_struct *task, unsigned int resource, struct rlimit *new_rlim) { - struct aa_profile *profile = __aa_current_profile(); + struct aa_profile *profile = __begin_current_profile_crit_section(); int error = 0; if (!unconfined(profile)) error = aa_task_setrlimit(profile, task, resource, new_rlim); + __end_current_profile_crit_section(profile); return error; } diff --git a/security/apparmor/policy_unpack.c b/security/apparmor/policy_unpack.c index e521df1bd1fb..cac69f2cb86d 100644 --- a/security/apparmor/policy_unpack.c +++ b/security/apparmor/policy_unpack.c @@ -107,7 +107,7 @@ static int audit_iface(struct aa_profile *new, const char *ns_name, const char *name, const char *info, struct aa_ext *e, int error) { - struct aa_profile *profile = __aa_current_profile(); + struct aa_profile *profile = aa_current_raw_profile(); DEFINE_AUDIT_DATA(sa, LSM_AUDIT_DATA_NONE, NULL); if (e) aad(&sa)->iface.pos = e->pos - e->start; diff --git a/security/apparmor/procattr.c b/security/apparmor/procattr.c index 3466a27bca09..41b7b64a906b 100644 --- a/security/apparmor/procattr.c +++ b/security/apparmor/procattr.c @@ -41,7 +41,7 @@ int aa_getprocattr(struct aa_profile *profile, char **string) const char *mode_str = aa_profile_mode_names[profile->mode]; const char *ns_name = NULL; struct aa_ns *ns = profile->ns; - struct aa_ns *current_ns = __aa_current_profile()->ns; + struct aa_ns *current_ns = aa_get_current_ns(); char *s; if (!aa_ns_visible(current_ns, ns, true)) @@ -75,6 +75,7 @@ int aa_getprocattr(struct aa_profile *profile, char **string) else sprintf(s, "%s (%s)\n", profile->base.hname, mode_str); *string = str; + aa_put_ns(current_ns); /* NOTE: len does not include \0 of string, not saved as part of file */ return len; diff --git a/security/apparmor/resource.c b/security/apparmor/resource.c index ae66151fdc38..b26f1dac5106 100644 --- a/security/apparmor/resource.c +++ b/security/apparmor/resource.c @@ -90,7 +90,7 @@ int aa_task_setrlimit(struct aa_profile *profile, struct task_struct *task, int error = 0; rcu_read_lock(); - task_profile = aa_get_profile(aa_cred_profile(__task_cred(task))); + task_profile = aa_get_newest_cred_profile((__task_cred(task))); rcu_read_unlock(); /* TODO: extend resource control to handle other (non current) -- cgit v1.2.3 From a1bd627b46d169268a0ee5960899fb5be960a317 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Fri, 9 Jun 2017 07:09:05 -0700 Subject: apparmor: share profile name on replacement The profile names are the same, leverage this. Signed-off-by: John Johansen --- security/apparmor/include/lib.h | 32 +++++++++++++++++++++++++++++++- security/apparmor/lib.c | 40 ++++++++++++++++++++++++++++++++-------- security/apparmor/policy.c | 9 +++++++++ 3 files changed, 72 insertions(+), 9 deletions(-) diff --git a/security/apparmor/include/lib.h b/security/apparmor/include/lib.h index 89524aade657..593877d38088 100644 --- a/security/apparmor/include/lib.h +++ b/security/apparmor/include/lib.h @@ -100,6 +100,36 @@ static inline bool path_mediated_fs(struct dentry *dentry) return !(dentry->d_sb->s_flags & MS_NOUSER); } + +struct counted_str { + struct kref count; + char name[]; +}; + +#define str_to_counted(str) \ + ((struct counted_str *)(str - offsetof(struct counted_str, name))) + +#define __counted /* atm just a notation */ + +void aa_str_kref(struct kref *kref); +char *aa_str_alloc(int size, gfp_t gfp); + + +static inline __counted char *aa_get_str(__counted char *str) +{ + if (str) + kref_get(&(str_to_counted(str)->count)); + + return str; +} + +static inline void aa_put_str(__counted char *str) +{ + if (str) + kref_put(&str_to_counted(str)->count, aa_str_kref); +} + + /* struct aa_policy - common part of both namespaces and profiles * @name: name of the object * @hname - The hierarchical name @@ -108,7 +138,7 @@ static inline bool path_mediated_fs(struct dentry *dentry) */ struct aa_policy { const char *name; - const char *hname; + __counted char *hname; struct list_head list; struct list_head profiles; }; diff --git a/security/apparmor/lib.c b/security/apparmor/lib.c index 3e9146e68c4a..0ceecdbb4658 100644 --- a/security/apparmor/lib.c +++ b/security/apparmor/lib.c @@ -134,6 +134,24 @@ void aa_info_message(const char *str) printk(KERN_INFO "AppArmor: %s\n", str); } +__counted char *aa_str_alloc(int size, gfp_t gfp) +{ + struct counted_str *str; + + str = kmalloc(sizeof(struct counted_str) + size, gfp); + if (!str) + return NULL; + + kref_init(&str->count); + return str->name; +} + +void aa_str_kref(struct kref *kref) +{ + kfree(container_of(kref, struct counted_str, count)); +} + + const char aa_file_perm_chrs[] = "xwracd km l "; const char *aa_file_perm_names[] = { "exec", @@ -296,6 +314,7 @@ void aa_compute_perms(struct aa_dfa *dfa, unsigned int state, * @policy: policy to initialize (NOT NULL) * @prefix: prefix name if any is required. (MAYBE NULL) * @name: name of the policy, init will make a copy of it (NOT NULL) + * @gfp: allocation mode * * Note: this fn creates a copy of strings passed in * @@ -304,16 +323,21 @@ void aa_compute_perms(struct aa_dfa *dfa, unsigned int state, bool aa_policy_init(struct aa_policy *policy, const char *prefix, const char *name, gfp_t gfp) { + char *hname; + /* freed by policy_free */ if (prefix) { - policy->hname = kmalloc(strlen(prefix) + strlen(name) + 3, - gfp); - if (policy->hname) - sprintf((char *)policy->hname, "%s//%s", prefix, name); - } else - policy->hname = kstrdup(name, gfp); - if (!policy->hname) + hname = aa_str_alloc(strlen(prefix) + strlen(name) + 3, gfp); + if (hname) + sprintf(hname, "%s//%s", prefix, name); + } else { + hname = aa_str_alloc(strlen(name) + 1, gfp); + if (hname) + strcpy(hname, name); + } + if (!hname) return false; + policy->hname = hname; /* base.name is a substring of fqname */ policy->name = basename(policy->hname); INIT_LIST_HEAD(&policy->list); @@ -332,5 +356,5 @@ void aa_policy_destroy(struct aa_policy *policy) AA_BUG(on_list_rcu(&policy->list)); /* don't free name as its a subset of hname */ - kzfree(policy->hname); + aa_put_str(policy->hname); } diff --git a/security/apparmor/policy.c b/security/apparmor/policy.c index 29e04638790f..af925c07ad4e 100644 --- a/security/apparmor/policy.c +++ b/security/apparmor/policy.c @@ -829,6 +829,14 @@ static int __lookup_replace(struct aa_ns *ns, const char *hname, return 0; } +static void share_name(struct aa_profile *old, struct aa_profile *new) +{ + aa_put_str(new->base.hname); + aa_get_str(old->base.hname); + new->base.hname = old->base.hname; + new->base.name = old->base.name; +} + /** * aa_replace_profiles - replace profile(s) on the profile list * @policy_ns: namespace load is occurring on @@ -1013,6 +1021,7 @@ ssize_t aa_replace_profiles(struct aa_ns *policy_ns, struct aa_profile *profile, NULL, error); if (ent->old) { + share_name(ent->old, ent->new); __replace_profile(ent->old, ent->new, 1); if (ent->rename) { /* aafs interface uses proxy */ -- cgit v1.2.3 From 435222bc1bcc11636f4159fd3ce9e481ab7f2c7c Mon Sep 17 00:00:00 2001 From: John Johansen Date: Fri, 9 Jun 2017 07:16:46 -0700 Subject: apparmor: refactor updating profiles to the newest parent Signed-off-by: John Johansen --- security/apparmor/policy.c | 35 +++++++++++++++++++++++++++++++---- 1 file changed, 31 insertions(+), 4 deletions(-) diff --git a/security/apparmor/policy.c b/security/apparmor/policy.c index af925c07ad4e..20613186b1d8 100644 --- a/security/apparmor/policy.c +++ b/security/apparmor/policy.c @@ -837,6 +837,27 @@ static void share_name(struct aa_profile *old, struct aa_profile *new) new->base.name = old->base.name; } +/* Update to newest version of parent after previous replacements + * Returns: unrefcount newest version of parent + */ +static struct aa_profile *update_to_newest_parent(struct aa_profile *new) +{ + struct aa_profile *parent, *newest; + + parent = rcu_dereference_protected(new->parent, + mutex_is_locked(&new->ns->lock)); + newest = aa_get_newest_profile(parent); + + /* parent replaced in this atomic set? */ + if (newest != parent) { + aa_put_profile(parent); + rcu_assign_pointer(new->parent, newest); + } else + aa_put_profile(newest); + + return newest; +} + /** * aa_replace_profiles - replace profile(s) on the profile list * @policy_ns: namespace load is occurring on @@ -1052,10 +1073,16 @@ ssize_t aa_replace_profiles(struct aa_ns *policy_ns, struct aa_profile *profile, __list_add_profile(&newest->base.profiles, ent->new); aa_put_profile(newest); } else { - /* aafs interface uses proxy */ - rcu_assign_pointer(ent->new->proxy->profile, - aa_get_profile(ent->new)); - __list_add_profile(&ns->base.profiles, ent->new); + struct list_head *lh; + + if (rcu_access_pointer(ent->new->parent)) { + struct aa_profile *parent; + + parent = update_to_newest_parent(ent->new); + lh = &parent->base.profiles; + } else + lh = &ns->base.profiles; + __list_add_profile(lh, ent->new); } skip: aa_load_ent_free(ent); -- cgit v1.2.3 From dca91402e999aa0824c4144ad216bd61dd4fe3ff Mon Sep 17 00:00:00 2001 From: John Johansen Date: Fri, 9 Jun 2017 07:24:18 -0700 Subject: apparmor: cleanup remove unused and not fully implemented profile rename Remove the partially implemented code, until this can be properly implemented. Signed-off-by: John Johansen --- security/apparmor/policy.c | 39 ++------------------------------------- 1 file changed, 2 insertions(+), 37 deletions(-) diff --git a/security/apparmor/policy.c b/security/apparmor/policy.c index 20613186b1d8..605cb5949c60 100644 --- a/security/apparmor/policy.c +++ b/security/apparmor/policy.c @@ -995,14 +995,7 @@ ssize_t aa_replace_profiles(struct aa_ns *policy_ns, struct aa_profile *profile, } } list_for_each_entry(ent, &lh, list) { - if (ent->old) { - /* inherit old interface files */ - - /* if (ent->rename) - TODO: support rename */ - /* } else if (ent->rename) { - TODO: support rename */ - } else { + if (!ent->old) { struct dentry *parent; if (rcu_access_pointer(ent->new->parent)) { struct aa_profile *p; @@ -1014,7 +1007,7 @@ ssize_t aa_replace_profiles(struct aa_ns *policy_ns, struct aa_profile *profile, } if (error) { - info = "failed to create "; + info = "failed to create"; goto fail_lock; } } @@ -1044,34 +1037,6 @@ ssize_t aa_replace_profiles(struct aa_ns *policy_ns, struct aa_profile *profile, if (ent->old) { share_name(ent->old, ent->new); __replace_profile(ent->old, ent->new, 1); - if (ent->rename) { - /* aafs interface uses proxy */ - struct aa_proxy *r = ent->new->proxy; - rcu_assign_pointer(r->profile, - aa_get_profile(ent->new)); - __replace_profile(ent->rename, ent->new, 0); - } - } else if (ent->rename) { - /* aafs interface uses proxy */ - rcu_assign_pointer(ent->new->proxy->profile, - aa_get_profile(ent->new)); - __replace_profile(ent->rename, ent->new, 0); - } else if (ent->new->parent) { - struct aa_profile *parent, *newest; - parent = aa_deref_parent(ent->new); - newest = aa_get_newest_profile(parent); - - /* parent replaced in this atomic set? */ - if (newest != parent) { - aa_get_profile(newest); - rcu_assign_pointer(ent->new->parent, newest); - aa_put_profile(parent); - } - /* aafs interface uses proxy */ - rcu_assign_pointer(ent->new->proxy->profile, - aa_get_profile(ent->new)); - __list_add_profile(&newest->base.profiles, ent->new); - aa_put_profile(newest); } else { struct list_head *lh; -- cgit v1.2.3 From df8073c67fd8acb7e79f203ba4c0fa456bb82762 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Fri, 9 Jun 2017 11:36:48 -0700 Subject: apparmor: convert aa_change_XXX bool parameters to flags Instead of passing multiple booleans consolidate on a single flags field. Signed-off-by: John Johansen --- security/apparmor/domain.c | 26 +++++++++++++------------- security/apparmor/include/domain.h | 10 +++++++--- security/apparmor/include/procattr.h | 6 +----- security/apparmor/lsm.c | 13 +++++-------- security/apparmor/procattr.c | 6 +++--- 5 files changed, 29 insertions(+), 32 deletions(-) diff --git a/security/apparmor/domain.c b/security/apparmor/domain.c index 0c02eac33a45..2ec4ae029215 100644 --- a/security/apparmor/domain.c +++ b/security/apparmor/domain.c @@ -563,7 +563,7 @@ static char *new_compound_name(const char *n1, const char *n2) * @hats: vector of hat names to try changing into (MAYBE NULL if @count == 0) * @count: number of hat names in @hats * @token: magic value to validate the hat change - * @permtest: true if this is just a permission test + * @flags: flags affecting behavior of the change * * Change to the first profile specified in @hats that exists, and store * the @hat_magic in the current task context. If the count == 0 and the @@ -572,7 +572,7 @@ static char *new_compound_name(const char *n1, const char *n2) * * Returns %0 on success, error otherwise. */ -int aa_change_hat(const char *hats[], int count, u64 token, bool permtest) +int aa_change_hat(const char *hats[], int count, u64 token, int flags) { const struct cred *cred; struct aa_task_ctx *ctx; @@ -616,7 +616,7 @@ int aa_change_hat(const char *hats[], int count, u64 token, bool permtest) /* released below */ hat = aa_find_child(root, hats[i]); if (!hat) { - if (!COMPLAIN_MODE(root) || permtest) { + if (!COMPLAIN_MODE(root) || (flags & AA_CHANGE_TEST)) { if (list_empty(&root->base.profiles)) error = -ECHILD; else @@ -663,7 +663,7 @@ int aa_change_hat(const char *hats[], int count, u64 token, bool permtest) goto audit; } - if (!permtest) { + if (!(flags & AA_CHANGE_TEST)) { error = aa_set_current_hat(hat, token); if (error == -EACCES) /* kill task in case of brute force attacks */ @@ -684,7 +684,7 @@ int aa_change_hat(const char *hats[], int count, u64 token, bool permtest) goto out; audit: - if (!permtest) + if (!(flags & AA_CHANGE_TEST)) error = aa_audit_file(profile, &perms, OP_CHANGE_HAT, AA_MAY_CHANGEHAT, NULL, target, GLOBAL_ROOT_UID, info, error); @@ -703,7 +703,7 @@ out: * aa_change_profile - perform a one-way profile transition * @fqname: name of profile may include namespace (NOT NULL) * @onexec: whether this transition is to take place immediately or at exec - * @permtest: true if this is just a permission test + * @flags: flags affecting change behavior * * Change to new profile @name. Unlike with hats, there is no way * to change back. If @name isn't specified the current profile name is @@ -713,8 +713,7 @@ out: * * Returns %0 on success, error otherwise. */ -int aa_change_profile(const char *fqname, bool onexec, - bool permtest, bool stack) +int aa_change_profile(const char *fqname, int flags) { const struct cred *cred; struct aa_profile *profile, *target = NULL; @@ -728,7 +727,7 @@ int aa_change_profile(const char *fqname, bool onexec, return -EINVAL; } - if (onexec) { + if (flags & AA_CHANGE_ONEXEC) { request = AA_MAY_ONEXEC; op = OP_CHANGE_ONEXEC; } else { @@ -755,7 +754,8 @@ int aa_change_profile(const char *fqname, bool onexec, if (!target) { info = "profile not found"; error = -ENOENT; - if (permtest || !COMPLAIN_MODE(profile)) + if ((flags & AA_CHANGE_TEST) || + !COMPLAIN_MODE(profile)) goto audit; /* released below */ target = aa_new_null_profile(profile, false, fqname, @@ -781,16 +781,16 @@ int aa_change_profile(const char *fqname, bool onexec, goto audit; } - if (permtest) + if (flags & AA_CHANGE_TEST) goto audit; - if (onexec) + if (flags & AA_CHANGE_ONEXEC) error = aa_set_current_onexec(target); else error = aa_replace_current_profile(target); audit: - if (!permtest) + if (!(flags & AA_CHANGE_TEST)) error = aa_audit_file(profile, &perms, op, request, NULL, fqname, GLOBAL_ROOT_UID, info, error); diff --git a/security/apparmor/include/domain.h b/security/apparmor/include/domain.h index 6587c4abb7e8..255aa40ec1d1 100644 --- a/security/apparmor/include/domain.h +++ b/security/apparmor/include/domain.h @@ -23,12 +23,16 @@ struct aa_domain { char **table; }; +#define AA_CHANGE_NOFLAGS 0 +#define AA_CHANGE_TEST 1 +#define AA_CHANGE_CHILD 2 +#define AA_CHANGE_ONEXEC 4 + int apparmor_bprm_set_creds(struct linux_binprm *bprm); int apparmor_bprm_secureexec(struct linux_binprm *bprm); void aa_free_domain_entries(struct aa_domain *domain); -int aa_change_hat(const char *hats[], int count, u64 token, bool permtest); -int aa_change_profile(const char *fqname, bool onexec, bool permtest, - bool stack); +int aa_change_hat(const char *hats[], int count, u64 token, int flags); +int aa_change_profile(const char *fqname, int flags); #endif /* __AA_DOMAIN_H */ diff --git a/security/apparmor/include/procattr.h b/security/apparmor/include/procattr.h index 6bd5f33d9533..c0055d727868 100644 --- a/security/apparmor/include/procattr.h +++ b/security/apparmor/include/procattr.h @@ -15,11 +15,7 @@ #ifndef __AA_PROCATTR_H #define __AA_PROCATTR_H -#define AA_DO_TEST 1 -#define AA_ONEXEC 1 - int aa_getprocattr(struct aa_profile *profile, char **string); -int aa_setprocattr_changehat(char *args, size_t size, int test); -int aa_setprocattr_changeprofile(char *fqname, bool onexec, int test); +int aa_setprocattr_changehat(char *args, size_t size, int flags); #endif /* __AA_PROCATTR_H */ diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c index 49b780b4c53b..e07dd5a204d7 100644 --- a/security/apparmor/lsm.c +++ b/security/apparmor/lsm.c @@ -554,22 +554,19 @@ static int apparmor_setprocattr(const char *name, void *value, if (strcmp(name, "current") == 0) { if (strcmp(command, "changehat") == 0) { error = aa_setprocattr_changehat(args, arg_size, - !AA_DO_TEST); + AA_CHANGE_NOFLAGS); } else if (strcmp(command, "permhat") == 0) { error = aa_setprocattr_changehat(args, arg_size, - AA_DO_TEST); + AA_CHANGE_TEST); } else if (strcmp(command, "changeprofile") == 0) { - error = aa_change_profile(args, !AA_ONEXEC, - !AA_DO_TEST, false); + error = aa_change_profile(args, AA_CHANGE_NOFLAGS); } else if (strcmp(command, "permprofile") == 0) { - error = aa_change_profile(args, !AA_ONEXEC, AA_DO_TEST, - false); + error = aa_change_profile(args, AA_CHANGE_TEST); } else goto fail; } else if (strcmp(name, "exec") == 0) { if (strcmp(command, "exec") == 0) - error = aa_change_profile(args, AA_ONEXEC, !AA_DO_TEST, - false); + error = aa_change_profile(args, AA_CHANGE_ONEXEC); else goto fail; } else diff --git a/security/apparmor/procattr.c b/security/apparmor/procattr.c index 41b7b64a906b..2f0cb424927a 100644 --- a/security/apparmor/procattr.c +++ b/security/apparmor/procattr.c @@ -109,11 +109,11 @@ static char *split_token_from_name(const char *op, char *args, u64 *token) * aa_setprocattr_chagnehat - handle procattr interface to change_hat * @args: args received from writing to /proc//attr/current (NOT NULL) * @size: size of the args - * @test: true if this is a test of change_hat permissions + * @flags: set of flags governing behavior * * Returns: %0 or error code if change_hat fails */ -int aa_setprocattr_changehat(char *args, size_t size, int test) +int aa_setprocattr_changehat(char *args, size_t size, int flags) { char *hat; u64 token; @@ -148,5 +148,5 @@ int aa_setprocattr_changehat(char *args, size_t size, int test) AA_DEBUG("%s: (pid %d) Magic 0x%llx count %d Hat '%s'\n", __func__, current->pid, token, count, ""); - return aa_change_hat(hats, count, token, test); + return aa_change_hat(hats, count, token, flags); } -- cgit v1.2.3 From 2835a13bbdc09d330eafdf5e67eb407c90c01ab7 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Fri, 9 Jun 2017 11:43:45 -0700 Subject: apparmor: cleanup rename XXX_file_context() to XXX_file_ctx() Signed-off-by: John Johansen --- security/apparmor/include/file.h | 17 ++++++++++++----- security/apparmor/lsm.c | 10 ++++------ 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/security/apparmor/include/file.h b/security/apparmor/include/file.h index 365ca7ead133..19c483850770 100644 --- a/security/apparmor/include/file.h +++ b/security/apparmor/include/file.h @@ -30,6 +30,8 @@ struct path; AA_MAY_CHMOD | AA_MAY_CHOWN | AA_MAY_LOCK | \ AA_EXEC_MMAP | AA_MAY_LINK) +#define file_ctx(X) ((struct aa_file_ctx *)(X)->f_security) + /* struct aa_file_ctx - the AppArmor context the file was opened in * @perms: the permission the file was opened with * @@ -42,21 +44,26 @@ struct aa_file_ctx { }; /** - * aa_alloc_file_context - allocate file_ctx + * aa_alloc_file_ctx - allocate file_ctx + * @label: initial label of task creating the file * @gfp: gfp flags for allocation * * Returns: file_ctx or NULL on failure */ -static inline struct aa_file_ctx *aa_alloc_file_context(gfp_t gfp) +static inline struct aa_file_ctx *aa_alloc_file_ctx(gfp_t gfp) { - return kzalloc(sizeof(struct aa_file_ctx), gfp); + struct aa_file_ctx *ctx; + + ctx = kzalloc(sizeof(struct aa_file_ctx), gfp); + + return ctx; } /** - * aa_free_file_context - free a file_ctx + * aa_free_file_ctx - free a file_ctx * @ctx: file_ctx to free (MAYBE_NULL) */ -static inline void aa_free_file_context(struct aa_file_ctx *ctx) +static inline void aa_free_file_ctx(struct aa_file_ctx *ctx) { if (ctx) kzfree(ctx); diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c index e07dd5a204d7..3c6fa9753675 100644 --- a/security/apparmor/lsm.c +++ b/security/apparmor/lsm.c @@ -398,9 +398,9 @@ static int apparmor_file_alloc_security(struct file *file) /* freed by apparmor_file_free_security */ struct aa_profile *profile = begin_current_profile_crit_section(); - file->f_security = aa_alloc_file_context(GFP_KERNEL); - if (!file->f_security) - return -ENOMEM; + file->f_security = aa_alloc_file_ctx(GFP_KERNEL); + if (!file_ctx(file)) + error = -ENOMEM; end_current_profile_crit_section(profile); return error; @@ -408,9 +408,7 @@ static int apparmor_file_alloc_security(struct file *file) static void apparmor_file_free_security(struct file *file) { - struct aa_file_ctx *ctx = file->f_security; - - aa_free_file_context(ctx); + aa_free_file_ctx(file_ctx(file)); } static int common_file_perm(const char *op, struct file *file, u32 mask) -- cgit v1.2.3 From 192ca6b55a866e838aee98d9cb6a0b5086467c03 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Fri, 9 Jun 2017 11:58:42 -0700 Subject: apparmor: revalidate files during exec Instead of running file revalidation lazily when read/write are called copy selinux and revalidate the file table on exec. This avoids extra mediation overhead in read/write and also prevents file handles being passed through to a grand child unchecked. Signed-off-by: John Johansen --- security/apparmor/file.c | 72 +++++++++++++++++++++++++++++++++++++++ security/apparmor/include/audit.h | 1 + security/apparmor/include/file.h | 2 ++ security/apparmor/lsm.c | 6 ++++ 4 files changed, 81 insertions(+) diff --git a/security/apparmor/file.c b/security/apparmor/file.c index 2e128c2aa4dc..bf508791cc1f 100644 --- a/security/apparmor/file.c +++ b/security/apparmor/file.c @@ -12,8 +12,13 @@ * License. */ +#include +#include +#include + #include "include/apparmor.h" #include "include/audit.h" +#include "include/context.h" #include "include/file.h" #include "include/match.h" #include "include/path.h" @@ -445,3 +450,70 @@ int aa_file_perm(const char *op, struct aa_profile *profile, struct file *file, return aa_path_perm(op, profile, &file->f_path, PATH_DELEGATE_DELETED, request, &cond); } + +static void revalidate_tty(struct aa_profile *profile) +{ + struct tty_struct *tty; + int drop_tty = 0; + + tty = get_current_tty(); + if (!tty) + return; + + spin_lock(&tty->files_lock); + if (!list_empty(&tty->tty_files)) { + struct tty_file_private *file_priv; + struct file *file; + /* TODO: Revalidate access to controlling tty. */ + file_priv = list_first_entry(&tty->tty_files, + struct tty_file_private, list); + file = file_priv->file; + + if (aa_file_perm(OP_INHERIT, profile, file, + MAY_READ | MAY_WRITE)) + drop_tty = 1; + } + spin_unlock(&tty->files_lock); + tty_kref_put(tty); + + if (drop_tty) + no_tty(); +} + +static int match_file(const void *p, struct file *file, unsigned int fd) +{ + struct aa_profile *profile = (struct aa_profile *)p; + + if (aa_file_perm(OP_INHERIT, profile, file, + aa_map_file_to_perms(file))) + return fd + 1; + return 0; +} + + +/* based on selinux's flush_unauthorized_files */ +void aa_inherit_files(const struct cred *cred, struct files_struct *files) +{ + struct aa_profile *profile = aa_get_newest_cred_profile(cred); + struct file *devnull = NULL; + unsigned int n; + + revalidate_tty(profile); + + /* Revalidate access to inherited open files. */ + n = iterate_fd(files, 0, match_file, profile); + if (!n) /* none found? */ + goto out; + + devnull = dentry_open(&aa_null, O_RDWR, cred); + if (IS_ERR(devnull)) + devnull = NULL; + /* replace all the matching ones with this */ + do { + replace_fd(n - 1, devnull, 0); + } while ((n = iterate_fd(files, n, match_file, profile)) != 0); + if (devnull) + fput(devnull); +out: + aa_put_profile(profile); +} diff --git a/security/apparmor/include/audit.h b/security/apparmor/include/audit.h index 1aeb8550fb82..d548261dd1b7 100644 --- a/security/apparmor/include/audit.h +++ b/security/apparmor/include/audit.h @@ -69,6 +69,7 @@ enum audit_type { #define OP_FLOCK "file_lock" #define OP_FMMAP "file_mmap" #define OP_FMPROT "file_mprotect" +#define OP_INHERIT "file_inherit" #define OP_CREATE "create" #define OP_POST_CREATE "post_create" diff --git a/security/apparmor/include/file.h b/security/apparmor/include/file.h index 19c483850770..df76c208473a 100644 --- a/security/apparmor/include/file.h +++ b/security/apparmor/include/file.h @@ -186,6 +186,8 @@ int aa_path_link(struct aa_profile *profile, struct dentry *old_dentry, int aa_file_perm(const char *op, struct aa_profile *profile, struct file *file, u32 request); +void aa_inherit_files(const struct cred *cred, struct files_struct *files); + static inline void aa_free_file_rules(struct aa_file_rules *rules) { aa_put_dfa(rules->dfa); diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c index 3c6fa9753675..7ba43c18687a 100644 --- a/security/apparmor/lsm.c +++ b/security/apparmor/lsm.c @@ -417,6 +417,10 @@ static int common_file_perm(const char *op, struct file *file, u32 mask) struct aa_profile *profile, *fprofile; int error = 0; + /* don't reaudit files closed during inheritance */ + if (file->f_path.dentry == aa_null.dentry) + return -EACCES; + fprofile = aa_cred_raw_profile(file->f_cred); AA_BUG(!fprofile); @@ -600,6 +604,8 @@ static void apparmor_bprm_committing_creds(struct linux_binprm *bprm) (unconfined(new_ctx->profile))) return; + aa_inherit_files(bprm->cred, current->files); + current->pdeath_signal = 0; /* reset soft limits and set hard limits for the new profile */ -- cgit v1.2.3 From f1bd904175e8190ce14aedee37e207ab51fe3b30 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Fri, 9 Jun 2017 06:19:19 -0700 Subject: apparmor: add the base fns() for domain labels Begin moving apparmor to using broader domain labels, that will allow run time computation of domain type splitting via "stacking" of profiles into a domain label vec. Signed-off-by: John Johansen --- security/apparmor/include/label.h | 441 ++++++++ security/apparmor/label.c | 2120 +++++++++++++++++++++++++++++++++++++ 2 files changed, 2561 insertions(+) create mode 100644 security/apparmor/include/label.h create mode 100644 security/apparmor/label.c diff --git a/security/apparmor/include/label.h b/security/apparmor/include/label.h new file mode 100644 index 000000000000..9a283b722755 --- /dev/null +++ b/security/apparmor/include/label.h @@ -0,0 +1,441 @@ +/* + * AppArmor security module + * + * This file contains AppArmor label definitions + * + * Copyright 2017 Canonical Ltd. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2 of the + * License. + */ + +#ifndef __AA_LABEL_H +#define __AA_LABEL_H + +#include +#include +#include +#include + +#include "apparmor.h" +#include "lib.h" + +struct aa_ns; + +#define LOCAL_VEC_ENTRIES 8 +#define DEFINE_VEC(T, V) \ + struct aa_ ## T *(_ ## V ## _localtmp)[LOCAL_VEC_ENTRIES]; \ + struct aa_ ## T **(V) + +#define vec_setup(T, V, N, GFP) \ +({ \ + if ((N) <= LOCAL_VEC_ENTRIES) { \ + typeof(N) i; \ + (V) = (_ ## V ## _localtmp); \ + for (i = 0; i < (N); i++) \ + (V)[i] = NULL; \ + } else \ + (V) = kzalloc(sizeof(struct aa_ ## T *) * (N), (GFP)); \ + (V) ? 0 : -ENOMEM; \ +}) + +#define vec_cleanup(T, V, N) \ +do { \ + int i; \ + for (i = 0; i < (N); i++) { \ + if (!IS_ERR_OR_NULL((V)[i])) \ + aa_put_ ## T((V)[i]); \ + } \ + if ((V) != _ ## V ## _localtmp) \ + kfree(V); \ +} while (0) + +#define vec_last(VEC, SIZE) ((VEC)[(SIZE) - 1]) +#define vec_ns(VEC, SIZE) (vec_last((VEC), (SIZE))->ns) +#define vec_labelset(VEC, SIZE) (&vec_ns((VEC), (SIZE))->labels) +#define cleanup_domain_vec(V, L) cleanup_label_vec((V), (L)->size) + +struct aa_profile; +#define VEC_FLAG_TERMINATE 1 +int aa_vec_unique(struct aa_profile **vec, int n, int flags); +struct aa_label *aa_vec_find_or_create_label(struct aa_profile **vec, int len, + gfp_t gfp); +#define aa_sort_and_merge_vec(N, V) \ + aa_sort_and_merge_profiles((N), (struct aa_profile **)(V)) + + +/* struct aa_labelset - set of labels for a namespace + * + * Labels are reference counted; aa_labelset does not contribute to label + * reference counts. Once a label's last refcount is put it is removed from + * the set. + */ +struct aa_labelset { + rwlock_t lock; + + struct rb_root root; +}; + +#define __labelset_for_each(LS, N) \ + for ((N) = rb_first(&(LS)->root); (N); (N) = rb_next(N)) + +void aa_labelset_destroy(struct aa_labelset *ls); +void aa_labelset_init(struct aa_labelset *ls); + + +enum label_flags { + FLAG_HAT = 1, /* profile is a hat */ + FLAG_UNCONFINED = 2, /* label unconfined only if all */ + FLAG_NULL = 4, /* profile is null learning profile */ + FLAG_IX_ON_NAME_ERROR = 8, /* fallback to ix on name lookup fail */ + FLAG_IMMUTIBLE = 0x10, /* don't allow changes/replacement */ + FLAG_USER_DEFINED = 0x20, /* user based profile - lower privs */ + FLAG_NO_LIST_REF = 0x40, /* list doesn't keep profile ref */ + FLAG_NS_COUNT = 0x80, /* carries NS ref count */ + FLAG_IN_TREE = 0x100, /* label is in tree */ + FLAG_PROFILE = 0x200, /* label is a profile */ + FLAG_EXPLICIT = 0x400, /* explicit static label */ + FLAG_STALE = 0x800, /* replaced/removed */ + FLAG_RENAMED = 0x1000, /* label has renaming in it */ + FLAG_REVOKED = 0x2000, /* label has revocation in it */ + + /* These flags must correspond with PATH_flags */ + /* TODO: add new path flags */ +}; + +struct aa_label; +struct aa_proxy { + struct kref count; + struct aa_label __rcu *label; +}; + +struct label_it { + int i, j; +}; + +/* struct aa_label - lazy labeling struct + * @count: ref count of active users + * @node: rbtree position + * @rcu: rcu callback struct + * @proxy: is set to the label that replaced this label + * @hname: text representation of the label (MAYBE_NULL) + * @flags: stale and other flags - values may change under label set lock + * @secid: secid that references this label + * @size: number of entries in @ent[] + * @ent: set of profiles for label, actual size determined by @size + */ +struct aa_label { + struct kref count; + struct rb_node node; + struct rcu_head rcu; + struct aa_proxy *proxy; + __counted char *hname; + long flags; + u32 secid; + int size; + struct aa_profile *vec[]; +}; + +#define last_error(E, FN) \ +do { \ + int __subE = (FN); \ + if (__subE) \ + (E) = __subE; \ +} while (0) + +#define label_isprofile(X) ((X)->flags & FLAG_PROFILE) +#define label_unconfined(X) ((X)->flags & FLAG_UNCONFINED) +#define unconfined(X) label_unconfined(X) +#define label_is_stale(X) ((X)->flags & FLAG_STALE) +#define __label_make_stale(X) ((X)->flags |= FLAG_STALE) +#define labels_ns(X) (vec_ns(&((X)->vec[0]), (X)->size)) +#define labels_set(X) (&labels_ns(X)->labels) +#define labels_profile(X) ((X)->vec[(X)->size - 1]) + + +int aa_label_next_confined(struct aa_label *l, int i); + +/* for each profile in a label */ +#define label_for_each(I, L, P) \ + for ((I).i = 0; ((P) = (L)->vec[(I).i]); ++((I).i)) + +/* assumes break/goto ended label_for_each */ +#define label_for_each_cont(I, L, P) \ + for (++((I).i); ((P) = (L)->vec[(I).i]); ++((I).i)) + +#define next_comb(I, L1, L2) \ +do { \ + (I).j++; \ + if ((I).j >= (L2)->size) { \ + (I).i++; \ + (I).j = 0; \ + } \ +} while (0) + + +/* for each combination of P1 in L1, and P2 in L2 */ +#define label_for_each_comb(I, L1, L2, P1, P2) \ +for ((I).i = (I).j = 0; \ + ((P1) = (L1)->vec[(I).i]) && ((P2) = (L2)->vec[(I).j]); \ + (I) = next_comb(I, L1, L2)) + +#define fn_for_each_comb(L1, L2, P1, P2, FN) \ +({ \ + struct label_it i; \ + int __E = 0; \ + label_for_each_comb(i, (L1), (L2), (P1), (P2)) { \ + last_error(__E, (FN)); \ + } \ + __E; \ +}) + +/* for each profile that is enforcing confinement in a label */ +#define label_for_each_confined(I, L, P) \ + for ((I).i = aa_label_next_confined((L), 0); \ + ((P) = (L)->vec[(I).i]); \ + (I).i = aa_label_next_confined((L), (I).i + 1)) + +#define label_for_each_in_merge(I, A, B, P) \ + for ((I).i = (I).j = 0; \ + ((P) = aa_label_next_in_merge(&(I), (A), (B))); \ + ) + +#define label_for_each_not_in_set(I, SET, SUB, P) \ + for ((I).i = (I).j = 0; \ + ((P) = __aa_label_next_not_in_set(&(I), (SET), (SUB))); \ + ) + +#define next_in_ns(i, NS, L) \ +({ \ + typeof(i) ___i = (i); \ + while ((L)->vec[___i] && (L)->vec[___i]->ns != (NS)) \ + (___i)++; \ + (___i); \ +}) + +#define label_for_each_in_ns(I, NS, L, P) \ + for ((I).i = next_in_ns(0, (NS), (L)); \ + ((P) = (L)->vec[(I).i]); \ + (I).i = next_in_ns((I).i + 1, (NS), (L))) + +#define fn_for_each_in_ns(L, P, FN) \ +({ \ + struct label_it __i; \ + struct aa_ns *__ns = labels_ns(L); \ + int __E = 0; \ + label_for_each_in_ns(__i, __ns, (L), (P)) { \ + last_error(__E, (FN)); \ + } \ + __E; \ +}) + + +#define fn_for_each_XXX(L, P, FN, ...) \ +({ \ + struct label_it i; \ + int __E = 0; \ + label_for_each ## __VA_ARGS__(i, (L), (P)) { \ + last_error(__E, (FN)); \ + } \ + __E; \ +}) + +#define fn_for_each(L, P, FN) fn_for_each_XXX(L, P, FN) +#define fn_for_each_confined(L, P, FN) fn_for_each_XXX(L, P, FN, _confined) + +#define fn_for_each2_XXX(L1, L2, P, FN, ...) \ +({ \ + struct label_it i; \ + int __E = 0; \ + label_for_each ## __VA_ARGS__(i, (L1), (L2), (P)) { \ + last_error(__E, (FN)); \ + } \ + __E; \ +}) + +#define fn_for_each_in_merge(L1, L2, P, FN) \ + fn_for_each2_XXX((L1), (L2), P, FN, _in_merge) +#define fn_for_each_not_in_set(L1, L2, P, FN) \ + fn_for_each2_XXX((L1), (L2), P, FN, _not_in_set) + +#define LABEL_MEDIATES(L, C) \ +({ \ + struct aa_profile *profile; \ + struct label_it i; \ + int ret = 0; \ + label_for_each(i, (L), profile) { \ + if (PROFILE_MEDIATES(profile, (C))) { \ + ret = 1; \ + break; \ + } \ + } \ + ret; \ +}) + + +void aa_labelset_destroy(struct aa_labelset *ls); +void aa_labelset_init(struct aa_labelset *ls); +void __aa_labelset_update_subtree(struct aa_ns *ns); + +void aa_label_free(struct aa_label *label); +void aa_label_kref(struct kref *kref); +bool aa_label_init(struct aa_label *label, int size); +struct aa_label *aa_label_alloc(int size, struct aa_proxy *proxy, gfp_t gfp); + +bool aa_label_is_subset(struct aa_label *set, struct aa_label *sub); +struct aa_profile *__aa_label_next_not_in_set(struct label_it *I, + struct aa_label *set, + struct aa_label *sub); +bool aa_label_remove(struct aa_label *label); +struct aa_label *aa_label_insert(struct aa_labelset *ls, struct aa_label *l); +bool aa_label_replace(struct aa_label *old, struct aa_label *new); +bool aa_label_make_newest(struct aa_labelset *ls, struct aa_label *old, + struct aa_label *new); + +struct aa_label *aa_label_find(struct aa_label *l); + +struct aa_profile *aa_label_next_in_merge(struct label_it *I, + struct aa_label *a, + struct aa_label *b); +struct aa_label *aa_label_find_merge(struct aa_label *a, struct aa_label *b); +struct aa_label *aa_label_merge(struct aa_label *a, struct aa_label *b, + gfp_t gfp); + + +bool aa_update_label_name(struct aa_ns *ns, struct aa_label *label, gfp_t gfp); + +#define FLAGS_NONE 0 +#define FLAG_SHOW_MODE 1 +#define FLAG_VIEW_SUBNS 2 +#define FLAG_HIDDEN_UNCONFINED 4 +int aa_label_snxprint(char *str, size_t size, struct aa_ns *view, + struct aa_label *label, int flags); +int aa_label_asxprint(char **strp, struct aa_ns *ns, struct aa_label *label, + int flags, gfp_t gfp); +int aa_label_acntsxprint(char __counted **strp, struct aa_ns *ns, + struct aa_label *label, int flags, gfp_t gfp); +void aa_label_xaudit(struct audit_buffer *ab, struct aa_ns *ns, + struct aa_label *label, int flags, gfp_t gfp); +void aa_label_seq_xprint(struct seq_file *f, struct aa_ns *ns, + struct aa_label *label, int flags, gfp_t gfp); +void aa_label_xprintk(struct aa_ns *ns, struct aa_label *label, int flags, + gfp_t gfp); +void aa_label_audit(struct audit_buffer *ab, struct aa_label *label, gfp_t gfp); +void aa_label_seq_print(struct seq_file *f, struct aa_label *label, gfp_t gfp); +void aa_label_printk(struct aa_label *label, gfp_t gfp); + +struct aa_label *aa_label_parse(struct aa_label *base, const char *str, + gfp_t gfp, bool create, bool force_stack); + + +struct aa_perms; +int aa_label_match(struct aa_profile *profile, struct aa_label *label, + unsigned int state, bool subns, u32 request, + struct aa_perms *perms); + + +/** + * __aa_get_label - get a reference count to uncounted label reference + * @l: reference to get a count on + * + * Returns: pointer to reference OR NULL if race is lost and reference is + * being repeated. + * Requires: lock held, and the return code MUST be checked + */ +static inline struct aa_label *__aa_get_label(struct aa_label *l) +{ + if (l && kref_get_unless_zero(&l->count)) + return l; + + return NULL; +} + +static inline struct aa_label *aa_get_label(struct aa_label *l) +{ + if (l) + kref_get(&(l->count)); + + return l; +} + + +/** + * aa_get_label_rcu - increment refcount on a label that can be replaced + * @l: pointer to label that can be replaced (NOT NULL) + * + * Returns: pointer to a refcounted label. + * else NULL if no label + */ +static inline struct aa_label *aa_get_label_rcu(struct aa_label __rcu **l) +{ + struct aa_label *c; + + rcu_read_lock(); + do { + c = rcu_dereference(*l); + } while (c && !kref_get_unless_zero(&c->count)); + rcu_read_unlock(); + + return c; +} + +/** + * aa_get_newest_label - find the newest version of @l + * @l: the label to check for newer versions of + * + * Returns: refcounted newest version of @l taking into account + * replacement, renames and removals + * return @l. + */ +static inline struct aa_label *aa_get_newest_label(struct aa_label *l) +{ + if (!l) + return NULL; + + if (label_is_stale(l)) { + struct aa_label *tmp; + + AA_BUG(!l->proxy); + AA_BUG(!l->proxy->label); + /* BUG: only way this can happen is @l ref count and its + * replacement count have gone to 0 and are on their way + * to destruction. ie. we have a refcounting error + */ + tmp = aa_get_label_rcu(&l->proxy->label); + AA_BUG(!tmp); + + return tmp; + } + + return aa_get_label(l); +} + +static inline void aa_put_label(struct aa_label *l) +{ + if (l) + kref_put(&l->count, aa_label_kref); +} + + +struct aa_proxy *aa_alloc_proxy(struct aa_label *l, gfp_t gfp); +void aa_proxy_kref(struct kref *kref); + +static inline struct aa_proxy *aa_get_proxy(struct aa_proxy *proxy) +{ + if (proxy) + kref_get(&(proxy->count)); + + return proxy; +} + +static inline void aa_put_proxy(struct aa_proxy *proxy) +{ + if (proxy) + kref_put(&proxy->count, aa_proxy_kref); +} + +void __aa_proxy_redirect(struct aa_label *orig, struct aa_label *new); + +#endif /* __AA_LABEL_H */ diff --git a/security/apparmor/label.c b/security/apparmor/label.c new file mode 100644 index 000000000000..e052eaba1cf6 --- /dev/null +++ b/security/apparmor/label.c @@ -0,0 +1,2120 @@ +/* + * AppArmor security module + * + * This file contains AppArmor label definitions + * + * Copyright 2017 Canonical Ltd. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2 of the + * License. + */ + +#include +#include +#include + +#include "include/apparmor.h" +#include "include/context.h" +#include "include/label.h" +#include "include/policy.h" +#include "include/secid.h" + + +/* + * the aa_label represents the set of profiles confining an object + * + * Labels maintain a reference count to the set of pointers they reference + * Labels are ref counted by + * tasks and object via the security field/security context off the field + * code - will take a ref count on a label if it needs the label + * beyond what is possible with an rcu_read_lock. + * profiles - each profile is a label + * secids - a pinned secid will keep a refcount of the label it is + * referencing + * objects - inode, files, sockets, ... + * + * Labels are not ref counted by the label set, so they maybe removed and + * freed when no longer in use. + * + */ + +#define PROXY_POISON 97 +#define LABEL_POISON 100 + +static void free_proxy(struct aa_proxy *proxy) +{ + if (proxy) { + /* p->label will not updated any more as p is dead */ + aa_put_label(rcu_dereference_protected(proxy->label, true)); + memset(proxy, 0, sizeof(*proxy)); + proxy->label = (struct aa_label *) PROXY_POISON; + kfree(proxy); + } +} + +void aa_proxy_kref(struct kref *kref) +{ + struct aa_proxy *proxy = container_of(kref, struct aa_proxy, count); + + free_proxy(proxy); +} + +struct aa_proxy *aa_alloc_proxy(struct aa_label *label, gfp_t gfp) +{ + struct aa_proxy *new; + + new = kzalloc(sizeof(struct aa_proxy), gfp); + if (new) { + kref_init(&new->count); + rcu_assign_pointer(new->label, aa_get_label(label)); + } + return new; +} + +/* requires profile list write lock held */ +void __aa_proxy_redirect(struct aa_label *orig, struct aa_label *new) +{ + struct aa_label *tmp; + + AA_BUG(!orig); + AA_BUG(!new); + AA_BUG(!write_is_locked(&labels_set(orig)->lock)); + + tmp = rcu_dereference_protected(orig->proxy->label, + &labels_ns(orig)->lock); + rcu_assign_pointer(orig->proxy->label, aa_get_label(new)); + orig->flags |= FLAG_STALE; + aa_put_label(tmp); +} + +static void __proxy_share(struct aa_label *old, struct aa_label *new) +{ + struct aa_proxy *proxy = new->proxy; + + new->proxy = aa_get_proxy(old->proxy); + __aa_proxy_redirect(old, new); + aa_put_proxy(proxy); +} + + +/** + * ns_cmp - compare ns for label set ordering + * @a: ns to compare (NOT NULL) + * @b: ns to compare (NOT NULL) + * + * Returns: <0 if a < b + * ==0 if a == b + * >0 if a > b + */ +static int ns_cmp(struct aa_ns *a, struct aa_ns *b) +{ + int res; + + AA_BUG(!a); + AA_BUG(!b); + AA_BUG(!a->base.hname); + AA_BUG(!b->base.hname); + + if (a == b) + return 0; + + res = a->level - b->level; + if (res) + return res; + + return strcmp(a->base.hname, b->base.hname); +} + +/** + * profile_cmp - profile comparision for set ordering + * @a: profile to compare (NOT NULL) + * @b: profile to compare (NOT NULL) + * + * Returns: <0 if a < b + * ==0 if a == b + * >0 if a > b + */ +static int profile_cmp(struct aa_profile *a, struct aa_profile *b) +{ + int res; + + AA_BUG(!a); + AA_BUG(!b); + AA_BUG(!a->ns); + AA_BUG(!b->ns); + AA_BUG(!a->base.hname); + AA_BUG(!b->base.hname); + + if (a == b || a->base.hname == b->base.hname) + return 0; + res = ns_cmp(a->ns, b->ns); + if (res) + return res; + + return strcmp(a->base.hname, b->base.hname); +} + +/** + * vec_cmp - label comparision for set ordering + * @a: label to compare (NOT NULL) + * @vec: vector of profiles to compare (NOT NULL) + * @n: length of @vec + * + * Returns: <0 if a < vec + * ==0 if a == vec + * >0 if a > vec + */ +static int vec_cmp(struct aa_profile **a, int an, struct aa_profile **b, int bn) +{ + int i; + + AA_BUG(!a); + AA_BUG(!*a); + AA_BUG(!b); + AA_BUG(!*b); + AA_BUG(an <= 0); + AA_BUG(bn <= 0); + + for (i = 0; i < an && i < bn; i++) { + int res = profile_cmp(a[i], b[i]); + + if (res != 0) + return res; + } + + return an - bn; +} + +static bool vec_is_stale(struct aa_profile **vec, int n) +{ + int i; + + AA_BUG(!vec); + + for (i = 0; i < n; i++) { + if (profile_is_stale(vec[i])) + return true; + } + + return false; +} + +static bool vec_unconfined(struct aa_profile **vec, int n) +{ + int i; + + AA_BUG(!vec); + + for (i = 0; i < n; i++) { + if (!profile_unconfined(vec[i])) + return false; + } + + return true; +} + +static int sort_cmp(const void *a, const void *b) +{ + return profile_cmp(*(struct aa_profile **)a, *(struct aa_profile **)b); +} + +/* + * assumes vec is sorted + * Assumes @vec has null terminator at vec[n], and will null terminate + * vec[n - dups] + */ +static inline int unique(struct aa_profile **vec, int n) +{ + int i, pos, dups = 0; + + AA_BUG(n < 1); + AA_BUG(!vec); + + pos = 0; + for (i = 1; i < n; i++) { + int res = profile_cmp(vec[pos], vec[i]); + + AA_BUG(res > 0, "vec not sorted"); + if (res == 0) { + /* drop duplicate */ + aa_put_profile(vec[i]); + dups++; + continue; + } + pos++; + if (dups) + vec[pos] = vec[i]; + } + + AA_BUG(dups < 0); + + return dups; +} + +/** + * aa_vec_unique - canonical sort and unique a list of profiles + * @n: number of refcounted profiles in the list (@n > 0) + * @vec: list of profiles to sort and merge + * + * Returns: the number of duplicates eliminated == references put + * + * If @flags & VEC_FLAG_TERMINATE @vec has null terminator at vec[n], and will + * null terminate vec[n - dups] + */ +int aa_vec_unique(struct aa_profile **vec, int n, int flags) +{ + int i, dups = 0; + + AA_BUG(n < 1); + AA_BUG(!vec); + + /* vecs are usually small and inorder, have a fallback for larger */ + if (n > 8) { + sort(vec, n, sizeof(struct aa_profile *), sort_cmp, NULL); + dups = unique(vec, n); + goto out; + } + + /* insertion sort + unique in one */ + for (i = 1; i < n; i++) { + struct aa_profile *tmp = vec[i]; + int pos, j; + + for (pos = i - 1 - dups; pos >= 0; pos--) { + int res = profile_cmp(vec[pos], tmp); + + if (res == 0) { + /* drop duplicate entry */ + aa_put_profile(tmp); + dups++; + goto continue_outer; + } else if (res < 0) + break; + } + /* pos is at entry < tmp, or index -1. Set to insert pos */ + pos++; + + for (j = i - dups; j > pos; j--) + vec[j] = vec[j - 1]; + vec[pos] = tmp; +continue_outer: + ; + } + + AA_BUG(dups < 0); + +out: + if (flags & VEC_FLAG_TERMINATE) + vec[n - dups] = NULL; + + return dups; +} + + +static void label_destroy(struct aa_label *label) +{ + struct aa_label *tmp; + + AA_BUG(!label); + + if (!label_isprofile(label)) { + struct aa_profile *profile; + struct label_it i; + + aa_put_str(label->hname); + + label_for_each(i, label, profile) { + aa_put_profile(profile); + label->vec[i.i] = (struct aa_profile *) + (LABEL_POISON + (long) i.i); + } + } + + if (rcu_dereference_protected(label->proxy->label, true) == label) + rcu_assign_pointer(label->proxy->label, NULL); + + aa_free_secid(label->secid); + + tmp = rcu_dereference_protected(label->proxy->label, true); + if (tmp == label) + rcu_assign_pointer(label->proxy->label, NULL); + + aa_put_proxy(label->proxy); + label->proxy = (struct aa_proxy *) PROXY_POISON + 1; +} + +void aa_label_free(struct aa_label *label) +{ + if (!label) + return; + + label_destroy(label); + kfree(label); +} + +static void label_free_switch(struct aa_label *label) +{ + if (label->flags & FLAG_NS_COUNT) + aa_free_ns(labels_ns(label)); + else if (label_isprofile(label)) + aa_free_profile(labels_profile(label)); + else + aa_label_free(label); +} + +static void label_free_rcu(struct rcu_head *head) +{ + struct aa_label *label = container_of(head, struct aa_label, rcu); + + if (label->flags & FLAG_IN_TREE) + (void) aa_label_remove(label); + label_free_switch(label); +} + +void aa_label_kref(struct kref *kref) +{ + struct aa_label *label = container_of(kref, struct aa_label, count); + struct aa_ns *ns = labels_ns(label); + + if (!ns) { + /* never live, no rcu callback needed, just using the fn */ + label_free_switch(label); + return; + } + /* TODO: update labels_profile macro so it works here */ + AA_BUG(label_isprofile(label) && + on_list_rcu(&label->vec[0]->base.profiles)); + AA_BUG(label_isprofile(label) && + on_list_rcu(&label->vec[0]->base.list)); + + /* TODO: if compound label and not stale add to reclaim cache */ + call_rcu(&label->rcu, label_free_rcu); +} + +static void label_free_or_put_new(struct aa_label *label, struct aa_label *new) +{ + if (label != new) + /* need to free directly to break circular ref with proxy */ + aa_label_free(new); + else + aa_put_label(new); +} + +bool aa_label_init(struct aa_label *label, int size) +{ + AA_BUG(!label); + AA_BUG(size < 1); + + label->secid = aa_alloc_secid(); + if (label->secid == AA_SECID_INVALID) + return false; + + label->size = size; /* doesn't include null */ + label->vec[size] = NULL; /* null terminate */ + kref_init(&label->count); + RB_CLEAR_NODE(&label->node); + + return true; +} + +/** + * aa_label_alloc - allocate a label with a profile vector of @size length + * @size: size of profile vector in the label + * @proxy: proxy to use OR null if to allocate a new one + * @gfp: memory allocation type + * + * Returns: new label + * else NULL if failed + */ +struct aa_label *aa_label_alloc(int size, struct aa_proxy *proxy, gfp_t gfp) +{ + struct aa_label *new; + + AA_BUG(size < 1); + + /* + 1 for null terminator entry on vec */ + new = kzalloc(sizeof(*new) + sizeof(struct aa_profile *) * (size + 1), + gfp); + AA_DEBUG("%s (%p)\n", __func__, new); + if (!new) + goto fail; + + if (!aa_label_init(new, size)) + goto fail; + + if (!proxy) { + proxy = aa_alloc_proxy(new, gfp); + if (!proxy) + goto fail; + } else + aa_get_proxy(proxy); + /* just set new's proxy, don't redirect proxy here if it was passed in*/ + new->proxy = proxy; + + return new; + +fail: + kfree(new); + + return NULL; +} + + +/** + * label_cmp - label comparision for set ordering + * @a: label to compare (NOT NULL) + * @b: label to compare (NOT NULL) + * + * Returns: <0 if a < b + * ==0 if a == b + * >0 if a > b + */ +static int label_cmp(struct aa_label *a, struct aa_label *b) +{ + AA_BUG(!b); + + if (a == b) + return 0; + + return vec_cmp(a->vec, a->size, b->vec, b->size); +} + +/* helper fn for label_for_each_confined */ +int aa_label_next_confined(struct aa_label *label, int i) +{ + AA_BUG(!label); + AA_BUG(i < 0); + + for (; i < label->size; i++) { + if (!profile_unconfined(label->vec[i])) + return i; + } + + return i; +} + +/** + * aa_label_next_not_in_set - return the next profile of @sub not in @set + * @I: label iterator + * @set: label to test against + * @sub: label to if is subset of @set + * + * Returns: profile in @sub that is not in @set, with iterator set pos after + * else NULL if @sub is a subset of @set + */ +struct aa_profile *__aa_label_next_not_in_set(struct label_it *I, + struct aa_label *set, + struct aa_label *sub) +{ + AA_BUG(!set); + AA_BUG(!I); + AA_BUG(I->i < 0); + AA_BUG(I->i > set->size); + AA_BUG(!sub); + AA_BUG(I->j < 0); + AA_BUG(I->j > sub->size); + + while (I->j < sub->size && I->i < set->size) { + int res = profile_cmp(sub->vec[I->j], set->vec[I->i]); + + if (res == 0) { + (I->j)++; + (I->i)++; + } else if (res > 0) + (I->i)++; + else + return sub->vec[(I->j)++]; + } + + if (I->j < sub->size) + return sub->vec[(I->j)++]; + + return NULL; +} + +/** + * aa_label_is_subset - test if @sub is a subset of @set + * @set: label to test against + * @sub: label to test if is subset of @set + * + * Returns: true if @sub is subset of @set + * else false + */ +bool aa_label_is_subset(struct aa_label *set, struct aa_label *sub) +{ + struct label_it i = { }; + + AA_BUG(!set); + AA_BUG(!sub); + + if (sub == set) + return true; + + return __aa_label_next_not_in_set(&i, set, sub) == NULL; +} + + + +/** + * __label_remove - remove @label from the label set + * @l: label to remove + * @new: label to redirect to + * + * Requires: labels_set(@label)->lock write_lock + * Returns: true if the label was in the tree and removed + */ +static bool __label_remove(struct aa_label *label, struct aa_label *new) +{ + struct aa_labelset *ls = labels_set(label); + + AA_BUG(!ls); + AA_BUG(!label); + AA_BUG(!write_is_locked(&ls->lock)); + + if (new) + __aa_proxy_redirect(label, new); + + if (!label_is_stale(label)) + __label_make_stale(label); + + if (label->flags & FLAG_IN_TREE) { + rb_erase(&label->node, &ls->root); + label->flags &= ~FLAG_IN_TREE; + return true; + } + + return false; +} + +/** + * __label_replace - replace @old with @new in label set + * @old: label to remove from label set + * @new: label to replace @old with + * + * Requires: labels_set(@old)->lock write_lock + * valid ref count be held on @new + * Returns: true if @old was in set and replaced by @new + * + * Note: current implementation requires label set be order in such a way + * that @new directly replaces @old position in the set (ie. + * using pointer comparison of the label address would not work) + */ +static bool __label_replace(struct aa_label *old, struct aa_label *new) +{ + struct aa_labelset *ls = labels_set(old); + + AA_BUG(!ls); + AA_BUG(!old); + AA_BUG(!new); + AA_BUG(!write_is_locked(&ls->lock)); + AA_BUG(new->flags & FLAG_IN_TREE); + + if (!label_is_stale(old)) + __label_make_stale(old); + + if (old->flags & FLAG_IN_TREE) { + rb_replace_node(&old->node, &new->node, &ls->root); + old->flags &= ~FLAG_IN_TREE; + new->flags |= FLAG_IN_TREE; + return true; + } + + return false; +} + +/** + * __label_insert - attempt to insert @l into a label set + * @ls: set of labels to insert @l into (NOT NULL) + * @label: new label to insert (NOT NULL) + * @replace: whether insertion should replace existing entry that is not stale + * + * Requires: @ls->lock + * caller to hold a valid ref on l + * if @replace is true l has a preallocated proxy associated + * Returns: @l if successful in inserting @l - with additional refcount + * else ref counted equivalent label that is already in the set, + * the else condition only happens if @replace is false + */ +static struct aa_label *__label_insert(struct aa_labelset *ls, + struct aa_label *label, bool replace) +{ + struct rb_node **new, *parent = NULL; + + AA_BUG(!ls); + AA_BUG(!label); + AA_BUG(labels_set(label) != ls); + AA_BUG(!write_is_locked(&ls->lock)); + AA_BUG(label->flags & FLAG_IN_TREE); + + /* Figure out where to put new node */ + new = &ls->root.rb_node; + while (*new) { + struct aa_label *this = rb_entry(*new, struct aa_label, node); + int result = label_cmp(label, this); + + parent = *new; + if (result == 0) { + /* !__aa_get_label means queued for destruction, + * so replace in place, however the label has + * died before the replacement so do not share + * the proxy + */ + if (!replace && !label_is_stale(this)) { + if (__aa_get_label(this)) + return this; + } else + __proxy_share(this, label); + AA_BUG(!__label_replace(this, label)); + return aa_get_label(label); + } else if (result < 0) + new = &((*new)->rb_left); + else /* (result > 0) */ + new = &((*new)->rb_right); + } + + /* Add new node and rebalance tree. */ + rb_link_node(&label->node, parent, new); + rb_insert_color(&label->node, &ls->root); + label->flags |= FLAG_IN_TREE; + + return aa_get_label(label); +} + +/** + * __vec_find - find label that matches @vec in label set + * @vec: vec of profiles to find matching label for (NOT NULL) + * @n: length of @vec + * + * Requires: @vec_labelset(vec) lock held + * caller to hold a valid ref on l + * + * Returns: ref counted @label if matching label is in tree + * ref counted label that is equiv to @l in tree + * else NULL if @vec equiv is not in tree + */ +static struct aa_label *__vec_find(struct aa_profile **vec, int n) +{ + struct rb_node *node; + + AA_BUG(!vec); + AA_BUG(!*vec); + AA_BUG(n <= 0); + + node = vec_labelset(vec, n)->root.rb_node; + while (node) { + struct aa_label *this = rb_entry(node, struct aa_label, node); + int result = vec_cmp(this->vec, this->size, vec, n); + + if (result > 0) + node = node->rb_left; + else if (result < 0) + node = node->rb_right; + else + return __aa_get_label(this); + } + + return NULL; +} + +/** + * __label_find - find label @label in label set + * @label: label to find (NOT NULL) + * + * Requires: labels_set(@label)->lock held + * caller to hold a valid ref on l + * + * Returns: ref counted @label if @label is in tree OR + * ref counted label that is equiv to @label in tree + * else NULL if @label or equiv is not in tree + */ +static struct aa_label *__label_find(struct aa_label *label) +{ + AA_BUG(!label); + + return __vec_find(label->vec, label->size); +} + + +/** + * aa_label_remove - remove a label from the labelset + * @label: label to remove + * + * Returns: true if @label was removed from the tree + * else @label was not in tree so it could not be removed + */ +bool aa_label_remove(struct aa_label *label) +{ + struct aa_labelset *ls = labels_set(label); + unsigned long flags; + bool res; + + AA_BUG(!ls); + + write_lock_irqsave(&ls->lock, flags); + res = __label_remove(label, ns_unconfined(labels_ns(label))); + write_unlock_irqrestore(&ls->lock, flags); + + return res; +} + +/** + * aa_label_replace - replace a label @old with a new version @new + * @old: label to replace + * @new: label replacing @old + * + * Returns: true if @old was in tree and replaced + * else @old was not in tree, and @new was not inserted + */ +bool aa_label_replace(struct aa_label *old, struct aa_label *new) +{ + unsigned long flags; + bool res; + + if (name_is_shared(old, new) && labels_ns(old) == labels_ns(new)) { + write_lock_irqsave(&labels_set(old)->lock, flags); + if (old->proxy != new->proxy) + __proxy_share(old, new); + else + __aa_proxy_redirect(old, new); + res = __label_replace(old, new); + write_unlock_irqrestore(&labels_set(old)->lock, flags); + } else { + struct aa_label *l; + struct aa_labelset *ls = labels_set(old); + + write_lock_irqsave(&ls->lock, flags); + res = __label_remove(old, new); + if (labels_ns(old) != labels_ns(new)) { + write_unlock_irqrestore(&ls->lock, flags); + ls = labels_set(new); + write_lock_irqsave(&ls->lock, flags); + } + l = __label_insert(ls, new, true); + res = (l == new); + write_unlock_irqrestore(&ls->lock, flags); + aa_put_label(l); + } + + return res; +} + +/** + * vec_find - find label @l in label set + * @vec: array of profiles to find equiv label for (NOT NULL) + * @n: length of @vec + * + * Returns: refcounted label if @vec equiv is in tree + * else NULL if @vec equiv is not in tree + */ +static struct aa_label *vec_find(struct aa_profile **vec, int n) +{ + struct aa_labelset *ls; + struct aa_label *label; + unsigned long flags; + + AA_BUG(!vec); + AA_BUG(!*vec); + AA_BUG(n <= 0); + + ls = vec_labelset(vec, n); + read_lock_irqsave(&ls->lock, flags); + label = __vec_find(vec, n); + read_unlock_irqrestore(&ls->lock, flags); + + return label; +} + +/* requires sort and merge done first */ +static struct aa_label *vec_create_and_insert_label(struct aa_profile **vec, + int len, gfp_t gfp) +{ + struct aa_label *label = NULL; + struct aa_labelset *ls; + unsigned long flags; + struct aa_label *new; + int i; + + AA_BUG(!vec); + + if (len == 1) + return aa_get_label(&vec[0]->label); + + ls = labels_set(&vec[len - 1]->label); + + /* TODO: enable when read side is lockless + * check if label exists before taking locks + */ + new = aa_label_alloc(len, NULL, gfp); + if (!new) + return NULL; + + for (i = 0; i < len; i++) + new->vec[i] = aa_get_profile(vec[i]); + + write_lock_irqsave(&ls->lock, flags); + label = __label_insert(ls, new, false); + write_unlock_irqrestore(&ls->lock, flags); + label_free_or_put_new(label, new); + + return label; +} + +struct aa_label *aa_vec_find_or_create_label(struct aa_profile **vec, int len, + gfp_t gfp) +{ + struct aa_label *label = vec_find(vec, len); + + if (label) + return label; + + return vec_create_and_insert_label(vec, len, gfp); +} + +/** + * aa_label_find - find label @label in label set + * @label: label to find (NOT NULL) + * + * Requires: caller to hold a valid ref on l + * + * Returns: refcounted @label if @label is in tree + * refcounted label that is equiv to @label in tree + * else NULL if @label or equiv is not in tree + */ +struct aa_label *aa_label_find(struct aa_label *label) +{ + AA_BUG(!label); + + return vec_find(label->vec, label->size); +} + + +/** + * aa_label_insert - insert label @label into @ls or return existing label + * @ls - labelset to insert @label into + * @label - label to insert + * + * Requires: caller to hold a valid ref on @label + * + * Returns: ref counted @label if successful in inserting @label + * else ref counted equivalent label that is already in the set + */ +struct aa_label *aa_label_insert(struct aa_labelset *ls, struct aa_label *label) +{ + struct aa_label *l; + unsigned long flags; + + AA_BUG(!ls); + AA_BUG(!label); + + /* check if label exists before taking lock */ + if (!label_is_stale(label)) { + read_lock_irqsave(&ls->lock, flags); + l = __label_find(label); + read_unlock_irqrestore(&ls->lock, flags); + if (l) + return l; + } + + write_lock_irqsave(&ls->lock, flags); + l = __label_insert(ls, label, false); + write_unlock_irqrestore(&ls->lock, flags); + + return l; +} + + +/** + * aa_label_next_in_merge - find the next profile when merging @a and @b + * @I: label iterator + * @a: label to merge + * @b: label to merge + * + * Returns: next profile + * else null if no more profiles + */ +struct aa_profile *aa_label_next_in_merge(struct label_it *I, + struct aa_label *a, + struct aa_label *b) +{ + AA_BUG(!a); + AA_BUG(!b); + AA_BUG(!I); + AA_BUG(I->i < 0); + AA_BUG(I->i > a->size); + AA_BUG(I->j < 0); + AA_BUG(I->j > b->size); + + if (I->i < a->size) { + if (I->j < b->size) { + int res = profile_cmp(a->vec[I->i], b->vec[I->j]); + + if (res > 0) + return b->vec[(I->j)++]; + if (res == 0) + (I->j)++; + } + + return a->vec[(I->i)++]; + } + + if (I->j < b->size) + return b->vec[(I->j)++]; + + return NULL; +} + +/** + * label_merge_cmp - cmp of @a merging with @b against @z for set ordering + * @a: label to merge then compare (NOT NULL) + * @b: label to merge then compare (NOT NULL) + * @z: label to compare merge against (NOT NULL) + * + * Assumes: using the most recent versions of @a, @b, and @z + * + * Returns: <0 if a < b + * ==0 if a == b + * >0 if a > b + */ +static int label_merge_cmp(struct aa_label *a, struct aa_label *b, + struct aa_label *z) +{ + struct aa_profile *p = NULL; + struct label_it i = { }; + int k; + + AA_BUG(!a); + AA_BUG(!b); + AA_BUG(!z); + + for (k = 0; + k < z->size && (p = aa_label_next_in_merge(&i, a, b)); + k++) { + int res = profile_cmp(p, z->vec[k]); + + if (res != 0) + return res; + } + + if (p) + return 1; + else if (k < z->size) + return -1; + return 0; +} + +/** + * label_merge_insert - create a new label by merging @a and @b + * @new: preallocated label to merge into (NOT NULL) + * @a: label to merge with @b (NOT NULL) + * @b: label to merge with @a (NOT NULL) + * + * Requires: preallocated proxy + * + * Returns: ref counted label either @new if merge is unique + * @a if @b is a subset of @a + * @b if @a is a subset of @b + * + * NOTE: will not use @new if the merge results in @new == @a or @b + * + * Must be used within labelset write lock to avoid racing with + * setting labels stale. + */ +static struct aa_label *label_merge_insert(struct aa_label *new, + struct aa_label *a, + struct aa_label *b) +{ + struct aa_label *label; + struct aa_labelset *ls; + struct aa_profile *next; + struct label_it i; + unsigned long flags; + int k = 0, invcount = 0; + bool stale = false; + + AA_BUG(!a); + AA_BUG(a->size < 0); + AA_BUG(!b); + AA_BUG(b->size < 0); + AA_BUG(!new); + AA_BUG(new->size < a->size + b->size); + + label_for_each_in_merge(i, a, b, next) { + AA_BUG(!next); + if (profile_is_stale(next)) { + new->vec[k] = aa_get_newest_profile(next); + AA_BUG(!new->vec[k]->label.proxy); + AA_BUG(!new->vec[k]->label.proxy->label); + if (next->label.proxy != new->vec[k]->label.proxy) + invcount++; + k++; + stale = true; + } else + new->vec[k++] = aa_get_profile(next); + } + /* set to actual size which is <= allocated len */ + new->size = k; + new->vec[k] = NULL; + + if (invcount) { + new->size -= aa_vec_unique(&new->vec[0], new->size, + VEC_FLAG_TERMINATE); + /* TODO: deal with reference labels */ + if (new->size == 1) { + label = aa_get_label(&new->vec[0]->label); + return label; + } + } else if (!stale) { + /* + * merge could be same as a || b, note: it is not possible + * for new->size == a->size == b->size unless a == b + */ + if (k == a->size) + return aa_get_label(a); + else if (k == b->size) + return aa_get_label(b); + } + if (vec_unconfined(new->vec, new->size)) + new->flags |= FLAG_UNCONFINED; + ls = labels_set(new); + write_lock_irqsave(&ls->lock, flags); + label = __label_insert(labels_set(new), new, false); + write_unlock_irqrestore(&ls->lock, flags); + + return label; +} + +/** + * labelset_of_merge - find which labelset a merged label should be inserted + * @a: label to merge and insert + * @b: label to merge and insert + * + * Returns: labelset that the merged label should be inserted into + */ +static struct aa_labelset *labelset_of_merge(struct aa_label *a, + struct aa_label *b) +{ + struct aa_ns *nsa = labels_ns(a); + struct aa_ns *nsb = labels_ns(b); + + if (ns_cmp(nsa, nsb) <= 0) + return &nsa->labels; + return &nsb->labels; +} + +/** + * __label_find_merge - find label that is equiv to merge of @a and @b + * @ls: set of labels to search (NOT NULL) + * @a: label to merge with @b (NOT NULL) + * @b: label to merge with @a (NOT NULL) + * + * Requires: ls->lock read_lock held + * + * Returns: ref counted label that is equiv to merge of @a and @b + * else NULL if merge of @a and @b is not in set + */ +static struct aa_label *__label_find_merge(struct aa_labelset *ls, + struct aa_label *a, + struct aa_label *b) +{ + struct rb_node *node; + + AA_BUG(!ls); + AA_BUG(!a); + AA_BUG(!b); + + if (a == b) + return __label_find(a); + + node = ls->root.rb_node; + while (node) { + struct aa_label *this = container_of(node, struct aa_label, + node); + int result = label_merge_cmp(a, b, this); + + if (result < 0) + node = node->rb_left; + else if (result > 0) + node = node->rb_right; + else + return __aa_get_label(this); + } + + return NULL; +} + + +/** + * aa_label_find_merge - find label that is equiv to merge of @a and @b + * @a: label to merge with @b (NOT NULL) + * @b: label to merge with @a (NOT NULL) + * + * Requires: labels be fully constructed with a valid ns + * + * Returns: ref counted label that is equiv to merge of @a and @b + * else NULL if merge of @a and @b is not in set + */ +struct aa_label *aa_label_find_merge(struct aa_label *a, struct aa_label *b) +{ + struct aa_labelset *ls; + struct aa_label *label, *ar = NULL, *br = NULL; + unsigned long flags; + + AA_BUG(!a); + AA_BUG(!b); + + if (label_is_stale(a)) + a = ar = aa_get_newest_label(a); + if (label_is_stale(b)) + b = br = aa_get_newest_label(b); + ls = labelset_of_merge(a, b); + read_lock_irqsave(&ls->lock, flags); + label = __label_find_merge(ls, a, b); + read_unlock_irqrestore(&ls->lock, flags); + aa_put_label(ar); + aa_put_label(br); + + return label; +} + +/** + * aa_label_merge - attempt to insert new merged label of @a and @b + * @ls: set of labels to insert label into (NOT NULL) + * @a: label to merge with @b (NOT NULL) + * @b: label to merge with @a (NOT NULL) + * @gfp: memory allocation type + * + * Requires: caller to hold valid refs on @a and @b + * labels be fully constructed with a valid ns + * + * Returns: ref counted new label if successful in inserting merge of a & b + * else ref counted equivalent label that is already in the set. + * else NULL if could not create label (-ENOMEM) + */ +struct aa_label *aa_label_merge(struct aa_label *a, struct aa_label *b, + gfp_t gfp) +{ + struct aa_label *label = NULL; + + AA_BUG(!a); + AA_BUG(!b); + + if (a == b) + return aa_get_newest_label(a); + + /* TODO: enable when read side is lockless + * check if label exists before taking locks + if (!label_is_stale(a) && !label_is_stale(b)) + label = aa_label_find_merge(a, b); + */ + + if (!label) { + struct aa_label *new; + + a = aa_get_newest_label(a); + b = aa_get_newest_label(b); + + /* could use label_merge_len(a, b), but requires double + * comparison for small savings + */ + new = aa_label_alloc(a->size + b->size, NULL, gfp); + if (!new) + goto out; + + label = label_merge_insert(new, a, b); + label_free_or_put_new(label, new); +out: + aa_put_label(a); + aa_put_label(b); + } + + return label; +} + +static inline bool label_is_visible(struct aa_profile *profile, + struct aa_label *label) +{ + return aa_ns_visible(profile->ns, labels_ns(label), true); +} + +/* match a profile and its associated ns component if needed + * Assumes visibility test has already been done. + * If a subns profile is not to be matched should be prescreened with + * visibility test. + */ +static inline unsigned int match_component(struct aa_profile *profile, + struct aa_profile *tp, + unsigned int state) +{ + const char *ns_name; + + if (profile->ns == tp->ns) + return aa_dfa_match(profile->policy.dfa, state, tp->base.hname); + + /* try matching with namespace name and then profile */ + ns_name = aa_ns_name(profile->ns, tp->ns, true); + state = aa_dfa_match_len(profile->policy.dfa, state, ":", 1); + state = aa_dfa_match(profile->policy.dfa, state, ns_name); + state = aa_dfa_match_len(profile->policy.dfa, state, ":", 1); + return aa_dfa_match(profile->policy.dfa, state, tp->base.hname); +} + +/** + * label_compound_match - find perms for full compound label + * @profile: profile to find perms for + * @label: label to check access permissions for + * @start: state to start match in + * @subns: whether to do permission checks on components in a subns + * @request: permissions to request + * @perms: perms struct to set + * + * Returns: 0 on success else ERROR + * + * 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 + * check to be stacked. + */ +static int label_compound_match(struct aa_profile *profile, + struct aa_label *label, + unsigned int state, bool subns, u32 request, + struct aa_perms *perms) +{ + struct aa_profile *tp; + struct label_it i; + + /* find first subcomponent that is visible */ + label_for_each(i, label, tp) { + if (!aa_ns_visible(profile->ns, tp->ns, subns)) + continue; + state = match_component(profile, tp, state); + if (!state) + goto fail; + goto next; + } + + /* no component visible */ + *perms = allperms; + return 0; + +next: + label_for_each_cont(i, label, tp) { + if (!aa_ns_visible(profile->ns, tp->ns, subns)) + continue; + state = aa_dfa_match(profile->policy.dfa, state, "//&"); + state = match_component(profile, tp, state); + if (!state) + goto fail; + } + aa_compute_perms(profile->policy.dfa, state, perms); + aa_apply_modes_to_perms(profile, perms); + if ((perms->allow & request) != request) + return -EACCES; + + return 0; + +fail: + *perms = nullperms; + return state; +} + +/** + * label_components_match - find perms for all subcomponents of a label + * @profile: profile to find perms for + * @label: label to check access permissions for + * @start: state to start match in + * @subns: whether to do permission checks on components in a subns + * @request: permissions to request + * @perms: an initialized perms struct to add accumulation to + * + * Returns: 0 on success else ERROR + * + * 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 + * check to be stacked. + */ +static int label_components_match(struct aa_profile *profile, + struct aa_label *label, unsigned int start, + bool subns, u32 request, + struct aa_perms *perms) +{ + struct aa_profile *tp; + struct label_it i; + struct aa_perms tmp; + unsigned int state = 0; + + /* find first subcomponent to test */ + label_for_each(i, label, tp) { + if (!aa_ns_visible(profile->ns, tp->ns, subns)) + continue; + state = match_component(profile, tp, start); + if (!state) + goto fail; + goto next; + } + + /* no subcomponents visible - no change in perms */ + return 0; + +next: + aa_compute_perms(profile->policy.dfa, state, &tmp); + aa_apply_modes_to_perms(profile, &tmp); + aa_perms_accum(perms, &tmp); + label_for_each_cont(i, label, tp) { + if (!aa_ns_visible(profile->ns, tp->ns, subns)) + continue; + state = match_component(profile, tp, start); + if (!state) + goto fail; + aa_compute_perms(profile->policy.dfa, state, &tmp); + aa_apply_modes_to_perms(profile, &tmp); + aa_perms_accum(perms, &tmp); + } + + if ((perms->allow & request) != request) + return -EACCES; + + return 0; + +fail: + *perms = nullperms; + return -EACCES; +} + +/** + * aa_label_match - do a multi-component label match + * @profile: profile to match against (NOT NULL) + * @label: label to match (NOT NULL) + * @state: state to start in + * @subns: whether to match subns components + * @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_label *label, + unsigned int state, bool subns, u32 request, + struct aa_perms *perms) +{ + int error = label_compound_match(profile, label, state, subns, request, + perms); + if (!error) + return error; + + *perms = allperms; + return label_components_match(profile, label, state, subns, request, + perms); +} + + +/** + * aa_update_label_name - update a label to have a stored name + * @ns: ns being viewed from (NOT NULL) + * @label: label to update (NOT NULL) + * @gfp: type of memory allocation + * + * Requires: labels_set(label) not locked in caller + * + * note: only updates the label name if it does not have a name already + * and if it is in the labelset + */ +bool aa_update_label_name(struct aa_ns *ns, struct aa_label *label, gfp_t gfp) +{ + struct aa_labelset *ls; + unsigned long flags; + char __counted *name; + bool res = false; + + AA_BUG(!ns); + AA_BUG(!label); + + if (label->hname || labels_ns(label) != ns) + return res; + + if (aa_label_acntsxprint(&name, ns, label, FLAGS_NONE, gfp) == -1) + return res; + + ls = labels_set(label); + write_lock_irqsave(&ls->lock, flags); + if (!label->hname && label->flags & FLAG_IN_TREE) { + label->hname = name; + res = true; + } else + aa_put_str(name); + write_unlock_irqrestore(&ls->lock, flags); + + return res; +} + +/* + * cached label name is present and visible + * @label->hname only exists if label is namespace hierachical + */ +static inline bool use_label_hname(struct aa_ns *ns, struct aa_label *label) +{ + if (label->hname && labels_ns(label) == ns) + return true; + + return false; +} + +/* helper macro for snprint routines */ +#define update_for_len(total, len, size, str) \ +do { \ + AA_BUG(len < 0); \ + total += len; \ + len = min(len, size); \ + size -= len; \ + str += len; \ +} while (0) + +/** + * aa_profile_snxprint - print a profile name to a buffer + * @str: buffer to write to. (MAY BE NULL if @size == 0) + * @size: size of buffer + * @view: namespace profile is being viewed from + * @profile: profile to view (NOT NULL) + * @flags: whether to include the mode string + * @prev_ns: last ns printed when used in compound print + * + * Returns: size of name written or would be written if larger than + * available buffer + * + * Note: will not print anything if the profile is not visible + */ +static int aa_profile_snxprint(char *str, size_t size, struct aa_ns *view, + struct aa_profile *profile, int flags, + struct aa_ns **prev_ns) +{ + const char *ns_name = NULL; + + AA_BUG(!str && size != 0); + AA_BUG(!profile); + + if (!view) + view = profiles_ns(profile); + + if (view != profile->ns && + (!prev_ns || (prev_ns && *prev_ns != profile->ns))) { + if (prev_ns) + *prev_ns = profile->ns; + ns_name = aa_ns_name(view, profile->ns, + flags & FLAG_VIEW_SUBNS); + if (ns_name == aa_hidden_ns_name) { + if (flags & FLAG_HIDDEN_UNCONFINED) + return snprintf(str, size, "%s", "unconfined"); + return snprintf(str, size, "%s", ns_name); + } + } + + if ((flags & FLAG_SHOW_MODE) && profile != profile->ns->unconfined) { + const char *modestr = aa_profile_mode_names[profile->mode]; + + if (ns_name) + return snprintf(str, size, ":%s:%s (%s)", ns_name, + profile->base.hname, modestr); + return snprintf(str, size, "%s (%s)", profile->base.hname, + modestr); + } + + if (ns_name) + return snprintf(str, size, ":%s:%s", ns_name, + profile->base.hname); + return snprintf(str, size, "%s", profile->base.hname); +} + +static const char *label_modename(struct aa_ns *ns, struct aa_label *label, + int flags) +{ + struct aa_profile *profile; + struct label_it i; + int mode = -1, count = 0; + + label_for_each(i, label, profile) { + if (aa_ns_visible(ns, profile->ns, flags & FLAG_VIEW_SUBNS)) { + if (profile->mode == APPARMOR_UNCONFINED) + /* special case unconfined so stacks with + * unconfined don't report as mixed. ie. + * profile_foo//&:ns1:unconfined (mixed) + */ + continue; + count++; + if (mode == -1) + mode = profile->mode; + else if (mode != profile->mode) + return "mixed"; + } + } + + if (count == 0) + return "-"; + if (mode == -1) + /* everything was unconfined */ + mode = APPARMOR_UNCONFINED; + + return aa_profile_mode_names[mode]; +} + +/* if any visible label is not unconfined the display_mode returns true */ +static inline bool display_mode(struct aa_ns *ns, struct aa_label *label, + int flags) +{ + if ((flags & FLAG_SHOW_MODE)) { + struct aa_profile *profile; + struct label_it i; + + label_for_each(i, label, profile) { + if (aa_ns_visible(ns, profile->ns, + flags & FLAG_VIEW_SUBNS) && + profile != profile->ns->unconfined) + return true; + } + /* only ns->unconfined in set of profiles in ns */ + return false; + } + + return false; +} + +/** + * aa_label_snxprint - print a label name to a string buffer + * @str: buffer to write to. (MAY BE NULL if @size == 0) + * @size: size of buffer + * @ns: namespace profile is being viewed from + * @label: label to view (NOT NULL) + * @flags: whether to include the mode string + * + * Returns: size of name written or would be written if larger than + * available buffer + * + * Note: labels do not have to be strictly hierarchical to the ns as + * objects may be shared across different namespaces and thus + * pickup labeling from each ns. If a particular part of the + * label is not visible it will just be excluded. And if none + * of the label is visible "---" will be used. + */ +int aa_label_snxprint(char *str, size_t size, struct aa_ns *ns, + struct aa_label *label, int flags) +{ + struct aa_profile *profile; + struct aa_ns *prev_ns = NULL; + struct label_it i; + int count = 0, total = 0; + size_t len; + + AA_BUG(!str && size != 0); + AA_BUG(!label); + + if (!ns) + ns = labels_ns(label); + + label_for_each(i, label, profile) { + if (aa_ns_visible(ns, profile->ns, flags & FLAG_VIEW_SUBNS)) { + if (count > 0) { + len = snprintf(str, size, "//&"); + update_for_len(total, len, size, str); + } + len = aa_profile_snxprint(str, size, ns, profile, + flags & FLAG_VIEW_SUBNS, + &prev_ns); + update_for_len(total, len, size, str); + count++; + } + } + + if (count == 0) { + if (flags & FLAG_HIDDEN_UNCONFINED) + return snprintf(str, size, "%s", "unconfined"); + return snprintf(str, size, "%s", aa_hidden_ns_name); + } + + /* count == 1 && ... is for backwards compat where the mode + * is not displayed for 'unconfined' in the current ns + */ + if (display_mode(ns, label, flags)) { + len = snprintf(str, size, " (%s)", + label_modename(ns, label, flags)); + update_for_len(total, len, size, str); + } + + return total; +} +#undef update_for_len + +/** + * aa_label_asxprint - allocate a string buffer and print label into it + * @strp: Returns - the allocated buffer with the label name. (NOT NULL) + * @ns: namespace profile is being viewed from + * @label: label to view (NOT NULL) + * @flags: flags controlling what label info is printed + * @gfp: kernel memory allocation type + * + * Returns: size of name written or would be written if larger than + * available buffer + */ +int aa_label_asxprint(char **strp, struct aa_ns *ns, struct aa_label *label, + int flags, gfp_t gfp) +{ + int size; + + AA_BUG(!strp); + AA_BUG(!label); + + size = aa_label_snxprint(NULL, 0, ns, label, flags); + if (size < 0) + return size; + + *strp = kmalloc(size + 1, gfp); + if (!*strp) + return -ENOMEM; + return aa_label_snxprint(*strp, size + 1, ns, label, flags); +} + +/** + * aa_label_acntsxprint - allocate a __counted string buffer and print label + * @strp: buffer to write to. (MAY BE NULL if @size == 0) + * @ns: namespace profile is being viewed from + * @label: label to view (NOT NULL) + * @flags: flags controlling what label info is printed + * @gfp: kernel memory allocation type + * + * Returns: size of name written or would be written if larger than + * available buffer + */ +int aa_label_acntsxprint(char __counted **strp, struct aa_ns *ns, + struct aa_label *label, int flags, gfp_t gfp) +{ + int size; + + AA_BUG(!strp); + AA_BUG(!label); + + size = aa_label_snxprint(NULL, 0, ns, label, flags); + if (size < 0) + return size; + + *strp = aa_str_alloc(size + 1, gfp); + if (!*strp) + return -ENOMEM; + return aa_label_snxprint(*strp, size + 1, ns, label, flags); +} + + +void aa_label_xaudit(struct audit_buffer *ab, struct aa_ns *ns, + struct aa_label *label, int flags, gfp_t gfp) +{ + const char *str; + char *name = NULL; + int len; + + AA_BUG(!ab); + AA_BUG(!label); + + if (!ns) + ns = labels_ns(label); + + if (!use_label_hname(ns, label) || display_mode(ns, label, flags)) { + len = aa_label_asxprint(&name, ns, label, flags, gfp); + if (len == -1) { + AA_DEBUG("label print error"); + return; + } + str = name; + } else { + str = (char *) label->hname; + len = strlen(str); + } + if (audit_string_contains_control(str, len)) + audit_log_n_hex(ab, str, len); + else + audit_log_n_string(ab, str, len); + + kfree(name); +} + +void aa_label_seq_xprint(struct seq_file *f, struct aa_ns *ns, + struct aa_label *label, int flags, gfp_t gfp) +{ + AA_BUG(!f); + AA_BUG(!label); + + if (!ns) + ns = labels_ns(label); + + if (!use_label_hname(ns, label)) { + char *str; + int len; + + len = aa_label_asxprint(&str, ns, label, flags, gfp); + if (len == -1) { + AA_DEBUG("label print error"); + return; + } + seq_printf(f, "%s", str); + kfree(str); + } else if (display_mode(ns, label, flags)) + seq_printf(f, "%s (%s)", label->hname, + label_modename(ns, label, flags)); + else + seq_printf(f, "%s", label->hname); +} + +void aa_label_xprintk(struct aa_ns *ns, struct aa_label *label, int flags, + gfp_t gfp) +{ + AA_BUG(!label); + + if (!ns) + ns = labels_ns(label); + + if (!use_label_hname(ns, label)) { + char *str; + int len; + + len = aa_label_asxprint(&str, ns, label, flags, gfp); + if (len == -1) { + AA_DEBUG("label print error"); + return; + } + pr_info("%s", str); + kfree(str); + } else if (display_mode(ns, label, flags)) + pr_info("%s (%s)", label->hname, + label_modename(ns, label, flags)); + else + pr_info("%s", label->hname); +} + +void aa_label_audit(struct audit_buffer *ab, struct aa_label *label, gfp_t gfp) +{ + struct aa_ns *ns = aa_get_current_ns(); + + aa_label_xaudit(ab, ns, label, FLAG_VIEW_SUBNS, gfp); + aa_put_ns(ns); +} + +void aa_label_seq_print(struct seq_file *f, struct aa_label *label, gfp_t gfp) +{ + struct aa_ns *ns = aa_get_current_ns(); + + aa_label_seq_xprint(f, ns, label, FLAG_VIEW_SUBNS, gfp); + aa_put_ns(ns); +} + +void aa_label_printk(struct aa_label *label, gfp_t gfp) +{ + struct aa_ns *ns = aa_get_current_ns(); + + aa_label_xprintk(ns, label, FLAG_VIEW_SUBNS, gfp); + aa_put_ns(ns); +} + +static int label_count_str_entries(const char *str) +{ + const char *split; + int count = 1; + + AA_BUG(!str); + + for (split = strstr(str, "//&"); split; split = strstr(str, "//&")) { + count++; + str = split + 3; + } + + return count; +} + +/* + * ensure stacks with components like + * :ns:A//&B + * have :ns: applied to both 'A' and 'B' by making the lookup relative + * to the base if the lookup specifies an ns, else making the stacked lookup + * relative to the last embedded ns in the string. + */ +static struct aa_profile *fqlookupn_profile(struct aa_label *base, + struct aa_label *currentbase, + const char *str, size_t n) +{ + const char *first = skipn_spaces(str, n); + + if (first && *first == ':') + return aa_fqlookupn_profile(base, str, n); + + return aa_fqlookupn_profile(currentbase, str, n); +} + +/** + * aa_label_parse - parse, validate and convert a text string to a label + * @base: base label to use for lookups (NOT NULL) + * @str: null terminated text string (NOT NULL) + * @gfp: allocation type + * @create: true if should create compound labels if they don't exist + * @force_stack: true if should stack even if no leading & + * + * Returns: the matching refcounted label if present + * else ERRPTR + */ +struct aa_label *aa_label_parse(struct aa_label *base, const char *str, + gfp_t gfp, bool create, bool force_stack) +{ + DEFINE_VEC(profile, vec); + struct aa_label *label, *currbase = base; + int i, len, stack = 0, error; + char *split; + + AA_BUG(!base); + AA_BUG(!str); + + str = skip_spaces(str); + len = label_count_str_entries(str); + if (*str == '&' || force_stack) { + /* stack on top of base */ + stack = base->size; + len += stack; + if (*str == '&') + str++; + } + error = vec_setup(profile, vec, len, gfp); + if (error) + return ERR_PTR(error); + + for (i = 0; i < stack; i++) + vec[i] = aa_get_profile(base->vec[i]); + + for (split = strstr(str, "//&"), i = stack; split && i < len; i++) { + vec[i] = fqlookupn_profile(base, currbase, str, split - str); + if (!vec[i]) + goto fail; + /* + * if component specified a new ns it becomes the new base + * so that subsequent lookups are relative to it + */ + if (vec[i]->ns != labels_ns(currbase)) + currbase = &vec[i]->label; + str = split + 3; + split = strstr(str, "//&"); + } + /* last element doesn't have a split */ + if (i < len) { + vec[i] = fqlookupn_profile(base, currbase, str, strlen(str)); + if (!vec[i]) + goto fail; + } + if (len == 1) + /* no need to free vec as len < LOCAL_VEC_ENTRIES */ + return &vec[0]->label; + + len -= aa_vec_unique(vec, len, VEC_FLAG_TERMINATE); + /* TODO: deal with reference labels */ + if (len == 1) { + label = aa_get_label(&vec[0]->label); + goto out; + } + + if (create) + label = aa_vec_find_or_create_label(vec, len, gfp); + else + label = vec_find(vec, len); + if (!label) + goto fail; + +out: + /* use adjusted len from after vec_unique, not original */ + vec_cleanup(profile, vec, len); + return label; + +fail: + label = ERR_PTR(-ENOENT); + goto out; +} + + +/** + * aa_labelset_destroy - remove all labels from the label set + * @ls: label set to cleanup (NOT NULL) + * + * Labels that are removed from the set may still exist beyond the set + * being destroyed depending on their reference counting + */ +void aa_labelset_destroy(struct aa_labelset *ls) +{ + struct rb_node *node; + unsigned long flags; + + AA_BUG(!ls); + + write_lock_irqsave(&ls->lock, flags); + for (node = rb_first(&ls->root); node; node = rb_first(&ls->root)) { + struct aa_label *this = rb_entry(node, struct aa_label, node); + + if (labels_ns(this) != root_ns) + __label_remove(this, + ns_unconfined(labels_ns(this)->parent)); + else + __label_remove(this, NULL); + } + write_unlock_irqrestore(&ls->lock, flags); +} + +/* + * @ls: labelset to init (NOT NULL) + */ +void aa_labelset_init(struct aa_labelset *ls) +{ + AA_BUG(!ls); + + rwlock_init(&ls->lock); + ls->root = RB_ROOT; +} + +static struct aa_label *labelset_next_stale(struct aa_labelset *ls) +{ + struct aa_label *label; + struct rb_node *node; + unsigned long flags; + + AA_BUG(!ls); + + read_lock_irqsave(&ls->lock, flags); + + __labelset_for_each(ls, node) { + label = rb_entry(node, struct aa_label, node); + if ((label_is_stale(label) || + vec_is_stale(label->vec, label->size)) && + __aa_get_label(label)) + goto out; + + } + label = NULL; + +out: + read_unlock_irqrestore(&ls->lock, flags); + + return label; +} + +/** + * __label_update - insert updated version of @label into labelset + * @label - the label to update/repace + * + * Returns: new label that is up to date + * else NULL on failure + * + * Requires: @ns lock be held + * + * Note: worst case is the stale @label does not get updated and has + * to be updated at a later time. + */ +static struct aa_label *__label_update(struct aa_label *label) +{ + struct aa_label *new, *tmp; + struct aa_labelset *ls; + unsigned long flags; + int i, invcount = 0; + + AA_BUG(!label); + AA_BUG(!mutex_is_locked(&labels_ns(label)->lock)); + + new = aa_label_alloc(label->size, label->proxy, GFP_KERNEL); + if (!new) + return NULL; + + /* + * while holding the ns_lock will stop profile replacement, removal, + * and label updates, label merging and removal can be occurring + */ + ls = labels_set(label); + write_lock_irqsave(&ls->lock, flags); + for (i = 0; i < label->size; i++) { + AA_BUG(!label->vec[i]); + new->vec[i] = aa_get_newest_profile(label->vec[i]); + AA_BUG(!new->vec[i]); + AA_BUG(!new->vec[i]->label.proxy); + AA_BUG(!new->vec[i]->label.proxy->label); + if (new->vec[i]->label.proxy != label->vec[i]->label.proxy) + invcount++; + } + + /* updated stale label by being removed/renamed from labelset */ + if (invcount) { + new->size -= aa_vec_unique(&new->vec[0], new->size, + VEC_FLAG_TERMINATE); + /* TODO: deal with reference labels */ + if (new->size == 1) { + tmp = aa_get_label(&new->vec[0]->label); + AA_BUG(tmp == label); + goto remove; + } + if (labels_set(label) != labels_set(new)) { + write_unlock_irqrestore(&ls->lock, flags); + tmp = aa_label_insert(labels_set(new), new); + write_lock_irqsave(&ls->lock, flags); + goto remove; + } + } else + AA_BUG(labels_ns(label) != labels_ns(new)); + + tmp = __label_insert(labels_set(label), new, true); +remove: + /* ensure label is removed, and redirected correctly */ + __label_remove(label, tmp); + write_unlock_irqrestore(&ls->lock, flags); + label_free_or_put_new(tmp, new); + + return tmp; +} + +/** + * __labelset_update - update labels in @ns + * @ns: namespace to update labels in (NOT NULL) + * + * Requires: @ns lock be held + * + * Walk the labelset ensuring that all labels are up to date and valid + * Any label that has a stale component is marked stale and replaced and + * by an updated version. + * + * If failures happen due to memory pressures then stale labels will + * be left in place until the next pass. + */ +static void __labelset_update(struct aa_ns *ns) +{ + struct aa_label *label; + + AA_BUG(!ns); + AA_BUG(!mutex_is_locked(&ns->lock)); + + do { + label = labelset_next_stale(&ns->labels); + if (label) { + struct aa_label *l = __label_update(label); + + aa_put_label(l); + aa_put_label(label); + } + } while (label); +} + +/** + * __aa_labelset_udate_subtree - update all labels with a stale component + * @ns: ns to start update at (NOT NULL) + * + * Requires: @ns lock be held + * + * Invalidates labels based on @p in @ns and any children namespaces. + */ +void __aa_labelset_update_subtree(struct aa_ns *ns) +{ + struct aa_ns *child; + + AA_BUG(!ns); + AA_BUG(!mutex_is_locked(&ns->lock)); + + __labelset_update(ns); + + list_for_each_entry(child, &ns->sub_ns, base.list) { + mutex_lock(&child->lock); + __aa_labelset_update_subtree(child); + mutex_unlock(&child->lock); + } +} -- cgit v1.2.3 From 637f688dc3dc304a89f441d76f49a0e35bc49c08 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Fri, 9 Jun 2017 08:14:28 -0700 Subject: apparmor: switch from profiles to using labels on contexts Begin the actual switch to using domain labels by storing them on the context and converting the label to a singular profile where possible. Signed-off-by: John Johansen --- security/apparmor/Makefile | 2 +- security/apparmor/apparmorfs.c | 128 +++++++++++---------- security/apparmor/audit.c | 27 +++-- security/apparmor/context.c | 87 +++++++------- security/apparmor/domain.c | 77 +++++++------ security/apparmor/file.c | 18 +-- security/apparmor/include/apparmor.h | 5 +- security/apparmor/include/audit.h | 9 +- security/apparmor/include/context.h | 158 +++++++++++++------------- security/apparmor/include/perms.h | 12 +- security/apparmor/include/policy.h | 110 ++++++------------ security/apparmor/include/policy_ns.h | 4 + security/apparmor/ipc.c | 29 +++-- security/apparmor/lib.c | 163 ++++++++++++++++++++++++++ security/apparmor/lsm.c | 134 ++++++++++++---------- security/apparmor/policy.c | 208 +++++++++++++++------------------- security/apparmor/policy_ns.c | 20 +++- security/apparmor/policy_unpack.c | 12 +- security/apparmor/procattr.c | 4 +- security/apparmor/resource.c | 8 +- 20 files changed, 686 insertions(+), 529 deletions(-) diff --git a/security/apparmor/Makefile b/security/apparmor/Makefile index b3e7c04b7e7b..a16b195274de 100644 --- a/security/apparmor/Makefile +++ b/security/apparmor/Makefile @@ -4,7 +4,7 @@ obj-$(CONFIG_SECURITY_APPARMOR) += apparmor.o apparmor-y := apparmorfs.o audit.o capability.o context.o ipc.o lib.o match.o \ path.o domain.o policy.o policy_unpack.o procattr.o lsm.o \ - resource.o secid.o file.o policy_ns.o + resource.o secid.o file.o policy_ns.o label.o apparmor-$(CONFIG_SECURITY_APPARMOR_HASH) += crypto.o clean-files := capability_names.h rlim_names.h diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c index e2919a0766b0..976af6da45c3 100644 --- a/security/apparmor/apparmorfs.c +++ b/security/apparmor/apparmorfs.c @@ -405,26 +405,26 @@ static struct aa_loaddata *aa_simple_write_to_buffer(const char __user *userbuf, static ssize_t policy_update(u32 mask, const char __user *buf, size_t size, loff_t *pos, struct aa_ns *ns) { - ssize_t error; struct aa_loaddata *data; - struct aa_profile *profile; + struct aa_label *label; + ssize_t error; - profile = begin_current_profile_crit_section(); + label = begin_current_label_crit_section(); /* high level check about policy management - fine grained in * below after unpack */ - error = aa_may_manage_policy(profile, ns, mask); + error = aa_may_manage_policy(label, ns, mask); if (error) return error; data = aa_simple_write_to_buffer(buf, size, size, pos); error = PTR_ERR(data); if (!IS_ERR(data)) { - error = aa_replace_profiles(ns, profile, mask, data); + error = aa_replace_profiles(ns, label, mask, data); aa_put_loaddata(data); } - end_current_profile_crit_section(profile); + end_current_label_crit_section(label); return error; } @@ -468,15 +468,15 @@ static ssize_t profile_remove(struct file *f, const char __user *buf, size_t size, loff_t *pos) { struct aa_loaddata *data; - struct aa_profile *profile; + struct aa_label *label; ssize_t error; struct aa_ns *ns = aa_get_ns(f->f_inode->i_private); - profile = begin_current_profile_crit_section(); + label = begin_current_label_crit_section(); /* high level check about policy management - fine grained in * below after unpack */ - error = aa_may_manage_policy(profile, ns, AA_MAY_REMOVE_POLICY); + error = aa_may_manage_policy(label, ns, AA_MAY_REMOVE_POLICY); if (error) goto out; @@ -489,11 +489,11 @@ static ssize_t profile_remove(struct file *f, const char __user *buf, error = PTR_ERR(data); if (!IS_ERR(data)) { data->data[size] = 0; - error = aa_remove_profiles(ns, profile, data->data, size); + error = aa_remove_profiles(ns, label, data->data, size); aa_put_loaddata(data); } out: - end_current_profile_crit_section(profile); + end_current_label_crit_section(label); aa_put_ns(ns); return error; } @@ -605,7 +605,7 @@ static void profile_query_cb(struct aa_profile *profile, struct aa_perms *perms, struct aa_dfa *dfa; unsigned int state = 0; - if (unconfined(profile)) + if (profile_unconfined(profile)) return; if (profile->file.dfa && *match_str == AA_CLASS_FILE) { dfa = profile->file.dfa; @@ -655,7 +655,7 @@ static ssize_t query_data(char *buf, size_t buf_len, { char *out; const char *key; - struct aa_profile *profile, *curr; + struct aa_label *label, *curr; struct aa_data *data; u32 bytes, blocks; __le32 outle32; @@ -672,11 +672,11 @@ static ssize_t query_data(char *buf, size_t buf_len, if (buf_len < sizeof(bytes) + sizeof(blocks)) return -EINVAL; /* not enough space */ - curr = begin_current_profile_crit_section(); - profile = aa_fqlookupn_profile(curr, query, strnlen(query, query_len)); - end_current_profile_crit_section(curr); - if (!profile) - return -ENOENT; + curr = begin_current_label_crit_section(); + label = aa_label_parse(curr, query, GFP_KERNEL, false, false); + end_current_label_crit_section(curr); + if (IS_ERR(label)) + return PTR_ERR(label); /* We are going to leave space for two numbers. The first is the total * number of bytes we are writing after the first number. This is so @@ -690,13 +690,16 @@ static ssize_t query_data(char *buf, size_t buf_len, out = buf + sizeof(bytes) + sizeof(blocks); blocks = 0; - if (profile->data) { - data = rhashtable_lookup_fast(profile->data, &key, - profile->data->p); + if (labels_profile(label)->data) { + data = rhashtable_lookup_fast(labels_profile(label)->data, &key, + labels_profile(label)->data->p); if (data) { - if (out + sizeof(outle32) + data->size > buf + buf_len) + if (out + sizeof(outle32) + data->size > + buf + buf_len) { + aa_put_label(label); return -EINVAL; /* not enough space */ + } outle32 = __cpu_to_le32(data->size); memcpy(out, &outle32, sizeof(outle32)); out += sizeof(outle32); @@ -705,7 +708,7 @@ static ssize_t query_data(char *buf, size_t buf_len, blocks++; } } - aa_put_profile(profile); + aa_put_label(label); outle32 = __cpu_to_le32(out - buf - sizeof(bytes)); memcpy(buf, &outle32, sizeof(outle32)); @@ -738,7 +741,7 @@ static ssize_t query_data(char *buf, size_t buf_len, static ssize_t query_label(char *buf, size_t buf_len, char *query, size_t query_len, bool view_only) { - struct aa_profile *profile, *curr; + struct aa_label *label, *curr; char *label_name, *match_str; size_t label_name_len, match_len; struct aa_perms perms; @@ -760,14 +763,14 @@ static ssize_t query_label(char *buf, size_t buf_len, match_str = label_name + label_name_len + 1; match_len = query_len - label_name_len - 1; - curr = begin_current_profile_crit_section(); - profile = aa_fqlookupn_profile(curr, label_name, label_name_len); - end_current_profile_crit_section(curr); - if (!profile) - return -ENOENT; + curr = begin_current_label_crit_section(); + label = aa_label_parse(curr, label_name, GFP_KERNEL, false, false); + end_current_label_crit_section(curr); + if (IS_ERR(label)) + return PTR_ERR(label); perms = allperms; - profile_query_cb(profile, &perms, match_str, match_len); + profile_query_cb(labels_profile(label), &perms, match_str, match_len); return scnprintf(buf, buf_len, "allow 0x%08x\ndeny 0x%08x\naudit 0x%08x\nquiet 0x%08x\n", @@ -1026,9 +1029,10 @@ static int seq_profile_release(struct inode *inode, struct file *file) static int seq_profile_name_show(struct seq_file *seq, void *v) { struct aa_proxy *proxy = seq->private; - struct aa_profile *profile = aa_get_profile_rcu(&proxy->profile); + struct aa_label *label = aa_get_label_rcu(&proxy->label); + struct aa_profile *profile = labels_profile(label); seq_printf(seq, "%s\n", profile->base.name); - aa_put_profile(profile); + aa_put_label(label); return 0; } @@ -1036,9 +1040,10 @@ static int seq_profile_name_show(struct seq_file *seq, void *v) static int seq_profile_mode_show(struct seq_file *seq, void *v) { struct aa_proxy *proxy = seq->private; - struct aa_profile *profile = aa_get_profile_rcu(&proxy->profile); + struct aa_label *label = aa_get_label_rcu(&proxy->label); + struct aa_profile *profile = labels_profile(label); seq_printf(seq, "%s\n", aa_profile_mode_names[profile->mode]); - aa_put_profile(profile); + aa_put_label(label); return 0; } @@ -1046,14 +1051,15 @@ static int seq_profile_mode_show(struct seq_file *seq, void *v) static int seq_profile_attach_show(struct seq_file *seq, void *v) { struct aa_proxy *proxy = seq->private; - struct aa_profile *profile = aa_get_profile_rcu(&proxy->profile); + struct aa_label *label = aa_get_label_rcu(&proxy->label); + struct aa_profile *profile = labels_profile(label); if (profile->attach) seq_printf(seq, "%s\n", profile->attach); else if (profile->xmatch) seq_puts(seq, "\n"); else seq_printf(seq, "%s\n", profile->base.name); - aa_put_profile(profile); + aa_put_label(label); return 0; } @@ -1061,7 +1067,8 @@ static int seq_profile_attach_show(struct seq_file *seq, void *v) static int seq_profile_hash_show(struct seq_file *seq, void *v) { struct aa_proxy *proxy = seq->private; - struct aa_profile *profile = aa_get_profile_rcu(&proxy->profile); + struct aa_label *label = aa_get_label_rcu(&proxy->label); + struct aa_profile *profile = labels_profile(label); unsigned int i, size = aa_hash_size(); if (profile->hash) { @@ -1069,7 +1076,7 @@ static int seq_profile_hash_show(struct seq_file *seq, void *v) seq_printf(seq, "%.2x", profile->hash[i]); seq_putc(seq, '\n'); } - aa_put_profile(profile); + aa_put_label(label); return 0; } @@ -1101,22 +1108,22 @@ static const struct file_operations seq_ns_ ##NAME ##_fops = { \ static int seq_ns_level_show(struct seq_file *seq, void *v) { - struct aa_profile *profile; + struct aa_label *label; - profile = begin_current_profile_crit_section(); - seq_printf(seq, "%d\n", profile->ns->level); - end_current_profile_crit_section(profile); + label = begin_current_label_crit_section(); + seq_printf(seq, "%d\n", labels_ns(label)->level); + end_current_label_crit_section(label); return 0; } static int seq_ns_name_show(struct seq_file *seq, void *v) { - struct aa_profile *profile; + struct aa_label *label = begin_current_label_crit_section(); - profile = begin_current_profile_crit_section(); - seq_printf(seq, "%s\n", aa_ns_name(profile->ns, profile->ns, true)); - end_current_profile_crit_section(profile); + seq_printf(seq, "%s\n", aa_ns_name(labels_ns(label), + labels_ns(label), true)); + end_current_label_crit_section(label); return 0; } @@ -1380,7 +1387,7 @@ static struct dentry *create_profile_file(struct dentry *dir, const char *name, struct aa_profile *profile, const struct file_operations *fops) { - struct aa_proxy *proxy = aa_get_proxy(profile->proxy); + struct aa_proxy *proxy = aa_get_proxy(profile->label.proxy); struct dentry *dent; dent = aafs_create_file(name, S_IFREG | 0444, dir, proxy, fops); @@ -1541,9 +1548,12 @@ static int ns_mkdir_op(struct inode *dir, struct dentry *dentry, umode_t mode) { struct aa_ns *ns, *parent; /* TODO: improve permission check */ - struct aa_profile *profile = begin_current_profile_crit_section(); - int error = aa_may_manage_policy(profile, NULL, AA_MAY_LOAD_POLICY); - end_current_profile_crit_section(profile); + struct aa_label *label; + int error; + + label = begin_current_label_crit_section(); + error = aa_may_manage_policy(label, NULL, AA_MAY_LOAD_POLICY); + end_current_label_crit_section(label); if (error) return error; @@ -1587,13 +1597,16 @@ static int ns_rmdir_op(struct inode *dir, struct dentry *dentry) { struct aa_ns *ns, *parent; /* TODO: improve permission check */ - struct aa_profile *profile = begin_current_profile_crit_section(); - int error = aa_may_manage_policy(profile, NULL, AA_MAY_LOAD_POLICY); - end_current_profile_crit_section(profile); + struct aa_label *label; + int error; + + label = begin_current_label_crit_section(); + error = aa_may_manage_policy(label, NULL, AA_MAY_LOAD_POLICY); + end_current_label_crit_section(label); if (error) return error; - parent = aa_get_ns(dir->i_private); + parent = aa_get_ns(dir->i_private); /* rmdir calls the generic securityfs functions to remove files * from the apparmor dir. It is up to the apparmor ns locking * to avoid races. @@ -1999,10 +2012,9 @@ static int seq_show_profile(struct seq_file *f, void *p) struct aa_profile *profile = (struct aa_profile *)p; struct aa_ns *root = f->private; - if (profile->ns != root) - seq_printf(f, ":%s://", aa_ns_name(root, profile->ns, true)); - seq_printf(f, "%s (%s)\n", profile->base.hname, - aa_profile_mode_names[profile->mode]); + aa_label_seq_xprint(f, root, &profile->label, + FLAG_SHOW_MODE | FLAG_VIEW_SUBNS, GFP_KERNEL); + seq_putc(f, '\n'); return 0; } diff --git a/security/apparmor/audit.c b/security/apparmor/audit.c index 87f40fa8c431..8f9ecac7f8de 100644 --- a/security/apparmor/audit.c +++ b/security/apparmor/audit.c @@ -77,14 +77,24 @@ static void audit_pre(struct audit_buffer *ab, void *ca) audit_log_format(ab, " error=%d", aad(sa)->error); } - if (aad(sa)->profile) { - struct aa_profile *profile = aad(sa)->profile; - if (profile->ns != root_ns) { - audit_log_format(ab, " namespace="); - audit_log_untrustedstring(ab, profile->ns->base.hname); + if (aad(sa)->label) { + struct aa_label *label = aad(sa)->label; + + if (label_isprofile(label)) { + struct aa_profile *profile = labels_profile(label); + + if (profile->ns != root_ns) { + audit_log_format(ab, " namespace="); + audit_log_untrustedstring(ab, + profile->ns->base.hname); + } + audit_log_format(ab, " profile="); + audit_log_untrustedstring(ab, profile->base.hname); + } else { + audit_log_format(ab, " label="); + aa_label_xaudit(ab, root_ns, label, FLAG_VIEW_SUBNS, + GFP_ATOMIC); } - audit_log_format(ab, " profile="); - audit_log_untrustedstring(ab, profile->base.hname); } if (aad(sa)->name) { @@ -139,8 +149,7 @@ int aa_audit(int type, struct aa_profile *profile, struct common_audit_data *sa, if (KILL_MODE(profile) && type == AUDIT_APPARMOR_DENIED) type = AUDIT_APPARMOR_KILL; - if (!unconfined(profile)) - aad(sa)->profile = profile; + aad(sa)->label = &profile->label; aa_audit_msg(type, sa, cb); diff --git a/security/apparmor/context.c b/security/apparmor/context.c index 410b9f7f68a1..c95f1ac6190b 100644 --- a/security/apparmor/context.c +++ b/security/apparmor/context.c @@ -14,9 +14,9 @@ * * * AppArmor sets confinement on every task, via the the aa_task_ctx and - * the aa_task_ctx.profile, both of which are required and are not allowed + * the aa_task_ctx.label, both of which are required and are not allowed * to be NULL. The aa_task_ctx is not reference counted and is unique - * to each cred (which is reference count). The profile pointed to by + * to each cred (which is reference count). The label pointed to by * the task_ctx is reference counted. * * TODO @@ -47,9 +47,9 @@ struct aa_task_ctx *aa_alloc_task_context(gfp_t flags) void aa_free_task_context(struct aa_task_ctx *ctx) { if (ctx) { - aa_put_profile(ctx->profile); - aa_put_profile(ctx->previous); - aa_put_profile(ctx->onexec); + aa_put_label(ctx->label); + aa_put_label(ctx->previous); + aa_put_label(ctx->onexec); kzfree(ctx); } @@ -63,41 +63,41 @@ void aa_free_task_context(struct aa_task_ctx *ctx) void aa_dup_task_context(struct aa_task_ctx *new, const struct aa_task_ctx *old) { *new = *old; - aa_get_profile(new->profile); - aa_get_profile(new->previous); - aa_get_profile(new->onexec); + aa_get_label(new->label); + aa_get_label(new->previous); + aa_get_label(new->onexec); } /** - * aa_get_task_profile - Get another task's profile + * aa_get_task_label - Get another task's label * @task: task to query (NOT NULL) * - * Returns: counted reference to @task's profile + * Returns: counted reference to @task's label */ -struct aa_profile *aa_get_task_profile(struct task_struct *task) +struct aa_label *aa_get_task_label(struct task_struct *task) { - struct aa_profile *p; + struct aa_label *p; rcu_read_lock(); - p = aa_get_newest_profile(__aa_task_raw_profile(task)); + p = aa_get_newest_label(__aa_task_raw_label(task)); rcu_read_unlock(); return p; } /** - * aa_replace_current_profile - replace the current tasks profiles - * @profile: new profile (NOT NULL) + * aa_replace_current_label - replace the current tasks label + * @label: new label (NOT NULL) * * Returns: 0 or error on failure */ -int aa_replace_current_profile(struct aa_profile *profile) +int aa_replace_current_label(struct aa_label *label) { struct aa_task_ctx *ctx = current_ctx(); struct cred *new; - AA_BUG(!profile); + AA_BUG(!label); - if (ctx->profile == profile) + if (ctx->label == label) return 0; if (current_cred() != current_real_cred()) @@ -108,8 +108,8 @@ int aa_replace_current_profile(struct aa_profile *profile) return -ENOMEM; ctx = cred_ctx(new); - if (unconfined(profile) || (ctx->profile->ns != profile->ns)) - /* if switching to unconfined or a different profile namespace + if (unconfined(label) || (labels_ns(ctx->label) != labels_ns(label))) + /* if switching to unconfined or a different label namespace * clear out context state */ aa_clear_task_ctx_trans(ctx); @@ -120,9 +120,9 @@ int aa_replace_current_profile(struct aa_profile *profile) * keeping @profile valid, so make sure to get its reference before * dropping the reference on ctx->profile */ - aa_get_profile(profile); - aa_put_profile(ctx->profile); - ctx->profile = profile; + aa_get_label(label); + aa_put_label(ctx->label); + ctx->label = label; commit_creds(new); return 0; @@ -130,11 +130,11 @@ int aa_replace_current_profile(struct aa_profile *profile) /** * aa_set_current_onexec - set the tasks change_profile to happen onexec - * @profile: system profile to set at exec (MAYBE NULL to clear value) - * + * @label: system label to set at exec (MAYBE NULL to clear value) + * @stack: whether stacking should be done * Returns: 0 or error on failure */ -int aa_set_current_onexec(struct aa_profile *profile) +int aa_set_current_onexec(struct aa_label *label, bool stack) { struct aa_task_ctx *ctx; struct cred *new = prepare_creds(); @@ -142,9 +142,10 @@ int aa_set_current_onexec(struct aa_profile *profile) return -ENOMEM; ctx = cred_ctx(new); - aa_get_profile(profile); - aa_put_profile(ctx->onexec); - ctx->onexec = profile; + aa_get_label(label); + aa_clear_task_ctx_trans(ctx); + ctx->onexec = label; + ctx->token = stack; commit_creds(new); return 0; @@ -152,7 +153,7 @@ int aa_set_current_onexec(struct aa_profile *profile) /** * aa_set_current_hat - set the current tasks hat - * @profile: profile to set as the current hat (NOT NULL) + * @label: label to set as the current hat (NOT NULL) * @token: token value that must be specified to change from the hat * * Do switch of tasks hat. If the task is currently in a hat @@ -160,29 +161,29 @@ int aa_set_current_onexec(struct aa_profile *profile) * * Returns: 0 or error on failure */ -int aa_set_current_hat(struct aa_profile *profile, u64 token) +int aa_set_current_hat(struct aa_label *label, u64 token) { struct aa_task_ctx *ctx; struct cred *new = prepare_creds(); if (!new) return -ENOMEM; - AA_BUG(!profile); + AA_BUG(!label); ctx = cred_ctx(new); if (!ctx->previous) { /* transfer refcount */ - ctx->previous = ctx->profile; + ctx->previous = ctx->label; ctx->token = token; } else if (ctx->token == token) { - aa_put_profile(ctx->profile); + aa_put_label(ctx->label); } else { /* previous_profile && ctx->token != token */ abort_creds(new); return -EACCES; } - ctx->profile = aa_get_newest_profile(profile); + ctx->label = aa_get_newest_label(label); /* clear exec on switching context */ - aa_put_profile(ctx->onexec); + aa_put_label(ctx->onexec); ctx->onexec = NULL; commit_creds(new); @@ -190,15 +191,15 @@ int aa_set_current_hat(struct aa_profile *profile, u64 token) } /** - * aa_restore_previous_profile - exit from hat context restoring the profile + * aa_restore_previous_label - exit from hat context restoring previous label * @token: the token that must be matched to exit hat context * - * Attempt to return out of a hat to the previous profile. The token + * Attempt to return out of a hat to the previous label. The token * must match the stored token value. * * Returns: 0 or error of failure */ -int aa_restore_previous_profile(u64 token) +int aa_restore_previous_label(u64 token) { struct aa_task_ctx *ctx; struct cred *new = prepare_creds(); @@ -210,15 +211,15 @@ int aa_restore_previous_profile(u64 token) abort_creds(new); return -EACCES; } - /* ignore restores when there is no saved profile */ + /* ignore restores when there is no saved label */ if (!ctx->previous) { abort_creds(new); return 0; } - aa_put_profile(ctx->profile); - ctx->profile = aa_get_newest_profile(ctx->previous); - AA_BUG(!ctx->profile); + aa_put_label(ctx->label); + ctx->label = aa_get_newest_label(ctx->previous); + AA_BUG(!ctx->label); /* clear exec && prev information when restoring to previous context */ aa_clear_task_ctx_trans(ctx); diff --git a/security/apparmor/domain.c b/security/apparmor/domain.c index 2ec4ae029215..8d6797c849fe 100644 --- a/security/apparmor/domain.c +++ b/security/apparmor/domain.c @@ -61,24 +61,25 @@ void aa_free_domain_entries(struct aa_domain *domain) static int may_change_ptraced_domain(struct aa_profile *to_profile) { struct task_struct *tracer; - struct aa_profile *tracerp = NULL; + struct aa_label *tracerl = NULL; int error = 0; rcu_read_lock(); tracer = ptrace_parent(current); if (tracer) /* released below */ - tracerp = aa_get_task_profile(tracer); + tracerl = aa_get_task_label(tracer); /* not ptraced */ - if (!tracer || unconfined(tracerp)) + if (!tracer || unconfined(tracerl)) goto out; - error = aa_may_ptrace(tracerp, to_profile, PTRACE_MODE_ATTACH); + error = aa_may_ptrace(labels_profile(tracerl), to_profile, + PTRACE_MODE_ATTACH); out: rcu_read_unlock(); - aa_put_profile(tracerp); + aa_put_label(tracerl); return error; } @@ -102,7 +103,7 @@ static struct aa_perms change_profile_perms(struct aa_profile *profile, struct path_cond cond = { }; unsigned int state; - if (unconfined(profile)) { + if (profile_unconfined(profile)) { perms.allow = AA_MAY_CHANGE_PROFILE | AA_MAY_ONEXEC; perms.audit = perms.quiet = perms.kill = 0; return perms; @@ -144,7 +145,7 @@ static struct aa_profile *__attach_match(const char *name, struct aa_profile *profile, *candidate = NULL; list_for_each_entry_rcu(profile, head, base.list) { - if (profile->flags & PFLAG_NULL) + if (profile->label.flags & FLAG_NULL) continue; if (profile->xmatch && profile->xmatch_len > len) { unsigned int state = aa_dfa_match(profile->xmatch, @@ -338,6 +339,7 @@ static struct aa_profile *x_to_profile(struct aa_profile *profile, int apparmor_bprm_set_creds(struct linux_binprm *bprm) { struct aa_task_ctx *ctx; + struct aa_label *label; struct aa_profile *profile, *new_profile = NULL; struct aa_ns *ns; char *buffer = NULL; @@ -356,7 +358,8 @@ int apparmor_bprm_set_creds(struct linux_binprm *bprm) ctx = cred_ctx(bprm->cred); AA_BUG(!ctx); - profile = aa_get_newest_profile(ctx->profile); + label = aa_get_newest_label(ctx->label); + profile = labels_profile(label); /* buffer freed below, name is pointer into buffer */ get_buffers(buffer); @@ -370,8 +373,8 @@ int apparmor_bprm_set_creds(struct linux_binprm *bprm) error = aa_path_name(&bprm->file->f_path, profile->path_flags, buffer, &name, &info, profile->disconnected); if (error) { - if (unconfined(profile) || - (profile->flags & PFLAG_IX_ON_NAME_ERROR)) + if (profile_unconfined(profile) || + (profile->label.flags & FLAG_IX_ON_NAME_ERROR)) error = 0; name = bprm->filename; goto audit; @@ -380,11 +383,11 @@ int apparmor_bprm_set_creds(struct linux_binprm *bprm) /* Test for onexec first as onexec directives override other * x transitions. */ - if (unconfined(profile)) { + if (profile_unconfined(profile)) { /* unconfined task */ if (ctx->onexec) /* change_profile on exec already been granted */ - new_profile = aa_get_profile(ctx->onexec); + new_profile = labels_profile(aa_get_label(ctx->onexec)); else new_profile = find_attach(ns, &ns->base.profiles, name); if (!new_profile) @@ -402,7 +405,7 @@ int apparmor_bprm_set_creds(struct linux_binprm *bprm) if (ctx->onexec) { struct aa_perms cp; info = "change_profile onexec"; - new_profile = aa_get_newest_profile(ctx->onexec); + new_profile = labels_profile(aa_get_newest_label(ctx->onexec)); if (!(perms.allow & AA_MAY_ONEXEC)) goto audit; @@ -411,9 +414,9 @@ int apparmor_bprm_set_creds(struct linux_binprm *bprm) * exec\0change_profile */ state = aa_dfa_null_transition(profile->file.dfa, state); - cp = change_profile_perms(profile, ctx->onexec->ns, - ctx->onexec->base.name, - AA_MAY_ONEXEC, state); + cp = change_profile_perms(profile, labels_ns(ctx->onexec), + labels_profile(ctx->onexec)->base.name, + AA_MAY_ONEXEC, state); if (!(cp.allow & AA_MAY_ONEXEC)) goto audit; @@ -501,9 +504,9 @@ apply: bprm->per_clear |= PER_CLEAR_ON_SETID; x_clear: - aa_put_profile(ctx->profile); + aa_put_label(ctx->label); /* transfer new profile reference will be released when ctx is freed */ - ctx->profile = new_profile; + ctx->label = &new_profile->label; new_profile = NULL; /* clear out all temporary/transitional state from the context */ @@ -516,7 +519,7 @@ audit: cleanup: aa_put_profile(new_profile); - aa_put_profile(profile); + aa_put_label(label); put_buffers(buffer); return error; @@ -576,7 +579,8 @@ int aa_change_hat(const char *hats[], int count, u64 token, int flags) { const struct cred *cred; struct aa_task_ctx *ctx; - struct aa_profile *profile, *previous_profile, *hat = NULL; + struct aa_label *label, *previous_label; + struct aa_profile *profile, *hat = NULL; char *name = NULL; int i; struct aa_perms perms = {}; @@ -594,10 +598,11 @@ int aa_change_hat(const char *hats[], int count, u64 token, int flags) /* released below */ cred = get_current_cred(); ctx = cred_ctx(cred); - profile = aa_get_newest_cred_profile(cred); - previous_profile = aa_get_newest_profile(ctx->previous); + label = aa_get_newest_cred_label(cred); + previous_label = aa_get_newest_label(ctx->previous); + profile = labels_profile(label); - if (unconfined(profile)) { + if (unconfined(label)) { info = "unconfined"; error = -EPERM; goto audit; @@ -664,7 +669,7 @@ int aa_change_hat(const char *hats[], int count, u64 token, int flags) } if (!(flags & AA_CHANGE_TEST)) { - error = aa_set_current_hat(hat, token); + error = aa_set_current_hat(&hat->label, token); if (error == -EACCES) /* kill task in case of brute force attacks */ perms.kill = AA_MAY_CHANGEHAT; @@ -672,12 +677,12 @@ int aa_change_hat(const char *hats[], int count, u64 token, int flags) /* reset error for learning of new hats */ error = -ENOENT; } - } else if (previous_profile) { + } else if (previous_label) { /* Return to saved profile. Kill task if restore fails * to avoid brute force attacks */ - target = previous_profile->base.hname; - error = aa_restore_previous_profile(token); + target = previous_label->hname; + error = aa_restore_previous_label(token); perms.kill = AA_MAY_CHANGEHAT; } else /* ignore restores when there is no saved profile */ @@ -692,8 +697,8 @@ audit: out: aa_put_profile(hat); kfree(name); - aa_put_profile(profile); - aa_put_profile(previous_profile); + aa_put_label(label); + aa_put_label(previous_label); put_cred(cred); return error; @@ -716,6 +721,7 @@ out: int aa_change_profile(const char *fqname, int flags) { const struct cred *cred; + struct aa_label *label; struct aa_profile *profile, *target = NULL; struct aa_perms perms = {}; const char *info = NULL, *op; @@ -736,7 +742,8 @@ int aa_change_profile(const char *fqname, int flags) } cred = get_current_cred(); - profile = aa_get_newest_cred_profile(cred); + label = aa_get_newest_cred_label(cred); + profile = labels_profile(label); /* * Fail explicitly requested domain transitions if no_new_privs @@ -745,12 +752,12 @@ int aa_change_profile(const char *fqname, int flags) * no_new_privs is set because this aways results in a reduction * of permissions. */ - if (task_no_new_privs(current) && !unconfined(profile)) { + if (task_no_new_privs(current) && !profile_unconfined(profile)) { put_cred(cred); return -EPERM; } - target = aa_fqlookupn_profile(profile, fqname, strlen(fqname)); + target = aa_fqlookupn_profile(label, fqname, strlen(fqname)); if (!target) { info = "profile not found"; error = -ENOENT; @@ -785,9 +792,9 @@ int aa_change_profile(const char *fqname, int flags) goto audit; if (flags & AA_CHANGE_ONEXEC) - error = aa_set_current_onexec(target); + error = aa_set_current_onexec(&target->label, 0); else - error = aa_replace_current_profile(target); + error = aa_replace_current_label(&target->label); audit: if (!(flags & AA_CHANGE_TEST)) @@ -795,7 +802,7 @@ audit: fqname, GLOBAL_ROOT_UID, info, error); aa_put_profile(target); - aa_put_profile(profile); + aa_put_label(label); put_cred(cred); return error; diff --git a/security/apparmor/file.c b/security/apparmor/file.c index bf508791cc1f..5289c8db832b 100644 --- a/security/apparmor/file.c +++ b/security/apparmor/file.c @@ -451,7 +451,7 @@ int aa_file_perm(const char *op, struct aa_profile *profile, struct file *file, request, &cond); } -static void revalidate_tty(struct aa_profile *profile) +static void revalidate_tty(struct aa_label *label) { struct tty_struct *tty; int drop_tty = 0; @@ -469,7 +469,7 @@ static void revalidate_tty(struct aa_profile *profile) struct tty_file_private, list); file = file_priv->file; - if (aa_file_perm(OP_INHERIT, profile, file, + if (aa_file_perm(OP_INHERIT, labels_profile(label), file, MAY_READ | MAY_WRITE)) drop_tty = 1; } @@ -482,9 +482,9 @@ static void revalidate_tty(struct aa_profile *profile) static int match_file(const void *p, struct file *file, unsigned int fd) { - struct aa_profile *profile = (struct aa_profile *)p; + struct aa_label *label = (struct aa_label *)p; - if (aa_file_perm(OP_INHERIT, profile, file, + if (aa_file_perm(OP_INHERIT, labels_profile(label), file, aa_map_file_to_perms(file))) return fd + 1; return 0; @@ -494,14 +494,14 @@ static int match_file(const void *p, struct file *file, unsigned int fd) /* based on selinux's flush_unauthorized_files */ void aa_inherit_files(const struct cred *cred, struct files_struct *files) { - struct aa_profile *profile = aa_get_newest_cred_profile(cred); + struct aa_label *label = aa_get_newest_cred_label(cred); struct file *devnull = NULL; unsigned int n; - revalidate_tty(profile); + revalidate_tty(label); /* Revalidate access to inherited open files. */ - n = iterate_fd(files, 0, match_file, profile); + n = iterate_fd(files, 0, match_file, label); if (!n) /* none found? */ goto out; @@ -511,9 +511,9 @@ void aa_inherit_files(const struct cred *cred, struct files_struct *files) /* replace all the matching ones with this */ do { replace_fd(n - 1, devnull, 0); - } while ((n = iterate_fd(files, n, match_file, profile)) != 0); + } while ((n = iterate_fd(files, n, match_file, label)) != 0); if (devnull) fput(devnull); out: - aa_put_profile(profile); + aa_put_label(label); } diff --git a/security/apparmor/include/apparmor.h b/security/apparmor/include/apparmor.h index 1750cc0721c1..c4a900488e76 100644 --- a/security/apparmor/include/apparmor.h +++ b/security/apparmor/include/apparmor.h @@ -4,7 +4,7 @@ * This file contains AppArmor basic global * * Copyright (C) 1998-2008 Novell/SUSE - * Copyright 2009-2010 Canonical Ltd. + * Copyright 2009-2017 Canonical Ltd. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -27,8 +27,9 @@ #define AA_CLASS_NET 4 #define AA_CLASS_RLIMITS 5 #define AA_CLASS_DOMAIN 6 +#define AA_CLASS_LABEL 16 -#define AA_CLASS_LAST AA_CLASS_DOMAIN +#define AA_CLASS_LAST AA_CLASS_LABEL /* Control parameters settable through module/boot flags */ extern enum audit_mode aa_g_audit; diff --git a/security/apparmor/include/audit.h b/security/apparmor/include/audit.h index d548261dd1b7..20fa6c77db05 100644 --- a/security/apparmor/include/audit.h +++ b/security/apparmor/include/audit.h @@ -22,8 +22,7 @@ #include #include "file.h" - -struct aa_profile; +#include "label.h" extern const char *const audit_mode_names[]; #define AUDIT_MAX_INDEX 5 @@ -103,9 +102,9 @@ enum audit_type { struct apparmor_audit_data { int error; - const char *op; int type; - void *profile; + const char *op; + struct aa_label *label; const char *name; const char *info; u32 request; @@ -113,7 +112,7 @@ struct apparmor_audit_data { union { /* these entries require a custom callback fn */ struct { - struct aa_profile *peer; + struct aa_label *peer; struct { const char *target; kuid_t ouid; diff --git a/security/apparmor/include/context.h b/security/apparmor/include/context.h index 7665fae7131f..6ae07e9aaa17 100644 --- a/security/apparmor/include/context.h +++ b/security/apparmor/include/context.h @@ -19,7 +19,7 @@ #include #include -#include "policy.h" +#include "label.h" #include "policy_ns.h" #define cred_ctx(X) ((X)->security) @@ -27,20 +27,20 @@ /** * struct aa_task_ctx - primary label for confined tasks - * @profile: the current profile (NOT NULL) - * @exec: profile to transition to on next exec (MAYBE NULL) - * @previous: profile the task may return to (MAYBE NULL) - * @token: magic value the task must know for returning to @previous_profile + * @label: the current label (NOT NULL) + * @exec: label to transition to on next exec (MAYBE NULL) + * @previous: label the task may return to (MAYBE NULL) + * @token: magic value the task must know for returning to @previous * - * Contains the task's current profile (which could change due to + * Contains the task's current label (which could change due to * change_hat). Plus the hat_magic needed during change_hat. * * TODO: make so a task can be confined by a stack of contexts */ struct aa_task_ctx { - struct aa_profile *profile; - struct aa_profile *onexec; - struct aa_profile *previous; + struct aa_label *label; + struct aa_label *onexec; + struct aa_label *previous; u64 token; }; @@ -48,52 +48,51 @@ struct aa_task_ctx *aa_alloc_task_context(gfp_t flags); void aa_free_task_context(struct aa_task_ctx *ctx); void aa_dup_task_context(struct aa_task_ctx *new, const struct aa_task_ctx *old); -int aa_replace_current_profile(struct aa_profile *profile); -int aa_set_current_onexec(struct aa_profile *profile); -int aa_set_current_hat(struct aa_profile *profile, u64 token); -int aa_restore_previous_profile(u64 cookie); -struct aa_profile *aa_get_task_profile(struct task_struct *task); +int aa_replace_current_label(struct aa_label *label); +int aa_set_current_onexec(struct aa_label *label, bool stack); +int aa_set_current_hat(struct aa_label *label, u64 token); +int aa_restore_previous_label(u64 cookie); +struct aa_label *aa_get_task_label(struct task_struct *task); /** - * aa_cred_raw_profile - obtain cred's profiles - * @cred: cred to obtain profiles from (NOT NULL) + * aa_cred_raw_label - obtain cred's label + * @cred: cred to obtain label from (NOT NULL) * - * Returns: confining profile + * Returns: confining label * * does NOT increment reference count */ -static inline struct aa_profile *aa_cred_raw_profile(const struct cred *cred) +static inline struct aa_label *aa_cred_raw_label(const struct cred *cred) { struct aa_task_ctx *ctx = cred_ctx(cred); - AA_BUG(!ctx || !ctx->profile); - return ctx->profile; + AA_BUG(!ctx || !ctx->label); + return ctx->label; } /** - * aa_get_newest_cred_profile - obtain the newest profile on a cred - * @cred: cred to obtain profile from (NOT NULL) + * aa_get_newest_cred_label - obtain the newest label on a cred + * @cred: cred to obtain label from (NOT NULL) * - * Returns: newest version of confining profile + * Returns: newest version of confining label */ -static inline -struct aa_profile *aa_get_newest_cred_profile(const struct cred *cred) +static inline struct aa_label *aa_get_newest_cred_label(const struct cred *cred) { - return aa_get_newest_profile(aa_cred_raw_profile(cred)); + return aa_get_newest_label(aa_cred_raw_label(cred)); } /** - * __aa_task_raw_profile - retrieve another task's profile + * __aa_task_raw_label - retrieve another task's label * @task: task to query (NOT NULL) * - * Returns: @task's profile without incrementing its ref count + * Returns: @task's label without incrementing its ref count * * If @task != current needs to be called in RCU safe critical section */ -static inline struct aa_profile *__aa_task_raw_profile(struct task_struct *task) +static inline struct aa_label *__aa_task_raw_label(struct task_struct *task) { - return aa_cred_raw_profile(__task_cred(task)); + return aa_cred_raw_label(__task_cred(task)); } /** @@ -104,113 +103,112 @@ static inline struct aa_profile *__aa_task_raw_profile(struct task_struct *task) */ static inline bool __aa_task_is_confined(struct task_struct *task) { - return !unconfined(__aa_task_raw_profile(task)); + return !unconfined(__aa_task_raw_label(task)); } /** - * aa_current_raw_profile - find the current tasks confining profile + * aa_current_raw_label - find the current tasks confining label * - * Returns: up to date confining profile or the ns unconfined profile (NOT NULL) + * Returns: up to date confining label or the ns unconfined label (NOT NULL) * * This fn will not update the tasks cred to the most up to date version - * of the profile so it is safe to call when inside of locks. + * of the label so it is safe to call when inside of locks. */ -static inline struct aa_profile *aa_current_raw_profile(void) +static inline struct aa_label *aa_current_raw_label(void) { - return aa_cred_raw_profile(current_cred()); + return aa_cred_raw_label(current_cred()); } /** - * aa_get_current_profile - get the newest version of the current tasks profile + * aa_get_current_label - get the newest version of the current tasks label * - * Returns: newest version of confining profile (NOT NULL) + * Returns: newest version of confining label (NOT NULL) * * This fn will not update the tasks cred, so it is safe inside of locks * - * The returned reference must be put with aa_put_profile() + * The returned reference must be put with aa_put_label() */ -static inline struct aa_profile *aa_get_current_profile(void) +static inline struct aa_label *aa_get_current_label(void) { - struct aa_profile *p = aa_current_raw_profile(); + struct aa_label *l = aa_current_raw_label(); - if (profile_is_stale(p)) - return aa_get_newest_profile(p); - return aa_get_profile(p); + if (label_is_stale(l)) + return aa_get_newest_label(l); + return aa_get_label(l); } -#define __end_current_profile_crit_section(X) \ - end_current_profile_crit_section(X) +#define __end_current_label_crit_section(X) end_current_label_crit_section(X) /** - * end_profile_crit_section - put a reference found with begin_current_profile.. - * @profile: profile reference to put + * end_label_crit_section - put a reference found with begin_current_label.. + * @label: label reference to put * * Should only be used with a reference obtained with - * begin_current_profile_crit_section and never used in situations where the + * begin_current_label_crit_section and never used in situations where the * task cred may be updated */ -static inline void end_current_profile_crit_section(struct aa_profile *profile) +static inline void end_current_label_crit_section(struct aa_label *label) { - if (profile != aa_current_raw_profile()) - aa_put_profile(profile); + if (label != aa_current_raw_label()) + aa_put_label(label); } /** - * __begin_current_profile_crit_section - current's confining profile + * __begin_current_label_crit_section - current's confining label * - * Returns: up to date confining profile or the ns unconfined profile (NOT NULL) + * Returns: up to date confining label or the ns unconfined label (NOT NULL) * * safe to call inside locks * - * The returned reference must be put with __end_current_profile_crit_section() + * The returned reference must be put with __end_current_label_crit_section() * This must NOT be used if the task cred could be updated within the - * critical section between __begin_current_profile_crit_section() .. - * __end_current_profile_crit_section() + * critical section between __begin_current_label_crit_section() .. + * __end_current_label_crit_section() */ -static inline struct aa_profile *__begin_current_profile_crit_section(void) +static inline struct aa_label *__begin_current_label_crit_section(void) { - struct aa_profile *profile = aa_current_raw_profile(); + struct aa_label *label = aa_current_raw_label(); - if (profile_is_stale(profile)) - profile = aa_get_newest_profile(profile); + if (label_is_stale(label)) + label = aa_get_newest_label(label); - return profile; + return label; } /** - * begin_current_profile_crit_section - current's profile and update if needed + * begin_current_label_crit_section - current's confining label and update it * - * Returns: up to date confining profile or the ns unconfined profile (NOT NULL) + * Returns: up to date confining label or the ns unconfined label (NOT NULL) * * Not safe to call inside locks * - * The returned reference must be put with end_current_profile_crit_section() + * The returned reference must be put with end_current_label_crit_section() * This must NOT be used if the task cred could be updated within the - * critical section between begin_current_profile_crit_section() .. - * end_current_profile_crit_section() + * critical section between begin_current_label_crit_section() .. + * end_current_label_crit_section() */ -static inline struct aa_profile *begin_current_profile_crit_section(void) +static inline struct aa_label *begin_current_label_crit_section(void) { - struct aa_profile *profile = aa_current_raw_profile(); + struct aa_label *label = aa_current_raw_label(); - if (profile_is_stale(profile)) { - profile = aa_get_newest_profile(profile); - if (aa_replace_current_profile(profile) == 0) + if (label_is_stale(label)) { + label = aa_get_newest_label(label); + if (aa_replace_current_label(label) == 0) /* task cred will keep the reference */ - aa_put_profile(profile); + aa_put_label(label); } - return profile; + return label; } static inline struct aa_ns *aa_get_current_ns(void) { - struct aa_profile *profile; + struct aa_label *label; struct aa_ns *ns; - profile = __begin_current_profile_crit_section(); - ns = aa_get_ns(profile->ns); - __end_current_profile_crit_section(profile); + label = __begin_current_label_crit_section(); + ns = aa_get_ns(labels_ns(label)); + __end_current_label_crit_section(label); return ns; } @@ -221,8 +219,8 @@ static inline struct aa_ns *aa_get_current_ns(void) */ static inline void aa_clear_task_ctx_trans(struct aa_task_ctx *ctx) { - aa_put_profile(ctx->previous); - aa_put_profile(ctx->onexec); + aa_put_label(ctx->previous); + aa_put_label(ctx->onexec); ctx->previous = NULL; ctx->onexec = NULL; ctx->token = 0; diff --git a/security/apparmor/include/perms.h b/security/apparmor/include/perms.h index 82946fb81f91..0c5c2b00be02 100644 --- a/security/apparmor/include/perms.h +++ b/security/apparmor/include/perms.h @@ -15,6 +15,7 @@ #define __AA_PERM_H #include +#include "label.h" #define AA_MAY_EXEC MAY_EXEC #define AA_MAY_WRITE MAY_WRITE @@ -101,5 +102,14 @@ void aa_apply_modes_to_perms(struct aa_profile *profile, struct aa_perms *perms); void aa_compute_perms(struct aa_dfa *dfa, unsigned int state, struct aa_perms *perms); - +void aa_perms_accum(struct aa_perms *accum, struct aa_perms *addend); +void aa_perms_accum_raw(struct aa_perms *accum, struct aa_perms *addend); +void aa_profile_match_label(struct aa_profile *profile, struct aa_label *label, + int type, u32 request, struct aa_perms *perms); +int aa_profile_label_perm(struct aa_profile *profile, struct aa_profile *target, + u32 request, int type, u32 *deny, + struct common_audit_data *sa); +int aa_check_perms(struct aa_profile *profile, struct aa_perms *perms, + u32 request, struct common_audit_data *sa, + void (*cb)(struct audit_buffer *, void *)); #endif /* __AA_PERM_H */ diff --git a/security/apparmor/include/policy.h b/security/apparmor/include/policy.h index d93f475bfd8b..17fe41a9cac3 100644 --- a/security/apparmor/include/policy.h +++ b/security/apparmor/include/policy.h @@ -29,6 +29,7 @@ #include "domain.h" #include "file.h" #include "lib.h" +#include "label.h" #include "perms.h" #include "resource.h" @@ -48,9 +49,9 @@ extern const char *const aa_profile_mode_names[]; #define KILL_MODE(_profile) PROFILE_MODE((_profile), APPARMOR_KILL) -#define PROFILE_IS_HAT(_profile) ((_profile)->flags & PFLAG_HAT) +#define PROFILE_IS_HAT(_profile) ((_profile)->label.flags & FLAG_HAT) -#define profile_is_stale(_profile) ((_profile)->flags & PFLAG_STALE) +#define profile_is_stale(_profile) (label_is_stale(&(_profile)->label)) #define on_list_rcu(X) (!list_empty(X) && (X)->prev != LIST_POISON2) @@ -67,22 +68,6 @@ enum profile_mode { APPARMOR_UNCONFINED, /* profile set to unconfined */ }; -enum profile_flags { - PFLAG_HAT = 1, /* profile is a hat */ - PFLAG_NULL = 4, /* profile is null learning profile */ - PFLAG_IX_ON_NAME_ERROR = 8, /* fallback to ix on name lookup fail */ - PFLAG_IMMUTABLE = 0x10, /* don't allow changes/replacement */ - PFLAG_USER_DEFINED = 0x20, /* user based profile - lower privs */ - PFLAG_NO_LIST_REF = 0x40, /* list doesn't keep profile ref */ - PFLAG_OLD_NULL_TRANS = 0x100, /* use // as the null transition */ - PFLAG_STALE = 0x200, /* profile replaced/removed */ - PFLAG_NS_COUNT = 0x400, /* carries NS ref count */ - - /* These flags must correspond with PATH_flags */ - PFLAG_MEDIATE_DELETED = 0x10000, /* mediate instead delegate deleted */ -}; - -struct aa_profile; /* struct aa_policydb - match engine for a policy * dfa: dfa pattern match @@ -95,11 +80,6 @@ struct aa_policydb { }; -struct aa_proxy { - struct kref count; - struct aa_profile __rcu *profile; -}; - /* struct aa_data - generic data structure * key: name for retrieving this data * size: size of data in bytes @@ -116,18 +96,15 @@ struct aa_data { /* struct aa_profile - basic confinement data * @base - base components of the profile (name, refcount, lists, lock ...) - * @count: reference count of the obj - * @rcu: rcu head used when removing from @list + * @label - label this profile is an extension of * @parent: parent of profile * @ns: namespace the profile is in - * @proxy: is set to the profile that replaced this profile * @rename: optional profile name that this profile renamed * @attach: human readable attachment string * @xmatch: optional extended matching for unconfined executables names * @xmatch_len: xmatch prefix len, used to determine xmatch priority * @audit: the auditing mode of the profile * @mode: the enforcement mode of the profile - * @flags: flags controlling profile behavior * @path_flags: flags controlling path generation behavior * @disconnected: what to prepend if attach_disconnected is specified * @size: the memory consumed by this profiles rules @@ -145,8 +122,6 @@ struct aa_data { * used to determine profile attachment against unconfined tasks. All other * attachments are determined by profile X transition rules. * - * The @proxy struct is write protected by the profile lock. - * * Profiles have a hierarchy where hats and children profiles keep * a reference to their parent. * @@ -156,12 +131,9 @@ struct aa_data { */ struct aa_profile { struct aa_policy base; - struct kref count; - struct rcu_head rcu; struct aa_profile __rcu *parent; struct aa_ns *ns; - struct aa_proxy *proxy; const char *rename; const char *attach; @@ -169,7 +141,6 @@ struct aa_profile { int xmatch_len; enum audit_mode audit; long mode; - long flags; u32 path_flags; const char *disconnected; int size; @@ -184,6 +155,7 @@ struct aa_profile { char *dirname; struct dentry *dents[AAFS_PROF_SIZEOF]; struct rhashtable *data; + struct aa_label label; }; extern enum profile_mode aa_g_profile_mode; @@ -192,13 +164,15 @@ extern enum profile_mode aa_g_profile_mode; #define AA_MAY_REPLACE_POLICY AA_MAY_WRITE #define AA_MAY_REMOVE_POLICY AA_MAY_DELETE -void __aa_update_proxy(struct aa_profile *orig, struct aa_profile *new); +#define profiles_ns(P) ((P)->ns) +#define name_is_shared(A, B) ((A)->hname && (A)->hname == (B)->hname) void aa_add_profile(struct aa_policy *common, struct aa_profile *profile); void aa_free_proxy_kref(struct kref *kref); -struct aa_profile *aa_alloc_profile(const char *name, gfp_t gfp); +struct aa_profile *aa_alloc_profile(const char *name, struct aa_proxy *proxy, + gfp_t gfp); struct aa_profile *aa_new_null_profile(struct aa_profile *parent, bool hat, const char *base, gfp_t gfp); void aa_free_profile(struct aa_profile *profile); @@ -207,20 +181,33 @@ struct aa_profile *aa_find_child(struct aa_profile *parent, const char *name); struct aa_profile *aa_lookupn_profile(struct aa_ns *ns, const char *hname, size_t n); struct aa_profile *aa_lookup_profile(struct aa_ns *ns, const char *name); -struct aa_profile *aa_fqlookupn_profile(struct aa_profile *base, +struct aa_profile *aa_fqlookupn_profile(struct aa_label *base, const char *fqname, size_t n); struct aa_profile *aa_match_profile(struct aa_ns *ns, const char *name); -ssize_t aa_replace_profiles(struct aa_ns *view, struct aa_profile *profile, +ssize_t aa_replace_profiles(struct aa_ns *view, struct aa_label *label, u32 mask, struct aa_loaddata *udata); -ssize_t aa_remove_profiles(struct aa_ns *view, struct aa_profile *profile, - char *name, size_t size); +ssize_t aa_remove_profiles(struct aa_ns *view, struct aa_label *label, + char *name, size_t size); void __aa_profile_list_release(struct list_head *head); #define PROF_ADD 1 #define PROF_REPLACE 0 -#define unconfined(X) ((X)->mode == APPARMOR_UNCONFINED) +#define profile_unconfined(X) ((X)->mode == APPARMOR_UNCONFINED) + +/** + * aa_get_newest_profile - simple wrapper fn to wrap the label version + * @p: profile (NOT NULL) + * + * Returns refcount to newest version of the profile (maybe @p) + * + * Requires: @p must be held with a valid refcount + */ +static inline struct aa_profile *aa_get_newest_profile(struct aa_profile *p) +{ + return labels_profile(aa_get_newest_label(&p->label)); +} #define PROFILE_MEDIATES(P, T) ((P)->policy.start[(T)]) /* safe version of POLICY_MEDIATES for full range input */ @@ -243,7 +230,7 @@ static inline unsigned int PROFILE_MEDIATES_SAFE(struct aa_profile *profile, static inline struct aa_profile *aa_get_profile(struct aa_profile *p) { if (p) - kref_get(&(p->count)); + kref_get(&(p->label.count)); return p; } @@ -257,7 +244,7 @@ static inline struct aa_profile *aa_get_profile(struct aa_profile *p) */ static inline struct aa_profile *aa_get_profile_not0(struct aa_profile *p) { - if (p && kref_get_unless_zero(&p->count)) + if (p && kref_get_unless_zero(&p->label.count)) return p; return NULL; @@ -277,31 +264,12 @@ static inline struct aa_profile *aa_get_profile_rcu(struct aa_profile __rcu **p) rcu_read_lock(); do { c = rcu_dereference(*p); - } while (c && !kref_get_unless_zero(&c->count)); + } while (c && !kref_get_unless_zero(&c->label.count)); rcu_read_unlock(); return c; } -/** - * aa_get_newest_profile - find the newest version of @profile - * @profile: the profile to check for newer versions of - * - * Returns: refcounted newest version of @profile taking into account - * replacement, renames and removals - * return @profile. - */ -static inline struct aa_profile *aa_get_newest_profile(struct aa_profile *p) -{ - if (!p) - return NULL; - - if (profile_is_stale(p)) - return aa_get_profile_rcu(&p->proxy->profile); - - return aa_get_profile(p); -} - /** * aa_put_profile - decrement refcount on profile @p * @p: profile (MAYBE NULL) @@ -309,21 +277,7 @@ static inline struct aa_profile *aa_get_newest_profile(struct aa_profile *p) static inline void aa_put_profile(struct aa_profile *p) { if (p) - kref_put(&p->count, aa_free_profile_kref); -} - -static inline struct aa_proxy *aa_get_proxy(struct aa_proxy *p) -{ - if (p) - kref_get(&(p->count)); - - return p; -} - -static inline void aa_put_proxy(struct aa_proxy *p) -{ - if (p) - kref_put(&p->count, aa_free_proxy_kref); + kref_put(&p->label.count, aa_label_kref); } static inline int AUDIT_MODE(struct aa_profile *profile) @@ -336,7 +290,7 @@ static inline int AUDIT_MODE(struct aa_profile *profile) bool policy_view_capable(struct aa_ns *ns); bool policy_admin_capable(struct aa_ns *ns); -int aa_may_manage_policy(struct aa_profile *profile, struct aa_ns *ns, +int aa_may_manage_policy(struct aa_label *label, struct aa_ns *ns, u32 mask); #endif /* __AA_POLICY_H */ diff --git a/security/apparmor/include/policy_ns.h b/security/apparmor/include/policy_ns.h index 2f7e480a34e0..9605f18624e2 100644 --- a/security/apparmor/include/policy_ns.h +++ b/security/apparmor/include/policy_ns.h @@ -19,6 +19,7 @@ #include "apparmor.h" #include "apparmorfs.h" +#include "label.h" #include "policy.h" @@ -71,6 +72,7 @@ struct aa_ns { long revision; wait_queue_head_t wait; + struct aa_labelset labels; struct list_head rawdata_list; struct dentry *dents[AAFS_NS_SIZEOF]; @@ -80,6 +82,8 @@ extern struct aa_ns *root_ns; extern const char *aa_hidden_ns_name; +#define ns_unconfined(NS) (&(NS)->unconfined->label) + bool aa_ns_visible(struct aa_ns *curr, struct aa_ns *view, bool subns); const char *aa_ns_name(struct aa_ns *parent, struct aa_ns *child, bool subns); void aa_free_ns(struct aa_ns *ns); diff --git a/security/apparmor/ipc.c b/security/apparmor/ipc.c index edac790923c3..fa68cd42bd15 100644 --- a/security/apparmor/ipc.c +++ b/security/apparmor/ipc.c @@ -22,11 +22,12 @@ #include "include/ipc.h" /* call back to audit ptrace fields */ -static void audit_cb(struct audit_buffer *ab, void *va) +static void audit_ptrace_cb(struct audit_buffer *ab, void *va) { struct common_audit_data *sa = va; audit_log_format(ab, " peer="); - audit_log_untrustedstring(ab, aad(sa)->peer->base.hname); + aa_label_xaudit(ab, labels_ns(aad(sa)->label), aad(sa)->peer, + FLAGS_NONE, GFP_ATOMIC); } /** @@ -42,10 +43,10 @@ static int aa_audit_ptrace(struct aa_profile *profile, { DEFINE_AUDIT_DATA(sa, LSM_AUDIT_DATA_NONE, OP_PTRACE); - aad(&sa)->peer = target; + aad(&sa)->peer = &target->label; aad(&sa)->error = error; - return aa_audit(AUDIT_APPARMOR_AUTO, profile, &sa, audit_cb); + return aa_audit(AUDIT_APPARMOR_AUTO, profile, &sa, audit_ptrace_cb); } /** @@ -64,7 +65,7 @@ int aa_may_ptrace(struct aa_profile *tracer, struct aa_profile *tracee, * Test mode for PTRACE_MODE_READ || PTRACE_MODE_ATTACH */ - if (unconfined(tracer) || tracer == tracee) + if (profile_unconfined(tracer) || tracer == tracee) return 0; /* log this capability request */ return aa_capable(tracer, CAP_SYS_PTRACE, 1); @@ -90,18 +91,22 @@ int aa_ptrace(struct task_struct *tracer, struct task_struct *tracee, * - tracer profile has CAP_SYS_PTRACE */ - struct aa_profile *tracer_p = aa_get_task_profile(tracer); + struct aa_label *tracer_l = aa_get_task_label(tracer); int error = 0; - if (!unconfined(tracer_p)) { - struct aa_profile *tracee_p = aa_get_task_profile(tracee); + if (!unconfined(tracer_l)) { + struct aa_label *tracee_l = aa_get_task_label(tracee); - error = aa_may_ptrace(tracer_p, tracee_p, mode); - error = aa_audit_ptrace(tracer_p, tracee_p, error); + error = aa_may_ptrace(labels_profile(tracer_l), + labels_profile(tracee_l), + mode); + error = aa_audit_ptrace(labels_profile(tracer_l), + labels_profile(tracee_l), + error); - aa_put_profile(tracee_p); + aa_put_label(tracee_l); } - aa_put_profile(tracer_p); + aa_put_label(tracer_l); return error; } diff --git a/security/apparmor/lib.c b/security/apparmor/lib.c index 0ceecdbb4658..08ca26bcca77 100644 --- a/security/apparmor/lib.c +++ b/security/apparmor/lib.c @@ -246,6 +246,32 @@ void aa_audit_perm_mask(struct audit_buffer *ab, u32 mask, const char *chrs, audit_log_format(ab, "\""); } +/** + * aa_audit_perms_cb - generic callback fn for auditing perms + * @ab: audit buffer (NOT NULL) + * @va: audit struct to audit values of (NOT NULL) + */ +static void aa_audit_perms_cb(struct audit_buffer *ab, void *va) +{ + struct common_audit_data *sa = va; + + if (aad(sa)->request) { + audit_log_format(ab, " requested_mask="); + aa_audit_perm_mask(ab, aad(sa)->request, aa_file_perm_chrs, + PERMS_CHRS_MASK, aa_file_perm_names, + PERMS_NAMES_MASK); + } + if (aad(sa)->denied) { + audit_log_format(ab, "denied_mask="); + aa_audit_perm_mask(ab, aad(sa)->denied, aa_file_perm_chrs, + PERMS_CHRS_MASK, aa_file_perm_names, + PERMS_NAMES_MASK); + } + audit_log_format(ab, " peer="); + aa_label_xaudit(ab, labels_ns(aad(sa)->label), aad(sa)->peer, + FLAGS_NONE, GFP_ATOMIC); +} + /** * aa_apply_modes_to_perms - apply namespace and profile flags to perms * @profile: that perms where computed from @@ -309,6 +335,143 @@ void aa_compute_perms(struct aa_dfa *dfa, unsigned int state, // perms->xindex = dfa_user_xindex(dfa, state); } +/** + * aa_perms_accum_raw - accumulate perms with out masking off overlapping perms + * @accum - perms struct to accumulate into + * @addend - perms struct to add to @accum + */ +void aa_perms_accum_raw(struct aa_perms *accum, struct aa_perms *addend) +{ + accum->deny |= addend->deny; + accum->allow &= addend->allow & ~addend->deny; + accum->audit |= addend->audit & addend->allow; + accum->quiet &= addend->quiet & ~addend->allow; + accum->kill |= addend->kill & ~addend->allow; + accum->stop |= addend->stop & ~addend->allow; + accum->complain |= addend->complain & ~addend->allow & ~addend->deny; + accum->cond |= addend->cond & ~addend->allow & ~addend->deny; + accum->hide &= addend->hide & ~addend->allow; + accum->prompt |= addend->prompt & ~addend->allow & ~addend->deny; +} + +/** + * aa_perms_accum - accumulate perms, masking off overlapping perms + * @accum - perms struct to accumulate into + * @addend - perms struct to add to @accum + */ +void aa_perms_accum(struct aa_perms *accum, struct aa_perms *addend) +{ + accum->deny |= addend->deny; + accum->allow &= addend->allow & ~accum->deny; + accum->audit |= addend->audit & accum->allow; + accum->quiet &= addend->quiet & ~accum->allow; + accum->kill |= addend->kill & ~accum->allow; + accum->stop |= addend->stop & ~accum->allow; + accum->complain |= addend->complain & ~accum->allow & ~accum->deny; + accum->cond |= addend->cond & ~accum->allow & ~accum->deny; + accum->hide &= addend->hide & ~accum->allow; + accum->prompt |= addend->prompt & ~accum->allow & ~accum->deny; +} + +void aa_profile_match_label(struct aa_profile *profile, struct aa_label *label, + int type, u32 request, struct aa_perms *perms) +{ + /* TODO: doesn't yet handle extended types */ + unsigned int state; + + state = aa_dfa_next(profile->policy.dfa, + profile->policy.start[AA_CLASS_LABEL], + type); + aa_label_match(profile, label, state, false, request, perms); +} + + +/* currently unused */ +int aa_profile_label_perm(struct aa_profile *profile, struct aa_profile *target, + u32 request, int type, u32 *deny, + struct common_audit_data *sa) +{ + struct aa_perms perms; + + aad(sa)->label = &profile->label; + aad(sa)->peer = &target->label; + aad(sa)->request = request; + + aa_profile_match_label(profile, &target->label, type, request, &perms); + aa_apply_modes_to_perms(profile, &perms); + *deny |= request & perms.deny; + return aa_check_perms(profile, &perms, request, sa, aa_audit_perms_cb); +} + +/** + * aa_check_perms - do audit mode selection based on perms set + * @profile: profile being checked + * @perms: perms computed for the request + * @request: requested perms + * @deny: Returns: explicit deny set + * @sa: initialized audit structure (MAY BE NULL if not auditing) + * @cb: callback fn for tpye specific fields (MAY BE NULL) + * + * Returns: 0 if permission else error code + * + * Note: profile audit modes need to be set before calling by setting the + * perm masks appropriately. + * + * If not auditing then complain mode is not enabled and the + * error code will indicate whether there was an explicit deny + * with a positive value. + */ +int aa_check_perms(struct aa_profile *profile, struct aa_perms *perms, + u32 request, struct common_audit_data *sa, + void (*cb)(struct audit_buffer *, void *)) +{ + int type, error; + bool stop = false; + u32 denied = request & (~perms->allow | perms->deny); + + if (likely(!denied)) { + /* mask off perms that are not being force audited */ + request &= perms->audit; + if (!request || !sa) + return 0; + + type = AUDIT_APPARMOR_AUDIT; + error = 0; + } else { + error = -EACCES; + + if (denied & perms->kill) + type = AUDIT_APPARMOR_KILL; + else if (denied == (denied & perms->complain)) + type = AUDIT_APPARMOR_ALLOWED; + else + type = AUDIT_APPARMOR_DENIED; + + if (denied & perms->stop) + stop = true; + if (denied == (denied & perms->hide)) + error = -ENOENT; + + denied &= ~perms->quiet; + if (!sa || !denied) + return error; + } + + if (sa) { + aad(sa)->label = &profile->label; + aad(sa)->request = request; + aad(sa)->denied = denied; + aad(sa)->error = error; + aa_audit_msg(type, sa, cb); + } + + if (type == AUDIT_APPARMOR_ALLOWED) + error = 0; + + return error; +} + + /** * aa_policy_init - initialize a policy structure * @policy: policy to initialize (NOT NULL) diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c index 7ba43c18687a..3ba08530c92e 100644 --- a/security/apparmor/lsm.c +++ b/security/apparmor/lsm.c @@ -34,6 +34,7 @@ #include "include/file.h" #include "include/ipc.h" #include "include/path.h" +#include "include/label.h" #include "include/policy.h" #include "include/policy_ns.h" #include "include/procattr.h" @@ -49,7 +50,7 @@ DEFINE_PER_CPU(struct aa_buffers, aa_buffers); */ /* - * free the associated aa_task_ctx and put its profiles + * free the associated aa_task_ctx and put its labels */ static void apparmor_cred_free(struct cred *cred) { @@ -115,23 +116,24 @@ static int apparmor_ptrace_traceme(struct task_struct *parent) static int apparmor_capget(struct task_struct *target, kernel_cap_t *effective, kernel_cap_t *inheritable, kernel_cap_t *permitted) { + struct aa_label *label; struct aa_profile *profile; const struct cred *cred; rcu_read_lock(); cred = __task_cred(target); - profile = aa_get_newest_cred_profile(cred); - + label = aa_get_newest_cred_label(cred); + profile = labels_profile(label); /* * cap_capget is stacked ahead of this and will * initialize effective and permitted. */ - if (!unconfined(profile) && !COMPLAIN_MODE(profile)) { + if (!profile_unconfined(profile) && !COMPLAIN_MODE(profile)) { *effective = cap_intersect(*effective, profile->caps.allow); *permitted = cap_intersect(*permitted, profile->caps.allow); } rcu_read_unlock(); - aa_put_profile(profile); + aa_put_label(label); return 0; } @@ -139,13 +141,13 @@ static int apparmor_capget(struct task_struct *target, kernel_cap_t *effective, static int apparmor_capable(const struct cred *cred, struct user_namespace *ns, int cap, int audit) { - struct aa_profile *profile; + struct aa_label *label; int error = 0; - profile = aa_get_newest_cred_profile(cred); - if (!unconfined(profile)) - error = aa_capable(profile, cap, audit); - aa_put_profile(profile); + label = aa_get_newest_cred_label(cred); + if (!unconfined(label)) + error = aa_capable(labels_profile(label), cap, audit); + aa_put_label(label); return error; } @@ -162,13 +164,14 @@ static int apparmor_capable(const struct cred *cred, struct user_namespace *ns, static int common_perm(const char *op, const struct path *path, u32 mask, struct path_cond *cond) { - struct aa_profile *profile; + struct aa_label *label; int error = 0; - profile = __begin_current_profile_crit_section(); - if (!unconfined(profile)) - error = aa_path_perm(op, profile, path, 0, mask, cond); - __end_current_profile_crit_section(profile); + label = __begin_current_label_crit_section(); + if (!unconfined(label)) + error = aa_path_perm(op, labels_profile(label), path, 0, mask, + cond); + __end_current_label_crit_section(label); return error; } @@ -295,16 +298,17 @@ static int apparmor_path_symlink(const struct path *dir, struct dentry *dentry, static int apparmor_path_link(struct dentry *old_dentry, const struct path *new_dir, struct dentry *new_dentry) { - struct aa_profile *profile; + struct aa_label *label; int error = 0; if (!path_mediated_fs(old_dentry)) return 0; - profile = begin_current_profile_crit_section(); - if (!unconfined(profile)) - error = aa_path_link(profile, old_dentry, new_dir, new_dentry); - end_current_profile_crit_section(profile); + label = begin_current_label_crit_section(); + if (!unconfined(label)) + error = aa_path_link(labels_profile(label), old_dentry, new_dir, + new_dentry); + end_current_label_crit_section(label); return error; } @@ -312,14 +316,14 @@ static int apparmor_path_link(struct dentry *old_dentry, const struct path *new_ static int apparmor_path_rename(const struct path *old_dir, struct dentry *old_dentry, const struct path *new_dir, struct dentry *new_dentry) { - struct aa_profile *profile; + struct aa_label *label; int error = 0; if (!path_mediated_fs(old_dentry)) return 0; - profile = begin_current_profile_crit_section(); - if (!unconfined(profile)) { + label = begin_current_label_crit_section(); + if (!unconfined(label)) { struct path old_path = { .mnt = old_dir->mnt, .dentry = old_dentry }; struct path new_path = { .mnt = new_dir->mnt, @@ -328,17 +332,20 @@ static int apparmor_path_rename(const struct path *old_dir, struct dentry *old_d d_backing_inode(old_dentry)->i_mode }; - error = aa_path_perm(OP_RENAME_SRC, profile, &old_path, 0, + error = aa_path_perm(OP_RENAME_SRC, labels_profile(label), + &old_path, 0, MAY_READ | AA_MAY_GETATTR | MAY_WRITE | AA_MAY_SETATTR | AA_MAY_DELETE, &cond); if (!error) - error = aa_path_perm(OP_RENAME_DEST, profile, &new_path, + error = aa_path_perm(OP_RENAME_DEST, + labels_profile(label), + &new_path, 0, MAY_WRITE | AA_MAY_SETATTR | AA_MAY_CREATE, &cond); } - end_current_profile_crit_section(profile); + end_current_label_crit_section(label); return error; } @@ -360,8 +367,8 @@ static int apparmor_inode_getattr(const struct path *path) static int apparmor_file_open(struct file *file, const struct cred *cred) { - struct aa_file_ctx *fctx = file->f_security; - struct aa_profile *profile; + struct aa_file_ctx *fctx = file_ctx(file); + struct aa_label *label; int error = 0; if (!path_mediated_fs(file->f_path.dentry)) @@ -377,17 +384,18 @@ static int apparmor_file_open(struct file *file, const struct cred *cred) return 0; } - profile = aa_get_newest_cred_profile(cred); - if (!unconfined(profile)) { + label = aa_get_newest_cred_label(cred); + if (!unconfined(label)) { struct inode *inode = file_inode(file); struct path_cond cond = { inode->i_uid, inode->i_mode }; - error = aa_path_perm(OP_OPEN, profile, &file->f_path, 0, + error = aa_path_perm(OP_OPEN, labels_profile(label), + &file->f_path, 0, aa_map_file_to_perms(file), &cond); /* todo cache full allowed permissions set and state */ fctx->allow = aa_map_file_to_perms(file); } - aa_put_profile(profile); + aa_put_label(label); return error; } @@ -397,11 +405,11 @@ static int apparmor_file_alloc_security(struct file *file) int error = 0; /* freed by apparmor_file_free_security */ - struct aa_profile *profile = begin_current_profile_crit_section(); + struct aa_label *label = begin_current_label_crit_section(); file->f_security = aa_alloc_file_ctx(GFP_KERNEL); if (!file_ctx(file)) error = -ENOMEM; - end_current_profile_crit_section(profile); + end_current_label_crit_section(label); return error; } @@ -414,21 +422,21 @@ static void apparmor_file_free_security(struct file *file) static int common_file_perm(const char *op, struct file *file, u32 mask) { struct aa_file_ctx *fctx = file->f_security; - struct aa_profile *profile, *fprofile; + struct aa_label *label, *flabel; int error = 0; /* don't reaudit files closed during inheritance */ if (file->f_path.dentry == aa_null.dentry) return -EACCES; - fprofile = aa_cred_raw_profile(file->f_cred); - AA_BUG(!fprofile); + flabel = aa_cred_raw_label(file->f_cred); + AA_BUG(!flabel); if (!file->f_path.mnt || !path_mediated_fs(file->f_path.dentry)) return 0; - profile = __begin_current_profile_crit_section(); + label = __begin_current_label_crit_section(); /* revalidate access, if task is unconfined, or the cached cred * doesn't match or if the request is for more permissions than @@ -437,10 +445,10 @@ static int common_file_perm(const char *op, struct file *file, u32 mask) * Note: the test for !unconfined(fprofile) is to handle file * delegation from unconfined tasks */ - if (!unconfined(profile) && !unconfined(fprofile) && - ((fprofile != profile) || (mask & ~fctx->allow))) - error = aa_file_perm(op, profile, file, mask); - __end_current_profile_crit_section(profile); + if (!unconfined(label) && !unconfined(flabel) && + ((flabel != label) || (mask & ~fctx->allow))) + error = aa_file_perm(op, labels_profile(label), file, mask); + __end_current_label_crit_section(label); return error; } @@ -465,7 +473,7 @@ static int common_mmap(const char *op, struct file *file, unsigned long prot, { int mask = 0; - if (!file || !file->f_security) + if (!file || !file_ctx(file)) return 0; if (prot & PROT_READ) @@ -502,21 +510,21 @@ static int apparmor_getprocattr(struct task_struct *task, char *name, /* released below */ const struct cred *cred = get_task_cred(task); struct aa_task_ctx *ctx = cred_ctx(cred); - struct aa_profile *profile = NULL; + struct aa_label *label = NULL; if (strcmp(name, "current") == 0) - profile = aa_get_newest_profile(ctx->profile); + label = aa_get_newest_label(ctx->label); else if (strcmp(name, "prev") == 0 && ctx->previous) - profile = aa_get_newest_profile(ctx->previous); + label = aa_get_newest_label(ctx->previous); else if (strcmp(name, "exec") == 0 && ctx->onexec) - profile = aa_get_newest_profile(ctx->onexec); + label = aa_get_newest_label(ctx->onexec); else error = -EINVAL; - if (profile) - error = aa_getprocattr(profile, value); + if (label) + error = aa_getprocattr(labels_profile(label), value); - aa_put_profile(profile); + aa_put_label(label); put_cred(cred); return error; @@ -582,11 +590,11 @@ out: return error; fail: - aad(&sa)->profile = begin_current_profile_crit_section(); + aad(&sa)->label = begin_current_label_crit_section(); aad(&sa)->info = name; aad(&sa)->error = error = -EINVAL; aa_audit_msg(AUDIT_APPARMOR_DENIED, &sa, NULL); - end_current_profile_crit_section(aad(&sa)->profile); + end_current_label_crit_section(aad(&sa)->label); goto out; } @@ -596,20 +604,21 @@ fail: */ static void apparmor_bprm_committing_creds(struct linux_binprm *bprm) { - struct aa_profile *profile = aa_current_raw_profile(); + struct aa_label *label = aa_current_raw_label(); struct aa_task_ctx *new_ctx = cred_ctx(bprm->cred); /* bail out if unconfined or not changing profile */ - if ((new_ctx->profile == profile) || - (unconfined(new_ctx->profile))) + if ((new_ctx->label->proxy == label->proxy) || + (unconfined(new_ctx->label))) return; aa_inherit_files(bprm->cred, current->files); current->pdeath_signal = 0; - /* reset soft limits and set hard limits for the new profile */ - __aa_transition_rlimits(profile, new_ctx->profile); + /* reset soft limits and set hard limits for the new label */ + __aa_transition_rlimits(labels_profile(label), + labels_profile(new_ctx->label)); } /** @@ -625,12 +634,13 @@ static void apparmor_bprm_committed_creds(struct linux_binprm *bprm) static int apparmor_task_setrlimit(struct task_struct *task, unsigned int resource, struct rlimit *new_rlim) { - struct aa_profile *profile = __begin_current_profile_crit_section(); + struct aa_label *label = __begin_current_label_crit_section(); int error = 0; - if (!unconfined(profile)) - error = aa_task_setrlimit(profile, task, resource, new_rlim); - __end_current_profile_crit_section(profile); + if (!unconfined(label)) + error = aa_task_setrlimit(labels_profile(label), task, + resource, new_rlim); + __end_current_label_crit_section(label); return error; } @@ -924,7 +934,7 @@ static int __init set_init_ctx(void) if (!ctx) return -ENOMEM; - ctx->profile = aa_get_profile(root_ns->unconfined); + ctx->label = aa_get_label(ns_unconfined(root_ns)); cred_ctx(cred) = ctx; return 0; diff --git a/security/apparmor/policy.c b/security/apparmor/policy.c index 605cb5949c60..244ea4a4a8f0 100644 --- a/security/apparmor/policy.c +++ b/security/apparmor/policy.c @@ -101,20 +101,9 @@ const char *const aa_profile_mode_names[] = { "unconfined", }; -/* requires profile list write lock held */ -void __aa_update_proxy(struct aa_profile *orig, struct aa_profile *new) -{ - struct aa_profile *tmp; - - tmp = rcu_dereference_protected(orig->proxy->profile, - mutex_is_locked(&orig->ns->lock)); - rcu_assign_pointer(orig->proxy->profile, aa_get_profile(new)); - orig->flags |= PFLAG_STALE; - aa_put_profile(tmp); -} /** - * __list_add_profile - add a profile to a list + * __add_profile - add a profiles to list and label tree * @list: list to add it to (NOT NULL) * @profile: the profile to add (NOT NULL) * @@ -122,12 +111,21 @@ void __aa_update_proxy(struct aa_profile *orig, struct aa_profile *new) * * Requires: namespace lock be held, or list not be shared */ -static void __list_add_profile(struct list_head *list, - struct aa_profile *profile) +static void __add_profile(struct list_head *list, struct aa_profile *profile) { + struct aa_label *l; + + AA_BUG(!list); + AA_BUG(!profile); + AA_BUG(!profile->ns); + AA_BUG(!mutex_is_locked(&profile->ns->lock)); + list_add_rcu(&profile->base.list, list); /* get list reference */ aa_get_profile(profile); + l = aa_label_insert(&profile->ns->labels, &profile->label); + AA_BUG(l != &profile->label); + aa_put_label(l); } /** @@ -144,6 +142,10 @@ static void __list_add_profile(struct list_head *list, */ static void __list_remove_profile(struct aa_profile *profile) { + AA_BUG(!profile); + AA_BUG(!profile->ns); + AA_BUG(!mutex_is_locked(&profile->ns->lock)); + list_del_rcu(&profile->base.list); aa_put_profile(profile); } @@ -156,10 +158,14 @@ static void __list_remove_profile(struct aa_profile *profile) */ static void __remove_profile(struct aa_profile *profile) { + AA_BUG(!profile); + AA_BUG(!profile->ns); + AA_BUG(!mutex_is_locked(&profile->ns->lock)); + /* release any children lists first */ __aa_profile_list_release(&profile->base.profiles); /* released by free_profile */ - __aa_update_proxy(profile, profile->ns->unconfined); + aa_label_remove(&profile->label); __aafs_profile_rmdir(profile); __list_remove_profile(profile); } @@ -177,24 +183,6 @@ void __aa_profile_list_release(struct list_head *head) __remove_profile(profile); } - -static void free_proxy(struct aa_proxy *p) -{ - if (p) { - /* r->profile will not be updated any more as r is dead */ - aa_put_profile(rcu_dereference_protected(p->profile, true)); - kzfree(p); - } -} - - -void aa_free_proxy_kref(struct kref *kref) -{ - struct aa_proxy *p = container_of(kref, struct aa_proxy, count); - - free_proxy(p); -} - /** * aa_free_data - free a data blob * @ptr: data to free @@ -242,7 +230,6 @@ void aa_free_profile(struct aa_profile *profile) kzfree(profile->dirname); aa_put_dfa(profile->xmatch); aa_put_dfa(profile->policy.dfa); - aa_put_proxy(profile->proxy); if (profile->data) { rht = profile->data; @@ -253,30 +240,8 @@ void aa_free_profile(struct aa_profile *profile) kzfree(profile->hash); aa_put_loaddata(profile->rawdata); - kzfree(profile); -} -/** - * aa_free_profile_rcu - free aa_profile by rcu (called by aa_free_profile_kref) - * @head: rcu_head callback for freeing of a profile (NOT NULL) - */ -static void aa_free_profile_rcu(struct rcu_head *head) -{ - struct aa_profile *p = container_of(head, struct aa_profile, rcu); - if (p->flags & PFLAG_NS_COUNT) - aa_free_ns(p->ns); - else - aa_free_profile(p); -} - -/** - * aa_free_profile_kref - free aa_profile by kref (called by aa_put_profile) - * @kr: kref callback for freeing of a profile (NOT NULL) - */ -void aa_free_profile_kref(struct kref *kref) -{ - struct aa_profile *p = container_of(kref, struct aa_profile, count); - call_rcu(&p->rcu, aa_free_profile_rcu); + kzfree(profile); } /** @@ -286,30 +251,40 @@ void aa_free_profile_kref(struct kref *kref) * * Returns: refcount profile or NULL on failure */ -struct aa_profile *aa_alloc_profile(const char *hname, gfp_t gfp) +struct aa_profile *aa_alloc_profile(const char *hname, struct aa_proxy *proxy, + gfp_t gfp) { struct aa_profile *profile; /* freed by free_profile - usually through aa_put_profile */ - profile = kzalloc(sizeof(*profile), gfp); + profile = kzalloc(sizeof(*profile) + sizeof(struct aa_profile *) * 2, + gfp); if (!profile) return NULL; - profile->proxy = kzalloc(sizeof(struct aa_proxy), gfp); - if (!profile->proxy) - goto fail; - kref_init(&profile->proxy->count); - if (!aa_policy_init(&profile->base, NULL, hname, gfp)) goto fail; - kref_init(&profile->count); + if (!aa_label_init(&profile->label, 1)) + goto fail; + + /* update being set needed by fs interface */ + if (!proxy) { + proxy = aa_alloc_proxy(&profile->label, gfp); + if (!proxy) + goto fail; + } else + aa_get_proxy(proxy); + profile->label.proxy = proxy; + + profile->label.hname = profile->base.hname; + profile->label.flags |= FLAG_PROFILE; + profile->label.vec[0] = profile; /* refcount released by caller */ return profile; fail: - kzfree(profile->proxy); - kzfree(profile); + aa_free_profile(profile); return NULL; } @@ -362,14 +337,14 @@ name: if (profile) goto out; - profile = aa_alloc_profile(name, gfp); + profile = aa_alloc_profile(name, NULL, gfp); if (!profile) goto fail; profile->mode = APPARMOR_COMPLAIN; - profile->flags |= PFLAG_NULL; + profile->label.flags |= FLAG_NULL; if (hat) - profile->flags |= PFLAG_HAT; + profile->label.flags |= FLAG_HAT; profile->path_flags = parent->path_flags; /* released on free_profile */ @@ -379,7 +354,7 @@ name: profile->policy.dfa = aa_get_dfa(nulldfa); mutex_lock(&profile->ns->lock); - __list_add_profile(&parent->base.profiles, profile); + __add_profile(&parent->base.profiles, profile); mutex_unlock(&profile->ns->lock); /* refcount released by caller */ @@ -389,7 +364,6 @@ out: return profile; fail: - kfree(name); aa_free_profile(profile); return NULL; } @@ -556,7 +530,7 @@ struct aa_profile *aa_lookup_profile(struct aa_ns *ns, const char *hname) return aa_lookupn_profile(ns, hname, strlen(hname)); } -struct aa_profile *aa_fqlookupn_profile(struct aa_profile *base, +struct aa_profile *aa_fqlookupn_profile(struct aa_label *base, const char *fqname, size_t n) { struct aa_profile *profile; @@ -566,11 +540,11 @@ struct aa_profile *aa_fqlookupn_profile(struct aa_profile *base, name = aa_splitn_fqname(fqname, n, &ns_name, &ns_len); if (ns_name) { - ns = aa_lookupn_ns(base->ns, ns_name, ns_len); + ns = aa_lookupn_ns(labels_ns(base), ns_name, ns_len); if (!ns) return NULL; } else - ns = aa_get_ns(base->ns); + ns = aa_get_ns(labels_ns(base)); if (name) profile = aa_lookupn_profile(ns, name, n - (name - fqname)); @@ -596,7 +570,7 @@ static int replacement_allowed(struct aa_profile *profile, int noreplace, const char **info) { if (profile) { - if (profile->flags & PFLAG_IMMUTABLE) { + if (profile->label.flags & FLAG_IMMUTIBLE) { *info = "cannot replace immutible profile"; return -EPERM; } else if (noreplace) { @@ -619,29 +593,31 @@ static void audit_cb(struct audit_buffer *ab, void *va) } /** - * aa_audit_policy - Do auditing of policy changes - * @profile: profile to check if it can manage policy + * audit_policy - Do auditing of policy changes + * @label: label to check if it can manage policy * @op: policy operation being performed - * @gfp: memory allocation flags - * @nsname: name of the ns being manipulated (MAY BE NULL) + * @ns_name: name of namespace being manipulated * @name: name of profile being manipulated (NOT NULL) * @info: any extra information to be audited (MAYBE NULL) * @error: error code * * Returns: the error to be returned after audit is done */ -static int audit_policy(struct aa_profile *profile, const char *op, - const char *nsname, const char *name, +static int audit_policy(struct aa_label *label, const char *op, + const char *ns_name, const char *name, const char *info, int error) { DEFINE_AUDIT_DATA(sa, LSM_AUDIT_DATA_NONE, op); - aad(&sa)->iface.ns = nsname; + aad(&sa)->iface.ns = ns_name; aad(&sa)->name = name; aad(&sa)->info = info; aad(&sa)->error = error; + aad(&sa)->label = label; - return aa_audit(AUDIT_APPARMOR_STATUS, profile, &sa, audit_cb); + aa_audit_msg(AUDIT_APPARMOR_STATUS, &sa, audit_cb); + + return error; } /** @@ -685,12 +661,12 @@ bool policy_admin_capable(struct aa_ns *ns) /** * aa_may_manage_policy - can the current task manage policy - * @profile: profile to check if it can manage policy + * @label: label to check if it can manage policy * @op: the policy manipulation operation being done * * Returns: 0 if the task is allowed to manipulate policy else error */ -int aa_may_manage_policy(struct aa_profile *profile, struct aa_ns *ns, u32 mask) +int aa_may_manage_policy(struct aa_label *label, struct aa_ns *ns, u32 mask) { const char *op; @@ -703,11 +679,11 @@ int aa_may_manage_policy(struct aa_profile *profile, struct aa_ns *ns, u32 mask) /* check if loading policy is locked out */ if (aa_g_lock_policy) - return audit_policy(profile, op, NULL, NULL, "policy_locked", + return audit_policy(label, op, NULL, NULL, "policy_locked", -EACCES); if (!policy_admin_capable(ns)) - return audit_policy(profile, op, NULL, NULL, "not policy admin", + return audit_policy(label, op, NULL, NULL, "not policy admin", -EACCES); /* TODO: add fine grained mediation of policy loads */ @@ -750,8 +726,7 @@ static struct aa_profile *__list_lookup_parent(struct list_head *lh, * * Requires: namespace list lock be held, or list not be shared */ -static void __replace_profile(struct aa_profile *old, struct aa_profile *new, - bool share_proxy) +static void __replace_profile(struct aa_profile *old, struct aa_profile *new) { struct aa_profile *child, *tmp; @@ -766,7 +741,7 @@ static void __replace_profile(struct aa_profile *old, struct aa_profile *new, p = __find_child(&new->base.profiles, child->base.name); if (p) { /* @p replaces @child */ - __replace_profile(child, p, share_proxy); + __replace_profile(child, p); continue; } @@ -784,14 +759,8 @@ static void __replace_profile(struct aa_profile *old, struct aa_profile *new, struct aa_profile *parent = aa_deref_parent(old); rcu_assign_pointer(new->parent, aa_get_profile(parent)); } - __aa_update_proxy(old, new); - if (share_proxy) { - aa_put_proxy(new->proxy); - new->proxy = aa_get_proxy(old->proxy); - } else if (!rcu_access_pointer(new->proxy->profile)) - /* aafs interface uses proxy */ - rcu_assign_pointer(new->proxy->profile, - aa_get_profile(new)); + aa_label_replace(&old->label, &new->label); + /* migrate dents must come after label replacement b/c update */ __aafs_profile_migrate_dents(old, new); if (list_empty(&new->base.list)) { @@ -835,6 +804,7 @@ static void share_name(struct aa_profile *old, struct aa_profile *new) aa_get_str(old->base.hname); new->base.hname = old->base.hname; new->base.name = old->base.name; + new->label.hname = old->label.hname; } /* Update to newest version of parent after previous replacements @@ -871,7 +841,7 @@ static struct aa_profile *update_to_newest_parent(struct aa_profile *new) * * Returns: size of data consumed else error code on failure. */ -ssize_t aa_replace_profiles(struct aa_ns *policy_ns, struct aa_profile *profile, +ssize_t aa_replace_profiles(struct aa_ns *policy_ns, struct aa_label *label, u32 mask, struct aa_loaddata *udata) { const char *ns_name, *info = NULL; @@ -914,7 +884,7 @@ ssize_t aa_replace_profiles(struct aa_ns *policy_ns, struct aa_profile *profile, count++; } if (ns_name) { - ns = aa_prepare_ns(policy_ns ? policy_ns : profile->ns, + ns = aa_prepare_ns(policy_ns ? policy_ns : labels_ns(label), ns_name); if (IS_ERR(ns)) { op = OP_PROF_LOAD; @@ -925,7 +895,7 @@ ssize_t aa_replace_profiles(struct aa_ns *policy_ns, struct aa_profile *profile, goto fail; } } else - ns = aa_get_ns(policy_ns ? policy_ns : profile->ns); + ns = aa_get_ns(policy_ns ? policy_ns : labels_ns(label)); mutex_lock(&ns->lock); /* check for duplicate rawdata blobs: space and file dedup */ @@ -955,8 +925,8 @@ ssize_t aa_replace_profiles(struct aa_ns *policy_ns, struct aa_profile *profile, if (ent->new->rename) { error = __lookup_replace(ns, ent->new->rename, - !(mask & AA_MAY_REPLACE_POLICY), - &ent->rename, &info); + !(mask & AA_MAY_REPLACE_POLICY), + &ent->rename, &info); if (error) goto fail_lock; } @@ -1021,7 +991,7 @@ ssize_t aa_replace_profiles(struct aa_ns *policy_ns, struct aa_profile *profile, if (ent->old && ent->old->rawdata == ent->new->rawdata) { /* dedup actual profile replacement */ - audit_policy(profile, op, ns_name, ent->new->base.hname, + audit_policy(label, op, ns_name, ent->new->base.hname, "same as current profile, skipping", error); goto skip; @@ -1031,12 +1001,12 @@ ssize_t aa_replace_profiles(struct aa_ns *policy_ns, struct aa_profile *profile, * TODO: finer dedup based on profile range in data. Load set * can differ but profile may remain unchanged */ - audit_policy(profile, op, NULL, ent->new->base.hname, - NULL, error); + audit_policy(label, op, ns_name, ent->new->base.hname, NULL, + error); if (ent->old) { share_name(ent->old, ent->new); - __replace_profile(ent->old, ent->new, 1); + __replace_profile(ent->old, ent->new); } else { struct list_head *lh; @@ -1047,11 +1017,12 @@ ssize_t aa_replace_profiles(struct aa_ns *policy_ns, struct aa_profile *profile, lh = &parent->base.profiles; } else lh = &ns->base.profiles; - __list_add_profile(lh, ent->new); + __add_profile(lh, ent->new); } skip: aa_load_ent_free(ent); } + __aa_labelset_update_subtree(ns); mutex_unlock(&ns->lock); out: @@ -1068,8 +1039,8 @@ fail_lock: /* audit cause of failure */ op = (ent && !ent->old) ? OP_PROF_LOAD : OP_PROF_REPL; fail: - audit_policy(profile, op, ns_name, ent ? ent->new->base.hname : NULL, - info, error); + audit_policy(label, op, ns_name, ent ? ent->new->base.hname : NULL, + info, error); /* audit status that rest of profiles in the atomic set failed too */ info = "valid profile in failed atomic policy load"; list_for_each_entry(tmp, &lh, list) { @@ -1079,8 +1050,8 @@ fail: continue; } op = (!tmp->old) ? OP_PROF_LOAD : OP_PROF_REPL; - audit_policy(profile, op, ns_name, - tmp->new->base.hname, info, error); + audit_policy(label, op, ns_name, tmp->new->base.hname, info, + error); } list_for_each_entry_safe(ent, tmp, &lh, list) { list_del_init(&ent->list); @@ -1093,7 +1064,7 @@ fail: /** * aa_remove_profiles - remove profile(s) from the system * @policy_ns: namespace the remove is being done from - * @subj: profile attempting to remove policy + * @subj: label attempting to remove policy * @fqname: name of the profile or namespace to remove (NOT NULL) * @size: size of the name * @@ -1104,7 +1075,7 @@ fail: * * Returns: size of data consume else error code if fails */ -ssize_t aa_remove_profiles(struct aa_ns *policy_ns, struct aa_profile *subj, +ssize_t aa_remove_profiles(struct aa_ns *policy_ns, struct aa_label *subj, char *fqname, size_t size) { struct aa_ns *ns = NULL; @@ -1124,8 +1095,8 @@ ssize_t aa_remove_profiles(struct aa_ns *policy_ns, struct aa_profile *subj, name = aa_splitn_fqname(fqname, size, &ns_name, &ns_len); /* released below */ - ns = aa_lookupn_ns(policy_ns ? policy_ns : subj->ns, ns_name, - ns_len); + ns = aa_lookupn_ns(policy_ns ? policy_ns : labels_ns(subj), + ns_name, ns_len); if (!ns) { info = "namespace does not exist"; error = -ENOENT; @@ -1133,7 +1104,7 @@ ssize_t aa_remove_profiles(struct aa_ns *policy_ns, struct aa_profile *subj, } } else /* released below */ - ns = aa_get_ns(policy_ns ? policy_ns : subj->ns); + ns = aa_get_ns(policy_ns ? policy_ns : labels_ns(subj)); if (!name) { /* remove namespace - can only happen if fqname[0] == ':' */ @@ -1152,6 +1123,7 @@ ssize_t aa_remove_profiles(struct aa_ns *policy_ns, struct aa_profile *subj, } name = profile->base.hname; __remove_profile(profile); + __aa_labelset_update_subtree(ns); __aa_bump_ns_revision(ns); mutex_unlock(&ns->lock); } diff --git a/security/apparmor/policy_ns.c b/security/apparmor/policy_ns.c index c05316809a5e..351d3bab3a3d 100644 --- a/security/apparmor/policy_ns.c +++ b/security/apparmor/policy_ns.c @@ -23,6 +23,7 @@ #include "include/apparmor.h" #include "include/context.h" #include "include/policy_ns.h" +#include "include/label.h" #include "include/policy.h" /* root profile namespace */ @@ -104,12 +105,12 @@ static struct aa_ns *alloc_ns(const char *prefix, const char *name) init_waitqueue_head(&ns->wait); /* released by aa_free_ns() */ - ns->unconfined = aa_alloc_profile("unconfined", GFP_KERNEL); + ns->unconfined = aa_alloc_profile("unconfined", NULL, GFP_KERNEL); if (!ns->unconfined) goto fail_unconfined; - ns->unconfined->flags = PFLAG_IX_ON_NAME_ERROR | - PFLAG_IMMUTABLE | PFLAG_NS_COUNT; + ns->unconfined->label.flags |= FLAG_IX_ON_NAME_ERROR | + FLAG_IMMUTIBLE | FLAG_NS_COUNT | FLAG_UNCONFINED; ns->unconfined->mode = APPARMOR_UNCONFINED; /* ns and ns->unconfined share ns->unconfined refcount */ @@ -117,6 +118,8 @@ static struct aa_ns *alloc_ns(const char *prefix, const char *name) atomic_set(&ns->uniq_null, 0); + aa_labelset_init(&ns->labels); + return ns; fail_unconfined: @@ -139,6 +142,7 @@ void aa_free_ns(struct aa_ns *ns) return; aa_policy_destroy(&ns->base); + aa_labelset_destroy(&ns->labels); aa_put_ns(ns->parent); ns->unconfined->ns = NULL; @@ -337,8 +341,14 @@ static void destroy_ns(struct aa_ns *ns) /* release all sub namespaces */ __ns_list_release(&ns->sub_ns); - if (ns->parent) - __aa_update_proxy(ns->unconfined, ns->parent->unconfined); + if (ns->parent) { + unsigned long flags; + + write_lock_irqsave(&ns->labels.lock, flags); + __aa_proxy_redirect(ns_unconfined(ns), + ns_unconfined(ns->parent)); + write_unlock_irqrestore(&ns->labels.lock, flags); + } __aafs_ns_rmdir(ns); mutex_unlock(&ns->lock); } diff --git a/security/apparmor/policy_unpack.c b/security/apparmor/policy_unpack.c index cac69f2cb86d..f42bb9575cb5 100644 --- a/security/apparmor/policy_unpack.c +++ b/security/apparmor/policy_unpack.c @@ -26,6 +26,7 @@ #include "include/context.h" #include "include/crypto.h" #include "include/match.h" +#include "include/path.h" #include "include/policy.h" #include "include/policy_unpack.h" @@ -107,7 +108,7 @@ static int audit_iface(struct aa_profile *new, const char *ns_name, const char *name, const char *info, struct aa_ext *e, int error) { - struct aa_profile *profile = aa_current_raw_profile(); + struct aa_profile *profile = labels_profile(aa_current_raw_label()); DEFINE_AUDIT_DATA(sa, LSM_AUDIT_DATA_NONE, NULL); if (e) aad(&sa)->iface.pos = e->pos - e->start; @@ -602,7 +603,7 @@ static struct aa_profile *unpack_profile(struct aa_ext *e, char **ns_name) name = tmpname; } - profile = aa_alloc_profile(name, GFP_KERNEL); + profile = aa_alloc_profile(name, NULL, GFP_KERNEL); if (!profile) return ERR_PTR(-ENOMEM); @@ -635,7 +636,7 @@ static struct aa_profile *unpack_profile(struct aa_ext *e, char **ns_name) if (!unpack_u32(e, &tmp, NULL)) goto fail; if (tmp & PACKED_FLAG_HAT) - profile->flags |= PFLAG_HAT; + profile->label.flags |= FLAG_HAT; if (!unpack_u32(e, &tmp, NULL)) goto fail; if (tmp == PACKED_MODE_COMPLAIN || (e->version & FORCE_COMPLAIN_FLAG)) @@ -654,10 +655,11 @@ static struct aa_profile *unpack_profile(struct aa_ext *e, char **ns_name) /* path_flags is optional */ if (unpack_u32(e, &profile->path_flags, "path_flags")) - profile->path_flags |= profile->flags & PFLAG_MEDIATE_DELETED; + profile->path_flags |= profile->label.flags & + PATH_MEDIATE_DELETED; else /* set a default value if path_flags field is not present */ - profile->path_flags = PFLAG_MEDIATE_DELETED; + profile->path_flags = PATH_MEDIATE_DELETED; if (!unpack_u32(e, &(profile->caps.allow.cap[0]), NULL)) goto fail; diff --git a/security/apparmor/procattr.c b/security/apparmor/procattr.c index 2f0cb424927a..dce970d1f46b 100644 --- a/security/apparmor/procattr.c +++ b/security/apparmor/procattr.c @@ -55,7 +55,7 @@ int aa_getprocattr(struct aa_profile *profile, char **string) ns_len += 4; /* unconfined profiles don't have a mode string appended */ - if (!unconfined(profile)) + if (!profile_unconfined(profile)) mode_len = strlen(mode_str) + 3; /* + 3 for _() */ name_len = strlen(profile->base.hname); @@ -69,7 +69,7 @@ int aa_getprocattr(struct aa_profile *profile, char **string) sprintf(s, ":%s://", ns_name); s += ns_len; } - if (unconfined(profile)) + if (profile_unconfined(profile)) /* mode string not being appended */ sprintf(s, "%s\n", profile->base.hname); else diff --git a/security/apparmor/resource.c b/security/apparmor/resource.c index b26f1dac5106..ab8e104c1970 100644 --- a/security/apparmor/resource.c +++ b/security/apparmor/resource.c @@ -86,11 +86,11 @@ int aa_map_resource(int resource) int aa_task_setrlimit(struct aa_profile *profile, struct task_struct *task, unsigned int resource, struct rlimit *new_rlim) { - struct aa_profile *task_profile; + struct aa_label *task_label; int error = 0; rcu_read_lock(); - task_profile = aa_get_newest_cred_profile((__task_cred(task))); + task_label = aa_get_newest_cred_label((__task_cred(task))); rcu_read_unlock(); /* TODO: extend resource control to handle other (non current) @@ -99,13 +99,13 @@ int aa_task_setrlimit(struct aa_profile *profile, struct task_struct *task, * the same profile or that the task setting the resource of another * task has CAP_SYS_RESOURCE. */ - if ((profile != task_profile && + if ((profile != labels_profile(task_label) && aa_capable(profile, CAP_SYS_RESOURCE, 1)) || (profile->rlimits.mask & (1 << resource) && new_rlim->rlim_max > profile->rlimits.limits[resource].rlim_max)) error = -EACCES; - aa_put_profile(task_profile); + aa_put_label(task_label); return audit_resource(profile, resource, new_rlim->rlim_max, error); } -- cgit v1.2.3 From 76a1d263aba3c97db7a2ba673059e0f17d983efb Mon Sep 17 00:00:00 2001 From: John Johansen Date: Fri, 9 Jun 2017 12:47:17 -0700 Subject: apparmor: switch getprocattr to using label_print fns() Signed-off-by: John Johansen --- security/apparmor/include/procattr.h | 2 +- security/apparmor/lsm.c | 2 +- security/apparmor/procattr.c | 60 +++++++++++++++--------------------- 3 files changed, 27 insertions(+), 37 deletions(-) diff --git a/security/apparmor/include/procattr.h b/security/apparmor/include/procattr.h index c0055d727868..c8fd99c9357d 100644 --- a/security/apparmor/include/procattr.h +++ b/security/apparmor/include/procattr.h @@ -15,7 +15,7 @@ #ifndef __AA_PROCATTR_H #define __AA_PROCATTR_H -int aa_getprocattr(struct aa_profile *profile, char **string); +int aa_getprocattr(struct aa_label *label, char **string); int aa_setprocattr_changehat(char *args, size_t size, int flags); #endif /* __AA_PROCATTR_H */ diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c index 3ba08530c92e..f7f82ce00d73 100644 --- a/security/apparmor/lsm.c +++ b/security/apparmor/lsm.c @@ -522,7 +522,7 @@ static int apparmor_getprocattr(struct task_struct *task, char *name, error = -EINVAL; if (label) - error = aa_getprocattr(labels_profile(label), value); + error = aa_getprocattr(label, value); aa_put_label(label); put_cred(cred); diff --git a/security/apparmor/procattr.c b/security/apparmor/procattr.c index dce970d1f46b..d81617379d63 100644 --- a/security/apparmor/procattr.c +++ b/security/apparmor/procattr.c @@ -34,51 +34,41 @@ * * Returns: size of string placed in @string else error code on failure */ -int aa_getprocattr(struct aa_profile *profile, char **string) +int aa_getprocattr(struct aa_label *label, char **string) { - char *str; - int len = 0, mode_len = 0, ns_len = 0, name_len; - const char *mode_str = aa_profile_mode_names[profile->mode]; - const char *ns_name = NULL; - struct aa_ns *ns = profile->ns; + struct aa_ns *ns = labels_ns(label); struct aa_ns *current_ns = aa_get_current_ns(); - char *s; + int len; - if (!aa_ns_visible(current_ns, ns, true)) + if (!aa_ns_visible(current_ns, ns, true)) { + aa_put_ns(current_ns); return -EACCES; + } - ns_name = aa_ns_name(current_ns, ns, true); - ns_len = strlen(ns_name); - - /* if the visible ns_name is > 0 increase size for : :// seperator */ - if (ns_len) - ns_len += 4; - - /* unconfined profiles don't have a mode string appended */ - if (!profile_unconfined(profile)) - mode_len = strlen(mode_str) + 3; /* + 3 for _() */ + len = aa_label_snxprint(NULL, 0, current_ns, label, + FLAG_SHOW_MODE | FLAG_VIEW_SUBNS | + FLAG_HIDDEN_UNCONFINED); + AA_BUG(len < 0); - name_len = strlen(profile->base.hname); - len = mode_len + ns_len + name_len + 1; /* + 1 for \n */ - s = str = kmalloc(len + 1, GFP_KERNEL); /* + 1 \0 */ - if (!str) + *string = kmalloc(len + 2, GFP_KERNEL); + if (!*string) { + aa_put_ns(current_ns); return -ENOMEM; + } - if (ns_len) { - /* skip over prefix current_ns->base.hname and separating // */ - sprintf(s, ":%s://", ns_name); - s += ns_len; + len = aa_label_snxprint(*string, len + 2, current_ns, label, + FLAG_SHOW_MODE | FLAG_VIEW_SUBNS | + FLAG_HIDDEN_UNCONFINED); + if (len < 0) { + aa_put_ns(current_ns); + return len; } - if (profile_unconfined(profile)) - /* mode string not being appended */ - sprintf(s, "%s\n", profile->base.hname); - else - sprintf(s, "%s (%s)\n", profile->base.hname, mode_str); - *string = str; - aa_put_ns(current_ns); - /* NOTE: len does not include \0 of string, not saved as part of file */ - return len; + (*string)[len] = '\n'; + (*string)[len + 1] = 0; + + aa_put_ns(current_ns); + return len + 1; } /** -- cgit v1.2.3 From 317d9a054e1c6d5f18b02b99ce09911942f8e603 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Fri, 9 Jun 2017 13:55:38 -0700 Subject: apparmor: update query interface to support label queries Signed-off-by: John Johansen --- security/apparmor/apparmorfs.c | 46 +++++++++++++++++++++++++++++++++++------- 1 file changed, 39 insertions(+), 7 deletions(-) diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c index 976af6da45c3..d24100f8fd98 100644 --- a/security/apparmor/apparmorfs.c +++ b/security/apparmor/apparmorfs.c @@ -33,6 +33,7 @@ #include "include/context.h" #include "include/crypto.h" #include "include/policy_ns.h" +#include "include/label.h" #include "include/policy.h" #include "include/policy_ns.h" #include "include/resource.h" @@ -629,6 +630,7 @@ static void profile_query_cb(struct aa_profile *profile, struct aa_perms *perms, tmp = nullperms; } aa_apply_modes_to_perms(profile, &tmp); + aa_perms_accum_raw(perms, &tmp); } @@ -655,7 +657,9 @@ static ssize_t query_data(char *buf, size_t buf_len, { char *out; const char *key; + struct label_it i; struct aa_label *label, *curr; + struct aa_profile *profile; struct aa_data *data; u32 bytes, blocks; __le32 outle32; @@ -690,13 +694,16 @@ static ssize_t query_data(char *buf, size_t buf_len, out = buf + sizeof(bytes) + sizeof(blocks); blocks = 0; - if (labels_profile(label)->data) { - data = rhashtable_lookup_fast(labels_profile(label)->data, &key, - labels_profile(label)->data->p); + label_for_each_confined(i, label, profile) { + if (!profile->data) + continue; + + data = rhashtable_lookup_fast(profile->data, &key, + profile->data->p); if (data) { - if (out + sizeof(outle32) + data->size > - buf + buf_len) { + if (out + sizeof(outle32) + data->size > buf + + buf_len) { aa_put_label(label); return -EINVAL; /* not enough space */ } @@ -741,10 +748,12 @@ static ssize_t query_data(char *buf, size_t buf_len, static ssize_t query_label(char *buf, size_t buf_len, char *query, size_t query_len, bool view_only) { + struct aa_profile *profile; struct aa_label *label, *curr; char *label_name, *match_str; size_t label_name_len, match_len; struct aa_perms perms; + struct label_it i; if (!query_len) return -EINVAL; @@ -770,7 +779,16 @@ static ssize_t query_label(char *buf, size_t buf_len, return PTR_ERR(label); perms = allperms; - profile_query_cb(labels_profile(label), &perms, match_str, match_len); + if (view_only) { + label_for_each_in_ns(i, labels_ns(label), label, profile) { + profile_query_cb(profile, &perms, match_str, match_len); + } + } else { + label_for_each(i, label, profile) { + profile_query_cb(profile, &perms, match_str, match_len); + } + } + aa_put_label(label); return scnprintf(buf, buf_len, "allow 0x%08x\ndeny 0x%08x\naudit 0x%08x\nquiet 0x%08x\n", @@ -877,9 +895,12 @@ static int multi_transaction_release(struct inode *inode, struct file *file) return 0; } +#define QUERY_CMD_LABEL "label\0" +#define QUERY_CMD_LABEL_LEN 6 #define QUERY_CMD_PROFILE "profile\0" #define QUERY_CMD_PROFILE_LEN 8 - +#define QUERY_CMD_LABELALL "labelall\0" +#define QUERY_CMD_LABELALL_LEN 9 #define QUERY_CMD_DATA "data\0" #define QUERY_CMD_DATA_LEN 5 @@ -922,6 +943,17 @@ static ssize_t aa_write_access(struct file *file, const char __user *ubuf, len = query_label(t->data, MULTI_TRANSACTION_LIMIT, t->data + QUERY_CMD_PROFILE_LEN, count - QUERY_CMD_PROFILE_LEN, true); + } else if (count > QUERY_CMD_LABEL_LEN && + !memcmp(t->data, QUERY_CMD_LABEL, QUERY_CMD_LABEL_LEN)) { + len = query_label(t->data, MULTI_TRANSACTION_LIMIT, + t->data + QUERY_CMD_LABEL_LEN, + count - QUERY_CMD_LABEL_LEN, true); + } else if (count > QUERY_CMD_LABELALL_LEN && + !memcmp(t->data, QUERY_CMD_LABELALL, + QUERY_CMD_LABELALL_LEN)) { + len = query_label(t->data, MULTI_TRANSACTION_LIMIT, + t->data + QUERY_CMD_LABELALL_LEN, + count - QUERY_CMD_LABELALL_LEN, false); } else if (count > QUERY_CMD_DATA_LEN && !memcmp(t->data, QUERY_CMD_DATA, QUERY_CMD_DATA_LEN)) { len = query_data(t->data, MULTI_TRANSACTION_LIMIT, -- cgit v1.2.3 From c70c86c421427fd8487867de66c4104b15abd772 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Fri, 9 Jun 2017 14:07:02 -0700 Subject: apparmor: move capability checks to using labels Signed-off-by: John Johansen --- security/apparmor/capability.c | 57 ++++++++++++++++++++++------------ security/apparmor/include/capability.h | 6 ++-- security/apparmor/ipc.c | 2 +- security/apparmor/lsm.c | 20 ++++++++---- security/apparmor/resource.c | 2 +- 5 files changed, 58 insertions(+), 29 deletions(-) diff --git a/security/apparmor/capability.c b/security/apparmor/capability.c index 3bc19843d8df..67e347192a55 100644 --- a/security/apparmor/capability.c +++ b/security/apparmor/capability.c @@ -48,15 +48,16 @@ static DEFINE_PER_CPU(struct audit_cache, audit_cache); static void audit_cb(struct audit_buffer *ab, void *va) { struct common_audit_data *sa = va; + audit_log_format(ab, " capname="); audit_log_untrustedstring(ab, capability_names[sa->u.cap]); } /** * audit_caps - audit a capability + * @sa: audit data * @profile: profile being tested for confinement (NOT NULL) * @cap: capability tested - @audit: whether an audit record should be generated * @error: error code returned by test * * Do auditing of capability and handle, audit/complain/kill modes switching @@ -64,16 +65,13 @@ static void audit_cb(struct audit_buffer *ab, void *va) * * Returns: 0 or sa->error on success, error code on failure */ -static int audit_caps(struct aa_profile *profile, int cap, int audit, - int error) +static int audit_caps(struct common_audit_data *sa, struct aa_profile *profile, + int cap, int error) { struct audit_cache *ent; int type = AUDIT_APPARMOR_AUTO; - DEFINE_AUDIT_DATA(sa, LSM_AUDIT_DATA_CAP, OP_CAPABLE); - sa.u.cap = cap; - aad(&sa)->error = error; - if (audit == SECURITY_CAP_NOAUDIT) - aad(&sa)->info = "optional: no audit"; + + aad(sa)->error = error; if (likely(!error)) { /* test if auditing is being forced */ @@ -105,24 +103,44 @@ static int audit_caps(struct aa_profile *profile, int cap, int audit, } put_cpu_var(audit_cache); - return aa_audit(type, profile, &sa, audit_cb); + return aa_audit(type, profile, sa, audit_cb); } /** * profile_capable - test if profile allows use of capability @cap * @profile: profile being enforced (NOT NULL, NOT unconfined) * @cap: capability to test if allowed + * @audit: whether an audit record should be generated + * @sa: audit data (MAY BE NULL indicating no auditing) * * Returns: 0 if allowed else -EPERM */ -static int profile_capable(struct aa_profile *profile, int cap) +static int profile_capable(struct aa_profile *profile, int cap, int audit, + struct common_audit_data *sa) { - return cap_raised(profile->caps.allow, cap) ? 0 : -EPERM; + int error; + + if (cap_raised(profile->caps.allow, cap) && + !cap_raised(profile->caps.denied, cap)) + error = 0; + else + error = -EPERM; + + if (audit == SECURITY_CAP_NOAUDIT) { + if (!COMPLAIN_MODE(profile)) + return error; + /* audit the cap request in complain mode but note that it + * should be optional. + */ + aad(sa)->info = "optional: no audit"; + } + + return audit_caps(sa, profile, cap, error); } /** * aa_capable - test permission to use capability - * @profile: profile being tested against (NOT NULL) + * @label: label being tested for capability (NOT NULL) * @cap: capability to be tested * @audit: whether an audit record should be generated * @@ -130,14 +148,15 @@ static int profile_capable(struct aa_profile *profile, int cap) * * Returns: 0 on success, or else an error code. */ -int aa_capable(struct aa_profile *profile, int cap, int audit) +int aa_capable(struct aa_label *label, int cap, int audit) { - int error = profile_capable(profile, cap); + struct aa_profile *profile; + int error = 0; + DEFINE_AUDIT_DATA(sa, LSM_AUDIT_DATA_CAP, OP_CAPABLE); - if (audit == SECURITY_CAP_NOAUDIT) { - if (!COMPLAIN_MODE(profile)) - return error; - } + sa.u.cap = cap; + error = fn_for_each_confined(label, profile, + profile_capable(profile, cap, audit, &sa)); - return audit_caps(profile, cap, audit, error); + return error; } diff --git a/security/apparmor/include/capability.h b/security/apparmor/include/capability.h index 1218e95ebe49..e0304e2aeb7f 100644 --- a/security/apparmor/include/capability.h +++ b/security/apparmor/include/capability.h @@ -19,11 +19,12 @@ #include "apparmorfs.h" -struct aa_profile; +struct aa_label; /* aa_caps - confinement data for capabilities * @allowed: capabilities mask * @audit: caps that are to be audited + * @denied: caps that are explicitly denied * @quiet: caps that should not be audited * @kill: caps that when requested will result in the task being killed * @extended: caps that are subject finer grained mediation @@ -31,6 +32,7 @@ struct aa_profile; struct aa_caps { kernel_cap_t allow; kernel_cap_t audit; + kernel_cap_t denied; kernel_cap_t quiet; kernel_cap_t kill; kernel_cap_t extended; @@ -38,7 +40,7 @@ struct aa_caps { extern struct aa_sfs_entry aa_sfs_entry_caps[]; -int aa_capable(struct aa_profile *profile, int cap, int audit); +int aa_capable(struct aa_label *label, int cap, int audit); static inline void aa_free_cap_rules(struct aa_caps *caps) { diff --git a/security/apparmor/ipc.c b/security/apparmor/ipc.c index fa68cd42bd15..7678d94c4002 100644 --- a/security/apparmor/ipc.c +++ b/security/apparmor/ipc.c @@ -68,7 +68,7 @@ int aa_may_ptrace(struct aa_profile *tracer, struct aa_profile *tracee, if (profile_unconfined(tracer) || tracer == tracee) return 0; /* log this capability request */ - return aa_capable(tracer, CAP_SYS_PTRACE, 1); + return aa_capable(&tracer->label, CAP_SYS_PTRACE, 1); } /** diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c index f7f82ce00d73..bcfdcdb3eae2 100644 --- a/security/apparmor/lsm.c +++ b/security/apparmor/lsm.c @@ -117,20 +117,28 @@ static int apparmor_capget(struct task_struct *target, kernel_cap_t *effective, kernel_cap_t *inheritable, kernel_cap_t *permitted) { struct aa_label *label; - struct aa_profile *profile; const struct cred *cred; rcu_read_lock(); cred = __task_cred(target); label = aa_get_newest_cred_label(cred); - profile = labels_profile(label); + /* * cap_capget is stacked ahead of this and will * initialize effective and permitted. */ - if (!profile_unconfined(profile) && !COMPLAIN_MODE(profile)) { - *effective = cap_intersect(*effective, profile->caps.allow); - *permitted = cap_intersect(*permitted, profile->caps.allow); + if (!unconfined(label)) { + struct aa_profile *profile; + struct label_it i; + + label_for_each_confined(i, label, profile) { + if (COMPLAIN_MODE(profile)) + continue; + *effective = cap_intersect(*effective, + profile->caps.allow); + *permitted = cap_intersect(*permitted, + profile->caps.allow); + } } rcu_read_unlock(); aa_put_label(label); @@ -146,7 +154,7 @@ static int apparmor_capable(const struct cred *cred, struct user_namespace *ns, label = aa_get_newest_cred_label(cred); if (!unconfined(label)) - error = aa_capable(labels_profile(label), cap, audit); + error = aa_capable(label, cap, audit); aa_put_label(label); return error; diff --git a/security/apparmor/resource.c b/security/apparmor/resource.c index ab8e104c1970..2474ee0b3467 100644 --- a/security/apparmor/resource.c +++ b/security/apparmor/resource.c @@ -100,7 +100,7 @@ int aa_task_setrlimit(struct aa_profile *profile, struct task_struct *task, * task has CAP_SYS_RESOURCE. */ if ((profile != labels_profile(task_label) && - aa_capable(profile, CAP_SYS_RESOURCE, 1)) || + aa_capable(&profile->label, CAP_SYS_RESOURCE, 1)) || (profile->rlimits.mask & (1 << resource) && new_rlim->rlim_max > profile->rlimits.limits[resource].rlim_max)) error = -EACCES; -- cgit v1.2.3 From 86b92cb782b38d71ee344af20fcbe5106dd19dbe Mon Sep 17 00:00:00 2001 From: John Johansen Date: Fri, 9 Jun 2017 14:15:20 -0700 Subject: apparmor: move resource checks to using labels Signed-off-by: John Johansen --- security/apparmor/include/resource.h | 4 +- security/apparmor/lsm.c | 6 +- security/apparmor/resource.c | 112 ++++++++++++++++++++++++----------- 3 files changed, 80 insertions(+), 42 deletions(-) diff --git a/security/apparmor/include/resource.h b/security/apparmor/include/resource.h index f6289f335c4d..76f1586c9adb 100644 --- a/security/apparmor/include/resource.h +++ b/security/apparmor/include/resource.h @@ -37,10 +37,10 @@ struct aa_rlimit { extern struct aa_sfs_entry aa_sfs_entry_rlimit[]; int aa_map_resource(int resource); -int aa_task_setrlimit(struct aa_profile *profile, struct task_struct *, +int aa_task_setrlimit(struct aa_label *label, struct task_struct *task, unsigned int resource, struct rlimit *new_rlim); -void __aa_transition_rlimits(struct aa_profile *old, struct aa_profile *new); +void __aa_transition_rlimits(struct aa_label *old, struct aa_label *new); static inline void aa_free_rlimit_rules(struct aa_rlimit *rlims) { diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c index bcfdcdb3eae2..c3e98f74268f 100644 --- a/security/apparmor/lsm.c +++ b/security/apparmor/lsm.c @@ -625,8 +625,7 @@ static void apparmor_bprm_committing_creds(struct linux_binprm *bprm) current->pdeath_signal = 0; /* reset soft limits and set hard limits for the new label */ - __aa_transition_rlimits(labels_profile(label), - labels_profile(new_ctx->label)); + __aa_transition_rlimits(label, new_ctx->label); } /** @@ -646,8 +645,7 @@ static int apparmor_task_setrlimit(struct task_struct *task, int error = 0; if (!unconfined(label)) - error = aa_task_setrlimit(labels_profile(label), task, - resource, new_rlim); + error = aa_task_setrlimit(label, task, resource, new_rlim); __end_current_label_crit_section(label); return error; diff --git a/security/apparmor/resource.c b/security/apparmor/resource.c index 2474ee0b3467..d8bc842594ed 100644 --- a/security/apparmor/resource.c +++ b/security/apparmor/resource.c @@ -13,6 +13,7 @@ */ #include +#include #include "include/audit.h" #include "include/context.h" @@ -36,6 +37,11 @@ static void audit_cb(struct audit_buffer *ab, void *va) audit_log_format(ab, " rlimit=%s value=%lu", rlim_names[aad(sa)->rlim.rlim], aad(sa)->rlim.max); + if (aad(sa)->peer) { + audit_log_format(ab, " peer="); + aa_label_xaudit(ab, labels_ns(aad(sa)->label), aad(sa)->peer, + FLAGS_NONE, GFP_ATOMIC); + } } /** @@ -48,13 +54,17 @@ static void audit_cb(struct audit_buffer *ab, void *va) * Returns: 0 or sa->error else other error code on failure */ static int audit_resource(struct aa_profile *profile, unsigned int resource, - unsigned long value, int error) + unsigned long value, struct aa_label *peer, + const char *info, int error) { DEFINE_AUDIT_DATA(sa, LSM_AUDIT_DATA_NONE, OP_SETRLIMIT); aad(&sa)->rlim.rlim = resource; aad(&sa)->rlim.max = value; + aad(&sa)->peer = peer; + aad(&sa)->info = info; aad(&sa)->error = error; + return aa_audit(AUDIT_APPARMOR_AUTO, profile, &sa, audit_cb); } @@ -72,9 +82,21 @@ int aa_map_resource(int resource) return rlim_map[resource]; } +static int profile_setrlimit(struct aa_profile *profile, unsigned int resource, + struct rlimit *new_rlim) +{ + int e = 0; + + if (profile->rlimits.mask & (1 << resource) && new_rlim->rlim_max > + profile->rlimits.limits[resource].rlim_max) + e = -EACCES; + return audit_resource(profile, resource, new_rlim->rlim_max, NULL, NULL, + e); +} + /** * aa_task_setrlimit - test permission to set an rlimit - * @profile - profile confining the task (NOT NULL) + * @label - label confining the task (NOT NULL) * @task - task the resource is being set on * @resource - the resource being set * @new_rlim - the new resource limit (NOT NULL) @@ -83,14 +105,15 @@ int aa_map_resource(int resource) * * Returns: 0 or error code if setting resource failed */ -int aa_task_setrlimit(struct aa_profile *profile, struct task_struct *task, +int aa_task_setrlimit(struct aa_label *label, struct task_struct *task, unsigned int resource, struct rlimit *new_rlim) { - struct aa_label *task_label; + struct aa_profile *profile; + struct aa_label *peer; int error = 0; rcu_read_lock(); - task_label = aa_get_newest_cred_label((__task_cred(task))); + peer = aa_get_newest_cred_label(__task_cred(task)); rcu_read_unlock(); /* TODO: extend resource control to handle other (non current) @@ -99,53 +122,70 @@ int aa_task_setrlimit(struct aa_profile *profile, struct task_struct *task, * the same profile or that the task setting the resource of another * task has CAP_SYS_RESOURCE. */ - if ((profile != labels_profile(task_label) && - aa_capable(&profile->label, CAP_SYS_RESOURCE, 1)) || - (profile->rlimits.mask & (1 << resource) && - new_rlim->rlim_max > profile->rlimits.limits[resource].rlim_max)) - error = -EACCES; - aa_put_label(task_label); - - return audit_resource(profile, resource, new_rlim->rlim_max, error); + if (label != peer && + !aa_capable(label, CAP_SYS_RESOURCE, SECURITY_CAP_NOAUDIT)) + error = fn_for_each(label, profile, + audit_resource(profile, resource, + new_rlim->rlim_max, peer, + "cap_sys_resoure", -EACCES)); + else + error = fn_for_each_confined(label, profile, + profile_setrlimit(profile, resource, new_rlim)); + aa_put_label(peer); + + return error; } /** * __aa_transition_rlimits - apply new profile rlimits - * @old: old profile on task (NOT NULL) - * @new: new profile with rlimits to apply (NOT NULL) + * @old_l: old label on task (NOT NULL) + * @new_l: new label with rlimits to apply (NOT NULL) */ -void __aa_transition_rlimits(struct aa_profile *old, struct aa_profile *new) +void __aa_transition_rlimits(struct aa_label *old_l, struct aa_label *new_l) { unsigned int mask = 0; struct rlimit *rlim, *initrlim; - int i; + struct aa_profile *old, *new; + struct label_it i; + + old = labels_profile(old_l); + new = labels_profile(new_l); - /* for any rlimits the profile controlled reset the soft limit - * to the less of the tasks hard limit and the init tasks soft limit + /* for any rlimits the profile controlled, reset the soft limit + * to the lesser of the tasks hard limit and the init tasks soft limit */ - if (old->rlimits.mask) { - for (i = 0, mask = 1; i < RLIM_NLIMITS; i++, mask <<= 1) { - if (old->rlimits.mask & mask) { - rlim = current->signal->rlim + i; - initrlim = init_task.signal->rlim + i; - rlim->rlim_cur = min(rlim->rlim_max, - initrlim->rlim_cur); + label_for_each_confined(i, old_l, old) { + if (old->rlimits.mask) { + int j; + + for (j = 0, mask = 1; j < RLIM_NLIMITS; j++, + mask <<= 1) { + if (old->rlimits.mask & mask) { + rlim = current->signal->rlim + j; + initrlim = init_task.signal->rlim + j; + rlim->rlim_cur = min(rlim->rlim_max, + initrlim->rlim_cur); + } } } } /* set any new hard limits as dictated by the new profile */ - if (!new->rlimits.mask) - return; - for (i = 0, mask = 1; i < RLIM_NLIMITS; i++, mask <<= 1) { - if (!(new->rlimits.mask & mask)) - continue; + label_for_each_confined(i, new_l, new) { + int j; - rlim = current->signal->rlim + i; - rlim->rlim_max = min(rlim->rlim_max, - new->rlimits.limits[i].rlim_max); - /* soft limit should not exceed hard limit */ - rlim->rlim_cur = min(rlim->rlim_cur, rlim->rlim_max); + if (!new->rlimits.mask) + continue; + for (j = 0, mask = 1; j < RLIM_NLIMITS; j++, mask <<= 1) { + if (!(new->rlimits.mask & mask)) + continue; + + rlim = current->signal->rlim + j; + rlim->rlim_max = min(rlim->rlim_max, + new->rlimits.limits[j].rlim_max); + /* soft limit should not exceed hard limit */ + rlim->rlim_cur = min(rlim->rlim_cur, rlim->rlim_max); + } } } -- cgit v1.2.3 From ca916e8e2d88e97134a313eb3100ce9c3d8fd3f2 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Fri, 9 Jun 2017 14:23:09 -0700 Subject: apparmor: add cross check permission helper macros The cross check permission helper macros will help simplify code that does cross task permission checks like ptrace. Signed-off-by: John Johansen --- security/apparmor/include/perms.h | 42 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/security/apparmor/include/perms.h b/security/apparmor/include/perms.h index 0c5c2b00be02..2b27bb79aec4 100644 --- a/security/apparmor/include/perms.h +++ b/security/apparmor/include/perms.h @@ -92,7 +92,47 @@ struct aa_perms { extern struct aa_perms nullperms; extern struct aa_perms allperms; -struct aa_profile; + +#define xcheck(FN1, FN2) \ +({ \ + int e, error = FN1; \ + e = FN2; \ + if (e) \ + error = e; \ + error; \ +}) + + +/* + * TODO: update for labels pointing to labels instead of profiles + * TODO: optimize the walk, currently does subwalk of L2 for each P in L1 + * gah this doesn't allow for label compound check!!!! + */ +#define xcheck_ns_profile_profile(P1, P2, FN, args...) \ +({ \ + int ____e = 0; \ + if (P1->ns == P2->ns) \ + ____e = FN((P1), (P2), args); \ + (____e); \ +}) + +#define xcheck_ns_profile_label(P, L, FN, args...) \ +({ \ + struct aa_profile *__p2; \ + fn_for_each((L), __p2, \ + xcheck_ns_profile_profile((P), __p2, (FN), args)); \ +}) + +#define xcheck_ns_labels(L1, L2, FN, args...) \ +({ \ + struct aa_profile *__p1; \ + fn_for_each((L1), __p1, FN(__p1, (L2), args)); \ +}) + +/* Do the cross check but applying FN at the profiles level */ +#define xcheck_labels_profiles(L1, L2, FN, args...) \ + xcheck_ns_labels((L1), (L2), xcheck_ns_profile_label, (FN), args) + void aa_perm_mask_to_str(char *str, const char *chrs, u32 mask); void aa_audit_perm_names(struct audit_buffer *ab, const char **names, u32 mask); -- cgit v1.2.3 From b2d09ae449cedc6f276ac485c013d22a97d36992 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Fri, 9 Jun 2017 14:22:14 -0700 Subject: apparmor: move ptrace checks to using labels Signed-off-by: John Johansen --- security/apparmor/domain.c | 17 ++++--- security/apparmor/include/apparmor.h | 1 + security/apparmor/include/ipc.h | 10 ++--- security/apparmor/ipc.c | 87 +++++++++--------------------------- security/apparmor/lsm.c | 23 +++++++++- 5 files changed, 58 insertions(+), 80 deletions(-) diff --git a/security/apparmor/domain.c b/security/apparmor/domain.c index 8d6797c849fe..fab8923ae38e 100644 --- a/security/apparmor/domain.c +++ b/security/apparmor/domain.c @@ -51,14 +51,16 @@ void aa_free_domain_entries(struct aa_domain *domain) /** * may_change_ptraced_domain - check if can change profile on ptraced task - * @to_profile: profile to change to (NOT NULL) + * @to_label: profile to change to (NOT NULL) + * @info: message if there is an error * * Check if current is ptraced and if so if the tracing task is allowed * to trace the new domain * * Returns: %0 or error if change not allowed */ -static int may_change_ptraced_domain(struct aa_profile *to_profile) +static int may_change_ptraced_domain(struct aa_label *to_label, + const char **info) { struct task_struct *tracer; struct aa_label *tracerl = NULL; @@ -74,13 +76,14 @@ static int may_change_ptraced_domain(struct aa_profile *to_profile) if (!tracer || unconfined(tracerl)) goto out; - error = aa_may_ptrace(labels_profile(tracerl), to_profile, - PTRACE_MODE_ATTACH); + error = aa_may_ptrace(tracerl, to_label, PTRACE_MODE_ATTACH); out: rcu_read_unlock(); aa_put_label(tracerl); + if (error) + *info = "ptrace prevents transition"; return error; } @@ -477,7 +480,7 @@ int apparmor_bprm_set_creds(struct linux_binprm *bprm) } if (bprm->unsafe & LSM_UNSAFE_PTRACE) { - error = may_change_ptraced_domain(new_profile); + error = may_change_ptraced_domain(&new_profile->label, &info); if (error) goto audit; } @@ -661,7 +664,7 @@ int aa_change_hat(const char *hats[], int count, u64 token, int flags) } } - error = may_change_ptraced_domain(hat); + error = may_change_ptraced_domain(&hat->label, &info); if (error) { info = "ptraced"; error = -EPERM; @@ -782,7 +785,7 @@ int aa_change_profile(const char *fqname, int flags) } /* check if tracing task is allowed to trace target domain */ - error = may_change_ptraced_domain(target); + error = may_change_ptraced_domain(&target->label, &info); if (error) { info = "ptrace prevents transition"; goto audit; diff --git a/security/apparmor/include/apparmor.h b/security/apparmor/include/apparmor.h index c4a900488e76..aaf893f4e4f5 100644 --- a/security/apparmor/include/apparmor.h +++ b/security/apparmor/include/apparmor.h @@ -27,6 +27,7 @@ #define AA_CLASS_NET 4 #define AA_CLASS_RLIMITS 5 #define AA_CLASS_DOMAIN 6 +#define AA_CLASS_PTRACE 9 #define AA_CLASS_LABEL 16 #define AA_CLASS_LAST AA_CLASS_LABEL diff --git a/security/apparmor/include/ipc.h b/security/apparmor/include/ipc.h index 288ca76e2fb1..fb3e751e6eed 100644 --- a/security/apparmor/include/ipc.h +++ b/security/apparmor/include/ipc.h @@ -4,7 +4,7 @@ * This file contains AppArmor ipc mediation function definitions. * * Copyright (C) 1998-2008 Novell/SUSE - * Copyright 2009-2010 Canonical Ltd. + * Copyright 2009-2017 Canonical Ltd. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -19,10 +19,10 @@ struct aa_profile; -int aa_may_ptrace(struct aa_profile *tracer, struct aa_profile *tracee, - unsigned int mode); +#define AA_PTRACE_TRACE MAY_WRITE +#define AA_PTRACE_READ MAY_READ -int aa_ptrace(struct task_struct *tracer, struct task_struct *tracee, - unsigned int mode); +int aa_may_ptrace(struct aa_label *tracer, struct aa_label *tracee, + u32 request); #endif /* __AA_IPC_H */ diff --git a/security/apparmor/ipc.c b/security/apparmor/ipc.c index 7678d94c4002..f81649369f05 100644 --- a/security/apparmor/ipc.c +++ b/security/apparmor/ipc.c @@ -4,7 +4,7 @@ * This file contains AppArmor ipc mediation * * Copyright (C) 1998-2008 Novell/SUSE - * Copyright 2009-2010 Canonical Ltd. + * Copyright 2009-2017 Canonical Ltd. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -25,88 +25,43 @@ static void audit_ptrace_cb(struct audit_buffer *ab, void *va) { struct common_audit_data *sa = va; + audit_log_format(ab, " peer="); aa_label_xaudit(ab, labels_ns(aad(sa)->label), aad(sa)->peer, FLAGS_NONE, GFP_ATOMIC); } -/** - * aa_audit_ptrace - do auditing for ptrace - * @profile: profile being enforced (NOT NULL) - * @target: profile being traced (NOT NULL) - * @error: error condition - * - * Returns: %0 or error code - */ -static int aa_audit_ptrace(struct aa_profile *profile, - struct aa_profile *target, int error) +static int cross_ptrace_perm(struct aa_profile *tracer, + struct aa_profile *tracee, u32 request, + struct common_audit_data *sa) { - DEFINE_AUDIT_DATA(sa, LSM_AUDIT_DATA_NONE, OP_PTRACE); + /* policy uses the old style capability check for ptrace */ + if (profile_unconfined(tracer) || tracer == tracee) + return 0; - aad(&sa)->peer = &target->label; - aad(&sa)->error = error; + aad(sa)->label = &tracer->label; + aad(sa)->peer = &tracee->label; + aad(sa)->request = 0; + aad(sa)->error = aa_capable(&tracer->label, CAP_SYS_PTRACE, 1); - return aa_audit(AUDIT_APPARMOR_AUTO, profile, &sa, audit_ptrace_cb); + return aa_audit(AUDIT_APPARMOR_AUTO, tracer, sa, audit_ptrace_cb); } /** * aa_may_ptrace - test if tracer task can trace the tracee - * @tracer: profile of the task doing the tracing (NOT NULL) - * @tracee: task to be traced - * @mode: whether PTRACE_MODE_READ || PTRACE_MODE_ATTACH + * @tracer: label of the task doing the tracing (NOT NULL) + * @tracee: task label to be traced + * @request: permission request * * Returns: %0 else error code if permission denied or error */ -int aa_may_ptrace(struct aa_profile *tracer, struct aa_profile *tracee, - unsigned int mode) +int aa_may_ptrace(struct aa_label *tracer, struct aa_label *tracee, + u32 request) { - /* TODO: currently only based on capability, not extended ptrace - * rules, - * Test mode for PTRACE_MODE_READ || PTRACE_MODE_ATTACH - */ + DEFINE_AUDIT_DATA(sa, LSM_AUDIT_DATA_NONE, OP_PTRACE); - if (profile_unconfined(tracer) || tracer == tracee) - return 0; - /* log this capability request */ - return aa_capable(&tracer->label, CAP_SYS_PTRACE, 1); + return xcheck_labels_profiles(tracer, tracee, cross_ptrace_perm, + request, &sa); } -/** - * aa_ptrace - do ptrace permission check and auditing - * @tracer: task doing the tracing (NOT NULL) - * @tracee: task being traced (NOT NULL) - * @mode: ptrace mode either PTRACE_MODE_READ || PTRACE_MODE_ATTACH - * - * Returns: %0 else error code if permission denied or error - */ -int aa_ptrace(struct task_struct *tracer, struct task_struct *tracee, - unsigned int mode) -{ - /* - * tracer can ptrace tracee when - * - tracer is unconfined || - * - tracer is in complain mode - * - tracer has rules allowing it to trace tracee currently this is: - * - confined by the same profile || - * - tracer profile has CAP_SYS_PTRACE - */ - struct aa_label *tracer_l = aa_get_task_label(tracer); - int error = 0; - - if (!unconfined(tracer_l)) { - struct aa_label *tracee_l = aa_get_task_label(tracee); - - error = aa_may_ptrace(labels_profile(tracer_l), - labels_profile(tracee_l), - mode); - error = aa_audit_ptrace(labels_profile(tracer_l), - labels_profile(tracee_l), - error); - - aa_put_label(tracee_l); - } - aa_put_label(tracer_l); - - return error; -} diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c index c3e98f74268f..bf28b48bf6dd 100644 --- a/security/apparmor/lsm.c +++ b/security/apparmor/lsm.c @@ -104,12 +104,31 @@ static void apparmor_cred_transfer(struct cred *new, const struct cred *old) static int apparmor_ptrace_access_check(struct task_struct *child, unsigned int mode) { - return aa_ptrace(current, child, mode); + struct aa_label *tracer, *tracee; + int error; + + tracer = begin_current_label_crit_section(); + tracee = aa_get_task_label(child); + error = aa_may_ptrace(tracer, tracee, + mode == PTRACE_MODE_READ ? AA_PTRACE_READ : AA_PTRACE_TRACE); + aa_put_label(tracee); + end_current_label_crit_section(tracer); + + return error; } static int apparmor_ptrace_traceme(struct task_struct *parent) { - return aa_ptrace(parent, current, PTRACE_MODE_ATTACH); + struct aa_label *tracer, *tracee; + int error; + + tracee = begin_current_label_crit_section(); + tracer = aa_get_task_label(parent); + error = aa_may_ptrace(tracer, tracee, AA_PTRACE_TRACE); + aa_put_label(tracer); + end_current_label_crit_section(tracee); + + return error; } /* Derived from security/commoncap.c:cap_capget */ -- cgit v1.2.3 From 290f458a4f16f9cf6cb6562b249e69fe1c3c3a07 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Fri, 9 Jun 2017 14:38:35 -0700 Subject: apparmor: allow ptrace checks to be finer grained than just capability Signed-off-by: John Johansen --- security/apparmor/apparmorfs.c | 6 +++++ security/apparmor/include/ipc.h | 6 +++++ security/apparmor/ipc.c | 56 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 68 insertions(+) diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c index d24100f8fd98..d1a6ce499776 100644 --- a/security/apparmor/apparmorfs.c +++ b/security/apparmor/apparmorfs.c @@ -2086,6 +2086,11 @@ static struct aa_sfs_entry aa_sfs_entry_file[] = { { } }; +static struct aa_sfs_entry aa_sfs_entry_ptrace[] = { + AA_SFS_FILE_STRING("mask", "read trace"), + { } +}; + static struct aa_sfs_entry aa_sfs_entry_domain[] = { AA_SFS_FILE_BOOLEAN("change_hat", 1), AA_SFS_FILE_BOOLEAN("change_hatv", 1), @@ -2125,6 +2130,7 @@ static struct aa_sfs_entry aa_sfs_entry_features[] = { AA_SFS_FILE_U64("capability", VFS_CAP_FLAGS_MASK), AA_SFS_DIR("rlimit", aa_sfs_entry_rlimit), AA_SFS_DIR("caps", aa_sfs_entry_caps), + AA_SFS_DIR("ptrace", aa_sfs_entry_ptrace), AA_SFS_DIR("query", aa_sfs_entry_query), { } }; diff --git a/security/apparmor/include/ipc.h b/security/apparmor/include/ipc.h index fb3e751e6eed..656fdb81c8a0 100644 --- a/security/apparmor/include/ipc.h +++ b/security/apparmor/include/ipc.h @@ -21,6 +21,12 @@ struct aa_profile; #define AA_PTRACE_TRACE MAY_WRITE #define AA_PTRACE_READ MAY_READ +#define AA_MAY_BE_TRACED AA_MAY_APPEND +#define AA_MAY_BE_READ AA_MAY_CREATE +#define PTRACE_PERM_SHIFT 2 + +#define AA_PTRACE_PERM_MASK (AA_PTRACE_READ | AA_PTRACE_TRACE | \ + AA_MAY_BE_READ | AA_MAY_BE_TRACED) int aa_may_ptrace(struct aa_label *tracer, struct aa_label *tracee, u32 request); diff --git a/security/apparmor/ipc.c b/security/apparmor/ipc.c index f81649369f05..11e66b5bbc42 100644 --- a/security/apparmor/ipc.c +++ b/security/apparmor/ipc.c @@ -21,20 +21,76 @@ #include "include/policy.h" #include "include/ipc.h" +/** + * audit_ptrace_mask - convert mask to permission string + * @buffer: buffer to write string to (NOT NULL) + * @mask: permission mask to convert + */ +static void audit_ptrace_mask(struct audit_buffer *ab, u32 mask) +{ + switch (mask) { + case MAY_READ: + audit_log_string(ab, "read"); + break; + case MAY_WRITE: + audit_log_string(ab, "trace"); + break; + case AA_MAY_BE_READ: + audit_log_string(ab, "readby"); + break; + case AA_MAY_BE_TRACED: + audit_log_string(ab, "tracedby"); + break; + } +} + /* call back to audit ptrace fields */ static void audit_ptrace_cb(struct audit_buffer *ab, void *va) { struct common_audit_data *sa = va; + if (aad(sa)->request & AA_PTRACE_PERM_MASK) { + audit_log_format(ab, " requested_mask="); + audit_ptrace_mask(ab, aad(sa)->request); + + if (aad(sa)->denied & AA_PTRACE_PERM_MASK) { + audit_log_format(ab, " denied_mask="); + audit_ptrace_mask(ab, aad(sa)->denied); + } + } audit_log_format(ab, " peer="); aa_label_xaudit(ab, labels_ns(aad(sa)->label), aad(sa)->peer, FLAGS_NONE, GFP_ATOMIC); } +/* TODO: conditionals */ +static int profile_ptrace_perm(struct aa_profile *profile, + struct aa_profile *peer, u32 request, + struct common_audit_data *sa) +{ + struct aa_perms perms = { }; + + /* need because of peer in cross check */ + if (profile_unconfined(profile) || + !PROFILE_MEDIATES(profile, AA_CLASS_PTRACE)) + return 0; + + aad(sa)->peer = &peer->label; + aa_profile_match_label(profile, &peer->label, AA_CLASS_PTRACE, request, + &perms); + aa_apply_modes_to_perms(profile, &perms); + return aa_check_perms(profile, &perms, request, sa, audit_ptrace_cb); +} + static int cross_ptrace_perm(struct aa_profile *tracer, struct aa_profile *tracee, u32 request, struct common_audit_data *sa) { + if (PROFILE_MEDIATES(tracer, AA_CLASS_PTRACE)) + return xcheck(profile_ptrace_perm(tracer, tracee, request, sa), + profile_ptrace_perm(tracee, tracer, + request << PTRACE_PERM_SHIFT, + sa)); /* policy uses the old style capability check for ptrace */ if (profile_unconfined(tracer) || tracer == tracee) return 0; -- cgit v1.2.3 From 190a95189eb9e2233ed71a85cd6dd0c8efc9d392 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Fri, 9 Jun 2017 14:59:51 -0700 Subject: apparmor: move aa_file_perm() to use labels Signed-off-by: John Johansen --- security/apparmor/file.c | 48 +++++++++++++++++++++++++++++++++------- security/apparmor/include/file.h | 29 +++++++++++++++++------- security/apparmor/lsm.c | 24 +++----------------- 3 files changed, 64 insertions(+), 37 deletions(-) diff --git a/security/apparmor/file.c b/security/apparmor/file.c index 5289c8db832b..c13e967137a8 100644 --- a/security/apparmor/file.c +++ b/security/apparmor/file.c @@ -23,6 +23,7 @@ #include "include/match.h" #include "include/path.h" #include "include/policy.h" +#include "include/label.h" static u32 map_mask_to_chr_mask(u32 mask) { @@ -433,22 +434,55 @@ audit: /** * aa_file_perm - do permission revalidation check & audit for @file * @op: operation being checked - * @profile: profile being enforced (NOT NULL) + * @label: label being enforced (NOT NULL) * @file: file to revalidate access permissions on (NOT NULL) * @request: requested permissions * * Returns: %0 if access allowed else error */ -int aa_file_perm(const char *op, struct aa_profile *profile, struct file *file, +int aa_file_perm(const char *op, struct aa_label *label, struct file *file, u32 request) { struct path_cond cond = { .uid = file_inode(file)->i_uid, .mode = file_inode(file)->i_mode }; + struct aa_file_ctx *fctx; + struct aa_label *flabel; + u32 denied; + int error = 0; + + AA_BUG(!label); + AA_BUG(!file); + + fctx = file_ctx(file); + + rcu_read_lock(); + flabel = rcu_dereference(fctx->label); + AA_BUG(!flabel); + + /* revalidate access, if task is unconfined, or the cached cred + * doesn't match or if the request is for more permissions than + * was granted. + * + * Note: the test for !unconfined(flabel) is to handle file + * delegation from unconfined tasks + */ + denied = request & ~fctx->allow; + if (unconfined(label) || unconfined(flabel) || + (!denied && aa_label_is_subset(flabel, label))) + goto done; + + /* TODO: label cross check */ + + if (file->f_path.mnt && path_mediated_fs(file->f_path.dentry)) + error = aa_path_perm(op, labels_profile(label), &file->f_path, + PATH_DELEGATE_DELETED, request, &cond); - return aa_path_perm(op, profile, &file->f_path, PATH_DELEGATE_DELETED, - request, &cond); +done: + rcu_read_unlock(); + + return error; } static void revalidate_tty(struct aa_label *label) @@ -469,8 +503,7 @@ static void revalidate_tty(struct aa_label *label) struct tty_file_private, list); file = file_priv->file; - if (aa_file_perm(OP_INHERIT, labels_profile(label), file, - MAY_READ | MAY_WRITE)) + if (aa_file_perm(OP_INHERIT, label, file, MAY_READ | MAY_WRITE)) drop_tty = 1; } spin_unlock(&tty->files_lock); @@ -484,8 +517,7 @@ static int match_file(const void *p, struct file *file, unsigned int fd) { struct aa_label *label = (struct aa_label *)p; - if (aa_file_perm(OP_INHERIT, labels_profile(label), file, - aa_map_file_to_perms(file))) + if (aa_file_perm(OP_INHERIT, label, file, aa_map_file_to_perms(file))) return fd + 1; return 0; } diff --git a/security/apparmor/include/file.h b/security/apparmor/include/file.h index df76c208473a..415512771bff 100644 --- a/security/apparmor/include/file.h +++ b/security/apparmor/include/file.h @@ -15,6 +15,8 @@ #ifndef __AA_FILE_H #define __AA_FILE_H +#include + #include "domain.h" #include "match.h" #include "perms.h" @@ -33,13 +35,13 @@ struct path; #define file_ctx(X) ((struct aa_file_ctx *)(X)->f_security) /* struct aa_file_ctx - the AppArmor context the file was opened in + * @lock: lock to update the ctx + * @label: label currently cached on the ctx * @perms: the permission the file was opened with - * - * The file_ctx could currently be directly stored in file->f_security - * as the profile reference is now stored in the f_cred. However the - * ctx struct will expand in the future so we keep the struct. */ struct aa_file_ctx { + spinlock_t lock; + struct aa_label __rcu *label; u32 allow; }; @@ -50,12 +52,16 @@ struct aa_file_ctx { * * Returns: file_ctx or NULL on failure */ -static inline struct aa_file_ctx *aa_alloc_file_ctx(gfp_t gfp) +static inline struct aa_file_ctx *aa_alloc_file_ctx(struct aa_label *label, + gfp_t gfp) { struct aa_file_ctx *ctx; ctx = kzalloc(sizeof(struct aa_file_ctx), gfp); - + if (ctx) { + spin_lock_init(&ctx->lock); + rcu_assign_pointer(ctx->label, aa_get_label(label)); + } return ctx; } @@ -65,8 +71,15 @@ static inline struct aa_file_ctx *aa_alloc_file_ctx(gfp_t gfp) */ static inline void aa_free_file_ctx(struct aa_file_ctx *ctx) { - if (ctx) + if (ctx) { + aa_put_label(rcu_access_pointer(ctx->label)); kzfree(ctx); + } +} + +static inline struct aa_label *aa_get_file_label(struct aa_file_ctx *ctx) +{ + return aa_get_label_rcu(&ctx->label); } /* @@ -183,7 +196,7 @@ int aa_path_perm(const char *op, struct aa_profile *profile, int aa_path_link(struct aa_profile *profile, struct dentry *old_dentry, const struct path *new_dir, struct dentry *new_dentry); -int aa_file_perm(const char *op, struct aa_profile *profile, struct file *file, +int aa_file_perm(const char *op, struct aa_label *label, struct file *file, u32 request); void aa_inherit_files(const struct cred *cred, struct files_struct *files); diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c index bf28b48bf6dd..011fbb009663 100644 --- a/security/apparmor/lsm.c +++ b/security/apparmor/lsm.c @@ -433,7 +433,7 @@ static int apparmor_file_alloc_security(struct file *file) /* freed by apparmor_file_free_security */ struct aa_label *label = begin_current_label_crit_section(); - file->f_security = aa_alloc_file_ctx(GFP_KERNEL); + file->f_security = aa_alloc_file_ctx(label, GFP_KERNEL); if (!file_ctx(file)) error = -ENOMEM; end_current_label_crit_section(label); @@ -448,33 +448,15 @@ static void apparmor_file_free_security(struct file *file) static int common_file_perm(const char *op, struct file *file, u32 mask) { - struct aa_file_ctx *fctx = file->f_security; - struct aa_label *label, *flabel; + struct aa_label *label; int error = 0; /* don't reaudit files closed during inheritance */ if (file->f_path.dentry == aa_null.dentry) return -EACCES; - flabel = aa_cred_raw_label(file->f_cred); - AA_BUG(!flabel); - - if (!file->f_path.mnt || - !path_mediated_fs(file->f_path.dentry)) - return 0; - label = __begin_current_label_crit_section(); - - /* revalidate access, if task is unconfined, or the cached cred - * doesn't match or if the request is for more permissions than - * was granted. - * - * Note: the test for !unconfined(fprofile) is to handle file - * delegation from unconfined tasks - */ - if (!unconfined(label) && !unconfined(flabel) && - ((flabel != label) || (mask & ~fctx->allow))) - error = aa_file_perm(op, labels_profile(label), file, mask); + error = aa_file_perm(op, label, file, mask); __end_current_label_crit_section(label); return error; -- cgit v1.2.3 From 98c3d182321d489d8bfaa596127020ec3027edb2 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Fri, 9 Jun 2017 15:48:20 -0700 Subject: apparmor: update aa_audit_file() to use labels Signed-off-by: John Johansen --- security/apparmor/domain.c | 6 ++++-- security/apparmor/file.c | 18 ++++++++++++------ security/apparmor/include/file.h | 3 ++- 3 files changed, 18 insertions(+), 9 deletions(-) diff --git a/security/apparmor/domain.c b/security/apparmor/domain.c index fab8923ae38e..896bca01828e 100644 --- a/security/apparmor/domain.c +++ b/security/apparmor/domain.c @@ -518,6 +518,7 @@ x_clear: audit: error = aa_audit_file(profile, &perms, OP_EXEC, MAY_EXEC, name, new_profile ? new_profile->base.hname : NULL, + new_profile ? &new_profile->label : NULL, cond.uid, info, error); cleanup: @@ -694,7 +695,7 @@ int aa_change_hat(const char *hats[], int count, u64 token, int flags) audit: if (!(flags & AA_CHANGE_TEST)) error = aa_audit_file(profile, &perms, OP_CHANGE_HAT, - AA_MAY_CHANGEHAT, NULL, target, + AA_MAY_CHANGEHAT, NULL, target, NULL, GLOBAL_ROOT_UID, info, error); out: @@ -802,7 +803,8 @@ int aa_change_profile(const char *fqname, int flags) audit: if (!(flags & AA_CHANGE_TEST)) error = aa_audit_file(profile, &perms, op, request, NULL, - fqname, GLOBAL_ROOT_UID, info, error); + fqname, NULL, GLOBAL_ROOT_UID, info, + error); aa_put_profile(target); aa_put_label(label); diff --git a/security/apparmor/file.c b/security/apparmor/file.c index c13e967137a8..a40bc1e276dc 100644 --- a/security/apparmor/file.c +++ b/security/apparmor/file.c @@ -75,7 +75,11 @@ static void file_audit_cb(struct audit_buffer *ab, void *va) from_kuid(&init_user_ns, aad(sa)->fs.ouid)); } - if (aad(sa)->fs.target) { + if (aad(sa)->peer) { + audit_log_format(ab, " target="); + aa_label_xaudit(ab, labels_ns(aad(sa)->label), aad(sa)->peer, + FLAG_VIEW_SUBNS, GFP_ATOMIC); + } else if (aad(sa)->fs.target) { audit_log_format(ab, " target="); audit_log_untrustedstring(ab, aad(sa)->fs.target); } @@ -85,11 +89,11 @@ static void file_audit_cb(struct audit_buffer *ab, void *va) * aa_audit_file - handle the auditing of file operations * @profile: the profile being enforced (NOT NULL) * @perms: the permissions computed for the request (NOT NULL) - * @gfp: allocation flags * @op: operation being mediated * @request: permissions requested * @name: name of object being mediated (MAYBE NULL) * @target: name of target (MAYBE NULL) + * @tlabel: target label (MAY BE NULL) * @ouid: object uid * @info: extra information message (MAYBE NULL) * @error: 0 if operation allowed else failure error code @@ -98,7 +102,8 @@ static void file_audit_cb(struct audit_buffer *ab, void *va) */ int aa_audit_file(struct aa_profile *profile, struct aa_perms *perms, const char *op, u32 request, const char *name, - const char *target, kuid_t ouid, const char *info, int error) + const char *target, struct aa_label *tlabel, + kuid_t ouid, const char *info, int error) { int type = AUDIT_APPARMOR_AUTO; DEFINE_AUDIT_DATA(sa, LSM_AUDIT_DATA_TASK, op); @@ -107,6 +112,7 @@ int aa_audit_file(struct aa_profile *profile, struct aa_perms *perms, aad(&sa)->request = request; aad(&sa)->name = name; aad(&sa)->fs.target = target; + aad(&sa)->peer = tlabel; aad(&sa)->fs.ouid = ouid; aad(&sa)->info = info; aad(&sa)->error = error; @@ -139,7 +145,7 @@ int aa_audit_file(struct aa_profile *profile, struct aa_perms *perms, aad(&sa)->request &= ~perms->quiet; if (!aad(&sa)->request) - return COMPLAIN_MODE(profile) ? 0 : aad(&sa)->error; + return aad(&sa)->error; } aad(&sa)->denied = aad(&sa)->request & ~perms->allow; @@ -295,7 +301,7 @@ int aa_path_perm(const char *op, struct aa_profile *profile, if (request & ~perms.allow) error = -EACCES; } - error = aa_audit_file(profile, &perms, op, request, name, NULL, + error = aa_audit_file(profile, &perms, op, request, name, NULL, NULL, cond->uid, info, error); put_buffers(buffer); @@ -425,7 +431,7 @@ done_tests: audit: error = aa_audit_file(profile, &lperms, OP_LINK, request, - lname, tname, cond.uid, info, error); + lname, tname, NULL, cond.uid, info, error); put_buffers(buffer, buffer2); return error; diff --git a/security/apparmor/include/file.h b/security/apparmor/include/file.h index 415512771bff..7c6026460272 100644 --- a/security/apparmor/include/file.h +++ b/security/apparmor/include/file.h @@ -162,7 +162,8 @@ static inline u16 dfa_map_xindex(u16 mask) int aa_audit_file(struct aa_profile *profile, struct aa_perms *perms, const char *op, u32 request, const char *name, - const char *target, kuid_t ouid, const char *info, int error); + const char *target, struct aa_label *tlabel, kuid_t ouid, + const char *info, int error); /** * struct aa_file_rules - components used for file rule permissions -- cgit v1.2.3 From aebd873e8d3e34757c9295eef074d1be229f5893 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Fri, 9 Jun 2017 16:02:25 -0700 Subject: apparmor: refactor path name lookup and permission checks around labels Signed-off-by: John Johansen --- security/apparmor/file.c | 112 +++++++++++++++++++++++++++------------ security/apparmor/include/file.h | 5 +- security/apparmor/lsm.c | 13 ++--- 3 files changed, 85 insertions(+), 45 deletions(-) diff --git a/security/apparmor/file.c b/security/apparmor/file.c index a40bc1e276dc..1b216f889131 100644 --- a/security/apparmor/file.c +++ b/security/apparmor/file.c @@ -152,6 +152,39 @@ int aa_audit_file(struct aa_profile *profile, struct aa_perms *perms, return aa_audit(type, profile, &sa, file_audit_cb); } +/** + * is_deleted - test if a file has been completely unlinked + * @dentry: dentry of file to test for deletion (NOT NULL) + * + * Returns: %1 if deleted else %0 + */ +static inline bool is_deleted(struct dentry *dentry) +{ + if (d_unlinked(dentry) && d_backing_inode(dentry)->i_nlink == 0) + return 1; + return 0; +} + +static int path_name(const char *op, struct aa_label *label, + const struct path *path, int flags, char *buffer, + const char **name, struct path_cond *cond, u32 request) +{ + struct aa_profile *profile; + const char *info = NULL; + int error; + + error = aa_path_name(path, flags, buffer, name, &info, + labels_profile(label)->disconnected); + if (error) { + fn_for_each_confined(label, profile, + aa_audit_file(profile, &nullperms, op, request, *name, + NULL, NULL, cond->uid, info, error)); + return error; + } + + return 0; +} + /** * map_old_perms - map old file perms layout to the new layout * @old: permission set in old mapping @@ -249,23 +282,46 @@ unsigned int aa_str_perms(struct aa_dfa *dfa, unsigned int start, return state; } -/** - * is_deleted - test if a file has been completely unlinked - * @dentry: dentry of file to test for deletion (NOT NULL) - * - * Returns: %1 if deleted else %0 - */ -static inline bool is_deleted(struct dentry *dentry) +int __aa_path_perm(const char *op, struct aa_profile *profile, const char *name, + u32 request, struct path_cond *cond, int flags, + struct aa_perms *perms) { - if (d_unlinked(dentry) && d_backing_inode(dentry)->i_nlink == 0) - return 1; - return 0; + int e = 0; + + if (profile_unconfined(profile)) + return 0; + aa_str_perms(profile->file.dfa, profile->file.start, name, cond, perms); + if (request & ~perms->allow) + e = -EACCES; + return aa_audit_file(profile, perms, op, request, name, NULL, NULL, + cond->uid, NULL, e); +} + + +static int profile_path_perm(const char *op, struct aa_profile *profile, + const struct path *path, char *buffer, u32 request, + struct path_cond *cond, int flags, + struct aa_perms *perms) +{ + const char *name; + int error; + + if (profile_unconfined(profile)) + return 0; + + error = path_name(op, &profile->label, path, + flags | profile->path_flags, buffer, &name, cond, + request); + if (error) + return error; + return __aa_path_perm(op, profile, name, request, cond, flags, + perms); } /** * aa_path_perm - do permissions check & audit for @path * @op: operation being checked - * @profile: profile being enforced (NOT NULL) + * @label: profile being enforced (NOT NULL) * @path: path to check permissions of (NOT NULL) * @flags: any additional path flags beyond what the profile specifies * @request: requested permissions @@ -273,36 +329,22 @@ static inline bool is_deleted(struct dentry *dentry) * * Returns: %0 else error if access denied or other error */ -int aa_path_perm(const char *op, struct aa_profile *profile, +int aa_path_perm(const char *op, struct aa_label *label, const struct path *path, int flags, u32 request, struct path_cond *cond) { - char *buffer = NULL; struct aa_perms perms = {}; - const char *name, *info = NULL; + struct aa_profile *profile; + char *buffer = NULL; int error; - flags |= profile->path_flags | (S_ISDIR(cond->mode) ? PATH_IS_DIR : 0); + flags |= PATH_DELEGATE_DELETED | (S_ISDIR(cond->mode) ? PATH_IS_DIR : + 0); get_buffers(buffer); - error = aa_path_name(path, flags, buffer, &name, &info, - profile->disconnected); - if (error) { - if (error == -ENOENT && is_deleted(path->dentry)) { - /* Access to open files that are deleted are - * give a pass (implicit delegation) - */ - error = 0; - info = NULL; - perms.allow = request; - } - } else { - aa_str_perms(profile->file.dfa, profile->file.start, name, cond, - &perms); - if (request & ~perms.allow) - error = -EACCES; - } - error = aa_audit_file(profile, &perms, op, request, name, NULL, NULL, - cond->uid, info, error); + error = fn_for_each_confined(label, profile, + profile_path_perm(op, profile, path, buffer, request, + cond, flags, &perms)); + put_buffers(buffer); return error; @@ -482,7 +524,7 @@ int aa_file_perm(const char *op, struct aa_label *label, struct file *file, /* TODO: label cross check */ if (file->f_path.mnt && path_mediated_fs(file->f_path.dentry)) - error = aa_path_perm(op, labels_profile(label), &file->f_path, + error = aa_path_perm(op, label, &file->f_path, PATH_DELEGATE_DELETED, request, &cond); done: diff --git a/security/apparmor/include/file.h b/security/apparmor/include/file.h index 7c6026460272..8daad14c47fd 100644 --- a/security/apparmor/include/file.h +++ b/security/apparmor/include/file.h @@ -190,7 +190,10 @@ unsigned int aa_str_perms(struct aa_dfa *dfa, unsigned int start, const char *name, struct path_cond *cond, struct aa_perms *perms); -int aa_path_perm(const char *op, struct aa_profile *profile, +int __aa_path_perm(const char *op, struct aa_profile *profile, + const char *name, u32 request, struct path_cond *cond, + int flags, struct aa_perms *perms); +int aa_path_perm(const char *op, struct aa_label *label, const struct path *path, int flags, u32 request, struct path_cond *cond); diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c index 011fbb009663..d0c5721aa8b3 100644 --- a/security/apparmor/lsm.c +++ b/security/apparmor/lsm.c @@ -196,8 +196,7 @@ static int common_perm(const char *op, const struct path *path, u32 mask, label = __begin_current_label_crit_section(); if (!unconfined(label)) - error = aa_path_perm(op, labels_profile(label), path, 0, mask, - cond); + error = aa_path_perm(op, label, path, 0, mask, cond); __end_current_label_crit_section(label); return error; @@ -359,15 +358,12 @@ static int apparmor_path_rename(const struct path *old_dir, struct dentry *old_d d_backing_inode(old_dentry)->i_mode }; - error = aa_path_perm(OP_RENAME_SRC, labels_profile(label), - &old_path, 0, + error = aa_path_perm(OP_RENAME_SRC, label, &old_path, 0, MAY_READ | AA_MAY_GETATTR | MAY_WRITE | AA_MAY_SETATTR | AA_MAY_DELETE, &cond); if (!error) - error = aa_path_perm(OP_RENAME_DEST, - labels_profile(label), - &new_path, + error = aa_path_perm(OP_RENAME_DEST, label, &new_path, 0, MAY_WRITE | AA_MAY_SETATTR | AA_MAY_CREATE, &cond); @@ -416,8 +412,7 @@ static int apparmor_file_open(struct file *file, const struct cred *cred) struct inode *inode = file_inode(file); struct path_cond cond = { inode->i_uid, inode->i_mode }; - error = aa_path_perm(OP_OPEN, labels_profile(label), - &file->f_path, 0, + error = aa_path_perm(OP_OPEN, label, &file->f_path, 0, aa_map_file_to_perms(file), &cond); /* todo cache full allowed permissions set and state */ fctx->allow = aa_map_file_to_perms(file); -- cgit v1.2.3 From 8014370f1257619226b79cb6de8e28563fbbc070 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Fri, 9 Jun 2017 16:06:21 -0700 Subject: apparmor: move path_link mediation to using labels Signed-off-by: John Johansen --- security/apparmor/file.c | 101 ++++++++++++++++++++++----------------- security/apparmor/include/file.h | 2 +- security/apparmor/lsm.c | 3 +- 3 files changed, 59 insertions(+), 47 deletions(-) diff --git a/security/apparmor/file.c b/security/apparmor/file.c index 1b216f889131..eb4c3006af67 100644 --- a/security/apparmor/file.c +++ b/security/apparmor/file.c @@ -370,66 +370,40 @@ static inline bool xindex_is_subset(u32 link, u32 target) return 1; } -/** - * aa_path_link - Handle hard link permission check - * @profile: the profile being enforced (NOT NULL) - * @old_dentry: the target dentry (NOT NULL) - * @new_dir: directory the new link will be created in (NOT NULL) - * @new_dentry: the link being created (NOT NULL) - * - * Handle the permission test for a link & target pair. Permission - * is encoded as a pair where the link permission is determined - * first, and if allowed, the target is tested. The target test - * is done from the point of the link match (not start of DFA) - * making the target permission dependent on the link permission match. - * - * The subset test if required forces that permissions granted - * on link are a subset of the permission granted to target. - * - * Returns: %0 if allowed else error - */ -int aa_path_link(struct aa_profile *profile, struct dentry *old_dentry, - const struct path *new_dir, struct dentry *new_dentry) +static int profile_path_link(struct aa_profile *profile, + const struct path *link, char *buffer, + const struct path *target, char *buffer2, + struct path_cond *cond) { - struct path link = { .mnt = new_dir->mnt, .dentry = new_dentry }; - struct path target = { .mnt = new_dir->mnt, .dentry = old_dentry }; - struct path_cond cond = { - d_backing_inode(old_dentry)->i_uid, - d_backing_inode(old_dentry)->i_mode - }; - char *buffer = NULL, *buffer2 = NULL; - const char *lname, *tname = NULL, *info = NULL; - struct aa_perms lperms, perms; + const char *lname, *tname = NULL; + struct aa_perms lperms = {}, perms; + const char *info = NULL; u32 request = AA_MAY_LINK; unsigned int state; int error; - get_buffers(buffer, buffer2); - lperms = nullperms; - - /* buffer freed below, lname is pointer in buffer */ - error = aa_path_name(&link, profile->path_flags, buffer, &lname, - &info, profile->disconnected); + error = path_name(OP_LINK, &profile->label, link, profile->path_flags, + buffer, &lname, cond, AA_MAY_LINK); if (error) goto audit; /* buffer2 freed below, tname is pointer in buffer2 */ - error = aa_path_name(&target, profile->path_flags, buffer2, &tname, - &info, profile->disconnected); + error = path_name(OP_LINK, &profile->label, target, profile->path_flags, + buffer2, &tname, cond, AA_MAY_LINK); if (error) goto audit; error = -EACCES; /* aa_str_perms - handles the case of the dfa being NULL */ state = aa_str_perms(profile->file.dfa, profile->file.start, lname, - &cond, &lperms); + cond, &lperms); if (!(lperms.allow & AA_MAY_LINK)) goto audit; /* test to see if target can be paired with link */ state = aa_dfa_null_transition(profile->file.dfa, state); - aa_str_perms(profile->file.dfa, state, tname, &cond, &perms); + aa_str_perms(profile->file.dfa, state, tname, cond, &perms); /* force audit/quiet masks for link are stored in the second entry * in the link pair. @@ -440,6 +414,7 @@ int aa_path_link(struct aa_profile *profile, struct dentry *old_dentry, if (!(perms.allow & AA_MAY_LINK)) { info = "target restricted"; + lperms = perms; goto audit; } @@ -447,10 +422,10 @@ int aa_path_link(struct aa_profile *profile, struct dentry *old_dentry, if (!(perms.allow & AA_LINK_SUBSET)) goto done_tests; - /* Do link perm subset test requiring allowed permission on link are a - * subset of the allowed permissions on target. + /* Do link perm subset test requiring allowed permission on link are + * a subset of the allowed permissions on target. */ - aa_str_perms(profile->file.dfa, profile->file.start, tname, &cond, + aa_str_perms(profile->file.dfa, profile->file.start, tname, cond, &perms); /* AA_MAY_LINK is not considered in the subset test */ @@ -472,8 +447,46 @@ done_tests: error = 0; audit: - error = aa_audit_file(profile, &lperms, OP_LINK, request, - lname, tname, NULL, cond.uid, info, error); + return aa_audit_file(profile, &lperms, OP_LINK, request, lname, tname, + NULL, cond->uid, info, error); +} + +/** + * aa_path_link - Handle hard link permission check + * @label: the label being enforced (NOT NULL) + * @old_dentry: the target dentry (NOT NULL) + * @new_dir: directory the new link will be created in (NOT NULL) + * @new_dentry: the link being created (NOT NULL) + * + * Handle the permission test for a link & target pair. Permission + * is encoded as a pair where the link permission is determined + * first, and if allowed, the target is tested. The target test + * is done from the point of the link match (not start of DFA) + * making the target permission dependent on the link permission match. + * + * The subset test if required forces that permissions granted + * on link are a subset of the permission granted to target. + * + * Returns: %0 if allowed else error + */ +int aa_path_link(struct aa_label *label, struct dentry *old_dentry, + const struct path *new_dir, struct dentry *new_dentry) +{ + struct path link = { new_dir->mnt, new_dentry }; + struct path target = { new_dir->mnt, old_dentry }; + struct path_cond cond = { + d_backing_inode(old_dentry)->i_uid, + d_backing_inode(old_dentry)->i_mode + }; + char *buffer = NULL, *buffer2 = NULL; + struct aa_profile *profile; + int error; + + /* buffer freed below, lname is pointer in buffer */ + get_buffers(buffer, buffer2); + error = fn_for_each_confined(label, profile, + profile_path_link(profile, &link, buffer, &target, + buffer2, &cond)); put_buffers(buffer, buffer2); return error; diff --git a/security/apparmor/include/file.h b/security/apparmor/include/file.h index 8daad14c47fd..001e40073ff9 100644 --- a/security/apparmor/include/file.h +++ b/security/apparmor/include/file.h @@ -197,7 +197,7 @@ int aa_path_perm(const char *op, struct aa_label *label, const struct path *path, int flags, u32 request, struct path_cond *cond); -int aa_path_link(struct aa_profile *profile, struct dentry *old_dentry, +int aa_path_link(struct aa_label *label, struct dentry *old_dentry, const struct path *new_dir, struct dentry *new_dentry); int aa_file_perm(const char *op, struct aa_label *label, struct file *file, diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c index d0c5721aa8b3..7a986763b2b7 100644 --- a/security/apparmor/lsm.c +++ b/security/apparmor/lsm.c @@ -332,8 +332,7 @@ static int apparmor_path_link(struct dentry *old_dentry, const struct path *new_ label = begin_current_label_crit_section(); if (!unconfined(label)) - error = aa_path_link(labels_profile(label), old_dentry, new_dir, - new_dentry); + error = aa_path_link(label, old_dentry, new_dir, new_dentry); end_current_label_crit_section(label); return error; -- cgit v1.2.3 From 496c93196654d3e604013d750b7047886af14506 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Fri, 9 Jun 2017 16:19:02 -0700 Subject: apparmor: rework file permission to cache file access in file->ctx This is a temporary step, towards using the file->ctx for delegation, and also helps speed up file queries, until the permission lookup cache is introduced. Signed-off-by: John Johansen --- security/apparmor/file.c | 82 ++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 76 insertions(+), 6 deletions(-) diff --git a/security/apparmor/file.c b/security/apparmor/file.c index eb4c3006af67..b6e8e5b11e05 100644 --- a/security/apparmor/file.c +++ b/security/apparmor/file.c @@ -492,6 +492,80 @@ int aa_path_link(struct aa_label *label, struct dentry *old_dentry, return error; } +static void update_file_ctx(struct aa_file_ctx *fctx, struct aa_label *label, + u32 request) +{ + struct aa_label *l, *old; + + /* update caching of label on file_ctx */ + spin_lock(&fctx->lock); + old = rcu_dereference_protected(fctx->label, + spin_is_locked(&fctx->lock)); + l = aa_label_merge(old, label, GFP_ATOMIC); + if (l) { + if (l != old) { + rcu_assign_pointer(fctx->label, l); + aa_put_label(old); + } else + aa_put_label(l); + fctx->allow |= request; + } + spin_unlock(&fctx->lock); +} + +static int __file_path_perm(const char *op, struct aa_label *label, + struct aa_label *flabel, struct file *file, + u32 request, u32 denied) +{ + struct aa_profile *profile; + struct aa_perms perms = {}; + struct path_cond cond = { + .uid = file_inode(file)->i_uid, + .mode = file_inode(file)->i_mode + }; + char *buffer; + int flags, error; + + /* revalidation due to label out of date. No revocation at this time */ + if (!denied && aa_label_is_subset(flabel, label)) + /* TODO: check for revocation on stale profiles */ + return 0; + + flags = PATH_DELEGATE_DELETED | (S_ISDIR(cond.mode) ? PATH_IS_DIR : 0); + get_buffers(buffer); + + /* check every profile in task label not in current cache */ + error = fn_for_each_not_in_set(flabel, label, profile, + profile_path_perm(op, profile, &file->f_path, buffer, + request, &cond, flags, &perms)); + if (denied && !error) { + /* + * check every profile in file label that was not tested + * in the initial check above. + * + * TODO: cache full perms so this only happens because of + * conditionals + * TODO: don't audit here + */ + if (label == flabel) + error = fn_for_each(label, profile, + profile_path_perm(op, profile, &file->f_path, + buffer, request, &cond, flags, + &perms)); + else + error = fn_for_each_not_in_set(label, flabel, profile, + profile_path_perm(op, profile, &file->f_path, + buffer, request, &cond, flags, + &perms)); + } + if (!error) + update_file_ctx(file_ctx(file), label, request); + + put_buffers(buffer); + + return error; +} + /** * aa_file_perm - do permission revalidation check & audit for @file * @op: operation being checked @@ -504,10 +578,6 @@ int aa_path_link(struct aa_label *label, struct dentry *old_dentry, int aa_file_perm(const char *op, struct aa_label *label, struct file *file, u32 request) { - struct path_cond cond = { - .uid = file_inode(file)->i_uid, - .mode = file_inode(file)->i_mode - }; struct aa_file_ctx *fctx; struct aa_label *flabel; u32 denied; @@ -537,8 +607,8 @@ int aa_file_perm(const char *op, struct aa_label *label, struct file *file, /* TODO: label cross check */ if (file->f_path.mnt && path_mediated_fs(file->f_path.dentry)) - error = aa_path_perm(op, label, &file->f_path, - PATH_DELEGATE_DELETED, request, &cond); + error = __file_path_perm(op, label, flabel, file, request, + denied); done: rcu_read_unlock(); -- cgit v1.2.3 From 064dc9472fa2bc31a7b178882bd7eff782c3d239 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Fri, 9 Jun 2017 17:15:56 -0700 Subject: apparmor: mediate files when they are received Signed-off-by: John Johansen --- security/apparmor/include/audit.h | 1 + security/apparmor/lsm.c | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/security/apparmor/include/audit.h b/security/apparmor/include/audit.h index 20fa6c77db05..99ed83cf6941 100644 --- a/security/apparmor/include/audit.h +++ b/security/apparmor/include/audit.h @@ -64,6 +64,7 @@ enum audit_type { #define OP_GETATTR "getattr" #define OP_OPEN "open" +#define OP_FRECEIVE "file_receive" #define OP_FPERM "file_perm" #define OP_FLOCK "file_lock" #define OP_FMMAP "file_mmap" diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c index 7a986763b2b7..0f7c5c2be732 100644 --- a/security/apparmor/lsm.c +++ b/security/apparmor/lsm.c @@ -456,6 +456,11 @@ static int common_file_perm(const char *op, struct file *file, u32 mask) return error; } +static int apparmor_file_receive(struct file *file) +{ + 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); @@ -665,6 +670,7 @@ static struct security_hook_list apparmor_hooks[] __lsm_ro_after_init = { LSM_HOOK_INIT(inode_getattr, apparmor_inode_getattr), LSM_HOOK_INIT(file_open, apparmor_file_open), + LSM_HOOK_INIT(file_receive, apparmor_file_receive), LSM_HOOK_INIT(file_permission, apparmor_file_permission), LSM_HOOK_INIT(file_alloc_security, apparmor_file_alloc_security), LSM_HOOK_INIT(file_free_security, apparmor_file_free_security), -- cgit v1.2.3 From 5379a3312024a8befe7728238fc50ed05d2938ac Mon Sep 17 00:00:00 2001 From: John Johansen Date: Fri, 9 Jun 2017 17:29:12 -0700 Subject: apparmor: support v7 transition format compatible with label_parse Signed-off-by: John Johansen --- security/apparmor/apparmorfs.c | 2 ++ security/apparmor/policy_unpack.c | 20 +++++++++++++------- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c index d1a6ce499776..e460f2d8337d 100644 --- a/security/apparmor/apparmorfs.c +++ b/security/apparmor/apparmorfs.c @@ -2103,6 +2103,8 @@ static struct aa_sfs_entry aa_sfs_entry_domain[] = { static struct aa_sfs_entry aa_sfs_entry_versions[] = { AA_SFS_FILE_BOOLEAN("v5", 1), + AA_SFS_FILE_BOOLEAN("v6", 1), + AA_SFS_FILE_BOOLEAN("v7", 1), { } }; diff --git a/security/apparmor/policy_unpack.c b/security/apparmor/policy_unpack.c index f42bb9575cb5..6e6f8c1a10a9 100644 --- a/security/apparmor/policy_unpack.c +++ b/security/apparmor/policy_unpack.c @@ -466,7 +466,7 @@ static bool unpack_trans_table(struct aa_ext *e, struct aa_profile *profile) profile->file.trans.size = size; for (i = 0; i < size; i++) { char *str; - int c, j, size2 = unpack_strdup(e, &str, NULL); + int c, j, pos, size2 = unpack_strdup(e, &str, NULL); /* unpack_strdup verifies that the last character is * null termination byte. */ @@ -478,19 +478,25 @@ static bool unpack_trans_table(struct aa_ext *e, struct aa_profile *profile) goto fail; /* count internal # of internal \0 */ - for (c = j = 0; j < size2 - 2; j++) { - if (!str[j]) + 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 unpack_strdup + * + * convert \0 back to : for label_parse */ - if (c != 1) - goto fail; - /* first character after : must be valid */ - if (!str[1]) + if (c == 1) + str[pos] = ':'; + else if (c > 1) goto fail; } else if (c) /* fail - all other cases with embedded \0 */ -- cgit v1.2.3 From 93c98a484c4900f0d2c65c16466d45f90ea5b175 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Fri, 9 Jun 2017 16:55:04 -0700 Subject: apparmor: move exec domain mediation to using labels Signed-off-by: John Johansen --- security/apparmor/domain.c | 850 ++++++++++++++++++++++++++++------------ security/apparmor/include/lib.h | 87 +++- 2 files changed, 678 insertions(+), 259 deletions(-) diff --git a/security/apparmor/domain.c b/security/apparmor/domain.c index 896bca01828e..cb8509373ea3 100644 --- a/security/apparmor/domain.c +++ b/security/apparmor/domain.c @@ -87,42 +87,236 @@ out: return error; } +/**** TODO: dedup to aa_label_match - needs perm and dfa, merging + * specifically this is an exact copy of aa_label_match except + * aa_compute_perms is replaced with aa_compute_fperms + * and policy.dfa with file.dfa + ****/ +/* match a profile and its associated ns component if needed + * Assumes visibility test has already been done. + * If a subns profile is not to be matched should be prescreened with + * visibility test. + */ +static inline unsigned int match_component(struct aa_profile *profile, + struct aa_profile *tp, + bool stack, unsigned int state) +{ + const char *ns_name; + + if (stack) + state = aa_dfa_match(profile->file.dfa, state, "&"); + if (profile->ns == tp->ns) + return aa_dfa_match(profile->file.dfa, state, tp->base.hname); + + /* try matching with namespace name and then profile */ + ns_name = aa_ns_name(profile->ns, tp->ns, true); + state = aa_dfa_match_len(profile->file.dfa, state, ":", 1); + state = aa_dfa_match(profile->file.dfa, state, ns_name); + state = aa_dfa_match_len(profile->file.dfa, state, ":", 1); + return aa_dfa_match(profile->file.dfa, state, tp->base.hname); +} + +/** + * label_compound_match - find perms for full compound label + * @profile: profile to find perms for + * @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 + * @request: permissions to request + * @perms: perms struct to set + * + * Returns: 0 on success else ERROR + * + * 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 + * check to be stacked. + */ +static int label_compound_match(struct aa_profile *profile, + struct aa_label *label, bool stack, + unsigned int state, bool subns, u32 request, + struct aa_perms *perms) +{ + struct aa_profile *tp; + struct label_it i; + struct path_cond cond = { }; + + /* find first subcomponent that is visible */ + label_for_each(i, label, tp) { + if (!aa_ns_visible(profile->ns, tp->ns, subns)) + continue; + state = match_component(profile, tp, stack, state); + if (!state) + goto fail; + goto next; + } + + /* no component visible */ + *perms = allperms; + return 0; + +next: + label_for_each_cont(i, label, tp) { + if (!aa_ns_visible(profile->ns, tp->ns, subns)) + continue; + state = aa_dfa_match(profile->file.dfa, state, "//&"); + state = match_component(profile, tp, false, state); + if (!state) + goto fail; + } + *perms = aa_compute_fperms(profile->file.dfa, state, &cond); + aa_apply_modes_to_perms(profile, perms); + if ((perms->allow & request) != request) + return -EACCES; + + return 0; + +fail: + *perms = nullperms; + return -EACCES; +} + +/** + * label_components_match - find perms for all subcomponents of a label + * @profile: profile to find perms for + * @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 + * @request: permissions to request + * @perms: an initialized perms struct to add accumulation to + * + * Returns: 0 on success else ERROR + * + * 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 + * check to be stacked. + */ +static int label_components_match(struct aa_profile *profile, + struct aa_label *label, bool stack, + unsigned int start, bool subns, u32 request, + struct aa_perms *perms) +{ + struct aa_profile *tp; + struct label_it i; + struct aa_perms tmp; + struct path_cond cond = { }; + unsigned int state = 0; + + /* find first subcomponent to test */ + label_for_each(i, label, tp) { + if (!aa_ns_visible(profile->ns, tp->ns, subns)) + continue; + state = match_component(profile, tp, stack, start); + if (!state) + goto fail; + goto next; + } + + /* no subcomponents visible - no change in perms */ + return 0; + +next: + tmp = aa_compute_fperms(profile->file.dfa, state, &cond); + aa_apply_modes_to_perms(profile, &tmp); + aa_perms_accum(perms, &tmp); + label_for_each_cont(i, label, tp) { + if (!aa_ns_visible(profile->ns, tp->ns, subns)) + continue; + state = match_component(profile, tp, stack, start); + if (!state) + goto fail; + tmp = aa_compute_fperms(profile->file.dfa, state, &cond); + aa_apply_modes_to_perms(profile, &tmp); + aa_perms_accum(perms, &tmp); + } + + if ((perms->allow & request) != request) + return -EACCES; + + return 0; + +fail: + *perms = nullperms; + return -EACCES; +} + +/** + * label_match - do a multi-component label match + * @profile: profile to match against (NOT NULL) + * @label: label to match (NOT NULL) + * @stack: whether this is a stacking request + * @state: state to start in + * @subns: whether to match subns components + * @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, unsigned int state, bool subns, u32 request, + struct aa_perms *perms) +{ + int error; + + *perms = nullperms; + error = label_compound_match(profile, label, stack, state, subns, + request, perms); + if (!error) + return error; + + *perms = allperms; + return label_components_match(profile, label, stack, state, subns, + request, perms); +} + +/******* end TODO: dedup *****/ + /** * change_profile_perms - find permissions for change_profile * @profile: the current profile (NOT NULL) - * @ns: the namespace being switched to (NOT NULL) - * @name: the name of the profile to change to (NOT NULL) + * @target: label to transition to (NOT NULL) + * @stack: whether this is a stacking request * @request: requested perms * @start: state to start matching in * + * * Returns: permission set + * + * currently only matches full label A//&B//&C or individual components A, B, C + * not arbitrary combinations. Eg. A//&B, C */ -static struct aa_perms change_profile_perms(struct aa_profile *profile, - struct aa_ns *ns, - const char *name, u32 request, - unsigned int start) +static int change_profile_perms(struct aa_profile *profile, + struct aa_label *target, bool stack, + u32 request, unsigned int start, + struct aa_perms *perms) +{ + if (profile_unconfined(profile)) { + perms->allow = AA_MAY_CHANGE_PROFILE | AA_MAY_ONEXEC; + perms->audit = perms->quiet = perms->kill = 0; + return 0; + } + + /* TODO: add profile in ns screening */ + return label_match(profile, target, stack, start, true, request, perms); +} + +static struct aa_perms change_profile_perms_wrapper(struct aa_profile *profile, + struct aa_profile *target, + u32 request, + unsigned int start) { struct aa_perms perms; - struct path_cond cond = { }; - unsigned int state; if (profile_unconfined(profile)) { perms.allow = AA_MAY_CHANGE_PROFILE | AA_MAY_ONEXEC; perms.audit = perms.quiet = perms.kill = 0; return perms; - } else if (!profile->file.dfa) { - return nullperms; - } else if ((ns == profile->ns)) { - /* try matching against rules with out namespace prepended */ - aa_str_perms(profile->file.dfa, start, name, &cond, &perms); - if (COMBINED_PERM_MASK(perms) & request) - return perms; } - /* try matching with namespace name and then profile */ - state = aa_dfa_match(profile->file.dfa, start, ns->base.name); - state = aa_dfa_match_len(profile->file.dfa, state, ":", 1); - aa_str_perms(profile->file.dfa, state, name, &cond, &perms); + if (change_profile_perms(profile, &target->label, false, request, + start, &perms)) + return nullperms; return perms; } @@ -173,10 +367,10 @@ static struct aa_profile *__attach_match(const char *name, * @list: list to search (NOT NULL) * @name: the executable name to match against (NOT NULL) * - * Returns: profile or NULL if no match found + * Returns: label or NULL if no match found */ -static struct aa_profile *find_attach(struct aa_ns *ns, - struct list_head *list, const char *name) +static struct aa_label *find_attach(struct aa_ns *ns, struct list_head *list, + const char *name) { struct aa_profile *profile; @@ -184,49 +378,7 @@ static struct aa_profile *find_attach(struct aa_ns *ns, profile = aa_get_profile(__attach_match(name, list)); rcu_read_unlock(); - return profile; -} - -/** - * separate_fqname - separate the namespace and profile names - * @fqname: the fqname name to split (NOT NULL) - * @ns_name: the namespace name if it exists (NOT NULL) - * - * This is the xtable equivalent routine of aa_split_fqname. It finds the - * split in an xtable fqname which contains an embedded \0 instead of a : - * if a namespace is specified. This is done so the xtable is constant and - * isn't re-split on every lookup. - * - * Either the profile or namespace name may be optional but if the namespace - * is specified the profile name termination must be present. This results - * in the following possible encodings: - * profile_name\0 - * :ns_name\0profile_name\0 - * :ns_name\0\0 - * - * NOTE: the xtable fqname is pre-validated at load time in unpack_trans_table - * - * Returns: profile name if it is specified else NULL - */ -static const char *separate_fqname(const char *fqname, const char **ns_name) -{ - const char *name; - - if (fqname[0] == ':') { - /* In this case there is guaranteed to be two \0 terminators - * in the string. They are verified at load time by - * by unpack_trans_table - */ - *ns_name = fqname + 1; /* skip : */ - name = *ns_name + strlen(*ns_name) + 1; - if (!*name) - name = NULL; - } else { - *ns_name = NULL; - name = fqname; - } - - return name; + return profile ? &profile->label : NULL; } static const char *next_name(int xtype, const char *name) @@ -238,295 +390,477 @@ static const char *next_name(int xtype, const char *name) * x_table_lookup - lookup an x transition name via transition table * @profile: current profile (NOT NULL) * @xindex: index into x transition table + * @name: returns: name tested to find label (NOT NULL) * - * Returns: refcounted profile, or NULL on failure (MAYBE NULL) + * Returns: refcounted label, or NULL on failure (MAYBE NULL) */ -static struct aa_profile *x_table_lookup(struct aa_profile *profile, u32 xindex) +static struct aa_label *x_table_lookup(struct aa_profile *profile, u32 xindex, + const char **name) { - struct aa_profile *new_profile = NULL; - struct aa_ns *ns = profile->ns; + struct aa_label *label = NULL; u32 xtype = xindex & AA_X_TYPE_MASK; int index = xindex & AA_X_INDEX_MASK; - const char *name; - /* index is guaranteed to be in range, validated at load time */ - for (name = profile->file.trans.table[index]; !new_profile && name; - name = next_name(xtype, name)) { - struct aa_ns *new_ns; - const char *xname = NULL; + AA_BUG(!name); - new_ns = NULL; + /* index is guaranteed to be in range, validated at load time */ + /* TODO: move lookup parsing to unpack time so this is a straight + * index into the resultant label + */ + for (*name = profile->file.trans.table[index]; !label && *name; + *name = next_name(xtype, *name)) { if (xindex & AA_X_CHILD) { + struct aa_profile *new_profile; /* release by caller */ - new_profile = aa_find_child(profile, name); + new_profile = aa_find_child(profile, *name); + if (new_profile) + label = &new_profile->label; continue; - } else if (*name == ':') { - /* switching namespace */ - const char *ns_name; - xname = name = separate_fqname(name, &ns_name); - if (!xname) - /* no name so use profile name */ - xname = profile->base.hname; - if (*ns_name == '@') { - /* TODO: variable support */ - ; - } - /* released below */ - new_ns = aa_find_ns(ns, ns_name); - if (!new_ns) - continue; - } else if (*name == '@') { - /* TODO: variable support */ - continue; - } else { - /* basic namespace lookup */ - xname = name; } - - /* released by caller */ - new_profile = aa_lookup_profile(new_ns ? new_ns : ns, xname); - aa_put_ns(new_ns); + label = aa_label_parse(&profile->label, *name, GFP_ATOMIC, + true, false); + if (IS_ERR(label)) + label = NULL; } /* released by caller */ - return new_profile; + + return label; } /** - * x_to_profile - get target profile for a given xindex + * x_to_label - get target label for a given xindex * @profile: current profile (NOT NULL) * @name: name to lookup (NOT NULL) * @xindex: index into x transition table + * @lookupname: returns: name used in lookup if one was specified (NOT NULL) * - * find profile for a transition index + * find label for a transition index * - * Returns: refcounted profile or NULL if not found available + * Returns: refcounted label or NULL if not found available */ -static struct aa_profile *x_to_profile(struct aa_profile *profile, - const char *name, u32 xindex) +static struct aa_label *x_to_label(struct aa_profile *profile, + const char *name, u32 xindex, + const char **lookupname, + const char **info) { - struct aa_profile *new_profile = NULL; + struct aa_label *new = NULL; struct aa_ns *ns = profile->ns; u32 xtype = xindex & AA_X_TYPE_MASK; + const char *stack = NULL; switch (xtype) { case AA_X_NONE: /* fail exec unless ix || ux fallback - handled by caller */ - return NULL; + *lookupname = NULL; + break; + case AA_X_TABLE: + /* TODO: fix when perm mapping done at unload */ + stack = profile->file.trans.table[xindex & AA_X_INDEX_MASK]; + if (*stack != '&') { + /* released by caller */ + new = x_table_lookup(profile, xindex, lookupname); + stack = NULL; + break; + } + /* fall through to X_NAME */ case AA_X_NAME: if (xindex & AA_X_CHILD) /* released by caller */ - new_profile = find_attach(ns, &profile->base.profiles, - name); + new = find_attach(ns, &profile->base.profiles, + name); else /* released by caller */ - new_profile = find_attach(ns, &ns->base.profiles, - name); - break; - case AA_X_TABLE: - /* released by caller */ - new_profile = x_table_lookup(profile, xindex); + new = find_attach(ns, &ns->base.profiles, + name); + *lookupname = name; break; } + if (!new) { + if (xindex & AA_X_INHERIT) { + /* (p|c|n)ix - don't change profile but do + * use the newest version + */ + *info = "ix fallback"; + /* no profile && no error */ + new = aa_get_newest_label(&profile->label); + } else if (xindex & AA_X_UNCONFINED) { + new = aa_get_newest_label(ns_unconfined(profile->ns)); + *info = "ux fallback"; + } + } + + if (new && stack) { + /* base the stack on post domain transition */ + struct aa_label *base = new; + + new = aa_label_parse(base, stack, GFP_ATOMIC, true, false); + if (IS_ERR(new)) + new = NULL; + aa_put_label(base); + } + /* released by caller */ - return new_profile; + return new; } -/** - * apparmor_bprm_set_creds - set the new creds on the bprm struct - * @bprm: binprm for the exec (NOT NULL) - * - * Returns: %0 or error on failure - */ -int apparmor_bprm_set_creds(struct linux_binprm *bprm) +static struct aa_label *profile_transition(struct aa_profile *profile, + const struct linux_binprm *bprm, + char *buffer, struct path_cond *cond, + bool *secure_exec) { - struct aa_task_ctx *ctx; - struct aa_label *label; - struct aa_profile *profile, *new_profile = NULL; - struct aa_ns *ns; - char *buffer = NULL; - unsigned int state; + struct aa_label *new = NULL; + const char *info = NULL, *name = NULL, *target = NULL; + unsigned int state = profile->file.start; struct aa_perms perms = {}; - struct path_cond cond = { - file_inode(bprm->file)->i_uid, - file_inode(bprm->file)->i_mode - }; - const char *name = NULL, *info = NULL; + bool nonewprivs = false; int error = 0; - if (bprm->cred_prepared) - return 0; - - ctx = cred_ctx(bprm->cred); - AA_BUG(!ctx); - - label = aa_get_newest_label(ctx->label); - profile = labels_profile(label); - - /* buffer freed below, name is pointer into buffer */ - get_buffers(buffer); - /* - * get the namespace from the replacement profile as replacement - * can change the namespace - */ - ns = profile->ns; - state = profile->file.start; + AA_BUG(!profile); + AA_BUG(!bprm); + AA_BUG(!buffer); error = aa_path_name(&bprm->file->f_path, profile->path_flags, buffer, &name, &info, profile->disconnected); if (error) { if (profile_unconfined(profile) || - (profile->label.flags & FLAG_IX_ON_NAME_ERROR)) + (profile->label.flags & FLAG_IX_ON_NAME_ERROR)) { + AA_DEBUG("name lookup ix on error"); error = 0; + new = aa_get_newest_label(&profile->label); + } name = bprm->filename; goto audit; } - /* Test for onexec first as onexec directives override other - * x transitions. - */ if (profile_unconfined(profile)) { - /* unconfined task */ - if (ctx->onexec) - /* change_profile on exec already been granted */ - new_profile = labels_profile(aa_get_label(ctx->onexec)); - else - new_profile = find_attach(ns, &ns->base.profiles, name); - if (!new_profile) - goto cleanup; - /* - * NOTE: Domain transitions from unconfined are allowed - * even when no_new_privs is set because this aways results - * in a further reduction of permissions. - */ - goto apply; + new = find_attach(profile->ns, &profile->ns->base.profiles, + name); + if (new) { + AA_DEBUG("unconfined attached to new label"); + return new; + } + AA_DEBUG("unconfined exec no attachment"); + return aa_get_newest_label(&profile->label); } /* find exec permissions for name */ - state = aa_str_perms(profile->file.dfa, state, name, &cond, &perms); - if (ctx->onexec) { - struct aa_perms cp; - info = "change_profile onexec"; - new_profile = labels_profile(aa_get_newest_label(ctx->onexec)); - if (!(perms.allow & AA_MAY_ONEXEC)) - goto audit; - - /* test if this exec can be paired with change_profile onexec. - * onexec permission is linked to exec with a standard pairing - * exec\0change_profile - */ - state = aa_dfa_null_transition(profile->file.dfa, state); - cp = change_profile_perms(profile, labels_ns(ctx->onexec), - labels_profile(ctx->onexec)->base.name, - AA_MAY_ONEXEC, state); - - if (!(cp.allow & AA_MAY_ONEXEC)) - goto audit; - goto apply; - } - + state = aa_str_perms(profile->file.dfa, state, name, cond, &perms); if (perms.allow & MAY_EXEC) { /* exec permission determine how to transition */ - new_profile = x_to_profile(profile, name, perms.xindex); - if (!new_profile) { - if (perms.xindex & AA_X_INHERIT) { - /* (p|c|n)ix - don't change profile but do - * use the newest version, which was picked - * up above when getting profile - */ - info = "ix fallback"; - new_profile = aa_get_profile(profile); - goto x_clear; - } else if (perms.xindex & AA_X_UNCONFINED) { - new_profile = aa_get_newest_profile(ns->unconfined); - info = "ux fallback"; - } else { - error = -EACCES; - info = "profile not found"; - /* remove MAY_EXEC to audit as failure */ - perms.allow &= ~MAY_EXEC; - } + new = x_to_label(profile, name, perms.xindex, &target, &info); + if (new && new->proxy == profile->label.proxy && info) { + /* hack ix fallback - improve how this is detected */ + goto audit; + } else if (!new) { + error = -EACCES; + info = "profile transition not found"; + /* remove MAY_EXEC to audit as failure */ + perms.allow &= ~MAY_EXEC; } } else if (COMPLAIN_MODE(profile)) { - /* no exec permission - are we in learning mode */ - new_profile = aa_new_null_profile(profile, false, name, - GFP_ATOMIC); + /* no exec permission - learning mode */ + struct aa_profile *new_profile = aa_new_null_profile(profile, + false, name, + GFP_ATOMIC); if (!new_profile) { error = -ENOMEM; info = "could not create null profile"; - } else + } else { error = -EACCES; + new = &new_profile->label; + } perms.xindex |= AA_X_UNSAFE; } else /* fail exec */ error = -EACCES; - /* - * Policy has specified a domain transition, if no_new_privs then - * fail the exec. + if (!new) + goto audit; + + /* Policy has specified a domain transitions. if no_new_privs and + * confined and not transitioning to the current domain fail. + * + * NOTE: Domain transitions from unconfined and to stritly stacked + * subsets are allowed even when no_new_privs is set because this + * aways results in a further reduction of permissions. */ - if (bprm->unsafe & LSM_UNSAFE_NO_NEW_PRIVS) { + if ((bprm->unsafe & LSM_UNSAFE_NO_NEW_PRIVS) && + !profile_unconfined(profile) && + !aa_label_is_subset(new, &profile->label)) { error = -EPERM; - goto cleanup; + info = "no new privs"; + nonewprivs = true; + perms.allow &= ~MAY_EXEC; + goto audit; + } + + if (!(perms.xindex & AA_X_UNSAFE)) { + if (DEBUG_ON) { + dbg_printk("apparmor: scrubbing environment variables" + " for %s profile=", name); + aa_label_printk(new, GFP_ATOMIC); + dbg_printk("\n"); + } + *secure_exec = true; } - if (!new_profile) +audit: + aa_audit_file(profile, &perms, OP_EXEC, MAY_EXEC, name, target, new, + cond->uid, info, error); + if (!new || nonewprivs) { + aa_put_label(new); + return ERR_PTR(error); + } + + return new; +} + +static int profile_onexec(struct aa_profile *profile, struct aa_label *onexec, + bool stack, const struct linux_binprm *bprm, + char *buffer, struct path_cond *cond, + bool *secure_exec) +{ + unsigned int state = profile->file.start; + struct aa_perms perms = {}; + const char *xname = NULL, *info = "change_profile onexec"; + int error = -EACCES; + + AA_BUG(!profile); + AA_BUG(!onexec); + AA_BUG(!bprm); + AA_BUG(!buffer); + + if (profile_unconfined(profile)) { + /* change_profile on exec already granted */ + /* + * NOTE: Domain transitions from unconfined are allowed + * even when no_new_privs is set because this aways results + * in a further reduction of permissions. + */ + return 0; + } + + error = aa_path_name(&bprm->file->f_path, profile->path_flags, buffer, + &xname, &info, profile->disconnected); + if (error) { + if (profile_unconfined(profile) || + (profile->label.flags & FLAG_IX_ON_NAME_ERROR)) { + AA_DEBUG("name lookup ix on error"); + error = 0; + } + xname = bprm->filename; goto audit; + } + + /* find exec permissions for name */ + state = aa_str_perms(profile->file.dfa, state, xname, cond, &perms); + if (!(perms.allow & AA_MAY_ONEXEC)) { + info = "no change_onexec valid for executable"; + goto audit; + } + /* test if this exec can be paired with change_profile onexec. + * onexec permission is linked to exec with a standard pairing + * exec\0change_profile + */ + state = aa_dfa_null_transition(profile->file.dfa, state); + error = change_profile_perms(profile, onexec, stack, AA_MAY_ONEXEC, + state, &perms); + if (error) { + perms.allow &= ~AA_MAY_ONEXEC; + goto audit; + } + /* Policy has specified a domain transitions. if no_new_privs and + * confined and not transitioning to the current domain fail. + * + * NOTE: Domain transitions from unconfined and to stritly stacked + * subsets are allowed even when no_new_privs is set because this + * aways results in a further reduction of permissions. + */ + if ((bprm->unsafe & LSM_UNSAFE_NO_NEW_PRIVS) && + !profile_unconfined(profile) && + !aa_label_is_subset(onexec, &profile->label)) { + error = -EPERM; + info = "no new privs"; + perms.allow &= ~AA_MAY_ONEXEC; + goto audit; + } + + if (!(perms.xindex & AA_X_UNSAFE)) { + if (DEBUG_ON) { + dbg_printk("apparmor: scrubbing environment " + "variables for %s label=", xname); + aa_label_printk(onexec, GFP_ATOMIC); + dbg_printk("\n"); + } + *secure_exec = true; + } + +audit: + return aa_audit_file(profile, &perms, OP_EXEC, AA_MAY_ONEXEC, xname, + NULL, onexec, cond->uid, info, error); +} + +/* ensure none ns domain transitions are correctly applied with onexec */ + +static struct aa_label *handle_onexec(struct aa_label *label, + struct aa_label *onexec, bool stack, + const struct linux_binprm *bprm, + char *buffer, struct path_cond *cond, + bool *unsafe) +{ + struct aa_profile *profile; + struct aa_label *new; + int error; + + AA_BUG(!label); + AA_BUG(!onexec); + AA_BUG(!bprm); + AA_BUG(!buffer); + + if (!stack) { + error = fn_for_each_in_ns(label, profile, + profile_onexec(profile, onexec, stack, + bprm, buffer, cond, unsafe)); + if (error) + return ERR_PTR(error); + new = fn_label_build_in_ns(label, profile, GFP_ATOMIC, + aa_get_newest_label(onexec), + profile_transition(profile, bprm, buffer, + cond, unsafe)); + + } else { + /* TODO: determine how much we want to losen this */ + error = fn_for_each_in_ns(label, profile, + profile_onexec(profile, onexec, stack, bprm, + buffer, cond, unsafe)); + if (error) + return ERR_PTR(error); + new = fn_label_build_in_ns(label, profile, GFP_ATOMIC, + aa_label_merge(&profile->label, onexec, + GFP_ATOMIC), + profile_transition(profile, bprm, buffer, + cond, unsafe)); + } + + if (new) + return new; + + /* TODO: get rid of GLOBAL_ROOT_UID */ + error = fn_for_each_in_ns(label, profile, + aa_audit_file(profile, &nullperms, OP_CHANGE_ONEXEC, + AA_MAY_ONEXEC, bprm->filename, NULL, + onexec, GLOBAL_ROOT_UID, + "failed to build target label", -ENOMEM)); + return ERR_PTR(error); +} + +/** + * apparmor_bprm_set_creds - set the new creds on the bprm struct + * @bprm: binprm for the exec (NOT NULL) + * + * Returns: %0 or error on failure + * + * TODO: once the other paths are done see if we can't refactor into a fn + */ +int apparmor_bprm_set_creds(struct linux_binprm *bprm) +{ + struct aa_task_ctx *ctx; + struct aa_label *label, *new = NULL; + struct aa_profile *profile; + char *buffer = NULL; + const char *info = NULL; + int error = 0; + bool unsafe = false; + struct path_cond cond = { + file_inode(bprm->file)->i_uid, + file_inode(bprm->file)->i_mode + }; + + if (bprm->cred_prepared) + return 0; + + ctx = cred_ctx(bprm->cred); + AA_BUG(!ctx); + + label = aa_get_newest_label(ctx->label); + + /* buffer freed below, name is pointer into buffer */ + get_buffers(buffer); + /* Test for onexec first as onexec override other x transitions. */ + if (ctx->onexec) + new = handle_onexec(label, ctx->onexec, ctx->token, + bprm, buffer, &cond, &unsafe); + else + new = fn_label_build(label, profile, GFP_ATOMIC, + profile_transition(profile, bprm, buffer, + &cond, &unsafe)); + + AA_BUG(!new); + if (IS_ERR(new)) { + error = PTR_ERR(new); + goto done; + } else if (!new) { + error = -ENOMEM; + goto done; + } + + /* TODO: Add ns level no_new_privs subset test */ if (bprm->unsafe & LSM_UNSAFE_SHARE) { /* FIXME: currently don't mediate shared state */ ; } - if (bprm->unsafe & LSM_UNSAFE_PTRACE) { - error = may_change_ptraced_domain(&new_profile->label, &info); + if (bprm->unsafe & (LSM_UNSAFE_PTRACE)) { + /* TODO: test needs to be profile of label to new */ + error = may_change_ptraced_domain(new, &info); if (error) goto audit; } - /* Determine if secure exec is needed. - * Can be at this point for the following reasons: - * 1. unconfined switching to confined - * 2. confined switching to different confinement - * 3. confined switching to unconfined - * - * Cases 2 and 3 are marked as requiring secure exec - * (unless policy specified "unsafe exec") - * - * bprm->unsafe is used to cache the AA_X_UNSAFE permission - * to avoid having to recompute in secureexec - */ - if (!(perms.xindex & AA_X_UNSAFE)) { - AA_DEBUG("scrubbing environment variables for %s profile=%s\n", - name, new_profile->base.hname); + if (unsafe) { + if (DEBUG_ON) { + dbg_printk("scrubbing environment variables for %s " + "label=", bprm->filename); + aa_label_printk(new, GFP_ATOMIC); + dbg_printk("\n"); + } bprm->unsafe |= AA_SECURE_X_NEEDED; } -apply: - /* when transitioning profiles clear unsafe personality bits */ - bprm->per_clear |= PER_CLEAR_ON_SETID; -x_clear: + if (label->proxy != new->proxy) { + /* when transitioning clear unsafe personality bits */ + if (DEBUG_ON) { + dbg_printk("apparmor: clearing unsafe personality " + "bits. %s label=", bprm->filename); + aa_label_printk(new, GFP_ATOMIC); + dbg_printk("\n"); + } + bprm->per_clear |= PER_CLEAR_ON_SETID; + } aa_put_label(ctx->label); - /* transfer new profile reference will be released when ctx is freed */ - ctx->label = &new_profile->label; - new_profile = NULL; + /* transfer reference, released when ctx is freed */ + ctx->label = new; - /* clear out all temporary/transitional state from the context */ +done: + /* clear out temporary/transitional state from the context */ aa_clear_task_ctx_trans(ctx); -audit: - error = aa_audit_file(profile, &perms, OP_EXEC, MAY_EXEC, name, - new_profile ? new_profile->base.hname : NULL, - new_profile ? &new_profile->label : NULL, - cond.uid, info, error); - -cleanup: - aa_put_profile(new_profile); aa_put_label(label); put_buffers(buffer); return error; + +audit: + error = fn_for_each(label, profile, + aa_audit_file(profile, &nullperms, OP_EXEC, MAY_EXEC, + bprm->filename, NULL, new, + file_inode(bprm->file)->i_uid, info, + error)); + aa_put_label(new); + goto done; } /** @@ -778,8 +1112,8 @@ int aa_change_profile(const char *fqname, int flags) } } - perms = change_profile_perms(profile, target->ns, target->base.hname, - request, profile->file.start); + perms = change_profile_perms_wrapper(profile, target, request, + profile->file.start); if (!(perms.allow & request)) { error = -EACCES; goto audit; diff --git a/security/apparmor/include/lib.h b/security/apparmor/include/lib.h index 593877d38088..436b3a722357 100644 --- a/security/apparmor/include/lib.h +++ b/security/apparmor/include/lib.h @@ -211,4 +211,89 @@ bool aa_policy_init(struct aa_policy *policy, const char *prefix, const char *name, gfp_t gfp); void aa_policy_destroy(struct aa_policy *policy); -#endif /* AA_LIB_H */ + +/* + * fn_label_build - abstract out the build of a label transition + * @L: label the transition is being computed for + * @P: profile parameter derived from L by this macro, can be passed to FN + * @GFP: memory allocation type to use + * @FN: fn to call for each profile transition. @P is set to the profile + * + * Returns: new label on success + * ERR_PTR if build @FN fails + * NULL if label_build fails due to low memory conditions + * + * @FN must return a label or ERR_PTR on failure. NULL is not allowed + */ +#define fn_label_build(L, P, GFP, FN) \ +({ \ + __label__ __cleanup, __done; \ + struct aa_label *__new_; \ + \ + if ((L)->size > 1) { \ + /* TODO: add cache of transitions already done */ \ + struct label_it __i; \ + int __j, __k, __count; \ + DEFINE_VEC(label, __lvec); \ + DEFINE_VEC(profile, __pvec); \ + if (vec_setup(label, __lvec, (L)->size, (GFP))) { \ + __new_ = NULL; \ + goto __done; \ + } \ + __j = 0; \ + label_for_each(__i, (L), (P)) { \ + __new_ = (FN); \ + AA_BUG(!__new_); \ + if (IS_ERR(__new_)) \ + goto __cleanup; \ + __lvec[__j++] = __new_; \ + } \ + for (__j = __count = 0; __j < (L)->size; __j++) \ + __count += __lvec[__j]->size; \ + if (!vec_setup(profile, __pvec, __count, (GFP))) { \ + for (__j = __k = 0; __j < (L)->size; __j++) { \ + label_for_each(__i, __lvec[__j], (P)) \ + __pvec[__k++] = aa_get_profile(P); \ + } \ + __count -= aa_vec_unique(__pvec, __count, 0); \ + if (__count > 1) { \ + __new_ = aa_vec_find_or_create_label(__pvec,\ + __count, (GFP)); \ + /* only fails if out of Mem */ \ + if (!__new_) \ + __new_ = NULL; \ + } else \ + __new_ = aa_get_label(&__pvec[0]->label); \ + vec_cleanup(profile, __pvec, __count); \ + } else \ + __new_ = NULL; \ +__cleanup: \ + vec_cleanup(label, __lvec, (L)->size); \ + } else { \ + (P) = labels_profile(L); \ + __new_ = (FN); \ + } \ +__done: \ + if (!__new_) \ + AA_DEBUG("label build failed\n"); \ + (__new_); \ +}) + + +#define __fn_build_in_ns(NS, P, NS_FN, OTHER_FN) \ +({ \ + struct aa_label *__new; \ + if ((P)->ns != (NS)) \ + __new = (OTHER_FN); \ + else \ + __new = (NS_FN); \ + (__new); \ +}) + +#define fn_label_build_in_ns(L, P, GFP, NS_FN, OTHER_FN) \ +({ \ + fn_label_build((L), (P), (GFP), \ + __fn_build_in_ns(labels_ns(L), (P), (NS_FN), (OTHER_FN))); \ +}) + +#endif /* __AA_LIB_H */ -- cgit v1.2.3 From 89dbf1962aa636341658c26034c0e113cc9d1776 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Fri, 9 Jun 2017 17:01:43 -0700 Subject: apparmor: move change_hat mediation to using labels Signed-off-by: John Johansen --- security/apparmor/domain.c | 303 ++++++++++++++++++++++++++++++--------------- 1 file changed, 201 insertions(+), 102 deletions(-) diff --git a/security/apparmor/domain.c b/security/apparmor/domain.c index cb8509373ea3..a1d73033b42e 100644 --- a/security/apparmor/domain.c +++ b/security/apparmor/domain.c @@ -884,19 +884,153 @@ int apparmor_bprm_secureexec(struct linux_binprm *bprm) * Functions for self directed profile change */ -/** - * new_compound_name - create an hname with @n2 appended to @n1 - * @n1: base of hname (NOT NULL) - * @n2: name to append (NOT NULL) + +/* helper fn for change_hat * - * Returns: new name or NULL on error + * Returns: label for hat transition OR ERR_PTR. Does NOT return NULL */ -static char *new_compound_name(const char *n1, const char *n2) +static struct aa_label *build_change_hat(struct aa_profile *profile, + const char *name, bool sibling) { - char *name = kmalloc(strlen(n1) + strlen(n2) + 3, GFP_KERNEL); - if (name) - sprintf(name, "%s//%s", n1, n2); - return name; + struct aa_profile *root, *hat = NULL; + const char *info = NULL; + int error = 0; + + if (sibling && PROFILE_IS_HAT(profile)) { + root = aa_get_profile_rcu(&profile->parent); + } else if (!sibling && !PROFILE_IS_HAT(profile)) { + root = aa_get_profile(profile); + } else { + info = "conflicting target types"; + error = -EPERM; + goto audit; + } + + hat = aa_find_child(root, name); + if (!hat) { + error = -ENOENT; + if (COMPLAIN_MODE(profile)) { + hat = aa_new_null_profile(profile, true, name, + GFP_KERNEL); + if (!hat) { + info = "failed null profile create"; + error = -ENOMEM; + } + } + } + aa_put_profile(root); + +audit: + aa_audit_file(profile, &nullperms, OP_CHANGE_HAT, AA_MAY_CHANGEHAT, + name, hat ? hat->base.hname : NULL, + hat ? &hat->label : NULL, GLOBAL_ROOT_UID, NULL, + error); + if (!hat || (error && error != -ENOENT)) + return ERR_PTR(error); + /* if hat && error - complain mode, already audited and we adjust for + * complain mode allow by returning hat->label + */ + return &hat->label; +} + +/* helper fn for changing into a hat + * + * Returns: label for hat transition or ERR_PTR. Does not return NULL + */ +static struct aa_label *change_hat(struct aa_label *label, const char *hats[], + int count, int flags) +{ + struct aa_profile *profile, *root, *hat = NULL; + struct aa_label *new; + struct label_it it; + bool sibling = false; + const char *name, *info = NULL; + int i, error; + + AA_BUG(!label); + AA_BUG(!hats); + AA_BUG(count < 1); + + if (PROFILE_IS_HAT(labels_profile(label))) + sibling = true; + + /*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) { + if (sibling && PROFILE_IS_HAT(profile)) { + root = aa_get_profile_rcu(&profile->parent); + } else if (!sibling && !PROFILE_IS_HAT(profile)) { + root = aa_get_profile(profile); + } else { /* conflicting change type */ + info = "conflicting targets types"; + error = -EPERM; + goto fail; + } + hat = aa_find_child(root, name); + aa_put_profile(root); + if (!hat) { + if (!COMPLAIN_MODE(profile)) + goto outer_continue; + /* complain mode succeed as if hat */ + } else if (!PROFILE_IS_HAT(hat)) { + info = "target not hat"; + error = -EPERM; + aa_put_profile(hat); + goto fail; + } + aa_put_profile(hat); + } + /* found a hat for all profiles in ns */ + goto build; +outer_continue: + ; + } + /* no hats that match, find appropriate error + * + * In complain mode audit of the failure is based off of the first + * hat supplied. This is done due how userspace interacts with + * change_hat. + */ + name = NULL; + label_for_each_in_ns(it, labels_ns(label), label, profile) { + if (!list_empty(&profile->base.profiles)) { + info = "hat not found"; + error = -ENOENT; + goto fail; + } + } + info = "no hats defined"; + error = -ECHILD; + +fail: + label_for_each_in_ns(it, labels_ns(label), label, profile) { + /* + * no target as it has failed to be found or built + * + * change_hat uses probing and should not log failures + * related to missing hats + */ + /* TODO: get rid of GLOBAL_ROOT_UID */ + if (count > 1 || COMPLAIN_MODE(profile)) { + aa_audit_file(profile, &nullperms, OP_CHANGE_HAT, + AA_MAY_CHANGEHAT, name, NULL, NULL, + GLOBAL_ROOT_UID, info, error); + } + } + return ERR_PTR(error); + +build: + new = fn_label_build_in_ns(label, profile, GFP_KERNEL, + build_change_hat(profile, name, sibling), + aa_get_label(&profile->label)); + if (!new) { + info = "label build failed"; + error = -ENOMEM; + goto fail; + } /* else if (IS_ERR) build_change_hat has logged error so return new */ + + return new; } /** @@ -906,23 +1040,24 @@ static char *new_compound_name(const char *n1, const char *n2) * @token: magic value to validate the hat change * @flags: flags affecting behavior of the change * + * Returns %0 on success, error otherwise. + * * Change to the first profile specified in @hats that exists, and store * the @hat_magic in the current task context. If the count == 0 and the * @token matches that stored in the current task context, return to the * top level profile. * - * Returns %0 on success, error otherwise. + * change_hat only applies to profiles in the current ns, and each profile + * in the ns must make the same transition otherwise change_hat will fail. */ int aa_change_hat(const char *hats[], int count, u64 token, int flags) { const struct cred *cred; struct aa_task_ctx *ctx; - struct aa_label *label, *previous_label; - struct aa_profile *profile, *hat = NULL; - char *name = NULL; - int i; + struct aa_label *label, *previous, *new = NULL, *target = NULL; + struct aa_profile *profile; struct aa_perms perms = {}; - const char *target = NULL, *info = NULL; + const char *info = NULL; int error = 0; /* @@ -930,118 +1065,82 @@ int aa_change_hat(const char *hats[], int count, u64 token, int flags) * There is no exception for unconfined as change_hat is not * available. */ - if (task_no_new_privs(current)) + if (task_no_new_privs(current)) { + /* not an apparmor denial per se, so don't log it */ + AA_DEBUG("no_new_privs - change_hat denied"); return -EPERM; + } /* released below */ cred = get_current_cred(); ctx = cred_ctx(cred); label = aa_get_newest_cred_label(cred); - previous_label = aa_get_newest_label(ctx->previous); - profile = labels_profile(label); + previous = aa_get_newest_label(ctx->previous); if (unconfined(label)) { - info = "unconfined"; + info = "unconfined can not change_hat"; error = -EPERM; - goto audit; + goto fail; } if (count) { - /* attempting to change into a new hat or switch to a sibling */ - struct aa_profile *root; - if (PROFILE_IS_HAT(profile)) - root = aa_get_profile_rcu(&profile->parent); - else - root = aa_get_profile(profile); - - /* find first matching hat */ - for (i = 0; i < count && !hat; i++) - /* released below */ - hat = aa_find_child(root, hats[i]); - if (!hat) { - if (!COMPLAIN_MODE(root) || (flags & AA_CHANGE_TEST)) { - if (list_empty(&root->base.profiles)) - error = -ECHILD; - else - error = -ENOENT; - aa_put_profile(root); - goto out; - } - - /* - * In complain mode and failed to match any hats. - * Audit the failure is based off of the first hat - * supplied. This is done due how userspace - * interacts with change_hat. - * - * TODO: Add logging of all failed hats - */ - - /* freed below */ - name = new_compound_name(root->base.hname, hats[0]); - aa_put_profile(root); - target = name; - /* released below */ - hat = aa_new_null_profile(profile, true, hats[0], - GFP_KERNEL); - if (!hat) { - info = "failed null profile create"; - error = -ENOMEM; - goto audit; - } - } else { - aa_put_profile(root); - target = hat->base.hname; - if (!PROFILE_IS_HAT(hat)) { - info = "target not hat"; - error = -EPERM; - goto audit; - } + new = change_hat(label, hats, count, flags); + AA_BUG(!new); + if (IS_ERR(new)) { + error = PTR_ERR(new); + new = NULL; + /* already audited */ + goto out; } - error = may_change_ptraced_domain(&hat->label, &info); - if (error) { - info = "ptraced"; - error = -EPERM; - goto audit; - } + error = may_change_ptraced_domain(new, &info); + if (error) + goto fail; - if (!(flags & AA_CHANGE_TEST)) { - error = aa_set_current_hat(&hat->label, token); - if (error == -EACCES) - /* kill task in case of brute force attacks */ - perms.kill = AA_MAY_CHANGEHAT; - else if (name && !error) - /* reset error for learning of new hats */ - error = -ENOENT; - } - } else if (previous_label) { - /* Return to saved profile. Kill task if restore fails + if (flags & AA_CHANGE_TEST) + goto out; + + target = new; + error = aa_set_current_hat(new, token); + if (error == -EACCES) + /* kill task in case of brute force attacks */ + goto kill; + } else if (previous && !(flags & AA_CHANGE_TEST)) { + /* Return to saved label. Kill task if restore fails * to avoid brute force attacks */ - target = previous_label->hname; + target = previous; error = aa_restore_previous_label(token); - perms.kill = AA_MAY_CHANGEHAT; - } else - /* ignore restores when there is no saved profile */ - goto out; - -audit: - if (!(flags & AA_CHANGE_TEST)) - error = aa_audit_file(profile, &perms, OP_CHANGE_HAT, - AA_MAY_CHANGEHAT, NULL, target, NULL, - GLOBAL_ROOT_UID, info, error); + if (error) { + if (error == -EACCES) + goto kill; + goto fail; + } + } /* else ignore @flags && restores when there is no saved profile */ out: - aa_put_profile(hat); - kfree(name); + aa_put_label(new); + aa_put_label(previous); aa_put_label(label); - aa_put_label(previous_label); put_cred(cred); return error; + +kill: + info = "failed token match"; + perms.kill = AA_MAY_CHANGEHAT; + +fail: + fn_for_each_in_ns(label, profile, + aa_audit_file(profile, &perms, OP_CHANGE_HAT, + AA_MAY_CHANGEHAT, NULL, NULL, target, + GLOBAL_ROOT_UID, info, error)); + + goto out; } + + /** * aa_change_profile - perform a one-way profile transition * @fqname: name of profile may include namespace (NOT NULL) -- cgit v1.2.3 From e00b02bb6ac2a1893227ce8014b649028d6425d2 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Fri, 9 Jun 2017 17:07:58 -0700 Subject: apparmor: move change_profile mediation to using labels Signed-off-by: John Johansen --- security/apparmor/domain.c | 191 +++++++++++++++++++++++++++++---------------- 1 file changed, 123 insertions(+), 68 deletions(-) diff --git a/security/apparmor/domain.c b/security/apparmor/domain.c index a1d73033b42e..d0594446ae3f 100644 --- a/security/apparmor/domain.c +++ b/security/apparmor/domain.c @@ -301,26 +301,6 @@ static int change_profile_perms(struct aa_profile *profile, return label_match(profile, target, stack, start, true, request, perms); } -static struct aa_perms change_profile_perms_wrapper(struct aa_profile *profile, - struct aa_profile *target, - u32 request, - unsigned int start) -{ - struct aa_perms perms; - - if (profile_unconfined(profile)) { - perms.allow = AA_MAY_CHANGE_PROFILE | AA_MAY_ONEXEC; - perms.audit = perms.quiet = perms.kill = 0; - return perms; - } - - if (change_profile_perms(profile, &target->label, false, request, - start, &perms)) - return nullperms; - - return perms; -} - /** * __attach_match_ - find an attachment match * @name - to match against (NOT NULL) @@ -1140,6 +1120,39 @@ fail: } +static int change_profile_perms_wrapper(const char *op, const char *name, + struct aa_profile *profile, + struct aa_label *target, bool stack, + u32 request, struct aa_perms *perms) +{ + const char *info = NULL; + int error = 0; + + /* + * Fail explicitly requested domain transitions when no_new_privs + * and not unconfined OR the transition results in a stack on + * the current label. + * Stacking domain transitions and transitions from unconfined are + * allowed even when no_new_privs is set because this aways results + * in a reduction of permissions. + */ + if (task_no_new_privs(current) && !stack && + !profile_unconfined(profile) && + !aa_label_is_subset(target, &profile->label)) { + info = "no new privs"; + error = -EPERM; + } + + if (!error) + error = change_profile_perms(profile, target, stack, request, + profile->file.start, perms); + if (error) + error = aa_audit_file(profile, perms, op, request, name, + NULL, target, GLOBAL_ROOT_UID, info, + error); + + return error; +} /** * aa_change_profile - perform a one-way profile transition @@ -1157,12 +1170,14 @@ fail: */ int aa_change_profile(const char *fqname, int flags) { - const struct cred *cred; - struct aa_label *label; - struct aa_profile *profile, *target = NULL; + struct aa_label *label, *new = NULL, *target = NULL; + struct aa_profile *profile; struct aa_perms perms = {}; - const char *info = NULL, *op; + const char *info = NULL; + const char *auditname = fqname; /* retain leading & if stack */ + bool stack = flags & AA_CHANGE_STACK; int error = 0; + char *op; u32 request; if (!fqname || !*fqname) { @@ -1172,76 +1187,116 @@ int aa_change_profile(const char *fqname, int flags) if (flags & AA_CHANGE_ONEXEC) { request = AA_MAY_ONEXEC; - op = OP_CHANGE_ONEXEC; + if (stack) + op = OP_STACK_ONEXEC; + else + op = OP_CHANGE_ONEXEC; } else { request = AA_MAY_CHANGE_PROFILE; - op = OP_CHANGE_PROFILE; + if (stack) + op = OP_STACK; + else + op = OP_CHANGE_PROFILE; } - cred = get_current_cred(); - label = aa_get_newest_cred_label(cred); - profile = labels_profile(label); + label = aa_get_current_label(); - /* - * Fail explicitly requested domain transitions if no_new_privs - * and not unconfined. - * Domain transitions from unconfined are allowed even when - * no_new_privs is set because this aways results in a reduction - * of permissions. - */ - if (task_no_new_privs(current) && !profile_unconfined(profile)) { - put_cred(cred); - return -EPERM; + if (*fqname == '&') { + stack = true; + /* don't have label_parse() do stacking */ + fqname++; } + target = aa_label_parse(label, fqname, GFP_KERNEL, true, false); + if (IS_ERR(target)) { + struct aa_profile *tprofile; - target = aa_fqlookupn_profile(label, fqname, strlen(fqname)); - if (!target) { - info = "profile not found"; - error = -ENOENT; + info = "label not found"; + error = PTR_ERR(target); + target = NULL; + /* + * TODO: fixme using labels_profile is not right - do profile + * per complain profile + */ if ((flags & AA_CHANGE_TEST) || - !COMPLAIN_MODE(profile)) + !COMPLAIN_MODE(labels_profile(label))) goto audit; /* released below */ - target = aa_new_null_profile(profile, false, fqname, - GFP_KERNEL); - if (!target) { + tprofile = aa_new_null_profile(labels_profile(label), false, + fqname, GFP_KERNEL); + if (!tprofile) { info = "failed null profile create"; error = -ENOMEM; goto audit; } + target = &tprofile->label; + goto check; } - perms = change_profile_perms_wrapper(profile, target, request, - profile->file.start); - if (!(perms.allow & request)) { - error = -EACCES; - goto audit; - } + /* + * self directed transitions only apply to current policy ns + * TODO: currently requiring perms for stacking and straight change + * stacking doesn't strictly need this. Determine how much + * we want to loosen this restriction for stacking + * + * if (!stack) { + */ + error = fn_for_each_in_ns(label, profile, + change_profile_perms_wrapper(op, auditname, + profile, target, stack, + request, &perms)); + if (error) + /* auditing done in change_profile_perms_wrapper */ + goto out; + /* } */ + +check: /* check if tracing task is allowed to trace target domain */ - error = may_change_ptraced_domain(&target->label, &info); - if (error) { - info = "ptrace prevents transition"; + error = may_change_ptraced_domain(target, &info); + if (error && !fn_for_each_in_ns(label, profile, + COMPLAIN_MODE(profile))) goto audit; - } + /* TODO: add permission check to allow this + * if ((flags & AA_CHANGE_ONEXEC) && !current_is_single_threaded()) { + * info = "not a single threaded task"; + * error = -EACCES; + * goto audit; + * } + */ if (flags & AA_CHANGE_TEST) - goto audit; + goto out; - if (flags & AA_CHANGE_ONEXEC) - error = aa_set_current_onexec(&target->label, 0); - else - error = aa_replace_current_label(&target->label); + if (!(flags & AA_CHANGE_ONEXEC)) { + /* only transition profiles in the current ns */ + if (stack) + new = aa_label_merge(label, target, GFP_KERNEL); + else + new = fn_label_build_in_ns(label, profile, GFP_KERNEL, + aa_get_label(target), + aa_get_label(&profile->label)); + if (IS_ERR_OR_NULL(new)) { + info = "failed to build target label"; + error = PTR_ERR(new); + new = NULL; + perms.allow = 0; + goto audit; + } + error = aa_replace_current_label(new); + } else + /* full transition will be built in exec path */ + error = aa_set_current_onexec(target, stack); audit: - if (!(flags & AA_CHANGE_TEST)) - error = aa_audit_file(profile, &perms, op, request, NULL, - fqname, NULL, GLOBAL_ROOT_UID, info, - error); + error = fn_for_each_in_ns(label, profile, + aa_audit_file(profile, &perms, op, request, auditname, + NULL, new ? new : target, + GLOBAL_ROOT_UID, info, error)); - aa_put_profile(target); +out: + aa_put_label(new); + aa_put_label(target); aa_put_label(label); - put_cred(cred); return error; } -- cgit v1.2.3 From 40cde7fcc344bc77c1ec9d291dcc35ab12f078aa Mon Sep 17 00:00:00 2001 From: John Johansen Date: Fri, 9 Jun 2017 17:11:17 -0700 Subject: apparmor: add domain label stacking info to apparmorfs Now that the domain label transition is complete advertise it to userspace. Signed-off-by: John Johansen --- security/apparmor/apparmorfs.c | 36 ++++++++++++++++++++++++++++++++++++ security/apparmor/include/audit.h | 2 ++ security/apparmor/include/domain.h | 1 + 3 files changed, 39 insertions(+) diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c index e460f2d8337d..6310bf1485b6 100644 --- a/security/apparmor/apparmorfs.c +++ b/security/apparmor/apparmorfs.c @@ -1138,6 +1138,40 @@ static const struct file_operations seq_ns_ ##NAME ##_fops = { \ .release = single_release, \ } \ +static int seq_ns_stacked_show(struct seq_file *seq, void *v) +{ + struct aa_label *label; + + label = begin_current_label_crit_section(); + seq_printf(seq, "%s\n", label->size > 1 ? "yes" : "no"); + end_current_label_crit_section(label); + + return 0; +} + +static int seq_ns_nsstacked_show(struct seq_file *seq, void *v) +{ + struct aa_label *label; + struct aa_profile *profile; + struct label_it it; + int count = 1; + + label = begin_current_label_crit_section(); + + if (label->size > 1) { + label_for_each(it, label, profile) + if (profile->ns != labels_ns(label)) { + count++; + break; + } + } + + seq_printf(seq, "%s\n", count > 1 ? "yes" : "no"); + end_current_label_crit_section(label); + + return 0; +} + static int seq_ns_level_show(struct seq_file *seq, void *v) { struct aa_label *label; @@ -1160,6 +1194,8 @@ static int seq_ns_name_show(struct seq_file *seq, void *v) return 0; } +SEQ_NS_FOPS(stacked); +SEQ_NS_FOPS(nsstacked); SEQ_NS_FOPS(level); SEQ_NS_FOPS(name); diff --git a/security/apparmor/include/audit.h b/security/apparmor/include/audit.h index 99ed83cf6941..c68839a44351 100644 --- a/security/apparmor/include/audit.h +++ b/security/apparmor/include/audit.h @@ -92,6 +92,8 @@ enum audit_type { #define OP_CHANGE_HAT "change_hat" #define OP_CHANGE_PROFILE "change_profile" #define OP_CHANGE_ONEXEC "change_onexec" +#define OP_STACK "stack" +#define OP_STACK_ONEXEC "stack_onexec" #define OP_SETPROCATTR "setprocattr" #define OP_SETRLIMIT "setrlimit" diff --git a/security/apparmor/include/domain.h b/security/apparmor/include/domain.h index 255aa40ec1d1..bab5810b6e9a 100644 --- a/security/apparmor/include/domain.h +++ b/security/apparmor/include/domain.h @@ -27,6 +27,7 @@ struct aa_domain { #define AA_CHANGE_TEST 1 #define AA_CHANGE_CHILD 2 #define AA_CHANGE_ONEXEC 4 +#define AA_CHANGE_STACK 8 int apparmor_bprm_set_creds(struct linux_binprm *bprm); int apparmor_bprm_secureexec(struct linux_binprm *bprm); -- cgit v1.2.3 From 6c5fc8f17a2528052bace1d91a3bef003bd1331d Mon Sep 17 00:00:00 2001 From: John Johansen Date: Fri, 9 Jun 2017 17:22:50 -0700 Subject: apparmor: add stacked domain labels interface Update the user interface to support the stacked change_profile transition. Signed-off-by: John Johansen --- security/apparmor/apparmorfs.c | 3 +++ security/apparmor/lsm.c | 5 +++++ 2 files changed, 8 insertions(+) diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c index 6310bf1485b6..229845009a95 100644 --- a/security/apparmor/apparmorfs.c +++ b/security/apparmor/apparmorfs.c @@ -2132,6 +2132,7 @@ static struct aa_sfs_entry aa_sfs_entry_domain[] = { AA_SFS_FILE_BOOLEAN("change_hatv", 1), AA_SFS_FILE_BOOLEAN("change_onexec", 1), AA_SFS_FILE_BOOLEAN("change_profile", 1), + AA_SFS_FILE_BOOLEAN("stack", 1), AA_SFS_FILE_BOOLEAN("fix_binfmt_elf_mmap", 1), AA_SFS_FILE_STRING("version", "1.2"), { } @@ -2175,6 +2176,8 @@ static struct aa_sfs_entry aa_sfs_entry_features[] = { static struct aa_sfs_entry aa_sfs_entry_apparmor[] = { AA_SFS_FILE_FOPS(".access", 0640, &aa_sfs_access), + AA_SFS_FILE_FOPS(".stacked", 0444, &seq_ns_stacked_fops), + AA_SFS_FILE_FOPS(".ns_stacked", 0444, &seq_ns_nsstacked_fops), AA_SFS_FILE_FOPS(".ns_level", 0666, &seq_ns_level_fops), AA_SFS_FILE_FOPS(".ns_name", 0640, &seq_ns_name_fops), AA_SFS_FILE_FOPS("profiles", 0440, &aa_sfs_profiles_fops), diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c index 0f7c5c2be732..867bcd154c7e 100644 --- a/security/apparmor/lsm.c +++ b/security/apparmor/lsm.c @@ -580,11 +580,16 @@ static int apparmor_setprocattr(const char *name, void *value, error = aa_change_profile(args, AA_CHANGE_NOFLAGS); } else if (strcmp(command, "permprofile") == 0) { error = aa_change_profile(args, AA_CHANGE_TEST); + } else if (strcmp(command, "stack") == 0) { + error = aa_change_profile(args, AA_CHANGE_STACK); } else goto fail; } else if (strcmp(name, "exec") == 0) { if (strcmp(command, "exec") == 0) error = aa_change_profile(args, AA_CHANGE_ONEXEC); + else if (strcmp(command, "stack") == 0) + error = aa_change_profile(args, (AA_CHANGE_ONEXEC | + AA_CHANGE_STACK)); else goto fail; } else -- cgit v1.2.3 From 33f2eadabba59cf1c763c46c4470279ec2054099 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Fri, 9 Jun 2017 17:25:03 -0700 Subject: apparmor: export that basic profile namespaces are supported Allow userspace to detect that basic profile policy namespaces are available. Signed-off-by: John Johansen --- security/apparmor/apparmorfs.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c index 229845009a95..853c2ec8e0c9 100644 --- a/security/apparmor/apparmorfs.c +++ b/security/apparmor/apparmorfs.c @@ -2151,6 +2151,12 @@ static struct aa_sfs_entry aa_sfs_entry_policy[] = { { } }; +static struct aa_sfs_entry aa_sfs_entry_ns[] = { + AA_SFS_FILE_BOOLEAN("profile", 1), + AA_SFS_FILE_BOOLEAN("pivot_root", 1), + { } +}; + static struct aa_sfs_entry aa_sfs_entry_query_label[] = { AA_SFS_FILE_STRING("perms", "allow deny audit quiet"), AA_SFS_FILE_BOOLEAN("data", 1), @@ -2166,6 +2172,7 @@ static struct aa_sfs_entry aa_sfs_entry_features[] = { AA_SFS_DIR("policy", aa_sfs_entry_policy), AA_SFS_DIR("domain", aa_sfs_entry_domain), AA_SFS_DIR("file", aa_sfs_entry_file), + AA_SFS_DIR("namespaces", aa_sfs_entry_ns), AA_SFS_FILE_U64("capability", VFS_CAP_FLAGS_MASK), AA_SFS_DIR("rlimit", aa_sfs_entry_rlimit), AA_SFS_DIR("caps", aa_sfs_entry_caps), -- cgit v1.2.3 From 06e93279ca77ce2820723427f118a22dd0b35f0f Mon Sep 17 00:00:00 2001 From: Roberto Sassu Date: Wed, 3 May 2017 18:19:09 +0200 Subject: tpm: move endianness conversion of TPM_TAG_RQU_COMMAND to tpm_input_header In the long term, TPM 1.2 functions in the driver interface will be modified to use tpm_buf_init(). However, tag and ordinals cannot be passed directly to tpm_buf_init(), because this function performs CPU native to big-endian conversion of these arguments. Since TPM_TAG_RQU_COMMAND and TPM_ORD_ are already converted, passing them to the function will undo the previous conversion. This patch moves the conversion of TPM_TAG_RQU_COMMAND from the tpm.h header file in the driver directory to the tpm_input_header declarations in the driver interface and tpm-sysfs.c. Signed-off-by: Roberto Sassu Reviewed-by: Jarkko Sakkinen Tested-by: Jarkko Sakkinen Signed-off-by: Jarkko Sakkinen --- drivers/char/tpm/tpm-interface.c | 14 +++++++------- drivers/char/tpm/tpm-sysfs.c | 2 +- drivers/char/tpm/tpm.h | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/drivers/char/tpm/tpm-interface.c b/drivers/char/tpm/tpm-interface.c index 158c1db83f05..a60d57d214a6 100644 --- a/drivers/char/tpm/tpm-interface.c +++ b/drivers/char/tpm/tpm-interface.c @@ -544,7 +544,7 @@ ssize_t tpm_transmit_cmd(struct tpm_chip *chip, struct tpm_space *space, #define TPM_ORD_GET_RANDOM cpu_to_be32(70) static const struct tpm_input_header tpm_getcap_header = { - .tag = TPM_TAG_RQU_COMMAND, + .tag = cpu_to_be16(TPM_TAG_RQU_COMMAND), .length = cpu_to_be32(22), .ordinal = TPM_ORD_GET_CAP }; @@ -586,7 +586,7 @@ EXPORT_SYMBOL_GPL(tpm_getcap); #define TPM_ST_STATE cpu_to_be16(2) #define TPM_ST_DEACTIVATED cpu_to_be16(3) static const struct tpm_input_header tpm_startup_header = { - .tag = TPM_TAG_RQU_COMMAND, + .tag = cpu_to_be16(TPM_TAG_RQU_COMMAND), .length = cpu_to_be32(12), .ordinal = TPM_ORD_STARTUP }; @@ -737,7 +737,7 @@ EXPORT_SYMBOL_GPL(tpm_get_timeouts); #define CONTINUE_SELFTEST_RESULT_SIZE 10 static const struct tpm_input_header continue_selftest_header = { - .tag = TPM_TAG_RQU_COMMAND, + .tag = cpu_to_be16(TPM_TAG_RQU_COMMAND), .length = cpu_to_be32(10), .ordinal = cpu_to_be32(TPM_ORD_CONTINUE_SELFTEST), }; @@ -764,7 +764,7 @@ static int tpm_continue_selftest(struct tpm_chip *chip) #define READ_PCR_RESULT_SIZE 30 #define READ_PCR_RESULT_BODY_SIZE 20 static const struct tpm_input_header pcrread_header = { - .tag = TPM_TAG_RQU_COMMAND, + .tag = cpu_to_be16(TPM_TAG_RQU_COMMAND), .length = cpu_to_be32(14), .ordinal = TPM_ORDINAL_PCRREAD }; @@ -842,7 +842,7 @@ EXPORT_SYMBOL_GPL(tpm_pcr_read); #define EXTEND_PCR_RESULT_SIZE 34 #define EXTEND_PCR_RESULT_BODY_SIZE 20 static const struct tpm_input_header pcrextend_header = { - .tag = TPM_TAG_RQU_COMMAND, + .tag = cpu_to_be16(TPM_TAG_RQU_COMMAND), .length = cpu_to_be32(34), .ordinal = TPM_ORD_PCR_EXTEND }; @@ -1064,7 +1064,7 @@ EXPORT_SYMBOL_GPL(wait_for_tpm_stat); #define SAVESTATE_RESULT_SIZE 10 static const struct tpm_input_header savestate_header = { - .tag = TPM_TAG_RQU_COMMAND, + .tag = cpu_to_be16(TPM_TAG_RQU_COMMAND), .length = cpu_to_be32(10), .ordinal = TPM_ORD_SAVESTATE }; @@ -1149,7 +1149,7 @@ EXPORT_SYMBOL_GPL(tpm_pm_resume); #define TPM_GETRANDOM_RESULT_SIZE 18 static const struct tpm_input_header tpm_getrandom_header = { - .tag = TPM_TAG_RQU_COMMAND, + .tag = cpu_to_be16(TPM_TAG_RQU_COMMAND), .length = cpu_to_be32(14), .ordinal = TPM_ORD_GET_RANDOM }; diff --git a/drivers/char/tpm/tpm-sysfs.c b/drivers/char/tpm/tpm-sysfs.c index 55405dbe43fa..b99fe66ca1b1 100644 --- a/drivers/char/tpm/tpm-sysfs.c +++ b/drivers/char/tpm/tpm-sysfs.c @@ -24,7 +24,7 @@ #define READ_PUBEK_RESULT_MIN_BODY_SIZE (28 + 256) #define TPM_ORD_READPUBEK cpu_to_be32(124) static const struct tpm_input_header tpm_readpubek_header = { - .tag = TPM_TAG_RQU_COMMAND, + .tag = cpu_to_be16(TPM_TAG_RQU_COMMAND), .length = cpu_to_be32(30), .ordinal = TPM_ORD_READPUBEK }; diff --git a/drivers/char/tpm/tpm.h b/drivers/char/tpm/tpm.h index 4b4c8dee3096..e81d8c7acb39 100644 --- a/drivers/char/tpm/tpm.h +++ b/drivers/char/tpm/tpm.h @@ -247,7 +247,7 @@ struct tpm_output_header { __be32 return_code; } __packed; -#define TPM_TAG_RQU_COMMAND cpu_to_be16(193) +#define TPM_TAG_RQU_COMMAND 193 struct stclear_flags_t { __be16 tag; -- cgit v1.2.3 From a69faebf4d3e98c6a7a656c26b09bc532edfed08 Mon Sep 17 00:00:00 2001 From: Roberto Sassu Date: Wed, 3 May 2017 18:19:10 +0200 Subject: tpm: move endianness conversion of ordinals to tpm_input_header Move CPU native value to big-endian conversion of ordinals to the tpm_input_header declarations. With the previous and this patch it will now be possible to modify TPM 1.2 functions to use tpm_buf_init(), which expects CPU native value for the tag and ordinal arguments. Signed-off-by: Roberto Sassu Reviewed-by: Jarkko Sakkinen Tested-by: Jarkko Sakkinen Signed-off-by: Jarkko Sakkinen --- drivers/char/tpm/tpm-interface.c | 24 ++++++++++++------------ drivers/char/tpm/tpm-sysfs.c | 4 ++-- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/drivers/char/tpm/tpm-interface.c b/drivers/char/tpm/tpm-interface.c index a60d57d214a6..7966d8d82d38 100644 --- a/drivers/char/tpm/tpm-interface.c +++ b/drivers/char/tpm/tpm-interface.c @@ -540,13 +540,13 @@ ssize_t tpm_transmit_cmd(struct tpm_chip *chip, struct tpm_space *space, #define TPM_DIGEST_SIZE 20 #define TPM_RET_CODE_IDX 6 #define TPM_INTERNAL_RESULT_SIZE 200 -#define TPM_ORD_GET_CAP cpu_to_be32(101) -#define TPM_ORD_GET_RANDOM cpu_to_be32(70) +#define TPM_ORD_GET_CAP 101 +#define TPM_ORD_GET_RANDOM 70 static const struct tpm_input_header tpm_getcap_header = { .tag = cpu_to_be16(TPM_TAG_RQU_COMMAND), .length = cpu_to_be32(22), - .ordinal = TPM_ORD_GET_CAP + .ordinal = cpu_to_be32(TPM_ORD_GET_CAP) }; ssize_t tpm_getcap(struct tpm_chip *chip, u32 subcap_id, cap_t *cap, @@ -581,14 +581,14 @@ ssize_t tpm_getcap(struct tpm_chip *chip, u32 subcap_id, cap_t *cap, } EXPORT_SYMBOL_GPL(tpm_getcap); -#define TPM_ORD_STARTUP cpu_to_be32(153) +#define TPM_ORD_STARTUP 153 #define TPM_ST_CLEAR cpu_to_be16(1) #define TPM_ST_STATE cpu_to_be16(2) #define TPM_ST_DEACTIVATED cpu_to_be16(3) static const struct tpm_input_header tpm_startup_header = { .tag = cpu_to_be16(TPM_TAG_RQU_COMMAND), .length = cpu_to_be32(12), - .ordinal = TPM_ORD_STARTUP + .ordinal = cpu_to_be32(TPM_ORD_STARTUP) }; static int tpm_startup(struct tpm_chip *chip, __be16 startup_type) @@ -760,13 +760,13 @@ static int tpm_continue_selftest(struct tpm_chip *chip) return rc; } -#define TPM_ORDINAL_PCRREAD cpu_to_be32(21) +#define TPM_ORDINAL_PCRREAD 21 #define READ_PCR_RESULT_SIZE 30 #define READ_PCR_RESULT_BODY_SIZE 20 static const struct tpm_input_header pcrread_header = { .tag = cpu_to_be16(TPM_TAG_RQU_COMMAND), .length = cpu_to_be32(14), - .ordinal = TPM_ORDINAL_PCRREAD + .ordinal = cpu_to_be32(TPM_ORDINAL_PCRREAD) }; int tpm_pcr_read_dev(struct tpm_chip *chip, int pcr_idx, u8 *res_buf) @@ -838,13 +838,13 @@ int tpm_pcr_read(u32 chip_num, int pcr_idx, u8 *res_buf) } EXPORT_SYMBOL_GPL(tpm_pcr_read); -#define TPM_ORD_PCR_EXTEND cpu_to_be32(20) +#define TPM_ORD_PCR_EXTEND 20 #define EXTEND_PCR_RESULT_SIZE 34 #define EXTEND_PCR_RESULT_BODY_SIZE 20 static const struct tpm_input_header pcrextend_header = { .tag = cpu_to_be16(TPM_TAG_RQU_COMMAND), .length = cpu_to_be32(34), - .ordinal = TPM_ORD_PCR_EXTEND + .ordinal = cpu_to_be32(TPM_ORD_PCR_EXTEND) }; /** @@ -1060,13 +1060,13 @@ again: } EXPORT_SYMBOL_GPL(wait_for_tpm_stat); -#define TPM_ORD_SAVESTATE cpu_to_be32(152) +#define TPM_ORD_SAVESTATE 152 #define SAVESTATE_RESULT_SIZE 10 static const struct tpm_input_header savestate_header = { .tag = cpu_to_be16(TPM_TAG_RQU_COMMAND), .length = cpu_to_be32(10), - .ordinal = TPM_ORD_SAVESTATE + .ordinal = cpu_to_be32(TPM_ORD_SAVESTATE) }; /* @@ -1151,7 +1151,7 @@ EXPORT_SYMBOL_GPL(tpm_pm_resume); static const struct tpm_input_header tpm_getrandom_header = { .tag = cpu_to_be16(TPM_TAG_RQU_COMMAND), .length = cpu_to_be32(14), - .ordinal = TPM_ORD_GET_RANDOM + .ordinal = cpu_to_be32(TPM_ORD_GET_RANDOM) }; /** diff --git a/drivers/char/tpm/tpm-sysfs.c b/drivers/char/tpm/tpm-sysfs.c index b99fe66ca1b1..4bd0997cfa2d 100644 --- a/drivers/char/tpm/tpm-sysfs.c +++ b/drivers/char/tpm/tpm-sysfs.c @@ -22,11 +22,11 @@ #define READ_PUBEK_RESULT_SIZE 314 #define READ_PUBEK_RESULT_MIN_BODY_SIZE (28 + 256) -#define TPM_ORD_READPUBEK cpu_to_be32(124) +#define TPM_ORD_READPUBEK 124 static const struct tpm_input_header tpm_readpubek_header = { .tag = cpu_to_be16(TPM_TAG_RQU_COMMAND), .length = cpu_to_be32(30), - .ordinal = TPM_ORD_READPUBEK + .ordinal = cpu_to_be32(TPM_ORD_READPUBEK) }; static ssize_t pubek_show(struct device *dev, struct device_attribute *attr, char *buf) -- cgit v1.2.3 From 175d5b2a570cc0f79a23dbaf86e35e660f6f544f Mon Sep 17 00:00:00 2001 From: Roberto Sassu Date: Thu, 4 May 2017 13:16:47 +0200 Subject: tpm: move TPM 1.2 code of tpm_pcr_extend() to tpm1_pcr_extend() In preparation of the modifications to tpm_pcr_extend(), which will allow callers to supply a digest for each PCR bank of a TPM 2.0, the TPM 1.2 specific code has been moved to tpm1_pcr_extend(). tpm1_pcr_extend() uses tpm_buf_init() to prepare the command buffer, which offers protection against buffer overflow. It is called by tpm_pcr_extend() and tpm_pm_suspend(). Signed-off-by: Roberto Sassu Reviewed-by: Jarkko Sakkinen Tested-by: Jarkko Sakkinen Signed-off-by: Jarkko Sakkinen --- drivers/char/tpm/tpm-interface.c | 41 +++++++++++++++++++++++----------------- 1 file changed, 24 insertions(+), 17 deletions(-) diff --git a/drivers/char/tpm/tpm-interface.c b/drivers/char/tpm/tpm-interface.c index 7966d8d82d38..4ed08ab4d2a8 100644 --- a/drivers/char/tpm/tpm-interface.c +++ b/drivers/char/tpm/tpm-interface.c @@ -847,6 +847,25 @@ static const struct tpm_input_header pcrextend_header = { .ordinal = cpu_to_be32(TPM_ORD_PCR_EXTEND) }; +static int tpm1_pcr_extend(struct tpm_chip *chip, int pcr_idx, const u8 *hash, + char *log_msg) +{ + struct tpm_buf buf; + int rc; + + rc = tpm_buf_init(&buf, TPM_TAG_RQU_COMMAND, TPM_ORD_PCR_EXTEND); + if (rc) + return rc; + + tpm_buf_append_u32(&buf, pcr_idx); + tpm_buf_append(&buf, hash, TPM_DIGEST_SIZE); + + rc = tpm_transmit_cmd(chip, NULL, buf.data, EXTEND_PCR_RESULT_SIZE, + EXTEND_PCR_RESULT_BODY_SIZE, 0, log_msg); + tpm_buf_destroy(&buf); + return rc; +} + /** * tpm_pcr_extend - extend pcr value with hash * @chip_num: tpm idx # or AN& @@ -859,7 +878,6 @@ static const struct tpm_input_header pcrextend_header = { */ int tpm_pcr_extend(u32 chip_num, int pcr_idx, const u8 *hash) { - struct tpm_cmd_t cmd; int rc; struct tpm_chip *chip; struct tpm2_digest digest_list[ARRAY_SIZE(chip->active_banks)]; @@ -885,13 +903,8 @@ int tpm_pcr_extend(u32 chip_num, int pcr_idx, const u8 *hash) return rc; } - cmd.header.in = pcrextend_header; - cmd.params.pcrextend_in.pcr_idx = cpu_to_be32(pcr_idx); - memcpy(cmd.params.pcrextend_in.hash, hash, TPM_DIGEST_SIZE); - rc = tpm_transmit_cmd(chip, NULL, &cmd, EXTEND_PCR_RESULT_SIZE, - EXTEND_PCR_RESULT_BODY_SIZE, 0, - "attempting extend a PCR value"); - + rc = tpm1_pcr_extend(chip, pcr_idx, hash, + "attempting extend a PCR value"); tpm_put_ops(chip); return rc; } @@ -1090,15 +1103,9 @@ int tpm_pm_suspend(struct device *dev) } /* for buggy tpm, flush pcrs with extend to selected dummy */ - if (tpm_suspend_pcr) { - cmd.header.in = pcrextend_header; - cmd.params.pcrextend_in.pcr_idx = cpu_to_be32(tpm_suspend_pcr); - memcpy(cmd.params.pcrextend_in.hash, dummy_hash, - TPM_DIGEST_SIZE); - rc = tpm_transmit_cmd(chip, NULL, &cmd, EXTEND_PCR_RESULT_SIZE, - EXTEND_PCR_RESULT_BODY_SIZE, 0, - "extending dummy pcr before suspend"); - } + if (tpm_suspend_pcr) + rc = tpm1_pcr_extend(chip, tpm_suspend_pcr, dummy_hash, + "extending dummy pcr before suspend"); /* now do the actual savestate */ for (try = 0; try < TPM_RETRY; try++) { -- cgit v1.2.3 From d27f81f061bbde627ac4fbd735114f9ea2c63615 Mon Sep 17 00:00:00 2001 From: Jason Gunthorpe Date: Thu, 4 May 2017 09:53:23 -0600 Subject: tpm_tis: Fix IRQ autoprobing when using platform_device The test was backwards, triggering IRQ autoprobing if the firmware did not specify an IRQ, instead of triggering it only when the module force parameter was specified. Since autoprobing is not enabled on !x86 and the platform device is currently only used on !x86, or with force, this has gone unnoticed. Fixes: 00194826e6be ("tpm_tis: Clean up the force=1 module parameter") Signed-off-by: Jason Gunthorpe Reviewed-by: Jarkko Sakkinen Tested-by: Jerry Snitselaar (with TPM 2.0) Tested-by: Jarkko Sakkinen (with TPM 1.2) Signed-off-by: Jarkko Sakkinen --- drivers/char/tpm/tpm_tis.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/char/tpm/tpm_tis.c b/drivers/char/tpm/tpm_tis.c index c7e1384f1b08..56ce2bb19166 100644 --- a/drivers/char/tpm/tpm_tis.c +++ b/drivers/char/tpm/tpm_tis.c @@ -336,7 +336,7 @@ static int tpm_tis_plat_probe(struct platform_device *pdev) if (res) { tpm_info.irq = res->start; } else { - if (pdev == force_pdev) + if (pdev != force_pdev) tpm_info.irq = -1; else /* When forcing auto probe the IRQ */ -- cgit v1.2.3 From fc0e132229a7ef5a8096e2876463403df0767b1b Mon Sep 17 00:00:00 2001 From: Jason Gunthorpe Date: Thu, 4 May 2017 09:53:24 -0600 Subject: tpm_tis: Use platform_get_irq Replace the open coded IORESOURCE_IRQ with platform_get_irq, which supports more cases. Fixes: 00194826e6be ("tpm_tis: Clean up the force=1 module parameter") Signed-off-by: Jason Gunthorpe Reviewed-by: Jarkko Sakkinen Tested-by: Jerry Snitselaar (with TPM 2.0) Tested-by: Jarkko Sakkinen (with TPM 1.2) Signed-off-by: Jarkko Sakkinen --- drivers/char/tpm/tpm_tis.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/drivers/char/tpm/tpm_tis.c b/drivers/char/tpm/tpm_tis.c index 56ce2bb19166..1807b284326b 100644 --- a/drivers/char/tpm/tpm_tis.c +++ b/drivers/char/tpm/tpm_tis.c @@ -332,10 +332,8 @@ static int tpm_tis_plat_probe(struct platform_device *pdev) } tpm_info.res = *res; - res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); - if (res) { - tpm_info.irq = res->start; - } else { + tpm_info.irq = platform_get_irq(pdev, 0); + if (tpm_info.irq <= 0) { if (pdev != force_pdev) tpm_info.irq = -1; else -- cgit v1.2.3 From 4cb586a188d468e05649575f0689dd2bf8c122e6 Mon Sep 17 00:00:00 2001 From: Jason Gunthorpe Date: Thu, 4 May 2017 09:53:25 -0600 Subject: tpm_tis: Consolidate the platform and acpi probe flow Now that the platform device was merged for OF support we can use the platform device to match ACPI devices as well and run everything through tpm_tis_init. pnp_acpi_device is replaced with ACPI_COMPANION, and ACPI_HANDLE is pushed further down. platform_get_resource is used instead of acpi_dev_get_resources. The itpm global module parameter is no longer changed during itpm detection, instead the phy specific bit is set directly. Signed-off-by: Jason Gunthorpe Reviewed-by: Jarkko Sakkinen Tested-by: Jerry Snitselaar (with TPM 2.0) Tested-by: Jarkko Sakkinen (with TPM 1.2) Signed-off-by: Jarkko Sakkinen --- drivers/char/tpm/tpm_tis.c | 167 +++++++++++++++------------------------------ 1 file changed, 54 insertions(+), 113 deletions(-) diff --git a/drivers/char/tpm/tpm_tis.c b/drivers/char/tpm/tpm_tis.c index 1807b284326b..b14d4aa97af8 100644 --- a/drivers/char/tpm/tpm_tis.c +++ b/drivers/char/tpm/tpm_tis.c @@ -80,6 +80,8 @@ static int has_hid(struct acpi_device *dev, const char *hid) static inline int is_itpm(struct acpi_device *dev) { + if (!dev) + return 0; return has_hid(dev, "INTC0102"); } #else @@ -89,6 +91,47 @@ static inline int is_itpm(struct acpi_device *dev) } #endif +#if defined(CONFIG_ACPI) +#define DEVICE_IS_TPM2 1 + +static const struct acpi_device_id tpm_acpi_tbl[] = { + {"MSFT0101", DEVICE_IS_TPM2}, + {}, +}; +MODULE_DEVICE_TABLE(acpi, tpm_acpi_tbl); + +static int check_acpi_tpm2(struct device *dev) +{ + const struct acpi_device_id *aid = acpi_match_device(tpm_acpi_tbl, dev); + struct acpi_table_tpm2 *tbl; + acpi_status st; + + if (!aid || aid->driver_data != DEVICE_IS_TPM2) + return 0; + + /* If the ACPI TPM2 signature is matched then a global ACPI_SIG_TPM2 + * table is mandatory + */ + st = + acpi_get_table(ACPI_SIG_TPM2, 1, (struct acpi_table_header **)&tbl); + if (ACPI_FAILURE(st) || tbl->header.length < sizeof(*tbl)) { + dev_err(dev, FW_BUG "failed to get TPM2 ACPI table\n"); + return -EINVAL; + } + + /* The tpm2_crb driver handles this device */ + if (tbl->start_method != ACPI_TPM2_MEMORY_MAPPED) + return -ENODEV; + + return 0; +} +#else +static int check_acpi_tpm2(struct device *dev) +{ + return 0; +} +#endif + static int tpm_tcg_read_bytes(struct tpm_tis_data *data, u32 addr, u16 len, u8 *result) { @@ -141,11 +184,15 @@ static const struct tpm_tis_phy_ops tpm_tcg = { .write32 = tpm_tcg_write32, }; -static int tpm_tis_init(struct device *dev, struct tpm_info *tpm_info, - acpi_handle acpi_dev_handle) +static int tpm_tis_init(struct device *dev, struct tpm_info *tpm_info) { struct tpm_tis_tcg_phy *phy; int irq = -1; + int rc; + + rc = check_acpi_tpm2(dev); + if (rc) + return rc; phy = devm_kzalloc(dev, sizeof(struct tpm_tis_tcg_phy), GFP_KERNEL); if (phy == NULL) @@ -158,11 +205,11 @@ static int tpm_tis_init(struct device *dev, struct tpm_info *tpm_info, if (interrupts) irq = tpm_info->irq; - if (itpm) + if (itpm || is_itpm(ACPI_COMPANION(dev))) phy->priv.flags |= TPM_TIS_ITPM_WORKAROUND; return tpm_tis_core_init(dev, &phy->priv, irq, &tpm_tcg, - acpi_dev_handle); + ACPI_HANDLE(dev)); } static SIMPLE_DEV_PM_OPS(tpm_tis_pm, tpm_pm_suspend, tpm_tis_resume); @@ -171,7 +218,6 @@ static int tpm_tis_pnp_init(struct pnp_dev *pnp_dev, const struct pnp_device_id *pnp_id) { struct tpm_info tpm_info = {}; - acpi_handle acpi_dev_handle = NULL; struct resource *res; res = pnp_get_resource(pnp_dev, IORESOURCE_MEM, 0); @@ -184,14 +230,7 @@ static int tpm_tis_pnp_init(struct pnp_dev *pnp_dev, else tpm_info.irq = -1; - if (pnp_acpi_device(pnp_dev)) { - if (is_itpm(pnp_acpi_device(pnp_dev))) - itpm = true; - - acpi_dev_handle = ACPI_HANDLE(&pnp_dev->dev); - } - - return tpm_tis_init(&pnp_dev->dev, &tpm_info, acpi_dev_handle); + return tpm_tis_init(&pnp_dev->dev, &tpm_info); } static struct pnp_device_id tpm_pnp_tbl[] = { @@ -231,93 +270,6 @@ module_param_string(hid, tpm_pnp_tbl[TIS_HID_USR_IDX].id, sizeof(tpm_pnp_tbl[TIS_HID_USR_IDX].id), 0444); MODULE_PARM_DESC(hid, "Set additional specific HID for this driver to probe"); -#ifdef CONFIG_ACPI -static int tpm_check_resource(struct acpi_resource *ares, void *data) -{ - struct tpm_info *tpm_info = (struct tpm_info *) data; - struct resource res; - - if (acpi_dev_resource_interrupt(ares, 0, &res)) - tpm_info->irq = res.start; - else if (acpi_dev_resource_memory(ares, &res)) { - tpm_info->res = res; - tpm_info->res.name = NULL; - } - - return 1; -} - -static int tpm_tis_acpi_init(struct acpi_device *acpi_dev) -{ - struct acpi_table_tpm2 *tbl; - acpi_status st; - struct list_head resources; - struct tpm_info tpm_info = {}; - int ret; - - st = acpi_get_table(ACPI_SIG_TPM2, 1, - (struct acpi_table_header **) &tbl); - if (ACPI_FAILURE(st) || tbl->header.length < sizeof(*tbl)) { - dev_err(&acpi_dev->dev, - FW_BUG "failed to get TPM2 ACPI table\n"); - return -EINVAL; - } - - if (tbl->start_method != ACPI_TPM2_MEMORY_MAPPED) - return -ENODEV; - - INIT_LIST_HEAD(&resources); - tpm_info.irq = -1; - ret = acpi_dev_get_resources(acpi_dev, &resources, tpm_check_resource, - &tpm_info); - if (ret < 0) - return ret; - - acpi_dev_free_resource_list(&resources); - - if (resource_type(&tpm_info.res) != IORESOURCE_MEM) { - dev_err(&acpi_dev->dev, - FW_BUG "TPM2 ACPI table does not define a memory resource\n"); - return -EINVAL; - } - - if (is_itpm(acpi_dev)) - itpm = true; - - return tpm_tis_init(&acpi_dev->dev, &tpm_info, acpi_dev->handle); -} - -static int tpm_tis_acpi_remove(struct acpi_device *dev) -{ - struct tpm_chip *chip = dev_get_drvdata(&dev->dev); - - tpm_chip_unregister(chip); - tpm_tis_remove(chip); - - return 0; -} - -static struct acpi_device_id tpm_acpi_tbl[] = { - {"MSFT0101", 0}, /* TPM 2.0 */ - /* Add new here */ - {"", 0}, /* User Specified */ - {"", 0} /* Terminator */ -}; -MODULE_DEVICE_TABLE(acpi, tpm_acpi_tbl); - -static struct acpi_driver tis_acpi_driver = { - .name = "tpm_tis", - .ids = tpm_acpi_tbl, - .ops = { - .add = tpm_tis_acpi_init, - .remove = tpm_tis_acpi_remove, - }, - .drv = { - .pm = &tpm_tis_pm, - }, -}; -#endif - static struct platform_device *force_pdev; static int tpm_tis_plat_probe(struct platform_device *pdev) @@ -341,7 +293,7 @@ static int tpm_tis_plat_probe(struct platform_device *pdev) tpm_info.irq = 0; } - return tpm_tis_init(&pdev->dev, &tpm_info, NULL); + return tpm_tis_init(&pdev->dev, &tpm_info); } static int tpm_tis_plat_remove(struct platform_device *pdev) @@ -369,6 +321,7 @@ static struct platform_driver tis_drv = { .name = "tpm_tis", .pm = &tpm_tis_pm, .of_match_table = of_match_ptr(tis_of_platform_match), + .acpi_match_table = ACPI_PTR(tpm_acpi_tbl), }, }; @@ -411,11 +364,6 @@ static int __init init_tis(void) if (rc) goto err_platform; -#ifdef CONFIG_ACPI - rc = acpi_bus_register_driver(&tis_acpi_driver); - if (rc) - goto err_acpi; -#endif if (IS_ENABLED(CONFIG_PNP)) { rc = pnp_register_driver(&tis_pnp_driver); @@ -426,10 +374,6 @@ static int __init init_tis(void) return 0; err_pnp: -#ifdef CONFIG_ACPI - acpi_bus_unregister_driver(&tis_acpi_driver); -err_acpi: -#endif platform_driver_unregister(&tis_drv); err_platform: if (force_pdev) @@ -441,9 +385,6 @@ err_force: static void __exit cleanup_tis(void) { pnp_unregister_driver(&tis_pnp_driver); -#ifdef CONFIG_ACPI - acpi_bus_unregister_driver(&tis_acpi_driver); -#endif platform_driver_unregister(&tis_drv); if (force_pdev) -- cgit v1.2.3 From d8c3eab5cb92f37ca8576fc641fa4bfd8a0c8b00 Mon Sep 17 00:00:00 2001 From: Bryan Freed Date: Mon, 22 May 2017 11:20:11 +0200 Subject: tpm: Apply a sane minimum adapterlimit value for retransmission. When the I2C Infineon part is attached to an I2C adapter that imposes a size limitation, large requests will fail with -EOPNOTSUPP. Retry them with a sane minimum size without re-issuing the 0x05 command as this appears to occasionally put the TPM in a bad state. Signed-off-by: Bryan Freed [rework the patch to adapt to the feedback received] Signed-off-by: Enric Balletbo i Serra Acked-by: Andrew Lunn Reviewed-by: Jarkko Sakkinen Reviewed-by: Andrew Lunn Signed-off-by: Jarkko Sakkinen --- drivers/char/tpm/tpm_i2c_infineon.c | 76 +++++++++++++++++++++++++++---------- 1 file changed, 56 insertions(+), 20 deletions(-) diff --git a/drivers/char/tpm/tpm_i2c_infineon.c b/drivers/char/tpm/tpm_i2c_infineon.c index dc47fa222a26..79d6bbb58e39 100644 --- a/drivers/char/tpm/tpm_i2c_infineon.c +++ b/drivers/char/tpm/tpm_i2c_infineon.c @@ -70,6 +70,7 @@ struct tpm_inf_dev { u8 buf[TPM_BUFSIZE + sizeof(u8)]; /* max. buffer size + addr */ struct tpm_chip *chip; enum i2c_chip_type chip_type; + unsigned int adapterlimit; }; static struct tpm_inf_dev tpm_dev; @@ -111,6 +112,7 @@ static int iic_tpm_read(u8 addr, u8 *buffer, size_t len) int rc = 0; int count; + unsigned int msglen = len; /* Lock the adapter for the duration of the whole sequence. */ if (!tpm_dev.client->adapter->algo->master_xfer) @@ -131,27 +133,61 @@ static int iic_tpm_read(u8 addr, u8 *buffer, size_t len) usleep_range(SLEEP_DURATION_LOW, SLEEP_DURATION_HI); } } else { - /* slb9635 protocol should work in all cases */ - for (count = 0; count < MAX_COUNT; count++) { - rc = __i2c_transfer(tpm_dev.client->adapter, &msg1, 1); - if (rc > 0) - break; /* break here to skip sleep */ - - usleep_range(SLEEP_DURATION_LOW, SLEEP_DURATION_HI); - } - - if (rc <= 0) - goto out; - - /* After the TPM has successfully received the register address - * it needs some time, thus we're sleeping here again, before - * retrieving the data + /* Expect to send one command message and one data message, but + * support looping over each or both if necessary. */ - for (count = 0; count < MAX_COUNT; count++) { - usleep_range(SLEEP_DURATION_LOW, SLEEP_DURATION_HI); - rc = __i2c_transfer(tpm_dev.client->adapter, &msg2, 1); - if (rc > 0) - break; + while (len > 0) { + /* slb9635 protocol should work in all cases */ + for (count = 0; count < MAX_COUNT; count++) { + rc = __i2c_transfer(tpm_dev.client->adapter, + &msg1, 1); + if (rc > 0) + break; /* break here to skip sleep */ + + usleep_range(SLEEP_DURATION_LOW, + SLEEP_DURATION_HI); + } + + if (rc <= 0) + goto out; + + /* After the TPM has successfully received the register + * address it needs some time, thus we're sleeping here + * again, before retrieving the data + */ + for (count = 0; count < MAX_COUNT; count++) { + if (tpm_dev.adapterlimit) { + msglen = min_t(unsigned int, + tpm_dev.adapterlimit, + len); + msg2.len = msglen; + } + usleep_range(SLEEP_DURATION_LOW, + SLEEP_DURATION_HI); + rc = __i2c_transfer(tpm_dev.client->adapter, + &msg2, 1); + if (rc > 0) { + /* Since len is unsigned, make doubly + * sure we do not underflow it. + */ + if (msglen > len) + len = 0; + else + len -= msglen; + msg2.buf += msglen; + break; + } + /* If the I2C adapter rejected the request (e.g + * when the quirk read_max_len < len) fall back + * to a sane minimum value and try again. + */ + if (rc == -EOPNOTSUPP) + tpm_dev.adapterlimit = + I2C_SMBUS_BLOCK_MAX; + } + + if (rc <= 0) + goto out; } } -- cgit v1.2.3 From 124bdcf4a697f9672d1150de60c5ea489bcad201 Mon Sep 17 00:00:00 2001 From: Jarkko Sakkinen Date: Sun, 7 May 2017 20:50:02 +0300 Subject: tpm: fix byte order related arithmetic inconsistency in tpm_getcap() You should not do arithmetic with __be32 or __le32 types because sometimes it results incorrect results. Calculations must be done only with integers that are in in the CPU byte order. This commit migrates tpm_getcap() to struct tpm_buf in order to sort out these issues. Signed-off-by: Jarkko Sakkinen Reviewed-by: Jason Gunthorpe --- drivers/char/tpm/tpm-interface.c | 30 ++++++++++++++++-------------- drivers/char/tpm/tpm.h | 13 ------------- 2 files changed, 16 insertions(+), 27 deletions(-) diff --git a/drivers/char/tpm/tpm-interface.c b/drivers/char/tpm/tpm-interface.c index 4ed08ab4d2a8..be5415923886 100644 --- a/drivers/char/tpm/tpm-interface.c +++ b/drivers/char/tpm/tpm-interface.c @@ -552,31 +552,33 @@ static const struct tpm_input_header tpm_getcap_header = { ssize_t tpm_getcap(struct tpm_chip *chip, u32 subcap_id, cap_t *cap, const char *desc, size_t min_cap_length) { - struct tpm_cmd_t tpm_cmd; + struct tpm_buf buf; int rc; - tpm_cmd.header.in = tpm_getcap_header; + rc = tpm_buf_init(&buf, TPM_TAG_RQU_COMMAND, TPM_ORD_GET_CAP); + if (rc) + return rc; + if (subcap_id == TPM_CAP_VERSION_1_1 || subcap_id == TPM_CAP_VERSION_1_2) { - tpm_cmd.params.getcap_in.cap = cpu_to_be32(subcap_id); - /*subcap field not necessary */ - tpm_cmd.params.getcap_in.subcap_size = cpu_to_be32(0); - tpm_cmd.header.in.length -= cpu_to_be32(sizeof(__be32)); + tpm_buf_append_u32(&buf, subcap_id); + tpm_buf_append_u32(&buf, 0); } else { if (subcap_id == TPM_CAP_FLAG_PERM || subcap_id == TPM_CAP_FLAG_VOL) - tpm_cmd.params.getcap_in.cap = - cpu_to_be32(TPM_CAP_FLAG); + tpm_buf_append_u32(&buf, TPM_CAP_FLAG); else - tpm_cmd.params.getcap_in.cap = - cpu_to_be32(TPM_CAP_PROP); - tpm_cmd.params.getcap_in.subcap_size = cpu_to_be32(4); - tpm_cmd.params.getcap_in.subcap = cpu_to_be32(subcap_id); + tpm_buf_append_u32(&buf, TPM_CAP_PROP); + + tpm_buf_append_u32(&buf, 4); + tpm_buf_append_u32(&buf, subcap_id); } - rc = tpm_transmit_cmd(chip, NULL, &tpm_cmd, TPM_INTERNAL_RESULT_SIZE, + rc = tpm_transmit_cmd(chip, NULL, buf.data, PAGE_SIZE, min_cap_length, 0, desc); if (!rc) - *cap = tpm_cmd.params.getcap_out.cap; + *cap = *(cap_t *)&buf.data[TPM_HEADER_SIZE + 4]; + + tpm_buf_destroy(&buf); return rc; } EXPORT_SYMBOL_GPL(tpm_getcap); diff --git a/drivers/char/tpm/tpm.h b/drivers/char/tpm/tpm.h index e81d8c7acb39..dd1173427fb2 100644 --- a/drivers/char/tpm/tpm.h +++ b/drivers/char/tpm/tpm.h @@ -339,17 +339,6 @@ enum tpm_sub_capabilities { TPM_CAP_PROP_TIS_DURATION = 0x120, }; -struct tpm_getcap_params_in { - __be32 cap; - __be32 subcap_size; - __be32 subcap; -} __packed; - -struct tpm_getcap_params_out { - __be32 cap_size; - cap_t cap; -} __packed; - struct tpm_readpubek_params_out { u8 algorithm[4]; u8 encscheme[2]; @@ -399,10 +388,8 @@ struct tpm_startup_in { } __packed; typedef union { - struct tpm_getcap_params_out getcap_out; struct tpm_readpubek_params_out readpubek_out; u8 readpubek_out_buffer[sizeof(struct tpm_readpubek_params_out)]; - struct tpm_getcap_params_in getcap_in; struct tpm_pcrread_in pcrread_in; struct tpm_pcrread_out pcrread_out; struct tpm_pcrextend_in pcrextend_in; -- cgit v1.2.3 From 30bbafe3e0d4be1b0570dc620bc362ca2f516160 Mon Sep 17 00:00:00 2001 From: Jarkko Sakkinen Date: Wed, 24 May 2017 14:29:16 -0700 Subject: tpm, tpm_infineon: remove useless snprintf() calls The memory copy from rodata to stack is useless. Signed-off-by: Jarkko Sakkinen Reviewed-by: Peter Huewe --- drivers/char/tpm/tpm_infineon.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/char/tpm/tpm_infineon.c b/drivers/char/tpm/tpm_infineon.c index e3cf9f3545c5..3b1b9f9322d5 100644 --- a/drivers/char/tpm/tpm_infineon.c +++ b/drivers/char/tpm/tpm_infineon.c @@ -397,7 +397,7 @@ static int tpm_inf_pnp_probe(struct pnp_dev *dev, int vendorid[2]; int version[2]; int productid[2]; - char chipname[20]; + const char *chipname; struct tpm_chip *chip; /* read IO-ports through PnP */ @@ -488,13 +488,13 @@ static int tpm_inf_pnp_probe(struct pnp_dev *dev, switch ((productid[0] << 8) | productid[1]) { case 6: - snprintf(chipname, sizeof(chipname), " (SLD 9630 TT 1.1)"); + chipname = " (SLD 9630 TT 1.1)"; break; case 11: - snprintf(chipname, sizeof(chipname), " (SLB 9635 TT 1.2)"); + chipname = " (SLB 9635 TT 1.2)"; break; default: - snprintf(chipname, sizeof(chipname), " (unknown chip)"); + chipname = " (unknown chip)"; break; } -- cgit v1.2.3 From 8816188f060a791e08eacfeba3d28343b931872b Mon Sep 17 00:00:00 2001 From: Jarkko Sakkinen Date: Wed, 24 May 2017 15:26:08 -0700 Subject: tpm: remove struct tpm_pcrextend_in Removed struct tpm_pcrextend_in as it is not used for anything anymore. Signed-off-by: Jarkko Sakkinen Reviewed-by: Peter Huewe --- drivers/char/tpm/tpm.h | 6 ------ 1 file changed, 6 deletions(-) diff --git a/drivers/char/tpm/tpm.h b/drivers/char/tpm/tpm.h index dd1173427fb2..af05c1403c6e 100644 --- a/drivers/char/tpm/tpm.h +++ b/drivers/char/tpm/tpm.h @@ -363,11 +363,6 @@ struct tpm_pcrread_in { __be32 pcr_idx; } __packed; -struct tpm_pcrextend_in { - __be32 pcr_idx; - u8 hash[TPM_DIGEST_SIZE]; -} __packed; - /* 128 bytes is an arbitrary cap. This could be as large as TPM_BUFSIZE - 18 * bytes, but 128 is still a relatively large number of random bytes and * anything much bigger causes users of struct tpm_cmd_t to start getting @@ -392,7 +387,6 @@ typedef union { u8 readpubek_out_buffer[sizeof(struct tpm_readpubek_params_out)]; struct tpm_pcrread_in pcrread_in; struct tpm_pcrread_out pcrread_out; - struct tpm_pcrextend_in pcrextend_in; struct tpm_getrandom_in getrandom_in; struct tpm_getrandom_out getrandom_out; struct tpm_startup_in startup_in; -- cgit v1.2.3 From 5e9fefd26b47205e423b23c3f0a41b068c84fa1d Mon Sep 17 00:00:00 2001 From: Peter Huewe Date: Thu, 25 May 2017 07:43:05 +0200 Subject: tpm, tpmrm: Mark tpmrm_write as static sparse complains that tpmrm_write can be made static, and since it is right we make it static. Signed-off-by: Peter Huewe Reviewed-by: Jarkko Sakkinen Signed-off-by: Jarkko Sakkinen --- drivers/char/tpm/tpmrm-dev.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/char/tpm/tpmrm-dev.c b/drivers/char/tpm/tpmrm-dev.c index c636e7fdd1f5..1a0e97a5da5a 100644 --- a/drivers/char/tpm/tpmrm-dev.c +++ b/drivers/char/tpm/tpmrm-dev.c @@ -45,7 +45,7 @@ static int tpmrm_release(struct inode *inode, struct file *file) return 0; } -ssize_t tpmrm_write(struct file *file, const char __user *buf, +static ssize_t tpmrm_write(struct file *file, const char __user *buf, size_t size, loff_t *off) { struct file_priv *fpriv = file->private_data; -- cgit v1.2.3 From 402149c6470d9562fe6891e0165df7f5f6bff7a7 Mon Sep 17 00:00:00 2001 From: Stefan Berger Date: Thu, 25 May 2017 18:29:13 -0400 Subject: tpm: vtpm_proxy: Suppress error logging when in closed state Suppress the error logging when the core TPM driver sends commands to the VTPM proxy driver and -EPIPE is returned in case the VTPM proxy driver is 'closed' (closed anonymous file descriptor). This error code is only returned by the send function and by tpm_transmit when the VTPM proxy driver is being used. Signed-off-by: Stefan Berger Reviewed-by: Jarkko Sakkinen Tested-by: Jarkko Sakkinen Signed-off-by: Jarkko Sakkinen --- drivers/char/tpm/tpm-interface.c | 5 +++-- drivers/char/tpm/tpm2-cmd.c | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/drivers/char/tpm/tpm-interface.c b/drivers/char/tpm/tpm-interface.c index be5415923886..a965a9f0e5d2 100644 --- a/drivers/char/tpm/tpm-interface.c +++ b/drivers/char/tpm/tpm-interface.c @@ -429,8 +429,9 @@ ssize_t tpm_transmit(struct tpm_chip *chip, struct tpm_space *space, rc = chip->ops->send(chip, (u8 *) buf, count); if (rc < 0) { - dev_err(&chip->dev, - "tpm_transmit: tpm_send: error %d\n", rc); + if (rc != -EPIPE) + dev_err(&chip->dev, + "%s: tpm_send: error %d\n", __func__, rc); goto out; } diff --git a/drivers/char/tpm/tpm2-cmd.c b/drivers/char/tpm/tpm2-cmd.c index 3ee6883f26c1..3a9964326279 100644 --- a/drivers/char/tpm/tpm2-cmd.c +++ b/drivers/char/tpm/tpm2-cmd.c @@ -840,7 +840,7 @@ void tpm2_shutdown(struct tpm_chip *chip, u16 shutdown_type) /* In places where shutdown command is sent there's no much we can do * except print the error code on a system failure. */ - if (rc < 0) + if (rc < 0 && rc != -EPIPE) dev_warn(&chip->dev, "transmit returned %d while stopping the TPM", rc); } -- cgit v1.2.3 From 85ab3bf305b96e5f4c83b23a0b7e11d90144eb18 Mon Sep 17 00:00:00 2001 From: Stefan Berger Date: Wed, 24 May 2017 17:39:39 -0400 Subject: tpm: Introduce flag TPM_TRANSMIT_RAW Introduce the flag TPM_TRANSMIT_RAW that allows us to transmit a command without recursing into the requesting of locality. Signed-off-by: Stefan Berger Reviewed-by: Jarkko Sakkinen Signed-off-by: Jarkko Sakkinen --- drivers/char/tpm/tpm-interface.c | 3 ++- drivers/char/tpm/tpm.h | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/char/tpm/tpm-interface.c b/drivers/char/tpm/tpm-interface.c index a965a9f0e5d2..8ef5e1723efb 100644 --- a/drivers/char/tpm/tpm-interface.c +++ b/drivers/char/tpm/tpm-interface.c @@ -416,7 +416,8 @@ ssize_t tpm_transmit(struct tpm_chip *chip, struct tpm_space *space, /* Store the decision as chip->locality will be changed. */ need_locality = chip->locality == -1; - if (need_locality && chip->ops->request_locality) { + if (!(flags & TPM_TRANSMIT_RAW) && + need_locality && chip->ops->request_locality) { rc = chip->ops->request_locality(chip, 0); if (rc < 0) goto out_no_locality; diff --git a/drivers/char/tpm/tpm.h b/drivers/char/tpm/tpm.h index af05c1403c6e..1df0521138d3 100644 --- a/drivers/char/tpm/tpm.h +++ b/drivers/char/tpm/tpm.h @@ -506,6 +506,7 @@ extern struct idr dev_nums_idr; enum tpm_transmit_flags { TPM_TRANSMIT_UNLOCKED = BIT(0), + TPM_TRANSMIT_RAW = BIT(1), }; ssize_t tpm_transmit(struct tpm_chip *chip, struct tpm_space *space, -- cgit v1.2.3 From be4c9acfe2976b6e024d15656254d2eb207b83a8 Mon Sep 17 00:00:00 2001 From: Stefan Berger Date: Wed, 24 May 2017 17:39:40 -0400 Subject: tpm: vtpm_proxy: Implement request_locality function. Implement the request_locality function. To set the locality on the backend we define vendor-specific TPM 1.2 and TPM 2 ordinals and send a command to the backend to set the locality for the next commands. To avoid recursing into requesting the locality, we set the TPM_TRANSMIT_RAW flag when calling tpm_transmit_cmd. To avoid recursing into TPM 2 space related commands, we set the space parameter to NULL. Signed-off-by: Stefan Berger Reviewed-by: Jarkko Sakkinen Signed-off-by: Jarkko Sakkinen --- drivers/char/tpm/tpm-interface.c | 1 + drivers/char/tpm/tpm_vtpm_proxy.c | 36 ++++++++++++++++++++++++++++++++++++ include/uapi/linux/vtpm_proxy.h | 4 ++++ 3 files changed, 41 insertions(+) diff --git a/drivers/char/tpm/tpm-interface.c b/drivers/char/tpm/tpm-interface.c index 8ef5e1723efb..d2b4df6d9894 100644 --- a/drivers/char/tpm/tpm-interface.c +++ b/drivers/char/tpm/tpm-interface.c @@ -538,6 +538,7 @@ ssize_t tpm_transmit_cmd(struct tpm_chip *chip, struct tpm_space *space, return 0; } +EXPORT_SYMBOL_GPL(tpm_transmit_cmd); #define TPM_DIGEST_SIZE 20 #define TPM_RET_CODE_IDX 6 diff --git a/drivers/char/tpm/tpm_vtpm_proxy.c b/drivers/char/tpm/tpm_vtpm_proxy.c index 751059d2140a..66024bf92097 100644 --- a/drivers/char/tpm/tpm_vtpm_proxy.c +++ b/drivers/char/tpm/tpm_vtpm_proxy.c @@ -371,6 +371,41 @@ static bool vtpm_proxy_tpm_req_canceled(struct tpm_chip *chip, u8 status) return ret; } +static int vtpm_proxy_request_locality(struct tpm_chip *chip, int locality) +{ + struct tpm_buf buf; + int rc; + const struct tpm_output_header *header; + + if (chip->flags & TPM_CHIP_FLAG_TPM2) + rc = tpm_buf_init(&buf, TPM2_ST_SESSIONS, + TPM2_CC_SET_LOCALITY); + else + rc = tpm_buf_init(&buf, TPM_TAG_RQU_COMMAND, + TPM_ORD_SET_LOCALITY); + if (rc) + return rc; + tpm_buf_append_u8(&buf, locality); + + rc = tpm_transmit_cmd(chip, NULL, buf.data, tpm_buf_length(&buf), 0, + TPM_TRANSMIT_UNLOCKED | TPM_TRANSMIT_RAW, + "attempting to set locality"); + if (rc < 0) { + locality = rc; + goto out; + } + + header = (const struct tpm_output_header *)buf.data; + rc = be32_to_cpu(header->return_code); + if (rc) + locality = -1; + +out: + tpm_buf_destroy(&buf); + + return locality; +} + static const struct tpm_class_ops vtpm_proxy_tpm_ops = { .flags = TPM_OPS_AUTO_STARTUP, .recv = vtpm_proxy_tpm_op_recv, @@ -380,6 +415,7 @@ static const struct tpm_class_ops vtpm_proxy_tpm_ops = { .req_complete_mask = VTPM_PROXY_REQ_COMPLETE_FLAG, .req_complete_val = VTPM_PROXY_REQ_COMPLETE_FLAG, .req_canceled = vtpm_proxy_tpm_req_canceled, + .request_locality = vtpm_proxy_request_locality, }; /* diff --git a/include/uapi/linux/vtpm_proxy.h b/include/uapi/linux/vtpm_proxy.h index a69e991eb080..58ac73cd38fe 100644 --- a/include/uapi/linux/vtpm_proxy.h +++ b/include/uapi/linux/vtpm_proxy.h @@ -46,4 +46,8 @@ struct vtpm_proxy_new_dev { #define VTPM_PROXY_IOC_NEW_DEV _IOWR(0xa1, 0x00, struct vtpm_proxy_new_dev) +/* vendor specific commands to set locality */ +#define TPM2_CC_SET_LOCALITY 0x20001000 +#define TPM_ORD_SET_LOCALITY 0x20001000 + #endif /* _UAPI_LINUX_VTPM_PROXY_H */ -- cgit v1.2.3 From d8b5d94538eb1cb18be36048b0ddb9bd2e80a252 Mon Sep 17 00:00:00 2001 From: Stefan Berger Date: Wed, 24 May 2017 17:39:41 -0400 Subject: tpm: vtpm_proxy: Prevent userspace from sending driver command To prevent userspace from sending the TPM driver command to set the locality, we need to check every command that is sent from user space. To distinguish user space commands from internally sent commands we introduce an additional state flag STATE_DRIVER_COMMAND that is set while the driver sends this command. Similar to the TPM 2 space commands we return an error code when this command is detected. Signed-off-by: Stefan Berger Reviewed-by: Jarkko Sakkinen Signed-off-by: Jarkko Sakkinen --- drivers/char/tpm/tpm_vtpm_proxy.c | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/drivers/char/tpm/tpm_vtpm_proxy.c b/drivers/char/tpm/tpm_vtpm_proxy.c index 66024bf92097..1d877cc9af97 100644 --- a/drivers/char/tpm/tpm_vtpm_proxy.c +++ b/drivers/char/tpm/tpm_vtpm_proxy.c @@ -43,6 +43,7 @@ struct proxy_dev { #define STATE_OPENED_FLAG BIT(0) #define STATE_WAIT_RESPONSE_FLAG BIT(1) /* waiting for emulator response */ #define STATE_REGISTERED_FLAG BIT(2) +#define STATE_DRIVER_COMMAND BIT(3) /* sending a driver specific command */ size_t req_len; /* length of queued TPM request */ size_t resp_len; /* length of queued TPM response */ @@ -299,6 +300,28 @@ out: return len; } +static int vtpm_proxy_is_driver_command(struct tpm_chip *chip, + u8 *buf, size_t count) +{ + struct tpm_input_header *hdr = (struct tpm_input_header *)buf; + + if (count < sizeof(struct tpm_input_header)) + return 0; + + if (chip->flags & TPM_CHIP_FLAG_TPM2) { + switch (be32_to_cpu(hdr->ordinal)) { + case TPM2_CC_SET_LOCALITY: + return 1; + } + } else { + switch (be32_to_cpu(hdr->ordinal)) { + case TPM_ORD_SET_LOCALITY: + return 1; + } + } + return 0; +} + /* * Called when core TPM driver forwards TPM requests to 'server side'. * @@ -321,6 +344,10 @@ static int vtpm_proxy_tpm_op_send(struct tpm_chip *chip, u8 *buf, size_t count) return -EIO; } + if (!(proxy_dev->state & STATE_DRIVER_COMMAND) && + vtpm_proxy_is_driver_command(chip, buf, count)) + return -EFAULT; + mutex_lock(&proxy_dev->buf_lock); if (!(proxy_dev->state & STATE_OPENED_FLAG)) { @@ -376,6 +403,7 @@ static int vtpm_proxy_request_locality(struct tpm_chip *chip, int locality) struct tpm_buf buf; int rc; const struct tpm_output_header *header; + struct proxy_dev *proxy_dev = dev_get_drvdata(&chip->dev); if (chip->flags & TPM_CHIP_FLAG_TPM2) rc = tpm_buf_init(&buf, TPM2_ST_SESSIONS, @@ -387,9 +415,14 @@ static int vtpm_proxy_request_locality(struct tpm_chip *chip, int locality) return rc; tpm_buf_append_u8(&buf, locality); + proxy_dev->state |= STATE_DRIVER_COMMAND; + rc = tpm_transmit_cmd(chip, NULL, buf.data, tpm_buf_length(&buf), 0, TPM_TRANSMIT_UNLOCKED | TPM_TRANSMIT_RAW, "attempting to set locality"); + + proxy_dev->state &= ~STATE_DRIVER_COMMAND; + if (rc < 0) { locality = rc; goto out; -- cgit v1.2.3 From 23c3beae581f7cee193c078093a4696040dd380a Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Mon, 12 Jun 2017 18:44:16 +0300 Subject: tpm/st33zp24: Switch to devm_acpi_dev_add_driver_gpios() Switch to use managed variant of acpi_dev_add_driver_gpios() to simplify error path and fix potentially wrong assignment if ->probe() fails. Signed-off-by: Andy Shevchenko Reviewed-by: Jarkko Sakkinen Tested-by: Jarkko Sakkinen (compilation) Signed-off-by: Jarkko Sakkinen --- drivers/char/tpm/st33zp24/i2c.c | 3 +-- drivers/char/tpm/st33zp24/spi.c | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/drivers/char/tpm/st33zp24/i2c.c b/drivers/char/tpm/st33zp24/i2c.c index 1b10e38f214e..be5d1abd3e8e 100644 --- a/drivers/char/tpm/st33zp24/i2c.c +++ b/drivers/char/tpm/st33zp24/i2c.c @@ -127,7 +127,7 @@ static int st33zp24_i2c_acpi_request_resources(struct i2c_client *client) struct device *dev = &client->dev; int ret; - ret = acpi_dev_add_driver_gpios(ACPI_COMPANION(dev), acpi_st33zp24_gpios); + ret = devm_acpi_dev_add_driver_gpios(dev, acpi_st33zp24_gpios); if (ret) return ret; @@ -285,7 +285,6 @@ static int st33zp24_i2c_remove(struct i2c_client *client) if (ret) return ret; - acpi_dev_remove_driver_gpios(ACPI_COMPANION(&client->dev)); return 0; } diff --git a/drivers/char/tpm/st33zp24/spi.c b/drivers/char/tpm/st33zp24/spi.c index c69d15198f84..0fc4f20b5f83 100644 --- a/drivers/char/tpm/st33zp24/spi.c +++ b/drivers/char/tpm/st33zp24/spi.c @@ -246,7 +246,7 @@ static int st33zp24_spi_acpi_request_resources(struct spi_device *spi_dev) struct device *dev = &spi_dev->dev; int ret; - ret = acpi_dev_add_driver_gpios(ACPI_COMPANION(dev), acpi_st33zp24_gpios); + ret = devm_acpi_dev_add_driver_gpios(dev, acpi_st33zp24_gpios); if (ret) return ret; @@ -402,7 +402,6 @@ static int st33zp24_spi_remove(struct spi_device *dev) if (ret) return ret; - acpi_dev_remove_driver_gpios(ACPI_COMPANION(&dev->dev)); return 0; } -- cgit v1.2.3 From e4b0852798bc15ed1a3ed6768ef2c4d2a1cb7599 Mon Sep 17 00:00:00 2001 From: "Gustavo A. R. Silva" Date: Tue, 13 Jun 2017 14:55:42 -0500 Subject: tpm/tpm_atmel: remove unnecessary NULL check Remove unnecessary NULL check. Pointer _chip_ cannot be NULL in this instance. Signed-off-by: Gustavo A. R. Silva Reviewed-by: Jarkko Sakkinen Tested-by: Jarkko Sakkinen (compilation) Signed-off-by: Jarkko Sakkinen --- drivers/char/tpm/tpm_atmel.c | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/drivers/char/tpm/tpm_atmel.c b/drivers/char/tpm/tpm_atmel.c index 0d322ab11faa..66a14526aaf4 100644 --- a/drivers/char/tpm/tpm_atmel.c +++ b/drivers/char/tpm/tpm_atmel.c @@ -144,13 +144,11 @@ static void atml_plat_remove(void) struct tpm_chip *chip = dev_get_drvdata(&pdev->dev); struct tpm_atmel_priv *priv = dev_get_drvdata(&chip->dev); - if (chip) { - tpm_chip_unregister(chip); - if (priv->have_region) - atmel_release_region(priv->base, priv->region_size); - atmel_put_base_addr(priv->iobase); - platform_device_unregister(pdev); - } + tpm_chip_unregister(chip); + if (priv->have_region) + atmel_release_region(priv->base, priv->region_size); + atmel_put_base_addr(priv->iobase); + platform_device_unregister(pdev); } static SIMPLE_DEV_PM_OPS(tpm_atml_pm, tpm_pm_suspend, tpm_pm_resume); -- cgit v1.2.3 From 6a3911837da0a90ed599fd0a9836472f5e7ddf1b Mon Sep 17 00:00:00 2001 From: Jeff Vander Stoep Date: Tue, 20 Jun 2017 09:35:33 -0700 Subject: selinux: enable genfscon labeling for tracefs In kernel version 4.1, tracefs was separated from debugfs into its own filesystem. Prior to this split, files in /sys/kernel/debug/tracing could be labeled during filesystem creation using genfscon or later from userspace using setxattr. This change re-enables support for genfscon labeling. Signed-off-by: Jeff Vander Stoep Acked-by: Stephen Smalley Signed-off-by: Paul Moore --- security/selinux/hooks.c | 1 + 1 file changed, 1 insertion(+) diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index 9cc042df10d1..3a06afbd2f6f 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -813,6 +813,7 @@ static int selinux_set_mnt_opts(struct super_block *sb, sbsec->flags |= SE_SBPROC | SE_SBGENFS; if (!strcmp(sb->s_type->name, "debugfs") || + !strcmp(sb->s_type->name, "tracefs") || !strcmp(sb->s_type->name, "sysfs") || !strcmp(sb->s_type->name, "pstore")) sbsec->flags |= SE_SBGENFS; -- cgit v1.2.3 From 33ce9549cfa1e71d77bc91a2e67e65d693e2e53f Mon Sep 17 00:00:00 2001 From: Mimi Zohar Date: Mon, 24 Apr 2017 12:04:09 -0400 Subject: ima: extend the "ima_policy" boot command line to support multiple policies Add support for providing multiple builtin policies on the "ima_policy=" boot command line. Use "|" as the delimitor separating the policy names. Signed-off-by: Mimi Zohar --- Documentation/admin-guide/kernel-parameters.txt | 17 +++++++++++------ security/integrity/ima/ima_policy.c | 15 ++++++++++----- 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt index 15f79c27748d..9b4381fee877 100644 --- a/Documentation/admin-guide/kernel-parameters.txt +++ b/Documentation/admin-guide/kernel-parameters.txt @@ -1477,12 +1477,17 @@ in crypto/hash_info.h. ima_policy= [IMA] - The builtin measurement policy to load during IMA - setup. Specyfing "tcb" as the value, measures all - programs exec'd, files mmap'd for exec, and all files - opened with the read mode bit set by either the - effective uid (euid=0) or uid=0. - Format: "tcb" + The builtin policies to load during IMA setup. + Format: "tcb | appraise_tcb" + + The "tcb" policy measures all programs exec'd, files + mmap'd for exec, and all files opened with the read + mode bit set by either the effective uid (euid=0) or + uid=0. + + The "appraise_tcb" policy appraises the integrity of + all files owned by root. (This is the equivalent + of ima_appraise_tcb.) ima_tcb [IMA] Deprecated. Use ima_policy= instead. Load a policy which meets the needs of the Trusted diff --git a/security/integrity/ima/ima_policy.c b/security/integrity/ima/ima_policy.c index 3ab1067db624..0ddc41389a9c 100644 --- a/security/integrity/ima/ima_policy.c +++ b/security/integrity/ima/ima_policy.c @@ -170,19 +170,24 @@ static int __init default_measure_policy_setup(char *str) } __setup("ima_tcb", default_measure_policy_setup); +static bool ima_use_appraise_tcb __initdata; static int __init policy_setup(char *str) { - if (ima_policy) - return 1; + char *p; - if (strcmp(str, "tcb") == 0) - ima_policy = DEFAULT_TCB; + while ((p = strsep(&str, " |\n")) != NULL) { + if (*p == ' ') + continue; + if ((strcmp(p, "tcb") == 0) && !ima_policy) + ima_policy = DEFAULT_TCB; + else if (strcmp(p, "appraise_tcb") == 0) + ima_use_appraise_tcb = 1; + } return 1; } __setup("ima_policy=", policy_setup); -static bool ima_use_appraise_tcb __initdata; static int __init default_appraise_policy_setup(char *str) { ima_use_appraise_tcb = 1; -- cgit v1.2.3 From 503ceaef8e2e7dbbdb04a867acc6fe4c548ede7f Mon Sep 17 00:00:00 2001 From: Mimi Zohar Date: Fri, 21 Apr 2017 18:58:27 -0400 Subject: ima: define a set of appraisal rules requiring file signatures The builtin "ima_appraise_tcb" policy should require file signatures for at least a few of the hooks (eg. kernel modules, firmware, and the kexec kernel image), but changing it would break the existing userspace/kernel ABI. This patch defines a new builtin policy named "secure_boot", which can be specified on the "ima_policy=" boot command line, independently or in conjunction with the "ima_appraise_tcb" policy, by specifing ima_policy="appraise_tcb | secure_boot". The new appraisal rules requiring file signatures will be added prior to the "ima_appraise_tcb" rules. Signed-off-by: Mimi Zohar Changelog: - Reference secure boot in the new builtin policy name. (Thiago Bauermann) --- Documentation/admin-guide/kernel-parameters.txt | 6 +++++- security/integrity/ima/ima_policy.c | 26 ++++++++++++++++++++++++- 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt index 9b4381fee877..e438a1fca554 100644 --- a/Documentation/admin-guide/kernel-parameters.txt +++ b/Documentation/admin-guide/kernel-parameters.txt @@ -1478,7 +1478,7 @@ ima_policy= [IMA] The builtin policies to load during IMA setup. - Format: "tcb | appraise_tcb" + Format: "tcb | appraise_tcb | secure_boot" The "tcb" policy measures all programs exec'd, files mmap'd for exec, and all files opened with the read @@ -1489,6 +1489,10 @@ all files owned by root. (This is the equivalent of ima_appraise_tcb.) + The "secure_boot" policy appraises the integrity + of files (eg. kexec kernel image, kernel modules, + firmware, policy, etc) based on file signatures. + ima_tcb [IMA] Deprecated. Use ima_policy= instead. Load a policy which meets the needs of the Trusted Computing Base. This means IMA will measure all diff --git a/security/integrity/ima/ima_policy.c b/security/integrity/ima/ima_policy.c index 0ddc41389a9c..3653c86c70df 100644 --- a/security/integrity/ima/ima_policy.c +++ b/security/integrity/ima/ima_policy.c @@ -153,6 +153,17 @@ static struct ima_rule_entry default_appraise_rules[] __ro_after_init = { #endif }; +static struct ima_rule_entry secure_boot_rules[] __ro_after_init = { + {.action = APPRAISE, .func = MODULE_CHECK, + .flags = IMA_FUNC | IMA_DIGSIG_REQUIRED}, + {.action = APPRAISE, .func = FIRMWARE_CHECK, + .flags = IMA_FUNC | IMA_DIGSIG_REQUIRED}, + {.action = APPRAISE, .func = KEXEC_KERNEL_CHECK, + .flags = IMA_FUNC | IMA_DIGSIG_REQUIRED}, + {.action = APPRAISE, .func = POLICY_CHECK, + .flags = IMA_FUNC | IMA_DIGSIG_REQUIRED}, +}; + static LIST_HEAD(ima_default_rules); static LIST_HEAD(ima_policy_rules); static LIST_HEAD(ima_temp_rules); @@ -171,6 +182,7 @@ static int __init default_measure_policy_setup(char *str) __setup("ima_tcb", default_measure_policy_setup); static bool ima_use_appraise_tcb __initdata; +static bool ima_use_secure_boot __initdata; static int __init policy_setup(char *str) { char *p; @@ -182,6 +194,8 @@ static int __init policy_setup(char *str) ima_policy = DEFAULT_TCB; else if (strcmp(p, "appraise_tcb") == 0) ima_use_appraise_tcb = 1; + else if (strcmp(p, "secure_boot") == 0) + ima_use_secure_boot = 1; } return 1; @@ -410,12 +424,14 @@ void ima_update_policy_flag(void) */ void __init ima_init_policy(void) { - int i, measure_entries, appraise_entries; + int i, measure_entries, appraise_entries, secure_boot_entries; /* if !ima_policy set entries = 0 so we load NO default rules */ measure_entries = ima_policy ? ARRAY_SIZE(dont_measure_rules) : 0; appraise_entries = ima_use_appraise_tcb ? ARRAY_SIZE(default_appraise_rules) : 0; + secure_boot_entries = ima_use_secure_boot ? + ARRAY_SIZE(secure_boot_rules) : 0; for (i = 0; i < measure_entries; i++) list_add_tail(&dont_measure_rules[i].list, &ima_default_rules); @@ -434,6 +450,14 @@ void __init ima_init_policy(void) break; } + /* + * Insert the appraise rules requiring file signatures, prior to + * any other appraise rules. + */ + for (i = 0; i < secure_boot_entries; i++) + list_add_tail(&secure_boot_rules[i].list, + &ima_default_rules); + for (i = 0; i < appraise_entries; i++) { list_add_tail(&default_appraise_rules[i].list, &ima_default_rules); -- cgit v1.2.3 From e1f5e01f4b035ced1c71b40866e4e5c0508fbb0b Mon Sep 17 00:00:00 2001 From: Mimi Zohar Date: Mon, 24 Apr 2017 22:06:49 -0400 Subject: ima: define Kconfig IMA_APPRAISE_BOOTPARAM option Permit enabling the different "ima_appraise=" modes (eg. log, fix) from the boot command line. Signed-off-by: Mimi Zohar --- security/integrity/ima/Kconfig | 8 ++++++++ security/integrity/ima/ima_appraise.c | 2 ++ 2 files changed, 10 insertions(+) diff --git a/security/integrity/ima/Kconfig b/security/integrity/ima/Kconfig index 370eb2f4dd37..8b688a26033d 100644 --- a/security/integrity/ima/Kconfig +++ b/security/integrity/ima/Kconfig @@ -155,6 +155,14 @@ config IMA_APPRAISE If unsure, say N. +config IMA_APPRAISE_BOOTPARAM + bool "ima_appraise boot parameter" + depends on IMA_APPRAISE + default y + help + This option enables the different "ima_appraise=" modes + (eg. fix, log) from the boot command line. + config IMA_TRUSTED_KEYRING bool "Require all keys on the .ima keyring be signed (deprecated)" depends on IMA_APPRAISE && SYSTEM_TRUSTED_KEYRING diff --git a/security/integrity/ima/ima_appraise.c b/security/integrity/ima/ima_appraise.c index 5d0785cfe063..ac546df73afc 100644 --- a/security/integrity/ima/ima_appraise.c +++ b/security/integrity/ima/ima_appraise.c @@ -20,12 +20,14 @@ static int __init default_appraise_setup(char *str) { +#ifdef CONFIG_IMA_APPRAISE_BOOTPARAM if (strncmp(str, "off", 3) == 0) ima_appraise = 0; else if (strncmp(str, "log", 3) == 0) ima_appraise = IMA_APPRAISE_LOG; else if (strncmp(str, "fix", 3) == 0) ima_appraise = IMA_APPRAISE_FIX; +#endif return 1; } -- cgit v1.2.3 From 6f6723e21589f4594bb72b27ddbb2f75defb33bb Mon Sep 17 00:00:00 2001 From: Mimi Zohar Date: Mon, 24 Apr 2017 22:43:52 -0400 Subject: ima: define is_ima_appraise_enabled() Only return enabled if in enforcing mode, not fix or log modes. Signed-off-by: Mimi Zohar Changes: - Define is_ima_appraise_enabled() as a bool (Thiago Bauermann) --- include/linux/ima.h | 6 ++++++ security/integrity/ima/ima_appraise.c | 10 ++++++++++ 2 files changed, 16 insertions(+) diff --git a/include/linux/ima.h b/include/linux/ima.h index 7f6952f8d6aa..0e4647e0eb60 100644 --- a/include/linux/ima.h +++ b/include/linux/ima.h @@ -75,11 +75,17 @@ static inline void ima_add_kexec_buffer(struct kimage *image) #endif #ifdef CONFIG_IMA_APPRAISE +extern bool is_ima_appraise_enabled(void); extern void ima_inode_post_setattr(struct dentry *dentry); extern int ima_inode_setxattr(struct dentry *dentry, const char *xattr_name, const void *xattr_value, size_t xattr_value_len); extern int ima_inode_removexattr(struct dentry *dentry, const char *xattr_name); #else +static inline bool is_ima_appraise_enabled(void) +{ + return 0; +} + static inline void ima_inode_post_setattr(struct dentry *dentry) { return; diff --git a/security/integrity/ima/ima_appraise.c b/security/integrity/ima/ima_appraise.c index ac546df73afc..7fe0566142d8 100644 --- a/security/integrity/ima/ima_appraise.c +++ b/security/integrity/ima/ima_appraise.c @@ -33,6 +33,16 @@ static int __init default_appraise_setup(char *str) __setup("ima_appraise=", default_appraise_setup); +/* + * is_ima_appraise_enabled - return appraise status + * + * Only return enabled, if not in ima_appraise="fix" or "log" modes. + */ +bool is_ima_appraise_enabled(void) +{ + return (ima_appraise & IMA_APPRAISE_ENFORCE) ? 1 : 0; +} + /* * ima_must_appraise - set appraise flag * -- cgit v1.2.3 From 38d192684e8b1811c352c208447d565f8f0a309f Mon Sep 17 00:00:00 2001 From: Ben Hutchings Date: Tue, 2 May 2017 19:27:00 +0100 Subject: IMA: Correct Kconfig dependencies for hash selection IMA uses the hash algorithm too early to be able to use a module. Require the selected hash algorithm to be built-in. Signed-off-by: Ben Hutchings Signed-off-by: Mimi Zohar --- security/integrity/ima/Kconfig | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/security/integrity/ima/Kconfig b/security/integrity/ima/Kconfig index 8b688a26033d..35ef69312811 100644 --- a/security/integrity/ima/Kconfig +++ b/security/integrity/ima/Kconfig @@ -96,19 +96,19 @@ choice config IMA_DEFAULT_HASH_SHA1 bool "SHA1 (default)" - depends on CRYPTO_SHA1 + depends on CRYPTO_SHA1=y config IMA_DEFAULT_HASH_SHA256 bool "SHA256" - depends on CRYPTO_SHA256 && !IMA_TEMPLATE + depends on CRYPTO_SHA256=y && !IMA_TEMPLATE config IMA_DEFAULT_HASH_SHA512 bool "SHA512" - depends on CRYPTO_SHA512 && !IMA_TEMPLATE + depends on CRYPTO_SHA512=y && !IMA_TEMPLATE config IMA_DEFAULT_HASH_WP512 bool "WP512" - depends on CRYPTO_WP512 && !IMA_TEMPLATE + depends on CRYPTO_WP512=y && !IMA_TEMPLATE endchoice config IMA_DEFAULT_HASH -- cgit v1.2.3 From 5d659f286d58d76064168c4cd7aad61a30d20c44 Mon Sep 17 00:00:00 2001 From: Tycho Andersen Date: Fri, 5 May 2017 11:15:47 -0600 Subject: ima: fix up #endif comments While reading the code, I noticed that these #endif comments don't match how they're actually nested. This patch fixes that. Signed-off-by: Tycho Andersen Signed-off-by: Mimi Zohar --- security/integrity/ima/ima.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/security/integrity/ima/ima.h b/security/integrity/ima/ima.h index b563fbd4d122..d26a30e37d13 100644 --- a/security/integrity/ima/ima.h +++ b/security/integrity/ima/ima.h @@ -306,12 +306,12 @@ static inline int security_filter_rule_match(u32 secid, u32 field, u32 op, { return -EINVAL; } -#endif /* CONFIG_IMA_TRUSTED_KEYRING */ +#endif /* CONFIG_IMA_LSM_RULES */ #ifdef CONFIG_IMA_READ_POLICY #define POLICY_FILE_FLAGS (S_IWUSR | S_IRUSR) #else #define POLICY_FILE_FLAGS S_IWUSR -#endif /* CONFIG_IMA_WRITE_POLICY */ +#endif /* CONFIG_IMA_READ_POLICY */ #endif /* __LINUX_IMA_H */ -- cgit v1.2.3 From b4e280304ddb2fb5b6970524e901fc8ae8ec6337 Mon Sep 17 00:00:00 2001 From: Geliang Tang Date: Sat, 6 May 2017 23:40:18 +0800 Subject: ima: use memdup_user_nul Use memdup_user_nul() helper instead of open-coding to simplify the code. Signed-off-by: Geliang Tang Signed-off-by: Mimi Zohar --- security/integrity/ima/ima_fs.c | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/security/integrity/ima/ima_fs.c b/security/integrity/ima/ima_fs.c index ca303e5d2b94..ad491c51e833 100644 --- a/security/integrity/ima/ima_fs.c +++ b/security/integrity/ima/ima_fs.c @@ -323,16 +323,11 @@ static ssize_t ima_write_policy(struct file *file, const char __user *buf, if (*ppos != 0) goto out; - result = -ENOMEM; - data = kmalloc(datalen + 1, GFP_KERNEL); - if (!data) + data = memdup_user_nul(buf, datalen); + if (IS_ERR(data)) { + result = PTR_ERR(data); goto out; - - *(data + datalen) = '\0'; - - result = -EFAULT; - if (copy_from_user(data, buf, datalen)) - goto out_free; + } result = mutex_lock_interruptible(&ima_write_mutex); if (result < 0) -- cgit v1.2.3 From 82e3bb4d44be21daefe8af857a68d3c9118c1048 Mon Sep 17 00:00:00 2001 From: Laura Abbott Date: Tue, 9 May 2017 11:25:27 -0700 Subject: ima: Add cgroups2 to the defaults list cgroups2 is beginning to show up in wider usage. Add it to the default nomeasure/noappraise list like other filesystems. Signed-off-by: Laura Abbott Signed-off-by: Mimi Zohar --- security/integrity/ima/ima_policy.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/security/integrity/ima/ima_policy.c b/security/integrity/ima/ima_policy.c index 3653c86c70df..0acd68decb17 100644 --- a/security/integrity/ima/ima_policy.c +++ b/security/integrity/ima/ima_policy.c @@ -96,6 +96,8 @@ static struct ima_rule_entry dont_measure_rules[] __ro_after_init = { {.action = DONT_MEASURE, .fsmagic = SELINUX_MAGIC, .flags = IMA_FSMAGIC}, {.action = DONT_MEASURE, .fsmagic = CGROUP_SUPER_MAGIC, .flags = IMA_FSMAGIC}, + {.action = DONT_MEASURE, .fsmagic = CGROUP2_SUPER_MAGIC, + .flags = IMA_FSMAGIC}, {.action = DONT_MEASURE, .fsmagic = NSFS_MAGIC, .flags = IMA_FSMAGIC} }; @@ -139,6 +141,7 @@ static struct ima_rule_entry default_appraise_rules[] __ro_after_init = { {.action = DONT_APPRAISE, .fsmagic = SELINUX_MAGIC, .flags = IMA_FSMAGIC}, {.action = DONT_APPRAISE, .fsmagic = NSFS_MAGIC, .flags = IMA_FSMAGIC}, {.action = DONT_APPRAISE, .fsmagic = CGROUP_SUPER_MAGIC, .flags = IMA_FSMAGIC}, + {.action = DONT_APPRAISE, .fsmagic = CGROUP2_SUPER_MAGIC, .flags = IMA_FSMAGIC}, #ifdef CONFIG_IMA_WRITE_POLICY {.action = APPRAISE, .func = POLICY_CHECK, .flags = IMA_FUNC | IMA_DIGSIG_REQUIRED}, -- cgit v1.2.3 From b17fd9ecf854e8f695e911d3ff9e1fe33bb1c76c Mon Sep 17 00:00:00 2001 From: Roberto Sassu Date: Tue, 16 May 2017 14:53:41 +0200 Subject: ima: introduce ima_parse_buf() ima_parse_buf() takes as input the buffer start and end pointers, and stores the result in a static array of ima_field_data structures, where the len field contains the length parsed from the buffer, and the data field contains the address of the buffer just after the length. Optionally, the function returns the current value of the buffer pointer and the number of array elements written. A bitmap has been added as parameter of ima_parse_buf() to handle the cases where the length is not prepended to data. Each bit corresponds to an element of the ima_field_data array. If a bit is set, the length is not parsed from the buffer, but is read from the corresponding element of the array (the length must be set before calling the function). ima_parse_buf() can perform three checks upon request by callers, depending on the enforce mask passed to it: - ENFORCE_FIELDS: matching of number of fields (length-data combination) - there must be enough data in the buffer to parse the number of fields requested (output: current value of buffer pointer) - ENFORCE_BUFEND: matching of buffer end - the ima_field_data array must be large enough to contain lengths and data pointers for the amount of data requested (output: number of fields written) - ENFORCE_FIELDS | ENFORCE_BUFEND: matching of both Use cases - measurement entry header: ENFORCE_FIELDS | ENFORCE_BUFEND - four fields must be parsed: pcr, digest, template name, template data - ENFORCE_BUFEND is enforced only for the last measurement entry - template digest (Crypto Agile): ENFORCE_BUFEND - since only the total template digest length is known, the function parses length-data combinations until the buffer end is reached - template data: ENFORCE_FIELDS | ENFORCE_BUFEND - since the number of fields and the total template data length are known, the function can perform both checks Signed-off-by: Roberto Sassu Signed-off-by: Mimi Zohar --- security/integrity/ima/ima_template_lib.c | 61 +++++++++++++++++++++++++++++++ security/integrity/ima/ima_template_lib.h | 6 +++ 2 files changed, 67 insertions(+) diff --git a/security/integrity/ima/ima_template_lib.c b/security/integrity/ima/ima_template_lib.c index f9ba37b3928d..28af43f63572 100644 --- a/security/integrity/ima/ima_template_lib.c +++ b/security/integrity/ima/ima_template_lib.c @@ -159,6 +159,67 @@ void ima_show_template_sig(struct seq_file *m, enum ima_show_type show, ima_show_template_field_data(m, show, DATA_FMT_HEX, field_data); } +/** + * ima_parse_buf() - Parses lengths and data from an input buffer + * @bufstartp: Buffer start address. + * @bufendp: Buffer end address. + * @bufcurp: Pointer to remaining (non-parsed) data. + * @maxfields: Length of fields array. + * @fields: Array containing lengths and pointers of parsed data. + * @curfields: Number of array items containing parsed data. + * @len_mask: Bitmap (if bit is set, data length should not be parsed). + * @enforce_mask: Check if curfields == maxfields and/or bufcurp == bufendp. + * @bufname: String identifier of the input buffer. + * + * Return: 0 on success, -EINVAL on error. + */ +int ima_parse_buf(void *bufstartp, void *bufendp, void **bufcurp, + int maxfields, struct ima_field_data *fields, int *curfields, + unsigned long *len_mask, int enforce_mask, char *bufname) +{ + void *bufp = bufstartp; + int i; + + for (i = 0; i < maxfields; i++) { + if (len_mask == NULL || !test_bit(i, len_mask)) { + if (bufp > (bufendp - sizeof(u32))) + break; + + fields[i].len = *(u32 *)bufp; + if (ima_canonical_fmt) + fields[i].len = le32_to_cpu(fields[i].len); + + bufp += sizeof(u32); + } + + if (bufp > (bufendp - fields[i].len)) + break; + + fields[i].data = bufp; + bufp += fields[i].len; + } + + if ((enforce_mask & ENFORCE_FIELDS) && i != maxfields) { + pr_err("%s: nr of fields mismatch: expected: %d, current: %d\n", + bufname, maxfields, i); + return -EINVAL; + } + + if ((enforce_mask & ENFORCE_BUFEND) && bufp != bufendp) { + pr_err("%s: buf end mismatch: expected: %p, current: %p\n", + bufname, bufendp, bufp); + return -EINVAL; + } + + if (curfields) + *curfields = i; + + if (bufcurp) + *bufcurp = bufp; + + return 0; +} + static int ima_eventdigest_init_common(u8 *digest, u32 digestsize, u8 hash_algo, struct ima_field_data *field_data) { diff --git a/security/integrity/ima/ima_template_lib.h b/security/integrity/ima/ima_template_lib.h index c344530c1d69..6a3d8b831deb 100644 --- a/security/integrity/ima/ima_template_lib.h +++ b/security/integrity/ima/ima_template_lib.h @@ -18,6 +18,9 @@ #include #include "ima.h" +#define ENFORCE_FIELDS 0x00000001 +#define ENFORCE_BUFEND 0x00000002 + void ima_show_template_digest(struct seq_file *m, enum ima_show_type show, struct ima_field_data *field_data); void ima_show_template_digest_ng(struct seq_file *m, enum ima_show_type show, @@ -26,6 +29,9 @@ void ima_show_template_string(struct seq_file *m, enum ima_show_type show, struct ima_field_data *field_data); void ima_show_template_sig(struct seq_file *m, enum ima_show_type show, struct ima_field_data *field_data); +int ima_parse_buf(void *bufstartp, void *bufendp, void **bufcurp, + int maxfields, struct ima_field_data *fields, int *curfields, + unsigned long *len_mask, int enforce_mask, char *bufname); int ima_eventdigest_init(struct ima_event_data *event_data, struct ima_field_data *field_data); int ima_eventname_init(struct ima_event_data *event_data, -- cgit v1.2.3 From 47fdee60b47fc2836b256761ab60ada26788b323 Mon Sep 17 00:00:00 2001 From: Roberto Sassu Date: Tue, 16 May 2017 14:53:42 +0200 Subject: ima: use ima_parse_buf() to parse measurements headers The binary_hdr_v1 and binary_data_v1 structures defined in ima_restore_measurement_list() have been replaced with an array of four ima_field_data structures where pcr, digest, template name and template data lengths and pointers are stored. The length of pcr and digest in the ima_field_data array and the bits in the bitmap are set before ima_parse_buf() is called. The ENFORCE_FIELDS bit is set for all entries except the last one (there is still data to parse), and ENFORCE_BUFEND is set only for the last entry. Signed-off-by: Roberto Sassu Signed-off-by: Mimi Zohar --- security/integrity/ima/ima_template.c | 80 ++++++++++++----------------------- 1 file changed, 28 insertions(+), 52 deletions(-) diff --git a/security/integrity/ima/ima_template.c b/security/integrity/ima/ima_template.c index cebb37c63629..624e2a1d0f89 100644 --- a/security/integrity/ima/ima_template.c +++ b/security/integrity/ima/ima_template.c @@ -19,6 +19,9 @@ #include "ima.h" #include "ima_template_lib.h" +enum header_fields { HDR_PCR, HDR_DIGEST, HDR_TEMPLATE_NAME, + HDR_TEMPLATE_DATA, HDR__LAST }; + static struct ima_template_desc builtin_templates[] = { {.name = IMA_TEMPLATE_IMA_NAME, .fmt = IMA_TEMPLATE_IMA_FMT}, {.name = "ima-ng", .fmt = "d-ng|n-ng"}, @@ -337,27 +340,19 @@ static int ima_restore_template_data(struct ima_template_desc *template_desc, /* Restore the serialized binary measurement list without extending PCRs. */ int ima_restore_measurement_list(loff_t size, void *buf) { - struct binary_hdr_v1 { - u32 pcr; - u8 digest[TPM_DIGEST_SIZE]; - u32 template_name_len; - char template_name[0]; - } __packed; char template_name[MAX_TEMPLATE_NAME_LEN]; - struct binary_data_v1 { - u32 template_data_size; - char template_data[0]; - } __packed; - struct ima_kexec_hdr *khdr = buf; - struct binary_hdr_v1 *hdr_v1; - struct binary_data_v1 *data_v1; + struct ima_field_data hdr[HDR__LAST] = { + [HDR_PCR] = {.len = sizeof(u32)}, + [HDR_DIGEST] = {.len = TPM_DIGEST_SIZE}, + }; void *bufp = buf + sizeof(*khdr); void *bufendp; struct ima_template_entry *entry; struct ima_template_desc *template_desc; + DECLARE_BITMAP(hdr_mask, HDR__LAST); unsigned long count = 0; int ret = 0; @@ -380,6 +375,10 @@ int ima_restore_measurement_list(loff_t size, void *buf) return -EINVAL; } + bitmap_zero(hdr_mask, HDR__LAST); + bitmap_set(hdr_mask, HDR_PCR, 1); + bitmap_set(hdr_mask, HDR_DIGEST, 1); + /* * ima kexec buffer prefix: version, buffer size, count * v1 format: pcr, digest, template-name-len, template-name, @@ -387,31 +386,25 @@ int ima_restore_measurement_list(loff_t size, void *buf) */ bufendp = buf + khdr->buffer_size; while ((bufp < bufendp) && (count++ < khdr->count)) { - hdr_v1 = bufp; - if (bufp > (bufendp - sizeof(*hdr_v1))) { - pr_err("attempting to restore partial measurement\n"); - ret = -EINVAL; - break; - } - bufp += sizeof(*hdr_v1); + int enforce_mask = ENFORCE_FIELDS; - if (ima_canonical_fmt) - hdr_v1->template_name_len = - le32_to_cpu(hdr_v1->template_name_len); + enforce_mask |= (count == khdr->count) ? ENFORCE_BUFEND : 0; + ret = ima_parse_buf(bufp, bufendp, &bufp, HDR__LAST, hdr, NULL, + hdr_mask, enforce_mask, "entry header"); + if (ret < 0) + break; - if ((hdr_v1->template_name_len >= MAX_TEMPLATE_NAME_LEN) || - (bufp > (bufendp - hdr_v1->template_name_len))) { + if (hdr[HDR_TEMPLATE_NAME].len >= MAX_TEMPLATE_NAME_LEN) { pr_err("attempting to restore a template name \ that is too long\n"); ret = -EINVAL; break; } - data_v1 = bufp += (u_int8_t)hdr_v1->template_name_len; /* template name is not null terminated */ - memcpy(template_name, hdr_v1->template_name, - hdr_v1->template_name_len); - template_name[hdr_v1->template_name_len] = 0; + memcpy(template_name, hdr[HDR_TEMPLATE_NAME].data, + hdr[HDR_TEMPLATE_NAME].len); + template_name[hdr[HDR_TEMPLATE_NAME].len] = 0; if (strcmp(template_name, "ima") == 0) { pr_err("attempting to restore an unsupported \ @@ -441,34 +434,17 @@ int ima_restore_measurement_list(loff_t size, void *buf) break; } - if (bufp > (bufendp - sizeof(data_v1->template_data_size))) { - pr_err("restoring the template data size failed\n"); - ret = -EINVAL; - break; - } - bufp += (u_int8_t) sizeof(data_v1->template_data_size); - - if (ima_canonical_fmt) - data_v1->template_data_size = - le32_to_cpu(data_v1->template_data_size); - - if (bufp > (bufendp - data_v1->template_data_size)) { - pr_err("restoring the template data failed\n"); - ret = -EINVAL; - break; - } - bufp += data_v1->template_data_size; - ret = ima_restore_template_data(template_desc, - data_v1->template_data, - data_v1->template_data_size, + hdr[HDR_TEMPLATE_DATA].data, + hdr[HDR_TEMPLATE_DATA].len, &entry); if (ret < 0) break; - memcpy(entry->digest, hdr_v1->digest, TPM_DIGEST_SIZE); - entry->pcr = - !ima_canonical_fmt ? hdr_v1->pcr : le32_to_cpu(hdr_v1->pcr); + memcpy(entry->digest, hdr[HDR_DIGEST].data, + hdr[HDR_DIGEST].len); + entry->pcr = !ima_canonical_fmt ? *(hdr[HDR_PCR].data) : + le32_to_cpu(*(hdr[HDR_PCR].data)); ret = ima_restore_measurement_entry(entry); if (ret < 0) break; -- cgit v1.2.3 From 28a8dc41279de2a8a635df51ad33d3cee7e0c0d1 Mon Sep 17 00:00:00 2001 From: Roberto Sassu Date: Tue, 16 May 2017 14:53:43 +0200 Subject: ima: use ima_parse_buf() to parse template data The binary_field_data structure definition has been removed from ima_restore_template_data(). The lengths and data pointers are directly stored into the template_data array of the ima_template_entry structure. For template data, both the number of fields and buffer end checks can be done, as these information are known (respectively from the template descriptor, and from the measurement header field). Signed-off-by: Roberto Sassu Signed-off-by: Mimi Zohar --- security/integrity/ima/ima_template.c | 44 +++++++++++------------------------ 1 file changed, 13 insertions(+), 31 deletions(-) diff --git a/security/integrity/ima/ima_template.c b/security/integrity/ima/ima_template.c index 624e2a1d0f89..7412d0291ab9 100644 --- a/security/integrity/ima/ima_template.c +++ b/security/integrity/ima/ima_template.c @@ -277,13 +277,6 @@ static int ima_restore_template_data(struct ima_template_desc *template_desc, int template_data_size, struct ima_template_entry **entry) { - struct binary_field_data { - u32 len; - u8 data[0]; - } __packed; - - struct binary_field_data *field_data; - int offset = 0; int ret = 0; int i; @@ -293,30 +286,19 @@ static int ima_restore_template_data(struct ima_template_desc *template_desc, if (!*entry) return -ENOMEM; + ret = ima_parse_buf(template_data, template_data + template_data_size, + NULL, template_desc->num_fields, + (*entry)->template_data, NULL, NULL, + ENFORCE_FIELDS | ENFORCE_BUFEND, "template data"); + if (ret < 0) { + kfree(*entry); + return ret; + } + (*entry)->template_desc = template_desc; for (i = 0; i < template_desc->num_fields; i++) { - field_data = template_data + offset; - - /* Each field of the template data is prefixed with a length. */ - if (offset > (template_data_size - sizeof(*field_data))) { - pr_err("Restoring the template field failed\n"); - ret = -EINVAL; - break; - } - offset += sizeof(*field_data); - - if (ima_canonical_fmt) - field_data->len = le32_to_cpu(field_data->len); - - if (offset > (template_data_size - field_data->len)) { - pr_err("Restoring the template field data failed\n"); - ret = -EINVAL; - break; - } - offset += field_data->len; - - (*entry)->template_data[i].len = field_data->len; - (*entry)->template_data_len += sizeof(field_data->len); + struct ima_field_data *field_data = &(*entry)->template_data[i]; + u8 *data = field_data->data; (*entry)->template_data[i].data = kzalloc(field_data->len + 1, GFP_KERNEL); @@ -324,8 +306,8 @@ static int ima_restore_template_data(struct ima_template_desc *template_desc, ret = -ENOMEM; break; } - memcpy((*entry)->template_data[i].data, field_data->data, - field_data->len); + memcpy((*entry)->template_data[i].data, data, field_data->len); + (*entry)->template_data_len += sizeof(field_data->len); (*entry)->template_data_len += field_data->len; } -- cgit v1.2.3 From e4586c79d4ba24a02f63a17e49207007c3bbdaea Mon Sep 17 00:00:00 2001 From: Roberto Sassu Date: Tue, 16 May 2017 14:53:47 +0200 Subject: ima: fix get_binary_runtime_size() Remove '+ 1' from 'size += strlen(entry->template_desc->name) + 1;', as the template name is sent to userspace without the '\0' character. Signed-off-by: Roberto Sassu Signed-off-by: Mimi Zohar --- security/integrity/ima/ima_queue.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/security/integrity/ima/ima_queue.c b/security/integrity/ima/ima_queue.c index d9aa5ab71204..a02a86d51102 100644 --- a/security/integrity/ima/ima_queue.c +++ b/security/integrity/ima/ima_queue.c @@ -81,7 +81,7 @@ static int get_binary_runtime_size(struct ima_template_entry *entry) size += sizeof(u32); /* pcr */ size += sizeof(entry->digest); size += sizeof(int); /* template name size field */ - size += strlen(entry->template_desc->name) + 1; + size += strlen(entry->template_desc->name); size += sizeof(entry->template_data_len); size += entry->template_data_len; return size; -- cgit v1.2.3 From bb543e3959b5909e7b5db4a216018c634a9d9898 Mon Sep 17 00:00:00 2001 From: Thiago Jung Bauermann Date: Wed, 7 Jun 2017 22:49:10 -0300 Subject: integrity: Small code improvements These changes are too small to warrant their own patches: The keyid and sig_size members of struct signature_v2_hdr are in BE format, so use a type that makes this assumption explicit. Also, use beXX_to_cpu instead of __beXX_to_cpu to read them. Change integrity_kernel_read to take a void * buffer instead of char * buffer, so that callers don't have to use a cast if they provide a buffer that isn't a char *. Add missing #endif comment in ima.h pointing out which macro it refers to. Add missing fall through comment in ima_appraise.c. Constify mask_tokens and func_tokens arrays. Signed-off-by: Thiago Jung Bauermann Signed-off-by: Mimi Zohar --- security/integrity/digsig_asymmetric.c | 4 ++-- security/integrity/iint.c | 2 +- security/integrity/ima/ima.h | 2 +- security/integrity/ima/ima_appraise.c | 1 + security/integrity/ima/ima_policy.c | 4 ++-- security/integrity/integrity.h | 7 ++++--- 6 files changed, 11 insertions(+), 9 deletions(-) diff --git a/security/integrity/digsig_asymmetric.c b/security/integrity/digsig_asymmetric.c index 80052ed8d467..ab6a029062a1 100644 --- a/security/integrity/digsig_asymmetric.c +++ b/security/integrity/digsig_asymmetric.c @@ -92,13 +92,13 @@ int asymmetric_verify(struct key *keyring, const char *sig, siglen -= sizeof(*hdr); - if (siglen != __be16_to_cpu(hdr->sig_size)) + if (siglen != be16_to_cpu(hdr->sig_size)) return -EBADMSG; if (hdr->hash_algo >= HASH_ALGO__LAST) return -ENOPKG; - key = request_asymmetric_key(keyring, __be32_to_cpu(hdr->keyid)); + key = request_asymmetric_key(keyring, be32_to_cpu(hdr->keyid)); if (IS_ERR(key)) return PTR_ERR(key); diff --git a/security/integrity/iint.c b/security/integrity/iint.c index c710d22042f9..6fc888ca468e 100644 --- a/security/integrity/iint.c +++ b/security/integrity/iint.c @@ -182,7 +182,7 @@ security_initcall(integrity_iintcache_init); * */ int integrity_kernel_read(struct file *file, loff_t offset, - char *addr, unsigned long count) + void *addr, unsigned long count) { mm_segment_t old_fs; char __user *buf = (char __user *)addr; diff --git a/security/integrity/ima/ima.h b/security/integrity/ima/ima.h index d26a30e37d13..215a93c41b51 100644 --- a/security/integrity/ima/ima.h +++ b/security/integrity/ima/ima.h @@ -284,7 +284,7 @@ static inline int ima_read_xattr(struct dentry *dentry, return 0; } -#endif +#endif /* CONFIG_IMA_APPRAISE */ /* LSM based policy rules require audit */ #ifdef CONFIG_IMA_LSM_RULES diff --git a/security/integrity/ima/ima_appraise.c b/security/integrity/ima/ima_appraise.c index 7fe0566142d8..ea36a4f134f4 100644 --- a/security/integrity/ima/ima_appraise.c +++ b/security/integrity/ima/ima_appraise.c @@ -240,6 +240,7 @@ int ima_appraise_measurement(enum ima_hooks func, case IMA_XATTR_DIGEST_NG: /* first byte contains algorithm id */ hash_start = 1; + /* fall through */ case IMA_XATTR_DIGEST: if (iint->flags & IMA_DIGSIG_REQUIRED) { cause = "IMA-signature-required"; diff --git a/security/integrity/ima/ima_policy.c b/security/integrity/ima/ima_policy.c index 0acd68decb17..949ad3858327 100644 --- a/security/integrity/ima/ima_policy.c +++ b/security/integrity/ima/ima_policy.c @@ -965,7 +965,7 @@ enum { mask_exec = 0, mask_write, mask_read, mask_append }; -static char *mask_tokens[] = { +static const char *const mask_tokens[] = { "MAY_EXEC", "MAY_WRITE", "MAY_READ", @@ -979,7 +979,7 @@ enum { func_policy }; -static char *func_tokens[] = { +static const char *const func_tokens[] = { "FILE_CHECK", "MMAP_CHECK", "BPRM_CHECK", diff --git a/security/integrity/integrity.h b/security/integrity/integrity.h index 24520b4ef3b0..a53e7e4ab06c 100644 --- a/security/integrity/integrity.h +++ b/security/integrity/integrity.h @@ -92,8 +92,8 @@ struct signature_v2_hdr { uint8_t type; /* xattr type */ uint8_t version; /* signature format version */ uint8_t hash_algo; /* Digest algorithm [enum hash_algo] */ - uint32_t keyid; /* IMA key identifier - not X509/PGP specific */ - uint16_t sig_size; /* signature size */ + __be32 keyid; /* IMA key identifier - not X509/PGP specific */ + __be16 sig_size; /* signature size */ uint8_t sig[0]; /* signature payload */ } __packed; @@ -118,7 +118,8 @@ struct integrity_iint_cache { struct integrity_iint_cache *integrity_iint_find(struct inode *inode); int integrity_kernel_read(struct file *file, loff_t offset, - char *addr, unsigned long count); + void *addr, unsigned long count); + int __init integrity_read_file(const char *path, char **data); #define INTEGRITY_KEYRING_EVM 0 -- cgit v1.2.3 From 2663218ba6e3dd6f27df9664e00fa3eb63be3a3f Mon Sep 17 00:00:00 2001 From: Thiago Jung Bauermann Date: Wed, 7 Jun 2017 22:49:11 -0300 Subject: ima: Simplify policy_func_show. If the func_tokens array uses the same indices as enum ima_hooks, policy_func_show can be a lot simpler, and the func_* enum becomes unnecessary. Also, if we use the same macro trick used by kernel_read_file_id_str we can use one hooks list for both the enum and the string array, making sure they are always in sync (suggested by Mimi Zohar). Finally, by using the printf pattern for the function token directly instead of using the pt macro we can simplify policy_func_show even further and avoid needing a temporary buffer. Signed-off-by: Thiago Jung Bauermann Signed-off-by: Mimi Zohar --- security/integrity/ima/ima.h | 25 +++++++++------- security/integrity/ima/ima_policy.c | 58 ++++--------------------------------- 2 files changed, 21 insertions(+), 62 deletions(-) diff --git a/security/integrity/ima/ima.h b/security/integrity/ima/ima.h index 215a93c41b51..d52b487ad259 100644 --- a/security/integrity/ima/ima.h +++ b/security/integrity/ima/ima.h @@ -172,17 +172,22 @@ static inline unsigned long ima_hash_key(u8 *digest) return hash_long(*digest, IMA_HASH_BITS); } +#define __ima_hooks(hook) \ + hook(NONE) \ + hook(FILE_CHECK) \ + hook(MMAP_CHECK) \ + hook(BPRM_CHECK) \ + hook(POST_SETATTR) \ + hook(MODULE_CHECK) \ + hook(FIRMWARE_CHECK) \ + hook(KEXEC_KERNEL_CHECK) \ + hook(KEXEC_INITRAMFS_CHECK) \ + hook(POLICY_CHECK) \ + hook(MAX_CHECK) +#define __ima_hook_enumify(ENUM) ENUM, + enum ima_hooks { - FILE_CHECK = 1, - MMAP_CHECK, - BPRM_CHECK, - POST_SETATTR, - MODULE_CHECK, - FIRMWARE_CHECK, - KEXEC_KERNEL_CHECK, - KEXEC_INITRAMFS_CHECK, - POLICY_CHECK, - MAX_CHECK + __ima_hooks(__ima_hook_enumify) }; /* LIM API function definitions */ diff --git a/security/integrity/ima/ima_policy.c b/security/integrity/ima/ima_policy.c index 949ad3858327..f4436626ccb7 100644 --- a/security/integrity/ima/ima_policy.c +++ b/security/integrity/ima/ima_policy.c @@ -972,23 +972,10 @@ static const char *const mask_tokens[] = { "MAY_APPEND" }; -enum { - func_file = 0, func_mmap, func_bprm, - func_module, func_firmware, func_post, - func_kexec_kernel, func_kexec_initramfs, - func_policy -}; +#define __ima_hook_stringify(str) (#str), static const char *const func_tokens[] = { - "FILE_CHECK", - "MMAP_CHECK", - "BPRM_CHECK", - "MODULE_CHECK", - "FIRMWARE_CHECK", - "POST_SETATTR", - "KEXEC_KERNEL_CHECK", - "KEXEC_INITRAMFS_CHECK", - "POLICY_CHECK" + __ima_hooks(__ima_hook_stringify) }; void *ima_policy_start(struct seq_file *m, loff_t *pos) @@ -1025,49 +1012,16 @@ void ima_policy_stop(struct seq_file *m, void *v) #define pt(token) policy_tokens[token + Opt_err].pattern #define mt(token) mask_tokens[token] -#define ft(token) func_tokens[token] /* * policy_func_show - display the ima_hooks policy rule */ static void policy_func_show(struct seq_file *m, enum ima_hooks func) { - char tbuf[64] = {0,}; - - switch (func) { - case FILE_CHECK: - seq_printf(m, pt(Opt_func), ft(func_file)); - break; - case MMAP_CHECK: - seq_printf(m, pt(Opt_func), ft(func_mmap)); - break; - case BPRM_CHECK: - seq_printf(m, pt(Opt_func), ft(func_bprm)); - break; - case MODULE_CHECK: - seq_printf(m, pt(Opt_func), ft(func_module)); - break; - case FIRMWARE_CHECK: - seq_printf(m, pt(Opt_func), ft(func_firmware)); - break; - case POST_SETATTR: - seq_printf(m, pt(Opt_func), ft(func_post)); - break; - case KEXEC_KERNEL_CHECK: - seq_printf(m, pt(Opt_func), ft(func_kexec_kernel)); - break; - case KEXEC_INITRAMFS_CHECK: - seq_printf(m, pt(Opt_func), ft(func_kexec_initramfs)); - break; - case POLICY_CHECK: - seq_printf(m, pt(Opt_func), ft(func_policy)); - break; - default: - snprintf(tbuf, sizeof(tbuf), "%d", func); - seq_printf(m, pt(Opt_func), tbuf); - break; - } - seq_puts(m, " "); + if (func > 0 && func < MAX_CHECK) + seq_printf(m, "func=%s ", func_tokens[func]); + else + seq_printf(m, "func=%d ", func); } int ima_policy_show(struct seq_file *m, void *v) -- cgit v1.2.3 From 915d9d255defeba80e1331a2b8bb8a79c0ca4db7 Mon Sep 17 00:00:00 2001 From: Thiago Jung Bauermann Date: Wed, 7 Jun 2017 22:49:12 -0300 Subject: ima: Log the same audit cause whenever a file has no signature If the file doesn't have an xattr, ima_appraise_measurement sets cause to "missing-hash" while if there's an xattr but it's a digest instead of a signature it sets cause to "IMA-signature-required". Fix it by setting cause to "IMA-signature-required" in both cases. Signed-off-by: Thiago Jung Bauermann Signed-off-by: Mimi Zohar --- security/integrity/ima/ima_appraise.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/security/integrity/ima/ima_appraise.c b/security/integrity/ima/ima_appraise.c index ea36a4f134f4..809ba70fbbbf 100644 --- a/security/integrity/ima/ima_appraise.c +++ b/security/integrity/ima/ima_appraise.c @@ -217,7 +217,8 @@ int ima_appraise_measurement(enum ima_hooks func, if (rc && rc != -ENODATA) goto out; - cause = "missing-hash"; + cause = iint->flags & IMA_DIGSIG_REQUIRED ? + "IMA-signature-required" : "missing-hash"; status = INTEGRITY_NOLABEL; if (opened & FILE_CREATED) iint->flags |= IMA_NEW_FILE; -- cgit v1.2.3 From fc26bd50539b6f52aa75ffbaec7b083825ec5451 Mon Sep 17 00:00:00 2001 From: Eric Richter Date: Thu, 15 Jun 2017 16:02:52 -0500 Subject: IMA: update IMA policy documentation to include pcr= option Commit 0260643ce "ima: add policy support for extending different pcrs" introduced a new IMA policy option "pcr=". Missing was the documentation for this option. This patch updates ima_policy to include this option, as well as an example. Signed-off-by: Eric Richter Signed-off-by: Mimi Zohar --- Documentation/ABI/testing/ima_policy | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Documentation/ABI/testing/ima_policy b/Documentation/ABI/testing/ima_policy index bb0f9a135e21..e76432b9954d 100644 --- a/Documentation/ABI/testing/ima_policy +++ b/Documentation/ABI/testing/ima_policy @@ -34,9 +34,10 @@ Description: fsuuid:= file system UUID (e.g 8bcbe394-4f13-4144-be8e-5aa9ea2ce2f6) uid:= decimal value euid:= decimal value - fowner:=decimal value + fowner:= decimal value lsm: are LSM specific option: appraise_type:= [imasig] + pcr:= decimal value default policy: # PROC_SUPER_MAGIC @@ -96,3 +97,8 @@ Description: Smack: measure subj_user=_ func=FILE_CHECK mask=MAY_READ + + Example of measure rules using alternate PCRs: + + measure func=KEXEC_KERNEL_CHECK pcr=4 + measure func=KEXEC_INITRAMFS_CHECK pcr=5 -- cgit v1.2.3 From 131b63515932d18a3b1a60db3958f3c0dd5462bc Mon Sep 17 00:00:00 2001 From: Kees Cook Date: Thu, 23 Feb 2017 09:24:24 -0800 Subject: seccomp: Clean up core dump logic This just cleans up the core dumping logic to avoid the braces around the RET_KILL case. Signed-off-by: Kees Cook --- kernel/seccomp.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/kernel/seccomp.c b/kernel/seccomp.c index 65f61077ad50..fce83885b7ef 100644 --- a/kernel/seccomp.c +++ b/kernel/seccomp.c @@ -641,11 +641,12 @@ static int __seccomp_filter(int this_syscall, const struct seccomp_data *sd, return 0; case SECCOMP_RET_KILL: - default: { - siginfo_t info; + default: audit_seccomp(this_syscall, SIGSYS, action); /* Dump core only if this is the last remaining thread. */ if (get_nr_threads(current) == 1) { + siginfo_t info; + /* Show the original registers in the dump. */ syscall_rollback(current, task_pt_regs(current)); /* Trigger a manual coredump since do_exit skips it. */ @@ -654,7 +655,6 @@ static int __seccomp_filter(int this_syscall, const struct seccomp_data *sd, } do_exit(SIGSYS); } - } unreachable(); -- cgit v1.2.3 From 93bd70e3330be45542c455dde11d8dc657ab3044 Mon Sep 17 00:00:00 2001 From: Kees Cook Date: Mon, 20 Mar 2017 16:41:35 -0700 Subject: seccomp: Adjust selftests to avoid double-join While glibc's pthread implementation is rather forgiving about repeat thread joining, Bionic has recently become much more strict. To deal with this, actually track which threads have been successfully joined and kill the rest at teardown. Based on a patch from Paul Lawrence. Cc: Paul Lawrence Signed-off-by: Kees Cook --- tools/testing/selftests/seccomp/seccomp_bpf.c | 51 ++++++++++++++++++--------- 1 file changed, 34 insertions(+), 17 deletions(-) diff --git a/tools/testing/selftests/seccomp/seccomp_bpf.c b/tools/testing/selftests/seccomp/seccomp_bpf.c index 03f1fa495d74..00a928b833d0 100644 --- a/tools/testing/selftests/seccomp/seccomp_bpf.c +++ b/tools/testing/selftests/seccomp/seccomp_bpf.c @@ -1822,6 +1822,23 @@ struct tsync_sibling { struct __test_metadata *metadata; }; +/* + * To avoid joining joined threads (which is not allowed by Bionic), + * make sure we both successfully join and clear the tid to skip a + * later join attempt during fixture teardown. Any remaining threads + * will be directly killed during teardown. + */ +#define PTHREAD_JOIN(tid, status) \ + do { \ + int _rc = pthread_join(tid, status); \ + if (_rc) { \ + TH_LOG("pthread_join of tid %u failed: %d\n", \ + (unsigned int)tid, _rc); \ + } else { \ + tid = 0; \ + } \ + } while (0) + FIXTURE_DATA(TSYNC) { struct sock_fprog root_prog, apply_prog; struct tsync_sibling sibling[TSYNC_SIBLINGS]; @@ -1890,14 +1907,14 @@ FIXTURE_TEARDOWN(TSYNC) for ( ; sib < self->sibling_count; ++sib) { struct tsync_sibling *s = &self->sibling[sib]; - void *status; if (!s->tid) continue; - if (pthread_kill(s->tid, 0)) { - pthread_cancel(s->tid); - pthread_join(s->tid, &status); - } + /* + * If a thread is still running, it may be stuck, so hit + * it over the head really hard. + */ + pthread_kill(s->tid, 9); } pthread_mutex_destroy(&self->mutex); pthread_cond_destroy(&self->cond); @@ -1987,9 +2004,9 @@ TEST_F(TSYNC, siblings_fail_prctl) pthread_mutex_unlock(&self->mutex); /* Ensure diverging sibling failed to call prctl. */ - pthread_join(self->sibling[0].tid, &status); + PTHREAD_JOIN(self->sibling[0].tid, &status); EXPECT_EQ(SIBLING_EXIT_FAILURE, (long)status); - pthread_join(self->sibling[1].tid, &status); + PTHREAD_JOIN(self->sibling[1].tid, &status); EXPECT_EQ(SIBLING_EXIT_UNKILLED, (long)status); } @@ -2029,9 +2046,9 @@ TEST_F(TSYNC, two_siblings_with_ancestor) } pthread_mutex_unlock(&self->mutex); /* Ensure they are both killed and don't exit cleanly. */ - pthread_join(self->sibling[0].tid, &status); + PTHREAD_JOIN(self->sibling[0].tid, &status); EXPECT_EQ(0x0, (long)status); - pthread_join(self->sibling[1].tid, &status); + PTHREAD_JOIN(self->sibling[1].tid, &status); EXPECT_EQ(0x0, (long)status); } @@ -2055,9 +2072,9 @@ TEST_F(TSYNC, two_sibling_want_nnp) pthread_mutex_unlock(&self->mutex); /* Ensure they are both upset about lacking nnp. */ - pthread_join(self->sibling[0].tid, &status); + PTHREAD_JOIN(self->sibling[0].tid, &status); EXPECT_EQ(SIBLING_EXIT_NEWPRIVS, (long)status); - pthread_join(self->sibling[1].tid, &status); + PTHREAD_JOIN(self->sibling[1].tid, &status); EXPECT_EQ(SIBLING_EXIT_NEWPRIVS, (long)status); } @@ -2095,9 +2112,9 @@ TEST_F(TSYNC, two_siblings_with_no_filter) pthread_mutex_unlock(&self->mutex); /* Ensure they are both killed and don't exit cleanly. */ - pthread_join(self->sibling[0].tid, &status); + PTHREAD_JOIN(self->sibling[0].tid, &status); EXPECT_EQ(0x0, (long)status); - pthread_join(self->sibling[1].tid, &status); + PTHREAD_JOIN(self->sibling[1].tid, &status); EXPECT_EQ(0x0, (long)status); } @@ -2140,9 +2157,9 @@ TEST_F(TSYNC, two_siblings_with_one_divergence) pthread_mutex_unlock(&self->mutex); /* Ensure they are both unkilled. */ - pthread_join(self->sibling[0].tid, &status); + PTHREAD_JOIN(self->sibling[0].tid, &status); EXPECT_EQ(SIBLING_EXIT_UNKILLED, (long)status); - pthread_join(self->sibling[1].tid, &status); + PTHREAD_JOIN(self->sibling[1].tid, &status); EXPECT_EQ(SIBLING_EXIT_UNKILLED, (long)status); } @@ -2199,7 +2216,7 @@ TEST_F(TSYNC, two_siblings_not_under_filter) TH_LOG("cond broadcast non-zero"); } pthread_mutex_unlock(&self->mutex); - pthread_join(self->sibling[sib].tid, &status); + PTHREAD_JOIN(self->sibling[sib].tid, &status); EXPECT_EQ(SIBLING_EXIT_UNKILLED, (long)status); /* Poll for actual task death. pthread_join doesn't guarantee it. */ while (!kill(self->sibling[sib].system_tid, 0)) @@ -2224,7 +2241,7 @@ TEST_F(TSYNC, two_siblings_not_under_filter) TH_LOG("cond broadcast non-zero"); } pthread_mutex_unlock(&self->mutex); - pthread_join(self->sibling[sib].tid, &status); + PTHREAD_JOIN(self->sibling[sib].tid, &status); EXPECT_EQ(0, (long)status); /* Poll for actual task death. pthread_join doesn't guarantee it. */ while (!kill(self->sibling[sib].system_tid, 0)) -- cgit v1.2.3 From 0b5fa2290637a3235898d18dc0e7a136783f1bd2 Mon Sep 17 00:00:00 2001 From: Kees Cook Date: Mon, 26 Jun 2017 09:24:00 -0700 Subject: seccomp: Switch from atomic_t to recount_t This switches the seccomp usage tracking from atomic_t to refcount_t to gain refcount overflow protections. Cc: Elena Reshetova Cc: David Windsor Cc: Hans Liljestrand Signed-off-by: Kees Cook --- kernel/seccomp.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/kernel/seccomp.c b/kernel/seccomp.c index fce83885b7ef..98b59b5db90b 100644 --- a/kernel/seccomp.c +++ b/kernel/seccomp.c @@ -13,7 +13,7 @@ * of Berkeley Packet Filters/Linux Socket Filters. */ -#include +#include #include #include #include @@ -56,7 +56,7 @@ * to a task_struct (other than @usage). */ struct seccomp_filter { - atomic_t usage; + refcount_t usage; struct seccomp_filter *prev; struct bpf_prog *prog; }; @@ -378,7 +378,7 @@ static struct seccomp_filter *seccomp_prepare_filter(struct sock_fprog *fprog) return ERR_PTR(ret); } - atomic_set(&sfilter->usage, 1); + refcount_set(&sfilter->usage, 1); return sfilter; } @@ -465,7 +465,7 @@ void get_seccomp_filter(struct task_struct *tsk) if (!orig) return; /* Reference count is bounded by the number of total processes. */ - atomic_inc(&orig->usage); + refcount_inc(&orig->usage); } static inline void seccomp_filter_free(struct seccomp_filter *filter) @@ -481,7 +481,7 @@ void put_seccomp_filter(struct task_struct *tsk) { struct seccomp_filter *orig = tsk->seccomp.filter; /* Clean up single-reference branches iteratively. */ - while (orig && atomic_dec_and_test(&orig->usage)) { + while (orig && refcount_dec_and_test(&orig->usage)) { struct seccomp_filter *freeme = orig; orig = orig->prev; seccomp_filter_free(freeme); -- cgit v1.2.3 From c4758fa59285fe4dbfeab4364a6957936d040fbf Mon Sep 17 00:00:00 2001 From: Stephen Rothwell Date: Tue, 20 Jun 2017 14:50:36 +1000 Subject: apparmor: put back designators in struct initialisers Fixes: 8014370f1257 ("apparmor: move path_link mediation to using labels") Signed-off-by: Stephen Rothwell Acked-by: John Johansen Acked-by: Kees Cook Signed-off-by: James Morris --- security/apparmor/file.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/security/apparmor/file.c b/security/apparmor/file.c index b6e8e5b11e05..3382518b87fa 100644 --- a/security/apparmor/file.c +++ b/security/apparmor/file.c @@ -472,8 +472,8 @@ audit: int aa_path_link(struct aa_label *label, struct dentry *old_dentry, const struct path *new_dir, struct dentry *new_dentry) { - struct path link = { new_dir->mnt, new_dentry }; - struct path target = { new_dir->mnt, old_dentry }; + struct path link = { .mnt = new_dir->mnt, .dentry = new_dentry }; + struct path target = { .mnt = new_dir->mnt, .dentry = old_dentry }; struct path_cond cond = { d_backing_inode(old_dentry)->i_uid, d_backing_inode(old_dentry)->i_mode -- cgit v1.2.3