apparmor: make it so work buffers can be allocated from atomic context
authorJohn Johansen <john.johansen@canonical.com>
Sat, 14 Sep 2019 10:34:06 +0000 (03:34 -0700)
committerJohn Johansen <john.johansen@canonical.com>
Sat, 23 Nov 2019 00:41:08 +0000 (16:41 -0800)
In some situations AppArmor needs to be able to use its work buffers
from atomic context. Add the ability to specify when in atomic context
and hold a set of work buffers in reserve for atomic context to
reduce the chance that a large work buffer allocation will need to
be done.

Fixes: df323337e507 ("apparmor: Use a memory pool instead per-CPU caches")
Signed-off-by: John Johansen <john.johansen@canonical.com>
security/apparmor/domain.c
security/apparmor/file.c
security/apparmor/include/file.h
security/apparmor/include/path.h
security/apparmor/lsm.c
security/apparmor/mount.c

index 0da0b2b9697209b8881f2edeec2990a6328b6063..51b3143ec256959e74d97a9a100c272959830e2f 100644 (file)
@@ -896,7 +896,7 @@ int apparmor_bprm_set_creds(struct linux_binprm *bprm)
                ctx->nnp = aa_get_label(label);
 
        /* buffer freed below, name is pointer into buffer */
-       buffer = aa_get_buffer();
+       buffer = aa_get_buffer(false);
        if (!buffer) {
                error = -ENOMEM;
                goto done;
index 37d62ecec29d184dff3354b9fcbf3a76df167f88..b520fdfc350480fff2d649052227cf5091d5547a 100644 (file)
@@ -336,7 +336,7 @@ int aa_path_perm(const char *op, struct aa_label *label,
 
        flags |= PATH_DELEGATE_DELETED | (S_ISDIR(cond->mode) ? PATH_IS_DIR :
                                                                0);
-       buffer = aa_get_buffer();
+       buffer = aa_get_buffer(false);
        if (!buffer)
                return -ENOMEM;
        error = fn_for_each_confined(label, profile,
@@ -481,8 +481,8 @@ int aa_path_link(struct aa_label *label, struct dentry *old_dentry,
        int error;
 
        /* buffer freed below, lname is pointer in buffer */
-       buffer = aa_get_buffer();
-       buffer2 = aa_get_buffer();
+       buffer = aa_get_buffer(false);
+       buffer2 = aa_get_buffer(false);
        error = -ENOMEM;
        if (!buffer || !buffer2)
                goto out;
@@ -519,7 +519,7 @@ static void update_file_ctx(struct aa_file_ctx *fctx, struct aa_label *label,
 
 static int __file_path_perm(const char *op, struct aa_label *label,
                            struct aa_label *flabel, struct file *file,
-                           u32 request, u32 denied)
+                           u32 request, u32 denied, bool in_atomic)
 {
        struct aa_profile *profile;
        struct aa_perms perms = {};
@@ -536,7 +536,7 @@ static int __file_path_perm(const char *op, struct aa_label *label,
                return 0;
 
        flags = PATH_DELEGATE_DELETED | (S_ISDIR(cond.mode) ? PATH_IS_DIR : 0);
-       buffer = aa_get_buffer();
+       buffer = aa_get_buffer(in_atomic);
        if (!buffer)
                return -ENOMEM;
 
@@ -604,11 +604,12 @@ static int __file_sock_perm(const char *op, struct aa_label *label,
  * @label: label being enforced   (NOT NULL)
  * @file: file to revalidate access permissions on  (NOT NULL)
  * @request: requested permissions
+ * @in_atomic: whether allocations need to be done in atomic context
  *
  * Returns: %0 if access allowed else error
  */
 int aa_file_perm(const char *op, struct aa_label *label, struct file *file,
-                u32 request)
+                u32 request, bool in_atomic)
 {
        struct aa_file_ctx *fctx;
        struct aa_label *flabel;
@@ -641,7 +642,7 @@ int aa_file_perm(const char *op, struct aa_label *label, struct file *file,
 
        if (file->f_path.mnt && path_mediated_fs(file->f_path.dentry))
                error = __file_path_perm(op, label, flabel, file, request,
-                                        denied);
+                                        denied, in_atomic);
 
        else if (S_ISSOCK(file_inode(file)->i_mode))
                error = __file_sock_perm(op, label, flabel, file, request,
@@ -669,7 +670,8 @@ static void revalidate_tty(struct aa_label *label)
                                             struct tty_file_private, list);
                file = file_priv->file;
 
-               if (aa_file_perm(OP_INHERIT, label, file, MAY_READ | MAY_WRITE))
+               if (aa_file_perm(OP_INHERIT, label, file, MAY_READ | MAY_WRITE,
+                                IN_ATOMIC))
                        drop_tty = 1;
        }
        spin_unlock(&tty->files_lock);
@@ -683,7 +685,8 @@ 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, label, file, aa_map_file_to_perms(file)))
+       if (aa_file_perm(OP_INHERIT, label, file, aa_map_file_to_perms(file),
+                        IN_ATOMIC))
                return fd + 1;
        return 0;
 }
index 8be09208cf7c119ebd0f20e494c9f466fc74c15a..67fadf06fa737beae9783a7d7fbec4ab065ccf1e 100644 (file)
@@ -201,7 +201,7 @@ 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,
-                u32 request);
+                u32 request, bool in_atomic);
 
 void aa_inherit_files(const struct cred *cred, struct files_struct *files);
 
index b0b2ab85e42d82579a88e8d9dd1c39386adb99e9..d2ab8a932bad2ae4bd8fdb987f6a7f80bf92230b 100644 (file)
@@ -29,7 +29,8 @@ int aa_path_name(const struct path *path, int flags, char *buffer,
                 const char **name, const char **info,
                 const char *disconnected);
 
-char *aa_get_buffer(void);
+#define IN_ATOMIC true
+char *aa_get_buffer(bool in_atomic);
 void aa_put_buffer(char *buf);
 
 #endif /* __AA_PATH_H */
index 3e0cfdebee456082ac46affb366d9a83ea01f46f..c940e8988444ce8b67b596cad0adabd0c2310601 100644 (file)
@@ -53,6 +53,10 @@ union aa_buffer {
        char buffer[1];
 };
 
+#define RESERVE_COUNT 2
+static int reserve_count = RESERVE_COUNT;
+static int buffer_count;
+
 static LIST_HEAD(aa_global_buffers);
 static DEFINE_SPINLOCK(aa_buffers_lock);
 
@@ -452,7 +456,8 @@ static void apparmor_file_free_security(struct file *file)
                aa_put_label(rcu_access_pointer(ctx->label));
 }
 
-static int common_file_perm(const char *op, struct file *file, u32 mask)
+static int common_file_perm(const char *op, struct file *file, u32 mask,
+                           bool in_atomic)
 {
        struct aa_label *label;
        int error = 0;
@@ -462,7 +467,7 @@ static int common_file_perm(const char *op, struct file *file, u32 mask)
                return -EACCES;
 
        label = __begin_current_label_crit_section();
-       error = aa_file_perm(op, label, file, mask);
+       error = aa_file_perm(op, label, file, mask, in_atomic);
        __end_current_label_crit_section(label);
 
        return error;
@@ -470,12 +475,13 @@ static int common_file_perm(const char *op, struct file *file, u32 mask)
 
 static int apparmor_file_receive(struct file *file)
 {
-       return common_file_perm(OP_FRECEIVE, file, aa_map_file_to_perms(file));
+       return common_file_perm(OP_FRECEIVE, file, aa_map_file_to_perms(file),
+                               false);
 }
 
 static int apparmor_file_permission(struct file *file, int mask)
 {
-       return common_file_perm(OP_FPERM, file, mask);
+       return common_file_perm(OP_FPERM, file, mask, false);
 }
 
 static int apparmor_file_lock(struct file *file, unsigned int cmd)
@@ -485,11 +491,11 @@ static int apparmor_file_lock(struct file *file, unsigned int cmd)
        if (cmd == F_WRLCK)
                mask |= MAY_WRITE;
 
-       return common_file_perm(OP_FLOCK, file, mask);
+       return common_file_perm(OP_FLOCK, file, mask, false);
 }
 
 static int common_mmap(const char *op, struct file *file, unsigned long prot,
-                      unsigned long flags)
+                      unsigned long flags, bool in_atomic)
 {
        int mask = 0;
 
@@ -507,20 +513,21 @@ static int common_mmap(const char *op, struct file *file, unsigned long prot,
        if (prot & PROT_EXEC)
                mask |= AA_EXEC_MMAP;
 
-       return common_file_perm(op, file, mask);
+       return common_file_perm(op, file, mask, in_atomic);
 }
 
 static int apparmor_mmap_file(struct file *file, unsigned long reqprot,
                              unsigned long prot, unsigned long flags)
 {
-       return common_mmap(OP_FMMAP, file, prot, flags);
+       return common_mmap(OP_FMMAP, file, prot, flags, GFP_ATOMIC);
 }
 
 static int apparmor_file_mprotect(struct vm_area_struct *vma,
                                  unsigned long reqprot, unsigned long prot)
 {
        return common_mmap(OP_FMPROT, vma->vm_file, prot,
-                          !(vma->vm_flags & VM_SHARED) ? MAP_PRIVATE : 0);
+                          !(vma->vm_flags & VM_SHARED) ? MAP_PRIVATE : 0,
+                          false);
 }
 
 static int apparmor_sb_mount(const char *dev_name, const struct path *path,
@@ -1571,24 +1578,36 @@ static int param_set_mode(const char *val, const struct kernel_param *kp)
        return 0;
 }
 
-char *aa_get_buffer(void)
+char *aa_get_buffer(bool in_atomic)
 {
        union aa_buffer *aa_buf;
        bool try_again = true;
+       gfp_t flags = (GFP_KERNEL | __GFP_RETRY_MAYFAIL | __GFP_NOWARN);
 
 retry:
        spin_lock(&aa_buffers_lock);
-       if (!list_empty(&aa_global_buffers)) {
+       if (buffer_count > reserve_count ||
+           (in_atomic && !list_empty(&aa_global_buffers))) {
                aa_buf = list_first_entry(&aa_global_buffers, union aa_buffer,
                                          list);
                list_del(&aa_buf->list);
+               buffer_count--;
                spin_unlock(&aa_buffers_lock);
                return &aa_buf->buffer[0];
        }
+       if (in_atomic) {
+               /*
+                * out of reserve buffers and in atomic context so increase
+                * how many buffers to keep in reserve
+                */
+               reserve_count++;
+               flags = GFP_ATOMIC;
+       }
        spin_unlock(&aa_buffers_lock);
 
-       aa_buf = kmalloc(aa_g_path_max, GFP_KERNEL | __GFP_RETRY_MAYFAIL |
-                        __GFP_NOWARN);
+       if (!in_atomic)
+               might_sleep();
+       aa_buf = kmalloc(aa_g_path_max, flags);
        if (!aa_buf) {
                if (try_again) {
                        try_again = false;
@@ -1610,6 +1629,7 @@ void aa_put_buffer(char *buf)
 
        spin_lock(&aa_buffers_lock);
        list_add(&aa_buf->list, &aa_global_buffers);
+       buffer_count++;
        spin_unlock(&aa_buffers_lock);
 }
 
@@ -1661,9 +1681,9 @@ static int __init alloc_buffers(void)
         * disabled early at boot if aa_g_path_max is extremly high.
         */
        if (num_online_cpus() > 1)
-               num = 4;
+               num = 4 + RESERVE_COUNT;
        else
-               num = 2;
+               num = 2 + RESERVE_COUNT;
 
        for (i = 0; i < num; i++) {
 
index 2dbccb021663b9c15514aaa3da3f559e670ad9f9..b06f5cba8fc485a61c9683fd5188603148078f4a 100644 (file)
@@ -412,7 +412,7 @@ int aa_remount(struct aa_label *label, const struct path *path,
 
        binary = path->dentry->d_sb->s_type->fs_flags & FS_BINARY_MOUNTDATA;
 
-       buffer = aa_get_buffer();
+       buffer = aa_get_buffer(false);
        if (!buffer)
                return -ENOMEM;
        error = fn_for_each_confined(label, profile,
@@ -443,8 +443,8 @@ int aa_bind_mount(struct aa_label *label, const struct path *path,
        if (error)
                return error;
 
-       buffer = aa_get_buffer();
-       old_buffer = aa_get_buffer();
+       buffer = aa_get_buffer(false);
+       old_buffer = aa_get_buffer(false);
        error = -ENOMEM;
        if (!buffer || old_buffer)
                goto out;
@@ -474,7 +474,7 @@ int aa_mount_change_type(struct aa_label *label, const struct path *path,
        flags &= (MS_REC | MS_SILENT | MS_SHARED | MS_PRIVATE | MS_SLAVE |
                  MS_UNBINDABLE);
 
-       buffer = aa_get_buffer();
+       buffer = aa_get_buffer(false);
        if (!buffer)
                return -ENOMEM;
        error = fn_for_each_confined(label, profile,
@@ -503,8 +503,8 @@ int aa_move_mount(struct aa_label *label, const struct path *path,
        if (error)
                return error;
 
-       buffer = aa_get_buffer();
-       old_buffer = aa_get_buffer();
+       buffer = aa_get_buffer(false);
+       old_buffer = aa_get_buffer(false);
        error = -ENOMEM;
        if (!buffer || !old_buffer)
                goto out;
@@ -554,13 +554,13 @@ int aa_new_mount(struct aa_label *label, const char *dev_name,
                }
        }
 
-       buffer = aa_get_buffer();
+       buffer = aa_get_buffer(false);
        if (!buffer) {
                error = -ENOMEM;
                goto out;
        }
        if (dev_path) {
-               dev_buffer = aa_get_buffer();
+               dev_buffer = aa_get_buffer(false);
                if (!dev_buffer) {
                        error = -ENOMEM;
                        goto out;
@@ -624,7 +624,7 @@ int aa_umount(struct aa_label *label, struct vfsmount *mnt, int flags)
        AA_BUG(!label);
        AA_BUG(!mnt);
 
-       buffer = aa_get_buffer();
+       buffer = aa_get_buffer(false);
        if (!buffer)
                return -ENOMEM;
 
@@ -703,8 +703,8 @@ int aa_pivotroot(struct aa_label *label, const struct path *old_path,
        AA_BUG(!old_path);
        AA_BUG(!new_path);
 
-       old_buffer = aa_get_buffer();
-       new_buffer = aa_get_buffer();
+       old_buffer = aa_get_buffer(false);
+       new_buffer = aa_get_buffer(false);
        error = -ENOMEM;
        if (!old_buffer || !new_buffer)
                goto out;