Commit 3fd945a7 authored by Jeff Layton's avatar Jeff Layton Committed by Ilya Dryomov
Browse files

ceph: encode encrypted name in ceph_mdsc_build_path and dentry release



Allow ceph_mdsc_build_path to encrypt and base64 encode the filename
when the parent is encrypted and we're sending the path to the MDS. In
a similar fashion, encode encrypted dentry names if including a dentry
release in a request.

In most cases, we just encrypt the filenames and base64 encode them,
but when the name is longer than CEPH_NOHASH_NAME_MAX, we use a similar
scheme to fscrypt proper, and hash the remaning bits with sha256.

When doing this, we then send along the full crypttext of the name in
the new alternate_name field of the MClientRequest. The MDS can then
send that along in readdir responses and traces.

[ idryomov: drop duplicate include reported by Abaci Robot ]

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 64e86f63
Loading
Loading
Loading
Loading
+28 −4
Original line number Diff line number Diff line
@@ -4663,6 +4663,18 @@ int ceph_encode_inode_release(void **p, struct inode *inode,
	return ret;
}

/**
 * ceph_encode_dentry_release - encode a dentry release into an outgoing request
 * @p: outgoing request buffer
 * @dentry: dentry to release
 * @dir: dir to release it from
 * @mds: mds that we're speaking to
 * @drop: caps being dropped
 * @unless: unless we have these caps
 *
 * Encode a dentry release into an outgoing request buffer. Returns 1 if the
 * thing was released, or a negative error code otherwise.
 */
int ceph_encode_dentry_release(void **p, struct dentry *dentry,
			       struct inode *dir,
			       int mds, int drop, int unless)
@@ -4695,13 +4707,25 @@ int ceph_encode_dentry_release(void **p, struct dentry *dentry,
	if (ret && di->lease_session && di->lease_session->s_mds == mds) {
		dout("encode_dentry_release %p mds%d seq %d\n",
		     dentry, mds, (int)di->lease_seq);
		rel->dname_seq = cpu_to_le32(di->lease_seq);
		__ceph_mdsc_drop_dentry_lease(dentry);
		spin_unlock(&dentry->d_lock);
		if (IS_ENCRYPTED(dir) && fscrypt_has_encryption_key(dir)) {
			int ret2 = ceph_encode_encrypted_fname(dir, dentry, *p);

			if (ret2 < 0)
				return ret2;

			rel->dname_len = cpu_to_le32(ret2);
			*p += ret2;
		} else {
			rel->dname_len = cpu_to_le32(dentry->d_name.len);
			memcpy(*p, dentry->d_name.name, dentry->d_name.len);
			*p += dentry->d_name.len;
		rel->dname_seq = cpu_to_le32(di->lease_seq);
		__ceph_mdsc_drop_dentry_lease(dentry);
		}
	} else {
		spin_unlock(&dentry->d_lock);
	}
	return ret;
}

+53 −0
Original line number Diff line number Diff line
@@ -191,3 +191,56 @@ void ceph_fscrypt_as_ctx_to_req(struct ceph_mds_request *req,
{
	swap(req->r_fscrypt_auth, as->fscrypt_auth);
}

int ceph_encode_encrypted_fname(const struct inode *parent,
				struct dentry *dentry, char *buf)
{
	u32 len;
	int elen;
	int ret;
	u8 *cryptbuf;

	WARN_ON_ONCE(!fscrypt_has_encryption_key(parent));

	/*
	 * Convert cleartext d_name to ciphertext. If result is longer than
	 * CEPH_NOHASH_NAME_MAX, sha256 the remaining bytes
	 *
	 * See: fscrypt_setup_filename
	 */
	if (!fscrypt_fname_encrypted_size(parent, dentry->d_name.len, NAME_MAX,
					  &len))
		return -ENAMETOOLONG;

	/* Allocate a buffer appropriate to hold the result */
	cryptbuf = kmalloc(len > CEPH_NOHASH_NAME_MAX ? NAME_MAX : len,
			   GFP_KERNEL);
	if (!cryptbuf)
		return -ENOMEM;

	ret = fscrypt_fname_encrypt(parent, &dentry->d_name, cryptbuf, len);
	if (ret) {
		kfree(cryptbuf);
		return ret;
	}

	/* hash the end if the name is long enough */
	if (len > CEPH_NOHASH_NAME_MAX) {
		u8 hash[SHA256_DIGEST_SIZE];
		u8 *extra = cryptbuf + CEPH_NOHASH_NAME_MAX;

		/*
		 * hash the extra bytes and overwrite crypttext beyond that
		 * point with it
		 */
		sha256(extra, len - CEPH_NOHASH_NAME_MAX, hash);
		memcpy(extra, hash, SHA256_DIGEST_SIZE);
		len = CEPH_NOHASH_NAME_MAX + SHA256_DIGEST_SIZE;
	}

	/* base64 encode the encrypted name */
	elen = ceph_base64_encode(cryptbuf, len, buf);
	kfree(cryptbuf);
	dout("base64-encoded ciphertext name = %.*s\n", elen, buf);
	return elen;
}
+9 −0
Original line number Diff line number Diff line
@@ -6,6 +6,7 @@
#ifndef _CEPH_CRYPTO_H
#define _CEPH_CRYPTO_H

#include <crypto/sha2.h>
#include <linux/fscrypt.h>

struct ceph_fs_client;
@@ -67,6 +68,8 @@ int ceph_fscrypt_prepare_context(struct inode *dir, struct inode *inode,
				 struct ceph_acl_sec_ctx *as);
void ceph_fscrypt_as_ctx_to_req(struct ceph_mds_request *req,
				struct ceph_acl_sec_ctx *as);
int ceph_encode_encrypted_fname(const struct inode *parent,
				struct dentry *dentry, char *buf);

#else /* CONFIG_FS_ENCRYPTION */

@@ -91,6 +94,12 @@ static inline void ceph_fscrypt_as_ctx_to_req(struct ceph_mds_request *req,
						struct ceph_acl_sec_ctx *as_ctx)
{
}

static inline int ceph_encode_encrypted_fname(const struct inode *parent,
					      struct dentry *dentry, char *buf)
{
	return -EOPNOTSUPP;
}
#endif /* CONFIG_FS_ENCRYPTION */

#endif
+81 −20
Original line number Diff line number Diff line
@@ -2435,18 +2435,29 @@ static inline u64 __get_oldest_tid(struct ceph_mds_client *mdsc)
	return mdsc->oldest_tid;
}

/*
 * Build a dentry's path.  Allocate on heap; caller must kfree.  Based
 * on build_path_from_dentry in fs/cifs/dir.c.
/**
 * ceph_mdsc_build_path - build a path string to a given dentry
 * @dentry: dentry to which path should be built
 * @plen: returned length of string
 * @pbase: returned base inode number
 * @for_wire: is this path going to be sent to the MDS?
 *
 * Build a string that represents the path to the dentry. This is mostly called
 * for two different purposes:
 *
 * 1) we need to build a path string to send to the MDS (for_wire == true)
 * 2) we need a path string for local presentation (e.g. debugfs)
 *    (for_wire == false)
 *
 * If @stop_on_nosnap, generate path relative to the first non-snapped
 * inode.
 * The path is built in reverse, starting with the dentry. Walk back up toward
 * the root, building the path until the first non-snapped inode is reached
 * (for_wire) or the root inode is reached (!for_wire).
 *
 * Encode hidden .snap dirs as a double /, i.e.
 *   foo/.snap/bar -> foo//bar
 */
char *ceph_mdsc_build_path(struct dentry *dentry, int *plen, u64 *pbase,
			   int stop_on_nosnap)
			   int for_wire)
{
	struct dentry *cur;
	struct inode *inode;
@@ -2468,30 +2479,67 @@ char *ceph_mdsc_build_path(struct dentry *dentry, int *plen, u64 *pbase,
	seq = read_seqbegin(&rename_lock);
	cur = dget(dentry);
	for (;;) {
		struct dentry *temp;
		struct dentry *parent;

		spin_lock(&cur->d_lock);
		inode = d_inode(cur);
		if (inode && ceph_snap(inode) == CEPH_SNAPDIR) {
			dout("build_path path+%d: %p SNAPDIR\n",
			     pos, cur);
		} else if (stop_on_nosnap && inode && dentry != cur &&
			spin_unlock(&cur->d_lock);
			parent = dget_parent(cur);
		} else if (for_wire && inode && dentry != cur &&
			   ceph_snap(inode) == CEPH_NOSNAP) {
			spin_unlock(&cur->d_lock);
			pos++; /* get rid of any prepended '/' */
			break;
		} else {
		} else if (!for_wire || !IS_ENCRYPTED(d_inode(cur->d_parent))) {
			pos -= cur->d_name.len;
			if (pos < 0) {
				spin_unlock(&cur->d_lock);
				break;
			}
			memcpy(path + pos, cur->d_name.name, cur->d_name.len);
			spin_unlock(&cur->d_lock);
			parent = dget_parent(cur);
		} else {
			int len, ret;
			char buf[NAME_MAX];

			/*
			 * Proactively copy name into buf, in case we need to
			 * present it as-is.
			 */
			memcpy(buf, cur->d_name.name, cur->d_name.len);
			len = cur->d_name.len;
			spin_unlock(&cur->d_lock);
			parent = dget_parent(cur);

			ret = __fscrypt_prepare_readdir(d_inode(parent));
			if (ret < 0) {
				dput(parent);
				dput(cur);
				return ERR_PTR(ret);
			}

			if (fscrypt_has_encryption_key(d_inode(parent))) {
				len = ceph_encode_encrypted_fname(d_inode(parent),
								  cur, buf);
				if (len < 0) {
					dput(parent);
					dput(cur);
					return ERR_PTR(len);
				}
			}
			pos -= len;
			if (pos < 0) {
				dput(parent);
				break;
			}
			memcpy(path + pos, buf, len);
		}
		temp = cur;
		spin_unlock(&temp->d_lock);
		cur = dget_parent(temp);
		dput(temp);
		dput(cur);
		cur = parent;

		/* Are we at the root? */
		if (IS_ROOT(cur))
@@ -2515,8 +2563,8 @@ char *ceph_mdsc_build_path(struct dentry *dentry, int *plen, u64 *pbase,
		 * A rename didn't occur, but somehow we didn't end up where
		 * we thought we would. Throw a warning and try again.
		 */
		pr_warn("build_path did not end path lookup where "
			"expected, pos is %d\n", pos);
		pr_warn("build_path did not end path lookup where expected (pos = %d)\n",
			pos);
		goto retry;
	}

@@ -2536,7 +2584,8 @@ static int build_dentry_path(struct dentry *dentry, struct inode *dir,
	rcu_read_lock();
	if (!dir)
		dir = d_inode_rcu(dentry->d_parent);
	if (dir && parent_locked && ceph_snap(dir) == CEPH_NOSNAP) {
	if (dir && parent_locked && ceph_snap(dir) == CEPH_NOSNAP &&
	    !IS_ENCRYPTED(dir)) {
		*pino = ceph_ino(dir);
		rcu_read_unlock();
		*ppath = dentry->d_name.name;
@@ -2765,15 +2814,23 @@ static struct ceph_msg *create_request_message(struct ceph_mds_session *session,
		      req->r_inode ? req->r_inode : d_inode(req->r_dentry),
		      mds, req->r_inode_drop, req->r_inode_unless,
		      req->r_op == CEPH_MDS_OP_READDIR);
	if (req->r_dentry_drop)
		releases += ceph_encode_dentry_release(&p, req->r_dentry,
	if (req->r_dentry_drop) {
		ret = ceph_encode_dentry_release(&p, req->r_dentry,
				req->r_parent, mds, req->r_dentry_drop,
				req->r_dentry_unless);
	if (req->r_old_dentry_drop)
		releases += ceph_encode_dentry_release(&p, req->r_old_dentry,
		if (ret < 0)
			goto out_err;
		releases += ret;
	}
	if (req->r_old_dentry_drop) {
		ret = ceph_encode_dentry_release(&p, req->r_old_dentry,
				req->r_old_dentry_dir, mds,
				req->r_old_dentry_drop,
				req->r_old_dentry_unless);
		if (ret < 0)
			goto out_err;
		releases += ret;
	}
	if (req->r_old_inode_drop)
		releases += ceph_encode_inode_release(&p,
		      d_inode(req->r_old_dentry),
@@ -2815,6 +2872,10 @@ static struct ceph_msg *create_request_message(struct ceph_mds_session *session,
		ceph_mdsc_free_path((char *)path1, pathlen1);
out:
	return msg;
out_err:
	ceph_msg_put(msg);
	msg = ERR_PTR(ret);
	goto out_free2;
}

/*
+1 −1
Original line number Diff line number Diff line
@@ -565,7 +565,7 @@ static inline void ceph_mdsc_free_path(char *path, int len)
}

extern char *ceph_mdsc_build_path(struct dentry *dentry, int *plen, u64 *base,
				  int stop_on_nosnap);
				  int for_wire);

extern void __ceph_mdsc_drop_dentry_lease(struct dentry *dentry);
extern void ceph_mdsc_lease_send_msg(struct ceph_mds_session *session,