Commit 2d332d5b authored by Jeff Layton's avatar Jeff Layton Committed by Ilya Dryomov
Browse files

ceph: fscrypt_auth handling for ceph



Most fscrypt-enabled filesystems store the crypto context in an xattr,
but that's problematic for ceph as xatts are governed by the XATTR cap,
but we really want the crypto context as part of the AUTH cap.

Because of this, the MDS has added two new inode metadata fields:
fscrypt_auth and fscrypt_file. The former is used to hold the crypto
context, and the latter is used to track the real file size.

Parse new fscrypt_auth and fscrypt_file fields in inode traces. For now,
we don't use fscrypt_file, but fscrypt_auth is used to hold the fscrypt
context.

Allow the client to use a setattr request for setting the fscrypt_auth
field. Since this is not a standard setattr request from the VFS, we add
a new field to __ceph_setattr that carries ceph-specific inode attrs.

Have the set_context op do a setattr that sets the fscrypt_auth value,
and get_context just return the contents of that field (since it should
always be available).

Signed-off-by: default avatarJeff Layton <jlayton@kernel.org>
Reviewed-by: default avatarXiubo Li <xiubli@redhat.com>
Reviewed-and-tested-by: default avatarLuís Henriques <lhenriques@suse.de>
Reviewed-by: default avatarMilind Changire <mchangir@redhat.com>
Signed-off-by: default avatarIlya Dryomov <idryomov@gmail.com>
parent 4de77f25
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -12,3 +12,4 @@ ceph-y := super.o inode.o dir.o file.o locks.o addr.o ioctl.o \

ceph-$(CONFIG_CEPH_FSCACHE) += cache.o
ceph-$(CONFIG_CEPH_FS_POSIX_ACL) += acl.o
ceph-$(CONFIG_FS_ENCRYPTION) += crypto.o
+2 −2
Original line number Diff line number Diff line
@@ -140,7 +140,7 @@ int ceph_set_acl(struct mnt_idmap *idmap, struct dentry *dentry,
		newattrs.ia_ctime = current_time(inode);
		newattrs.ia_mode = new_mode;
		newattrs.ia_valid = ATTR_MODE | ATTR_CTIME;
		ret = __ceph_setattr(inode, &newattrs);
		ret = __ceph_setattr(inode, &newattrs, NULL);
		if (ret)
			goto out_free;
	}
@@ -151,7 +151,7 @@ int ceph_set_acl(struct mnt_idmap *idmap, struct dentry *dentry,
			newattrs.ia_ctime = old_ctime;
			newattrs.ia_mode = old_mode;
			newattrs.ia_valid = ATTR_MODE | ATTR_CTIME;
			__ceph_setattr(inode, &newattrs);
			__ceph_setattr(inode, &newattrs, NULL);
		}
		goto out_free;
	}
+65 −13
Original line number Diff line number Diff line
@@ -14,6 +14,7 @@
#include "super.h"
#include "mds_client.h"
#include "cache.h"
#include "crypto.h"
#include <linux/ceph/decode.h>
#include <linux/ceph/messenger.h>

@@ -1216,15 +1217,12 @@ struct cap_msg_args {
	umode_t			mode;
	bool			inline_data;
	bool			wake;
	u32			fscrypt_auth_len;
	u32			fscrypt_file_len;
	u8			fscrypt_auth[sizeof(struct ceph_fscrypt_auth)]; // for context
	u8			fscrypt_file[sizeof(u64)]; // for size
};

/*
 * cap struct size + flock buffer size + inline version + inline data size +
 * osd_epoch_barrier + oldest_flush_tid
 */
#define CAP_MSG_SIZE (sizeof(struct ceph_mds_caps) + \
		      4 + 8 + 4 + 4 + 8 + 4 + 4 + 4 + 8 + 8 + 4)

/* Marshal up the cap msg to the MDS */
static void encode_cap_msg(struct ceph_msg *msg, struct cap_msg_args *arg)
{
@@ -1240,7 +1238,7 @@ static void encode_cap_msg(struct ceph_msg *msg, struct cap_msg_args *arg)
	     arg->size, arg->max_size, arg->xattr_version,
	     arg->xattr_buf ? (int)arg->xattr_buf->vec.iov_len : 0);

	msg->hdr.version = cpu_to_le16(10);
	msg->hdr.version = cpu_to_le16(12);
	msg->hdr.tid = cpu_to_le64(arg->flush_tid);

	fc = msg->front.iov_base;
@@ -1311,6 +1309,21 @@ static void encode_cap_msg(struct ceph_msg *msg, struct cap_msg_args *arg)

	/* Advisory flags (version 10) */
	ceph_encode_32(&p, arg->flags);

	/* dirstats (version 11) - these are r/o on the client */
	ceph_encode_64(&p, 0);
	ceph_encode_64(&p, 0);

#if IS_ENABLED(CONFIG_FS_ENCRYPTION)
	/* fscrypt_auth and fscrypt_file (version 12) */
	ceph_encode_32(&p, arg->fscrypt_auth_len);
	ceph_encode_copy(&p, arg->fscrypt_auth, arg->fscrypt_auth_len);
	ceph_encode_32(&p, arg->fscrypt_file_len);
	ceph_encode_copy(&p, arg->fscrypt_file, arg->fscrypt_file_len);
#else /* CONFIG_FS_ENCRYPTION */
	ceph_encode_32(&p, 0);
	ceph_encode_32(&p, 0);
#endif /* CONFIG_FS_ENCRYPTION */
}

/*
@@ -1432,8 +1445,38 @@ static void __prep_cap(struct cap_msg_args *arg, struct ceph_cap *cap,
		}
	}
	arg->flags = flags;
#if IS_ENABLED(CONFIG_FS_ENCRYPTION)
	if (ci->fscrypt_auth_len &&
	    WARN_ON_ONCE(ci->fscrypt_auth_len > sizeof(struct ceph_fscrypt_auth))) {
		/* Don't set this if it's too big */
		arg->fscrypt_auth_len = 0;
	} else {
		arg->fscrypt_auth_len = ci->fscrypt_auth_len;
		memcpy(arg->fscrypt_auth, ci->fscrypt_auth,
		       min_t(size_t, ci->fscrypt_auth_len,
			     sizeof(arg->fscrypt_auth)));
	}
	/* FIXME: use this to track "real" size */
	arg->fscrypt_file_len = 0;
#endif /* CONFIG_FS_ENCRYPTION */
}

#define CAP_MSG_FIXED_FIELDS (sizeof(struct ceph_mds_caps) + \
		      4 + 8 + 4 + 4 + 8 + 4 + 4 + 4 + 8 + 8 + 4 + 8 + 8 + 4 + 4)

#if IS_ENABLED(CONFIG_FS_ENCRYPTION)
static inline int cap_msg_size(struct cap_msg_args *arg)
{
	return CAP_MSG_FIXED_FIELDS + arg->fscrypt_auth_len +
			arg->fscrypt_file_len;
}
#else
static inline int cap_msg_size(struct cap_msg_args *arg)
{
	return CAP_MSG_FIXED_FIELDS;
}
#endif /* CONFIG_FS_ENCRYPTION */

/*
 * Send a cap msg on the given inode.
 *
@@ -1444,7 +1487,8 @@ static void __send_cap(struct cap_msg_args *arg, struct ceph_inode_info *ci)
	struct ceph_msg *msg;
	struct inode *inode = &ci->netfs.inode;

	msg = ceph_msg_new(CEPH_MSG_CLIENT_CAPS, CAP_MSG_SIZE, GFP_NOFS, false);
	msg = ceph_msg_new(CEPH_MSG_CLIENT_CAPS, cap_msg_size(arg), GFP_NOFS,
			   false);
	if (!msg) {
		pr_err("error allocating cap msg: ino (%llx.%llx) flushing %s tid %llu, requeuing cap.\n",
		       ceph_vinop(inode), ceph_cap_string(arg->dirty),
@@ -1470,10 +1514,6 @@ static inline int __send_flush_snap(struct inode *inode,
	struct cap_msg_args	arg;
	struct ceph_msg		*msg;

	msg = ceph_msg_new(CEPH_MSG_CLIENT_CAPS, CAP_MSG_SIZE, GFP_NOFS, false);
	if (!msg)
		return -ENOMEM;

	arg.session = session;
	arg.ino = ceph_vino(inode).ino;
	arg.cid = 0;
@@ -1511,6 +1551,18 @@ static inline int __send_flush_snap(struct inode *inode,
	arg.flags = 0;
	arg.wake = false;

	/*
	 * No fscrypt_auth changes from a capsnap. It will need
	 * to update fscrypt_file on size changes (TODO).
	 */
	arg.fscrypt_auth_len = 0;
	arg.fscrypt_file_len = 0;

	msg = ceph_msg_new(CEPH_MSG_CLIENT_CAPS, cap_msg_size(&arg),
			   GFP_NOFS, false);
	if (!msg)
		return -ENOMEM;

	encode_cap_msg(msg, &arg);
	ceph_con_send(&arg.session->s_con, msg);
	return 0;

fs/ceph/crypto.c

0 → 100644
+77 −0
Original line number Diff line number Diff line
// SPDX-License-Identifier: GPL-2.0
#include <linux/ceph/ceph_debug.h>
#include <linux/xattr.h>
#include <linux/fscrypt.h>

#include "super.h"
#include "crypto.h"

static int ceph_crypt_get_context(struct inode *inode, void *ctx, size_t len)
{
	struct ceph_inode_info *ci = ceph_inode(inode);
	struct ceph_fscrypt_auth *cfa = (struct ceph_fscrypt_auth *)ci->fscrypt_auth;
	u32 ctxlen;

	/* Non existent or too short? */
	if (!cfa || (ci->fscrypt_auth_len < (offsetof(struct ceph_fscrypt_auth, cfa_blob) + 1)))
		return -ENOBUFS;

	/* Some format we don't recognize? */
	if (le32_to_cpu(cfa->cfa_version) != CEPH_FSCRYPT_AUTH_VERSION)
		return -ENOBUFS;

	ctxlen = le32_to_cpu(cfa->cfa_blob_len);
	if (len < ctxlen)
		return -ERANGE;

	memcpy(ctx, cfa->cfa_blob, ctxlen);
	return ctxlen;
}

static int ceph_crypt_set_context(struct inode *inode, const void *ctx,
				  size_t len, void *fs_data)
{
	int ret;
	struct iattr attr = { };
	struct ceph_iattr cia = { };
	struct ceph_fscrypt_auth *cfa;

	WARN_ON_ONCE(fs_data);

	if (len > FSCRYPT_SET_CONTEXT_MAX_SIZE)
		return -EINVAL;

	cfa = kzalloc(sizeof(*cfa), GFP_KERNEL);
	if (!cfa)
		return -ENOMEM;

	cfa->cfa_version = cpu_to_le32(CEPH_FSCRYPT_AUTH_VERSION);
	cfa->cfa_blob_len = cpu_to_le32(len);
	memcpy(cfa->cfa_blob, ctx, len);

	cia.fscrypt_auth = cfa;

	ret = __ceph_setattr(inode, &attr, &cia);
	if (ret == 0)
		inode_set_flags(inode, S_ENCRYPTED, S_ENCRYPTED);
	kfree(cia.fscrypt_auth);
	return ret;
}

static bool ceph_crypt_empty_dir(struct inode *inode)
{
	struct ceph_inode_info *ci = ceph_inode(inode);

	return ci->i_rsubdirs + ci->i_rfiles == 1;
}

static struct fscrypt_operations ceph_fscrypt_ops = {
	.get_context		= ceph_crypt_get_context,
	.set_context		= ceph_crypt_set_context,
	.empty_dir		= ceph_crypt_empty_dir,
};

void ceph_fscrypt_set_ops(struct super_block *sb)
{
	fscrypt_set_ops(sb, &ceph_fscrypt_ops);
}

fs/ceph/crypto.h

0 → 100644
+36 −0
Original line number Diff line number Diff line
/* SPDX-License-Identifier: GPL-2.0 */
/*
 * Ceph fscrypt functionality
 */

#ifndef _CEPH_CRYPTO_H
#define _CEPH_CRYPTO_H

#include <linux/fscrypt.h>

struct ceph_fscrypt_auth {
	__le32	cfa_version;
	__le32	cfa_blob_len;
	u8	cfa_blob[FSCRYPT_SET_CONTEXT_MAX_SIZE];
} __packed;

#define CEPH_FSCRYPT_AUTH_VERSION	1
static inline u32 ceph_fscrypt_auth_len(struct ceph_fscrypt_auth *fa)
{
	u32 ctxsize = le32_to_cpu(fa->cfa_blob_len);

	return offsetof(struct ceph_fscrypt_auth, cfa_blob) + ctxsize;
}

#ifdef CONFIG_FS_ENCRYPTION
void ceph_fscrypt_set_ops(struct super_block *sb);

#else /* CONFIG_FS_ENCRYPTION */

static inline void ceph_fscrypt_set_ops(struct super_block *sb)
{
}

#endif /* CONFIG_FS_ENCRYPTION */

#endif
Loading