diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c
index d9b977c0f38d..69398eb7b325 100644
--- a/fs/fuse/dir.c
+++ b/fs/fuse/dir.c
@@ -17,6 +17,9 @@
#include <linux/xattr.h>
#include <linux/iversion.h>
#include <linux/posix_acl.h>
+#include <linux/security.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
static void fuse_advise_use_readdirplus(struct inode *dir)
{@@ -456,6 +459,69 @@ static struct dentry *fuse_lookup(struct inode *dir, struct dentry *entry,
return ERR_PTR(err);
}
+static int get_security_context(struct dentry *entry, umode_t mode,
+ void **security_ctx, u32 *security_ctxlen)
+{
+ struct fuse_secctx *fsecctx;
+ struct fuse_secctx_header *fsecctx_header;
+ void *ctx, *full_ctx;
+ u32 ctxlen, full_ctxlen;
+ int err = 0;
+ const char *name;
+
+ err = security_dentry_init_security(entry, mode, &entry->d_name,
+ &name, &ctx, &ctxlen);
+ if (err) {
+ if (err != -EOPNOTSUPP)
+ goto out_err;
+ /* No LSM is supporting this security hook. Ignore error */
+ err = 0;
+ ctxlen = 0;
+ }
+
+ if (ctxlen > 0) {
+ void *ptr;
+
+ full_ctxlen = sizeof(*fsecctx_header) + sizeof(*fsecctx) +
+ strlen(name) + ctxlen + 1;
+ full_ctx = kzalloc(full_ctxlen, GFP_KERNEL);
+ if (!full_ctx) {
+ err = -ENOMEM;
+ kfree(ctx);
+ goto out_err;
+ }
+
+ ptr = full_ctx;
+ fsecctx_header = (struct fuse_secctx_header*) ptr;
+ fsecctx_header->nr_secctx = 1;
+ fsecctx_header->size = full_ctxlen;
+ ptr += sizeof(*fsecctx_header);
+
+ fsecctx = (struct fuse_secctx*) ptr;
+ fsecctx->size = ctxlen;
+ ptr += sizeof(*fsecctx);
+
+ strcpy(ptr, name);
+ ptr += strlen(name) + 1;
+ memcpy(ptr, ctx, ctxlen);
+ kfree(ctx);
+ } else {
+ full_ctxlen = sizeof(*fsecctx_header);
+ full_ctx = kzalloc(full_ctxlen, GFP_KERNEL);
+ if (!full_ctx) {
+ err = -ENOMEM;
+ goto out_err;
+ }
+ fsecctx_header = full_ctx;
+ fsecctx_header->size = full_ctxlen;
+ }
+
+ *security_ctxlen = full_ctxlen;
+ *security_ctx = full_ctx;
+out_err:
+ return err;
+}
+
/*
* Atomic create+open operation
*@@ -476,6 +542,8 @@ 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;
/* Userspace expects S_IFREG in create mode */
BUG_ON((mode & S_IFMT) != S_IFREG);
@@ -517,6 +585,18 @@ static int fuse_create_open(struct inode *dir, struct dentry *entry,
args.out_args[0].value = &outentry;
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 = fuse_simple_request(fm, &args);
if (err)
goto out_free_ff;@@ -554,6 +634,7 @@ static int fuse_create_open(struct inode *dir, struct dentry *entry,
out_free_ff:
fuse_file_free(ff);
+ kfree(security_ctx);
out_put_forget_req:
kfree(forget);
out_err:
@@ -620,6 +701,8 @@ 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 = 0;
if (fuse_is_bad(dir))
return -EIO;
@@ -633,7 +716,27 @@ static int create_new_entry(struct fuse_mount *fm, struct fuse_args *args,
args->out_numargs = 1;
args->out_args[0].size = sizeof(outarg);
args->out_args[0].value = &outarg;
+
+ if (fm->fc->init_security && args->opcode != FUSE_LINK) {
+ unsigned short idx = args->in_numargs;
+
+ if ((size_t)idx >= ARRAY_SIZE(args->in_args)) {
+ err = -ENOMEM;
+ goto out_put_forget_req;
+ }
+
+ err = get_security_context(entry, mode, &security_ctx,
+ &security_ctxlen);
+ if (err)
+ goto out_put_forget_req;
+
+ args->in_args[idx].size = security_ctxlen;
+ args->in_args[idx].value = security_ctx;
+ args->in_numargs++;
+ }
+
err = fuse_simple_request(fm, args);
+ kfree(security_ctx);
if (err)
goto out_put_forget_req;
diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h
index f55f9f94b1a4..0d257c4eeb70 100644
--- a/fs/fuse/fuse_i.h
+++ b/fs/fuse/fuse_i.h
@@ -765,6 +765,9 @@ struct fuse_conn {
/* Propagate syncfs() to server */
unsigned int sync_fs:1;
+ /* Initialize security xattrs when creating a new inode */
+ unsigned int init_security:1;
+
/** The number of requests waiting for completion */
atomic_t num_waiting;
diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c
index 12d49a1914e8..40c5533243c0 100644
--- a/fs/fuse/inode.c
+++ b/fs/fuse/inode.c
@@ -1143,6 +1143,8 @@ static void process_init_reply(struct fuse_mount *fm, struct fuse_args *args,
}
if (arg->flags & FUSE_SETXATTR_EXT)
fc->setxattr_ext = 1;
+ if (arg->flags & FUSE_SECURITY_CTX)
+ fc->init_security = 1;
} else {
ra_pages = fc->max_read / PAGE_SIZE;
fc->no_lock = 1;@@ -1186,7 +1188,7 @@ void fuse_send_init(struct fuse_mount *fm)
FUSE_PARALLEL_DIROPS | FUSE_HANDLE_KILLPRIV | FUSE_POSIX_ACL |
FUSE_ABORT_ERROR | FUSE_MAX_PAGES | FUSE_CACHE_SYMLINKS |
FUSE_NO_OPENDIR_SUPPORT | FUSE_EXPLICIT_INVAL_DATA |
- FUSE_HANDLE_KILLPRIV_V2 | FUSE_SETXATTR_EXT;
+ FUSE_HANDLE_KILLPRIV_V2 | FUSE_SETXATTR_EXT | FUSE_SECURITY_CTX;
#ifdef CONFIG_FUSE_DAX
if (fm->fc->dax)
ia->in.flags |= FUSE_MAP_ALIGNMENT;
diff --git a/include/uapi/linux/fuse.h b/include/uapi/linux/fuse.h
index 36ed092227fa..710cdf20608d 100644
--- a/include/uapi/linux/fuse.h
+++ b/include/uapi/linux/fuse.h
@@ -184,6 +184,10 @@
*
* 7.34
* - add FUSE_SYNCFS
+ *
+ * 7.35
+ * - add FUSE_SECURITY_CTX flag for fuse_init_out
+ * - add security context to create, mkdir, symlink, and mknod requests
*/
#ifndef _LINUX_FUSE_H
@@ -219,7 +223,7 @@
#define FUSE_KERNEL_VERSION 7
/** Minor version number of this interface */
-#define FUSE_KERNEL_MINOR_VERSION 34
+#define FUSE_KERNEL_MINOR_VERSION 35
/** The node ID of the root inode */
#define FUSE_ROOT_ID 1
@@ -336,6 +340,8 @@ struct fuse_file_lock {
* write/truncate sgid is killed only if file has group
* execute permission. (Same as Linux VFS behavior).
* FUSE_SETXATTR_EXT: Server supports extended struct fuse_setxattr_in
+ * FUSE_SECURITY_CTX: add security context to create, mkdir, symlink, and
+ * mknod
*/
#define FUSE_ASYNC_READ (1 << 0)
#define FUSE_POSIX_LOCKS (1 << 1)@@ -367,6 +373,7 @@ struct fuse_file_lock {
#define FUSE_SUBMOUNTS (1 << 27)
#define FUSE_HANDLE_KILLPRIV_V2 (1 << 28)
#define FUSE_SETXATTR_EXT (1 << 29)
+#define FUSE_SECURITY_CTX (1 << 30)
/**
* CUSE INIT request/reply flags@@ -979,4 +986,26 @@ struct fuse_syncfs_in {
uint64_t padding;
};
+/*
+ * For each security context, send fuse_secctx with size of security context
+ * fuse_secctx will be followed by security context name and this in turn
+ * will be followed by actual context label.
+ * fuse_secctx, name, context
+ * */
+struct fuse_secctx {
+ uint32_t size;
+ uint32_t padding;
+};
+
+/*
+ * Contains the information about how many fuse_secctx structures are being
+ * sent and what's the total size of all security contexts (including
+ * size of fuse_secctx_header).
+ *
+ */
+struct fuse_secctx_header {
+ uint32_t size;
+ uint32_t nr_secctx;
+};
+
#endif /* _LINUX_FUSE_H */--
2.31.1