]> Git Repo - linux.git/commitdiff
Merge branch 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/ebiederm...
authorLinus Torvalds <[email protected]>
Tue, 1 Sep 2015 23:13:25 +0000 (16:13 -0700)
committerLinus Torvalds <[email protected]>
Tue, 1 Sep 2015 23:13:25 +0000 (16:13 -0700)
Pull user namespace updates from Eric Biederman:
 "This finishes up the changes to ensure proc and sysfs do not start
  implementing executable files, as the there are application today that
  are only secure because such files do not exist.

  It akso fixes a long standing misfeature of /proc/<pid>/mountinfo that
  did not show the proper source for files bind mounted from
  /proc/<pid>/ns/*.

  It also straightens out the handling of clone flags related to user
  namespaces, fixing an unnecessary failure of unshare(CLONE_NEWUSER)
  when files such as /proc/<pid>/environ are read while <pid> is calling
  unshare.  This winds up fixing a minor bug in unshare flag handling
  that dates back to the first version of unshare in the kernel.

  Finally, this fixes a minor regression caused by the introduction of
  sysfs_create_mount_point, which broke someone's in house application,
  by restoring the size of /sys/fs/cgroup to 0 bytes.  Apparently that
  application uses the directory size to determine if a tmpfs is mounted
  on /sys/fs/cgroup.

  The bind mount escape fixes are present in Al Viros for-next branch.
  and I expect them to come from there.  The bind mount escape is the
  last of the user namespace related security bugs that I am aware of"

* 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/ebiederm/user-namespace:
  fs: Set the size of empty dirs to 0.
  userns,pidns: Force thread group sharing, not signal handler sharing.
  unshare: Unsharing a thread does not require unsharing a vm
  nsfs: Add a show_path method to fix mountinfo
  mnt: fs_fully_visible enforce noexec and nosuid  if !SB_I_NOEXEC
  vfs: Commit to never having exectuables on proc and sysfs.

1  2 
fs/namespace.c
include/linux/fs.h
kernel/fork.c
security/security.c

diff --combined fs/namespace.c
index 2b8aa15fd6dfa755368ddf21136eaab5192f1e28,ce428cadd41f549425dca2fa8d772564f5da2553..0570729c87fd22dd299c50ee0303f43c384e4932
@@@ -1361,36 -1361,6 +1361,36 @@@ enum umount_tree_flags 
        UMOUNT_PROPAGATE = 2,
        UMOUNT_CONNECTED = 4,
  };
 +
 +static bool disconnect_mount(struct mount *mnt, enum umount_tree_flags how)
 +{
 +      /* Leaving mounts connected is only valid for lazy umounts */
 +      if (how & UMOUNT_SYNC)
 +              return true;
 +
 +      /* A mount without a parent has nothing to be connected to */
 +      if (!mnt_has_parent(mnt))
 +              return true;
 +
 +      /* Because the reference counting rules change when mounts are
 +       * unmounted and connected, umounted mounts may not be
 +       * connected to mounted mounts.
 +       */
 +      if (!(mnt->mnt_parent->mnt.mnt_flags & MNT_UMOUNT))
 +              return true;
 +
 +      /* Has it been requested that the mount remain connected? */
 +      if (how & UMOUNT_CONNECTED)
 +              return false;
 +
 +      /* Is the mount locked such that it needs to remain connected? */
 +      if (IS_MNT_LOCKED(mnt))
 +              return false;
 +
 +      /* By default disconnect the mount */
 +      return true;
 +}
 +
  /*
   * mount_lock must be held
   * namespace_sem must be held for write
@@@ -1428,7 -1398,10 +1428,7 @@@ static void umount_tree(struct mount *m
                if (how & UMOUNT_SYNC)
                        p->mnt.mnt_flags |= MNT_SYNC_UMOUNT;
  
 -              disconnect = !(((how & UMOUNT_CONNECTED) &&
 -                              mnt_has_parent(p) &&
 -                              (p->mnt_parent->mnt.mnt_flags & MNT_UMOUNT)) ||
 -                             IS_MNT_LOCKED_AND_LAZY(p));
 +              disconnect = disconnect_mount(p, how);
  
                pin_insert_group(&p->mnt_umount, &p->mnt_parent->mnt,
                                 disconnect ? &unmounted : NULL);
@@@ -1565,8 -1538,11 +1565,8 @@@ void __detach_mounts(struct dentry *den
        while (!hlist_empty(&mp->m_list)) {
                mnt = hlist_entry(mp->m_list.first, struct mount, mnt_mp_list);
                if (mnt->mnt.mnt_flags & MNT_UMOUNT) {
 -                      struct mount *p, *tmp;
 -                      list_for_each_entry_safe(p, tmp, &mnt->mnt_mounts,  mnt_child) {
 -                              hlist_add_head(&p->mnt_umount.s_list, &unmounted);
 -                              umount_mnt(p);
 -                      }
 +                      hlist_add_head(&mnt->mnt_umount.s_list, &unmounted);
 +                      umount_mnt(mnt);
                }
                else umount_tree(mnt, UMOUNT_CONNECTED);
        }
@@@ -3218,6 -3194,8 +3218,8 @@@ static bool fs_fully_visible(struct fil
        down_read(&namespace_sem);
        list_for_each_entry(mnt, &ns->list, mnt_list) {
                struct mount *child;
+               int mnt_flags;
                if (mnt->mnt.mnt_sb->s_type != type)
                        continue;
  
                if (mnt->mnt.mnt_root != mnt->mnt.mnt_sb->s_root)
                        continue;
  
+               /* Read the mount flags and filter out flags that
+                * may safely be ignored.
+                */
+               mnt_flags = mnt->mnt.mnt_flags;
+               if (mnt->mnt.mnt_sb->s_iflags & SB_I_NOEXEC)
+                       mnt_flags &= ~(MNT_LOCK_NOSUID | MNT_LOCK_NOEXEC);
                /* Verify the mount flags are equal to or more permissive
                 * than the proposed new mount.
                 */
-               if ((mnt->mnt.mnt_flags & MNT_LOCK_READONLY) &&
+               if ((mnt_flags & MNT_LOCK_READONLY) &&
                    !(new_flags & MNT_READONLY))
                        continue;
-               if ((mnt->mnt.mnt_flags & MNT_LOCK_NODEV) &&
+               if ((mnt_flags & MNT_LOCK_NODEV) &&
                    !(new_flags & MNT_NODEV))
                        continue;
-               if ((mnt->mnt.mnt_flags & MNT_LOCK_ATIME) &&
-                   ((mnt->mnt.mnt_flags & MNT_ATIME_MASK) != (new_flags & MNT_ATIME_MASK)))
+               if ((mnt_flags & MNT_LOCK_NOSUID) &&
+                   !(new_flags & MNT_NOSUID))
+                       continue;
+               if ((mnt_flags & MNT_LOCK_NOEXEC) &&
+                   !(new_flags & MNT_NOEXEC))
+                       continue;
+               if ((mnt_flags & MNT_LOCK_ATIME) &&
+                   ((mnt_flags & MNT_ATIME_MASK) != (new_flags & MNT_ATIME_MASK)))
                        continue;
  
                /* This mount is not fully visible if there are any
                list_for_each_entry(child, &mnt->mnt_mounts, mnt_child) {
                        struct inode *inode = child->mnt_mountpoint->d_inode;
                        /* Only worry about locked mounts */
-                       if (!(mnt->mnt.mnt_flags & MNT_LOCKED))
+                       if (!(mnt_flags & MNT_LOCKED))
                                continue;
                        /* Is the directory permanetly empty? */
                        if (!is_empty_dir_inode(inode))
                                goto next;
                }
                /* Preserve the locked attributes */
-               *new_mnt_flags |= mnt->mnt.mnt_flags & (MNT_LOCK_READONLY | \
-                                                       MNT_LOCK_NODEV    | \
-                                                       MNT_LOCK_ATIME);
+               *new_mnt_flags |= mnt_flags & (MNT_LOCK_READONLY | \
+                                              MNT_LOCK_NODEV    | \
+                                              MNT_LOCK_NOSUID   | \
+                                              MNT_LOCK_NOEXEC   | \
+                                              MNT_LOCK_ATIME);
                visible = true;
                goto found;
        next:   ;
diff --combined include/linux/fs.h
index 84b783f277f761a0ef7b940bd1fbab37db60567e,42912f8d286eb5c77fef0c39c550320e8ad1e736..fbd780c33c5fb3caf422b8127644e5b58207d795
@@@ -55,8 -55,7 +55,8 @@@ struct vm_fault
  
  extern void __init inode_init(void);
  extern void __init inode_init_early(void);
 -extern void __init files_init(unsigned long);
 +extern void __init files_init(void);
 +extern void __init files_maxfiles_init(void);
  
  extern struct files_stat_struct files_stat;
  extern unsigned long get_max_files(void);
@@@ -1047,12 -1046,12 +1047,12 @@@ extern void locks_remove_file(struct fi
  extern void locks_release_private(struct file_lock *);
  extern void posix_test_lock(struct file *, struct file_lock *);
  extern int posix_lock_file(struct file *, struct file_lock *, struct file_lock *);
 -extern int posix_lock_file_wait(struct file *, struct file_lock *);
 +extern int posix_lock_inode_wait(struct inode *, struct file_lock *);
  extern int posix_unblock_lock(struct file_lock *);
  extern int vfs_test_lock(struct file *, struct file_lock *);
  extern int vfs_lock_file(struct file *, unsigned int, struct file_lock *, struct file_lock *);
  extern int vfs_cancel_lock(struct file *filp, struct file_lock *fl);
 -extern int flock_lock_file_wait(struct file *filp, struct file_lock *fl);
 +extern int flock_lock_inode_wait(struct inode *inode, struct file_lock *fl);
  extern int __break_lease(struct inode *inode, unsigned int flags, unsigned int type);
  extern void lease_get_mtime(struct inode *, struct timespec *time);
  extern int generic_setlease(struct file *, long, struct file_lock **, void **priv);
@@@ -1138,8 -1137,7 +1138,8 @@@ static inline int posix_lock_file(struc
        return -ENOLCK;
  }
  
 -static inline int posix_lock_file_wait(struct file *filp, struct file_lock *fl)
 +static inline int posix_lock_inode_wait(struct inode *inode,
 +                                      struct file_lock *fl)
  {
        return -ENOLCK;
  }
@@@ -1165,8 -1163,8 +1165,8 @@@ static inline int vfs_cancel_lock(struc
        return 0;
  }
  
 -static inline int flock_lock_file_wait(struct file *filp,
 -                                     struct file_lock *request)
 +static inline int flock_lock_inode_wait(struct inode *inode,
 +                                      struct file_lock *request)
  {
        return -ENOLCK;
  }
@@@ -1204,20 -1202,6 +1204,20 @@@ static inline void show_fd_locks(struc
                        struct file *filp, struct files_struct *files) {}
  #endif /* !CONFIG_FILE_LOCKING */
  
 +static inline struct inode *file_inode(const struct file *f)
 +{
 +      return f->f_inode;
 +}
 +
 +static inline int posix_lock_file_wait(struct file *filp, struct file_lock *fl)
 +{
 +      return posix_lock_inode_wait(file_inode(filp), fl);
 +}
 +
 +static inline int flock_lock_file_wait(struct file *filp, struct file_lock *fl)
 +{
 +      return flock_lock_inode_wait(file_inode(filp), fl);
 +}
  
  struct fasync_struct {
        spinlock_t              fa_lock;
@@@ -1260,6 -1244,7 +1260,7 @@@ struct mm_struct
  
  /* sb->s_iflags */
  #define SB_I_CGROUPWB 0x00000001      /* cgroup-aware writeback enabled */
+ #define SB_I_NOEXEC   0x00000002      /* Ignore executables on this fs */
  
  /* Possible states of 'frozen' field */
  enum {
@@@ -2027,6 -2012,11 +2028,6 @@@ extern void ihold(struct inode * inode)
  extern void iput(struct inode *);
  extern int generic_update_time(struct inode *, struct timespec *, int);
  
 -static inline struct inode *file_inode(const struct file *f)
 -{
 -      return f->f_inode;
 -}
 -
  /* /sys/fs */
  extern struct kobject *fs_kobj;
  
@@@ -2246,7 -2236,7 +2247,7 @@@ extern int ioctl_preallocate(struct fil
  
  /* fs/dcache.c */
  extern void __init vfs_caches_init_early(void);
 -extern void __init vfs_caches_init(unsigned long);
 +extern void __init vfs_caches_init(void);
  
  extern struct kmem_cache *names_cachep;
  
@@@ -3041,4 -3031,6 +3042,6 @@@ static inline bool dir_relax(struct ino
        return !IS_DEADDIR(inode);
  }
  
+ extern bool path_noexec(const struct path *path);
  #endif /* _LINUX_FS_H */
diff --combined kernel/fork.c
index 0d93b4d0617b006007e782d6dfdc6afc05c65d20,2c72b8a8ae24947418fd359d308f685a9f6d5946..2b1a61cddc1954fc6be6d2c8a69c836e6caca33c
@@@ -287,11 -287,6 +287,11 @@@ static void set_max_threads(unsigned in
        max_threads = clamp_t(u64, threads, MIN_THREADS, MAX_THREADS);
  }
  
 +#ifdef CONFIG_ARCH_WANTS_DYNAMIC_TASK_STRUCT
 +/* Initialized by the architecture: */
 +int arch_task_struct_size __read_mostly;
 +#endif
 +
  void __init fork_init(void)
  {
  #ifndef CONFIG_ARCH_TASK_STRUCT_ALLOCATOR
  #endif
        /* create a slab on which task_structs can be allocated */
        task_struct_cachep =
 -              kmem_cache_create("task_struct", sizeof(struct task_struct),
 +              kmem_cache_create("task_struct", arch_task_struct_size,
                        ARCH_MIN_TASKALIGN, SLAB_PANIC | SLAB_NOTRACK, NULL);
  #endif
  
@@@ -1072,7 -1067,6 +1072,7 @@@ static int copy_sighand(unsigned long c
        rcu_assign_pointer(tsk->sighand, sig);
        if (!sig)
                return -ENOMEM;
 +
        atomic_set(&sig->count, 1);
        memcpy(sig->action, current->sighand->action, sizeof(sig->action));
        return 0;
@@@ -1134,7 -1128,6 +1134,7 @@@ static int copy_signal(unsigned long cl
        init_sigpending(&sig->shared_pending);
        INIT_LIST_HEAD(&sig->posix_timers);
        seqlock_init(&sig->stats_lock);
 +      prev_cputime_init(&sig->prev_cputime);
  
        hrtimer_init(&sig->real_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
        sig->real_timer.function = it_real_fn;
@@@ -1280,10 -1273,9 +1280,9 @@@ static struct task_struct *copy_process
  
        /*
         * If the new process will be in a different pid or user namespace
-        * do not allow it to share a thread group or signal handlers or
-        * parent with the forking task.
+        * do not allow it to share a thread group with the forking task.
         */
-       if (clone_flags & CLONE_SIGHAND) {
+       if (clone_flags & CLONE_THREAD) {
                if ((clone_flags & (CLONE_NEWUSER | CLONE_NEWPID)) ||
                    (task_active_pid_ns(current) !=
                                current->nsproxy->pid_ns_for_children))
  
        p->utime = p->stime = p->gtime = 0;
        p->utimescaled = p->stimescaled = 0;
 -#ifndef CONFIG_VIRT_CPU_ACCOUNTING_NATIVE
 -      p->prev_cputime.utime = p->prev_cputime.stime = 0;
 -#endif
 +      prev_cputime_init(&p->prev_cputime);
 +
  #ifdef CONFIG_VIRT_CPU_ACCOUNTING_GEN
        seqlock_init(&p->vtime_seqlock);
        p->vtime_snap = 0;
@@@ -1872,13 -1865,21 +1871,21 @@@ static int check_unshare_flags(unsigne
                                CLONE_NEWUSER|CLONE_NEWPID))
                return -EINVAL;
        /*
-        * Not implemented, but pretend it works if there is nothing to
-        * unshare. Note that unsharing CLONE_THREAD or CLONE_SIGHAND
-        * needs to unshare vm.
+        * Not implemented, but pretend it works if there is nothing
+        * to unshare.  Note that unsharing the address space or the
+        * signal handlers also need to unshare the signal queues (aka
+        * CLONE_THREAD).
         */
        if (unshare_flags & (CLONE_THREAD | CLONE_SIGHAND | CLONE_VM)) {
-               /* FIXME: get_task_mm() increments ->mm_users */
-               if (atomic_read(&current->mm->mm_users) > 1)
+               if (!thread_group_empty(current))
+                       return -EINVAL;
+       }
+       if (unshare_flags & (CLONE_SIGHAND | CLONE_VM)) {
+               if (atomic_read(&current->sighand->count) > 1)
+                       return -EINVAL;
+       }
+       if (unshare_flags & CLONE_VM) {
+               if (!current_is_single_threaded())
                        return -EINVAL;
        }
  
@@@ -1942,20 -1943,21 +1949,21 @@@ SYSCALL_DEFINE1(unshare, unsigned long
        int err;
  
        /*
-        * If unsharing a user namespace must also unshare the thread.
+        * If unsharing a user namespace must also unshare the thread group
+        * and unshare the filesystem root and working directories.
         */
        if (unshare_flags & CLONE_NEWUSER)
                unshare_flags |= CLONE_THREAD | CLONE_FS;
-       /*
-        * If unsharing a thread from a thread group, must also unshare vm.
-        */
-       if (unshare_flags & CLONE_THREAD)
-               unshare_flags |= CLONE_VM;
        /*
         * If unsharing vm, must also unshare signal handlers.
         */
        if (unshare_flags & CLONE_VM)
                unshare_flags |= CLONE_SIGHAND;
+       /*
+        * If unsharing a signal handlers, must also unshare the signal queues.
+        */
+       if (unshare_flags & CLONE_SIGHAND)
+               unshare_flags |= CLONE_THREAD;
        /*
         * If unsharing namespace, must also unshare filesystem information.
         */
diff --combined security/security.c
index 994283624bdb223e13fd99a93531909c6882b83d,062f3c997fdc32a585c0ac02f72a108fee094f53..75b85fdc4e9789bde2fe59b56a29a89ee4020c28
@@@ -380,8 -380,8 +380,8 @@@ int security_inode_init_security(struc
                return 0;
  
        if (!initxattrs)
 -              return call_int_hook(inode_init_security, 0, inode, dir, qstr,
 -                                                       NULL, NULL, NULL);
 +              return call_int_hook(inode_init_security, -EOPNOTSUPP, inode,
 +                                   dir, qstr, NULL, NULL, NULL);
        memset(new_xattrs, 0, sizeof(new_xattrs));
        lsm_xattr = new_xattrs;
        ret = call_int_hook(inode_init_security, -EOPNOTSUPP, inode, dir, qstr,
@@@ -409,8 -409,8 +409,8 @@@ int security_old_inode_init_security(st
  {
        if (unlikely(IS_PRIVATE(inode)))
                return -EOPNOTSUPP;
 -      return call_int_hook(inode_init_security, 0, inode, dir, qstr,
 -                              name, value, len);
 +      return call_int_hook(inode_init_security, -EOPNOTSUPP, inode, dir,
 +                           qstr, name, value, len);
  }
  EXPORT_SYMBOL(security_old_inode_init_security);
  
@@@ -776,7 -776,7 +776,7 @@@ static inline unsigned long mmap_prot(s
         * ditto if it's not on noexec mount, except that on !MMU we need
         * NOMMU_MAP_EXEC (== VM_MAYEXEC) in this case
         */
-       if (!(file->f_path.mnt->mnt_flags & MNT_NOEXEC)) {
+       if (!path_noexec(&file->f_path)) {
  #ifndef CONFIG_MMU
                if (file->f_op->mmap_capabilities) {
                        unsigned caps = file->f_op->mmap_capabilities(file);
@@@ -1281,8 -1281,7 +1281,8 @@@ int security_socket_getpeersec_stream(s
  
  int security_socket_getpeersec_dgram(struct socket *sock, struct sk_buff *skb, u32 *secid)
  {
 -      return call_int_hook(socket_getpeersec_dgram, 0, sock, skb, secid);
 +      return call_int_hook(socket_getpeersec_dgram, -ENOPROTOOPT, sock,
 +                           skb, secid);
  }
  EXPORT_SYMBOL(security_socket_getpeersec_dgram);
  
This page took 0.102713 seconds and 4 git commands to generate.