Merge tag 'fuse-update-4.18' of git://git.kernel.org/pub/scm/linux/kernel/git/mszered...
authorLinus Torvalds <torvalds@linux-foundation.org>
Thu, 7 Jun 2018 15:50:57 +0000 (08:50 -0700)
committerLinus Torvalds <torvalds@linux-foundation.org>
Thu, 7 Jun 2018 15:50:57 +0000 (08:50 -0700)
Pull fuse updates from Miklos Szeredi:
 "The most interesting part of this update is user namespace support,
  mostly done by Eric Biederman. This enables safe unprivileged fuse
  mounts within a user namespace.

  There are also a couple of fixes for bugs found by syzbot and
  miscellaneous fixes and cleanups"

* tag 'fuse-update-4.18' of git://git.kernel.org/pub/scm/linux/kernel/git/mszeredi/fuse:
  fuse: don't keep dead fuse_conn at fuse_fill_super().
  fuse: fix control dir setup and teardown
  fuse: fix congested state leak on aborted connections
  fuse: Allow fully unprivileged mounts
  fuse: Ensure posix acls are translated outside of init_user_ns
  fuse: add writeback documentation
  fuse: honor AT_STATX_FORCE_SYNC
  fuse: honor AT_STATX_DONT_SYNC
  fuse: Restrict allow_other to the superblock's namespace or a descendant
  fuse: Support fuse filesystems outside of init_user_ns
  fuse: Fail all requests with invalid uids or gids
  fuse: Remove the buggy retranslation of pids in fuse_dev_do_read
  fuse: return -ECONNABORTED on /dev/fuse read after abort
  fuse: atomic_o_trunc should truncate pagecache

1  2 
fs/fuse/inode.c

diff --combined fs/fuse/inode.c
index ef309958e06073ca0bcc7f8a5b487bf3e42628ba,061500c726083b39117a1d61136817142de0c7e6..ffcaf98044b9aeb81292532a838133c518ca6d73
@@@ -171,8 -171,8 +171,8 @@@ void fuse_change_attributes_common(stru
        inode->i_ino     = fuse_squash_ino(attr->ino);
        inode->i_mode    = (inode->i_mode & S_IFMT) | (attr->mode & 07777);
        set_nlink(inode, attr->nlink);
-       inode->i_uid     = make_kuid(&init_user_ns, attr->uid);
-       inode->i_gid     = make_kgid(&init_user_ns, attr->gid);
+       inode->i_uid     = make_kuid(fc->user_ns, attr->uid);
+       inode->i_gid     = make_kgid(fc->user_ns, attr->gid);
        inode->i_blocks  = attr->blocks;
        inode->i_atime.tv_sec   = attr->atime;
        inode->i_atime.tv_nsec  = attr->atimensec;
@@@ -371,7 -371,7 +371,7 @@@ void fuse_unlock_inode(struct inode *in
  
  static void fuse_umount_begin(struct super_block *sb)
  {
-       fuse_abort_conn(get_fuse_conn_super(sb));
+       fuse_abort_conn(get_fuse_conn_super(sb), false);
  }
  
  static void fuse_send_destroy(struct fuse_conn *fc)
@@@ -393,7 -393,7 +393,7 @@@ static void fuse_put_super(struct super
  
        fuse_send_destroy(fc);
  
-       fuse_abort_conn(fc);
+       fuse_abort_conn(fc, false);
        mutex_lock(&fuse_mutex);
        list_del(&fc->entry);
        fuse_ctl_remove_conn(fc);
@@@ -477,7 -477,8 +477,8 @@@ static int fuse_match_uint(substring_t 
        return err;
  }
  
- static int parse_fuse_opt(char *opt, struct fuse_mount_data *d, int is_bdev)
+ static int parse_fuse_opt(char *opt, struct fuse_mount_data *d, int is_bdev,
+                         struct user_namespace *user_ns)
  {
        char *p;
        memset(d, 0, sizeof(struct fuse_mount_data));
                case OPT_USER_ID:
                        if (fuse_match_uint(&args[0], &uv))
                                return 0;
-                       d->user_id = make_kuid(current_user_ns(), uv);
+                       d->user_id = make_kuid(user_ns, uv);
                        if (!uid_valid(d->user_id))
                                return 0;
                        d->user_id_present = 1;
                case OPT_GROUP_ID:
                        if (fuse_match_uint(&args[0], &uv))
                                return 0;
-                       d->group_id = make_kgid(current_user_ns(), uv);
+                       d->group_id = make_kgid(user_ns, uv);
                        if (!gid_valid(d->group_id))
                                return 0;
                        d->group_id_present = 1;
@@@ -565,8 -566,8 +566,8 @@@ static int fuse_show_options(struct seq
        struct super_block *sb = root->d_sb;
        struct fuse_conn *fc = get_fuse_conn_super(sb);
  
-       seq_printf(m, ",user_id=%u", from_kuid_munged(&init_user_ns, fc->user_id));
-       seq_printf(m, ",group_id=%u", from_kgid_munged(&init_user_ns, fc->group_id));
+       seq_printf(m, ",user_id=%u", from_kuid_munged(fc->user_ns, fc->user_id));
+       seq_printf(m, ",group_id=%u", from_kgid_munged(fc->user_ns, fc->group_id));
        if (fc->default_permissions)
                seq_puts(m, ",default_permissions");
        if (fc->allow_other)
@@@ -597,7 -598,7 +598,7 @@@ static void fuse_pqueue_init(struct fus
        fpq->connected = 1;
  }
  
- void fuse_conn_init(struct fuse_conn *fc)
+ void fuse_conn_init(struct fuse_conn *fc, struct user_namespace *user_ns)
  {
        memset(fc, 0, sizeof(*fc));
        spin_lock_init(&fc->lock);
        fc->attr_version = 1;
        get_random_bytes(&fc->scramble_key, sizeof(fc->scramble_key));
        fc->pid_ns = get_pid_ns(task_active_pid_ns(current));
+       fc->user_ns = get_user_ns(user_ns);
  }
  EXPORT_SYMBOL_GPL(fuse_conn_init);
  
@@@ -630,6 -632,7 +632,7 @@@ void fuse_conn_put(struct fuse_conn *fc
                if (fc->destroy_req)
                        fuse_request_free(fc->destroy_req);
                put_pid_ns(fc->pid_ns);
+               put_user_ns(fc->user_ns);
                fc->release(fc);
        }
  }
@@@ -918,6 -921,8 +921,8 @@@ static void process_init_reply(struct f
                                fc->posix_acl = 1;
                                fc->sb->s_xattr = fuse_acl_xattr_handlers;
                        }
+                       if (arg->flags & FUSE_ABORT_ERROR)
+                               fc->abort_err = 1;
                } else {
                        ra_pages = fc->max_read / PAGE_SIZE;
                        fc->no_lock = 1;
@@@ -948,7 -953,8 +953,8 @@@ static void fuse_send_init(struct fuse_
                FUSE_FLOCK_LOCKS | FUSE_HAS_IOCTL_DIR | FUSE_AUTO_INVAL_DATA |
                FUSE_DO_READDIRPLUS | FUSE_READDIRPLUS_AUTO | FUSE_ASYNC_DIO |
                FUSE_WRITEBACK_CACHE | FUSE_NO_OPEN_SUPPORT |
-               FUSE_PARALLEL_DIROPS | FUSE_HANDLE_KILLPRIV | FUSE_POSIX_ACL;
+               FUSE_PARALLEL_DIROPS | FUSE_HANDLE_KILLPRIV | FUSE_POSIX_ACL |
+               FUSE_ABORT_ERROR;
        req->in.h.opcode = FUSE_INIT;
        req->in.numargs = 1;
        req->in.args[0].size = sizeof(*arg);
@@@ -1061,7 -1067,7 +1067,7 @@@ static int fuse_fill_super(struct super
  
        sb->s_flags &= ~(SB_NOSEC | SB_I_VERSION);
  
-       if (!parse_fuse_opt(data, &d, is_bdev))
+       if (!parse_fuse_opt(data, &d, is_bdev, sb->s_user_ns))
                goto err;
  
        if (is_bdev) {
        sb->s_maxbytes = MAX_LFS_FILESIZE;
        sb->s_time_gran = 1;
        sb->s_export_op = &fuse_export_operations;
 +      sb->s_iflags |= SB_I_IMA_UNVERIFIABLE_SIGNATURE;
 +      if (sb->s_user_ns != &init_user_ns)
 +              sb->s_iflags |= SB_I_UNTRUSTED_MOUNTER;
  
        file = fget(d.fd);
        err = -EINVAL;
        if (!file)
                goto err;
  
-       if ((file->f_op != &fuse_dev_operations) ||
-           (file->f_cred->user_ns != &init_user_ns))
+       /*
+        * Require mount to happen from the same user namespace which
+        * opened /dev/fuse to prevent potential attacks.
+        */
+       if (file->f_op != &fuse_dev_operations ||
+           file->f_cred->user_ns != sb->s_user_ns)
                goto err_fput;
  
+       /*
+        * If we are not in the initial user namespace posix
+        * acls must be translated.
+        */
+       if (sb->s_user_ns != &init_user_ns)
+               sb->s_xattr = fuse_no_acl_xattr_handlers;
        fc = kmalloc(sizeof(*fc), GFP_KERNEL);
        err = -ENOMEM;
        if (!fc)
                goto err_fput;
  
-       fuse_conn_init(fc);
+       fuse_conn_init(fc, sb->s_user_ns);
        fc->release = fuse_free_conn;
  
        fud = fuse_dev_alloc(fc);
        fuse_dev_free(fud);
   err_put_conn:
        fuse_conn_put(fc);
+       sb->s_fs_info = NULL;
   err_fput:
        fput(file);
   err:
@@@ -1208,7 -1223,7 +1226,7 @@@ static void fuse_kill_sb_anon(struct su
  static struct file_system_type fuse_fs_type = {
        .owner          = THIS_MODULE,
        .name           = "fuse",
-       .fs_flags       = FS_HAS_SUBTYPE,
+       .fs_flags       = FS_HAS_SUBTYPE | FS_USERNS_MOUNT,
        .mount          = fuse_mount,
        .kill_sb        = fuse_kill_sb_anon,
  };