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: Jeff Layton <jlayton@kernel.org>
Reviewed-by: Xiubo Li <xiubli@redhat.com>
Reviewed-and-tested-by: Luís Henriques <lhenriques@suse.de>
Reviewed-by: Milind Changire <mchangir@redhat.com>
Signed-off-by: Ilya Dryomov <idryomov@gmail.com>
This commit is contained in:
Jeff Layton 2020-08-07 09:28:31 -04:00 committed by Ilya Dryomov
parent 64e86f632b
commit 3fd945a79e
5 changed files with 172 additions and 25 deletions

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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

View File

@ -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?
*
* If @stop_on_nosnap, generate path relative to the first non-snapped
* inode.
* 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)
*
* 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 @@ retry:
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);
}
temp = cur;
spin_unlock(&temp->d_lock);
cur = dget_parent(temp);
dput(temp);
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);
}
dput(cur);
cur = parent;
/* Are we at the root? */
if (IS_ROOT(cur))
@ -2515,8 +2563,8 @@ retry:
* 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 @@ out_free1:
ceph_mdsc_free_path((char *)path1, pathlen1);
out:
return msg;
out_err:
ceph_msg_put(msg);
msg = ERR_PTR(ret);
goto out_free2;
}
/*

View File

@ -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,