Merge tag 'fuse-update-6.3' of git://git.kernel.org/pub/scm/linux/kernel/git/mszeredi...
[linux-2.6-block.git] / fs / fuse / dir.c
index cd1eae61e84c6969a4d98c04f470e50f814fc168..35bc174f9ba21942ba4a01e6d2b99abee16dc1ed 100644 (file)
@@ -145,7 +145,7 @@ static void fuse_dir_changed(struct inode *dir)
        inode_maybe_inc_iversion(dir, false);
 }
 
-/**
+/*
  * Mark the attributes as stale due to an atime change.  Avoid the invalidate if
  * atime is not used.
  */
@@ -466,7 +466,7 @@ static struct dentry *fuse_lookup(struct inode *dir, struct dentry *entry,
 }
 
 static int get_security_context(struct dentry *entry, umode_t mode,
-                               void **security_ctx, u32 *security_ctxlen)
+                               struct fuse_in_arg *ext)
 {
        struct fuse_secctx *fctx;
        struct fuse_secctx_header *header;
@@ -513,14 +513,100 @@ static int get_security_context(struct dentry *entry, umode_t mode,
 
                memcpy(ptr, ctx, ctxlen);
        }
-       *security_ctxlen = total_len;
-       *security_ctx = header;
+       ext->size = total_len;
+       ext->value = header;
        err = 0;
 out_err:
        kfree(ctx);
        return err;
 }
 
+static void *extend_arg(struct fuse_in_arg *buf, u32 bytes)
+{
+       void *p;
+       u32 newlen = buf->size + bytes;
+
+       p = krealloc(buf->value, newlen, GFP_KERNEL);
+       if (!p) {
+               kfree(buf->value);
+               buf->size = 0;
+               buf->value = NULL;
+               return NULL;
+       }
+
+       memset(p + buf->size, 0, bytes);
+       buf->value = p;
+       buf->size = newlen;
+
+       return p + newlen - bytes;
+}
+
+static u32 fuse_ext_size(size_t size)
+{
+       return FUSE_REC_ALIGN(sizeof(struct fuse_ext_header) + size);
+}
+
+/*
+ * This adds just a single supplementary group that matches the parent's group.
+ */
+static int get_create_supp_group(struct inode *dir, struct fuse_in_arg *ext)
+{
+       struct fuse_conn *fc = get_fuse_conn(dir);
+       struct fuse_ext_header *xh;
+       struct fuse_supp_groups *sg;
+       kgid_t kgid = dir->i_gid;
+       gid_t parent_gid = from_kgid(fc->user_ns, kgid);
+       u32 sg_len = fuse_ext_size(sizeof(*sg) + sizeof(sg->groups[0]));
+
+       if (parent_gid == (gid_t) -1 || gid_eq(kgid, current_fsgid()) ||
+           !in_group_p(kgid))
+               return 0;
+
+       xh = extend_arg(ext, sg_len);
+       if (!xh)
+               return -ENOMEM;
+
+       xh->size = sg_len;
+       xh->type = FUSE_EXT_GROUPS;
+
+       sg = (struct fuse_supp_groups *) &xh[1];
+       sg->nr_groups = 1;
+       sg->groups[0] = parent_gid;
+
+       return 0;
+}
+
+static int get_create_ext(struct fuse_args *args,
+                         struct inode *dir, struct dentry *dentry,
+                         umode_t mode)
+{
+       struct fuse_conn *fc = get_fuse_conn_super(dentry->d_sb);
+       struct fuse_in_arg ext = { .size = 0, .value = NULL };
+       int err = 0;
+
+       if (fc->init_security)
+               err = get_security_context(dentry, mode, &ext);
+       if (!err && fc->create_supp_group)
+               err = get_create_supp_group(dir, &ext);
+
+       if (!err && ext.size) {
+               WARN_ON(args->in_numargs >= ARRAY_SIZE(args->in_args));
+               args->is_ext = true;
+               args->ext_idx = args->in_numargs++;
+               args->in_args[args->ext_idx] = ext;
+       } else {
+               kfree(ext.value);
+       }
+
+       return err;
+}
+
+static void free_ext_value(struct fuse_args *args)
+{
+       if (args->is_ext)
+               kfree(args->in_args[args->ext_idx].value);
+}
+
 /*
  * Atomic create+open operation
  *
@@ -541,8 +627,6 @@ static int fuse_create_open(struct inode *dir, struct dentry *entry,
        struct fuse_entry_out outentry;
        struct fuse_inode *fi;
        struct fuse_file *ff;
-       void *security_ctx = NULL;
-       u32 security_ctxlen;
        bool trunc = flags & O_TRUNC;
 
        /* Userspace expects S_IFREG in create mode */
@@ -586,19 +670,12 @@ static int fuse_create_open(struct inode *dir, struct dentry *entry,
        args.out_args[1].size = sizeof(outopen);
        args.out_args[1].value = &outopen;
 
-       if (fm->fc->init_security) {
-               err = get_security_context(entry, mode, &security_ctx,
-                                          &security_ctxlen);
-               if (err)
-                       goto out_put_forget_req;
-
-               args.in_numargs = 3;
-               args.in_args[2].size = security_ctxlen;
-               args.in_args[2].value = security_ctx;
-       }
+       err = get_create_ext(&args, dir, entry, mode);
+       if (err)
+               goto out_put_forget_req;
 
        err = fuse_simple_request(fm, &args);
-       kfree(security_ctx);
+       free_ext_value(&args);
        if (err)
                goto out_free_ff;
 
@@ -705,8 +782,6 @@ static int create_new_entry(struct fuse_mount *fm, struct fuse_args *args,
        struct dentry *d;
        int err;
        struct fuse_forget_link *forget;
-       void *security_ctx = NULL;
-       u32 security_ctxlen;
 
        if (fuse_is_bad(dir))
                return -EIO;
@@ -721,21 +796,14 @@ static int create_new_entry(struct fuse_mount *fm, struct fuse_args *args,
        args->out_args[0].size = sizeof(outarg);
        args->out_args[0].value = &outarg;
 
-       if (fm->fc->init_security && args->opcode != FUSE_LINK) {
-               err = get_security_context(entry, mode, &security_ctx,
-                                          &security_ctxlen);
+       if (args->opcode != FUSE_LINK) {
+               err = get_create_ext(args, dir, entry, mode);
                if (err)
                        goto out_put_forget_req;
-
-               BUG_ON(args->in_numargs != 2);
-
-               args->in_numargs = 3;
-               args->in_args[2].size = security_ctxlen;
-               args->in_args[2].value = security_ctx;
        }
 
        err = fuse_simple_request(fm, args);
-       kfree(security_ctx);
+       free_ext_value(args);
        if (err)
                goto out_put_forget_req;