aa_inherit_files() revalidates inherited files on exec using
iterate_fd(), which calls the callback with files->file_lock held,
so the revalidation is done with IN_ATOMIC. However the unix socket
mediation path can sleep: aa_unix_file_perm() drops a ref on the
peer's path with path_put(), and unix_fs_perm() -> aa_path_perm()
allocates a buffer with aa_get_buffer(false), resulting in

  BUG: sleeping function called from invalid context at fs/dcache.c:1039
  ...
   dput.part.0+0x24/0x180
   path_put+0x2c/0x60
   aa_unix_file_perm+0x42a/0x17b0
   aa_file_perm+0xcd3/0x1060
   match_file+0x10b/0x190
   iterate_fd+0xe6/0x280
   aa_inherit_files+0x250/0x4f0

Only grab a ref on each file under iterate_fd(), and revalidate it
after the lock is dropped, so revalidation is done with no locks held
and does not need to be atomic. Denied fds are still replaced with
/dev/null, or closed if it can not be opened.

Fixes: b1aea2c19607 ("apparmor: fix race in unix socket mediation when 
peer_path is used")
Signed-off-by: Georgia Garcia <[email protected]>
---
 security/apparmor/file.c | 61 ++++++++++++++++++++--------------------
 1 file changed, 31 insertions(+), 30 deletions(-)

diff --git a/security/apparmor/file.c b/security/apparmor/file.c
index 3e74b613db32..b52781e05a5a 100644
--- a/security/apparmor/file.c
+++ b/security/apparmor/file.c
@@ -706,49 +706,50 @@ static void revalidate_tty(const struct cred *subj_cred, 
struct aa_label *label)
                no_tty();
 }
 
-struct cred_label {
-       const struct cred *cred;
-       struct aa_label *label;
-};
-
-static int match_file(const void *p, struct file *file, unsigned int fd)
+/*
+ * iterate_fd() callback, called with files->file_lock held: grab a ref
+ * on the next file so it can be revalidated without the lock held.
+ */
+static int get_next_file(const void *p, struct file *file, unsigned int fd)
 {
-       struct cred_label *cl = (struct cred_label *)p;
+       *(struct file **)p = get_file(file);
 
-       if (aa_file_perm(OP_INHERIT, cl->cred, cl->label, file,
-                        aa_map_file_to_perms(file), IN_ATOMIC))
-               return fd + 1;
-       return 0;
+       return fd + 1;
 }
 
-
 /* based on selinux's flush_unauthorized_files */
 void aa_inherit_files(const struct cred *cred, struct files_struct *files)
 {
        struct aa_label *label = aa_get_newest_cred_label(cred);
-       struct cred_label cl = {
-               .cred = cred,
-               .label = label,
-       };
-       struct file *devnull = NULL;
-       unsigned int n;
+       struct file *devnull = NULL, *file;
+       bool devnull_tried = false;
+       unsigned int n = 0;
 
        revalidate_tty(cred, label);
 
-       /* Revalidate access to inherited open files. */
-       n = iterate_fd(files, 0, match_file, &cl);
-       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 {
+       /*
+        * Revalidate access to inherited open files. Each file is
+        * revalidated holding only a reference to it, with no locks
+        * held, as revalidation may sleep (e.g. unix socket mediation
+        * dropping path refs, or allocating path buffers).
+        */
+       while ((n = iterate_fd(files, n, get_next_file, &file)) != 0) {
+               int error = aa_file_perm(OP_INHERIT, cred, label, file,
+                                        aa_map_file_to_perms(file), false);
+
+               fput(file);
+               if (!error)
+                       continue;
+               if (!devnull_tried) {
+                       devnull_tried = true;
+                       devnull = dentry_open(&aa_null, O_RDWR, cred);
+                       if (IS_ERR(devnull))
+                               devnull = NULL;
+               }
+               /* replace the denied fd (n - 1) with devnull, or close it */
                replace_fd(n - 1, devnull, 0);
-       } while ((n = iterate_fd(files, n, match_file, &cl)) != 0);
+       }
        if (devnull)
                fput(devnull);
-out:
        aa_put_label(label);
 }
-- 
2.43.0


Reply via email to