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:
parent
64e86f632b
commit
3fd945a79e
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Reference in New Issue