exec: Ensure mm->user_ns contains the execed files
authorEric W. Biederman <ebiederm@xmission.com>
Thu, 17 Nov 2016 04:06:51 +0000 (22:06 -0600)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Fri, 6 Jan 2017 10:16:14 +0000 (11:16 +0100)
commit f84df2a6f268de584a201e8911384a2d244876e3 upstream.

When the user namespace support was merged the need to prevent
ptrace from revealing the contents of an unreadable executable
was overlooked.

Correct this oversight by ensuring that the executed file
or files are in mm->user_ns, by adjusting mm->user_ns.

Use the new function privileged_wrt_inode_uidgid to see if
the executable is a member of the user namespace, and as such
if having CAP_SYS_PTRACE in the user namespace should allow
tracing the executable.  If not update mm->user_ns to
the parent user namespace until an appropriate parent is found.

Reported-by: Jann Horn <jann@thejh.net>
Fixes: 9e4a36ece652 ("userns: Fail exec for suid and sgid binaries with ids outside our user namespace.")
Signed-off-by: "Eric W. Biederman" <ebiederm@xmission.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
fs/exec.c
include/linux/capability.h
kernel/capability.c

index 3479fdd4a6e7ec9280dcd20be9b892c0140fd929..3a6de10d38918db2dca9e3d0067b7bc0b456875f 100644 (file)
--- a/fs/exec.c
+++ b/fs/exec.c
@@ -56,6 +56,7 @@
 #include <linux/pipe_fs_i.h>
 #include <linux/oom.h>
 #include <linux/compat.h>
+#include <linux/user_namespace.h>
 
 #include <asm/uaccess.h>
 #include <asm/mmu_context.h>
@@ -1130,8 +1131,22 @@ EXPORT_SYMBOL(flush_old_exec);
 
 void would_dump(struct linux_binprm *bprm, struct file *file)
 {
-       if (inode_permission(file_inode(file), MAY_READ) < 0)
+       struct inode *inode = file_inode(file);
+       if (inode_permission(inode, MAY_READ) < 0) {
+               struct user_namespace *old, *user_ns;
                bprm->interp_flags |= BINPRM_FLAGS_ENFORCE_NONDUMP;
+
+               /* Ensure mm->user_ns contains the executable */
+               user_ns = old = bprm->mm->user_ns;
+               while ((user_ns != &init_user_ns) &&
+                      !privileged_wrt_inode_uidgid(user_ns, inode))
+                       user_ns = user_ns->parent;
+
+               if (old != user_ns) {
+                       bprm->mm->user_ns = get_user_ns(user_ns);
+                       put_user_ns(old);
+               }
+       }
 }
 EXPORT_SYMBOL(would_dump);
 
@@ -1161,7 +1176,6 @@ void setup_new_exec(struct linux_binprm * bprm)
            !gid_eq(bprm->cred->gid, current_egid())) {
                current->pdeath_signal = 0;
        } else {
-               would_dump(bprm, bprm->file);
                if (bprm->interp_flags & BINPRM_FLAGS_ENFORCE_NONDUMP)
                        set_dumpable(current->mm, suid_dumpable);
        }
@@ -1593,6 +1607,8 @@ static int do_execveat_common(int fd, struct filename *filename,
        if (retval < 0)
                goto out;
 
+       would_dump(bprm, bprm->file);
+
        retval = exec_binprm(bprm);
        if (retval < 0)
                goto out;
index 7d38697c7e63b06b0c7c70cfdb5d4146f9f24e98..2654f75a4c46afff3644a69cfd3f01150e8a54a4 100644 (file)
@@ -247,6 +247,7 @@ static inline bool ns_capable_noaudit(struct user_namespace *ns, int cap)
        return true;
 }
 #endif /* CONFIG_MULTIUSER */
+extern bool privileged_wrt_inode_uidgid(struct user_namespace *ns, const struct inode *inode);
 extern bool capable_wrt_inode_uidgid(const struct inode *inode, int cap);
 extern bool file_ns_capable(const struct file *file, struct user_namespace *ns, int cap);
 extern bool ptracer_capable(struct task_struct *tsk, struct user_namespace *ns);
index dfa0e4528b0b6d887dca7f75a2e71ace761c9dea..4984e1f552ebfb97fa460b527b35c67ebd0f324d 100644 (file)
@@ -456,6 +456,19 @@ bool file_ns_capable(const struct file *file, struct user_namespace *ns,
 }
 EXPORT_SYMBOL(file_ns_capable);
 
+/**
+ * privileged_wrt_inode_uidgid - Do capabilities in the namespace work over the inode?
+ * @ns: The user namespace in question
+ * @inode: The inode in question
+ *
+ * Return true if the inode uid and gid are within the namespace.
+ */
+bool privileged_wrt_inode_uidgid(struct user_namespace *ns, const struct inode *inode)
+{
+       return kuid_has_mapping(ns, inode->i_uid) &&
+               kgid_has_mapping(ns, inode->i_gid);
+}
+
 /**
  * capable_wrt_inode_uidgid - Check nsown_capable and uid and gid mapped
  * @inode: The inode in question
@@ -469,8 +482,7 @@ bool capable_wrt_inode_uidgid(const struct inode *inode, int cap)
 {
        struct user_namespace *ns = current_user_ns();
 
-       return ns_capable(ns, cap) && kuid_has_mapping(ns, inode->i_uid) &&
-               kgid_has_mapping(ns, inode->i_gid);
+       return ns_capable(ns, cap) && privileged_wrt_inode_uidgid(ns, inode);
 }
 EXPORT_SYMBOL(capable_wrt_inode_uidgid);