Re: [RFC PATCH v4 13/17] ceph: add support to readdir for encrypted filenames

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Jeff Layton <jlayton@xxxxxxxxxx> writes:

> Add helper functions for buffer management and for decrypting filenames
> returned by the MDS. Wire those into the readdir codepaths.
>
> Signed-off-by: Jeff Layton <jlayton@xxxxxxxxxx>
> ---
>  fs/ceph/crypto.c | 76 ++++++++++++++++++++++++++++++++++++++++++++++++
>  fs/ceph/crypto.h | 41 ++++++++++++++++++++++++++
>  fs/ceph/dir.c    | 62 +++++++++++++++++++++++++++++++--------
>  fs/ceph/inode.c  | 38 ++++++++++++++++++++++--
>  4 files changed, 202 insertions(+), 15 deletions(-)
>
> diff --git a/fs/ceph/crypto.c b/fs/ceph/crypto.c
> index f037a4939026..7ddd434c5baf 100644
> --- a/fs/ceph/crypto.c
> +++ b/fs/ceph/crypto.c
> @@ -107,3 +107,79 @@ int ceph_fscrypt_prepare_context(struct inode *dir, struct inode *inode,
>  		ceph_pagelist_release(pagelist);
>  	return ret;
>  }
> +
> +/**
> + * ceph_fname_to_usr - convert a filename for userland presentation
> + * @fname: ceph_fname to be converted
> + * @tname: temporary name buffer to use for conversion (may be NULL)
> + * @oname: where converted name should be placed
> + * @is_nokey: set to true if key wasn't available during conversion (may be NULL)
> + *
> + * Given a filename (usually from the MDS), format it for presentation to
> + * userland. If @parent is not encrypted, just pass it back as-is.
> + *
> + * Otherwise, base64 decode the string, and then ask fscrypt to format it
> + * for userland presentation.
> + *
> + * Returns 0 on success or negative error code on error.
> + */
> +int ceph_fname_to_usr(const struct ceph_fname *fname, struct fscrypt_str *tname,
> +		      struct fscrypt_str *oname, bool *is_nokey)
> +{
> +	int ret;
> +	struct fscrypt_str _tname = FSTR_INIT(NULL, 0);
> +	struct fscrypt_str iname;
> +
> +	if (!IS_ENCRYPTED(fname->dir)) {
> +		oname->name = fname->name;
> +		oname->len = fname->name_len;
> +		return 0;
> +	}
> +
> +	/* Sanity check that the resulting name will fit in the buffer */
> +	if (fname->name_len > FSCRYPT_BASE64_CHARS(NAME_MAX))
> +		return -EIO;
> +
> +	ret = __fscrypt_prepare_readdir(fname->dir);
> +	if (ret)
> +		return ret;
> +
> +	/*
> +	 * Use the raw dentry name as sent by the MDS instead of
> +	 * generating a nokey name via fscrypt.
> +	 */
> +	if (!fscrypt_has_encryption_key(fname->dir)) {

While chasing a different the bug (the one I mention yesterday on IRC), I
came across this memory leak: oname->name needs to be freed here.  I
believe that a

	fscrypt_fname_free_buffer(oname);

before the kmemdup() below should be enough.

Cheers,
-- 
Luis

> +		oname->name = kmemdup(fname->name, fname->name_len, GFP_KERNEL);
> +		oname->len = fname->name_len;
> +		if (is_nokey)
> +			*is_nokey = true;
> +		return 0;
> +	}
> +
> +	if (fname->ctext_len == 0) {
> +		int declen;
> +
> +		if (!tname) {
> +			ret = fscrypt_fname_alloc_buffer(NAME_MAX, &_tname);
> +			if (ret)
> +				return ret;
> +			tname = &_tname;
> +		}
> +
> +		declen = fscrypt_base64_decode(fname->name, fname->name_len, tname->name);
> +		if (declen <= 0) {
> +			ret = -EIO;
> +			goto out;
> +		}
> +		iname.name = tname->name;
> +		iname.len = declen;
> +	} else {
> +		iname.name = fname->ctext;
> +		iname.len = fname->ctext_len;
> +	}
> +
> +	ret = fscrypt_fname_disk_to_usr(fname->dir, 0, 0, &iname, oname);
> +out:
> +	fscrypt_fname_free_buffer(&_tname);
> +	return ret;
> +}
> diff --git a/fs/ceph/crypto.h b/fs/ceph/crypto.h
> index 331b9c8da7fb..5a3fb68eb814 100644
> --- a/fs/ceph/crypto.h
> +++ b/fs/ceph/crypto.h
> @@ -11,6 +11,14 @@
>  
>  #define	CEPH_XATTR_NAME_ENCRYPTION_CONTEXT	"encryption.ctx"
>  
> +struct ceph_fname {
> +	struct inode	*dir;
> +	char 		*name;		// b64 encoded, possibly hashed
> +	unsigned char	*ctext;		// binary crypttext (if any)
> +	u32		name_len;	// length of name buffer
> +	u32		ctext_len;	// length of crypttext
> +};
> +
>  #ifdef CONFIG_FS_ENCRYPTION
>  
>  /*
> @@ -37,6 +45,22 @@ static inline void ceph_fscrypt_free_dummy_policy(struct ceph_fs_client *fsc)
>  int ceph_fscrypt_prepare_context(struct inode *dir, struct inode *inode,
>  				 struct ceph_acl_sec_ctx *as);
>  
> +static inline int ceph_fname_alloc_buffer(struct inode *parent, struct fscrypt_str *fname)
> +{
> +	if (!IS_ENCRYPTED(parent))
> +		return 0;
> +	return fscrypt_fname_alloc_buffer(NAME_MAX, fname);
> +}
> +
> +static inline void ceph_fname_free_buffer(struct inode *parent, struct fscrypt_str *fname)
> +{
> +	if (IS_ENCRYPTED(parent))
> +		fscrypt_fname_free_buffer(fname);
> +}
> +
> +int ceph_fname_to_usr(const struct ceph_fname *fname, struct fscrypt_str *tname,
> +			struct fscrypt_str *oname, bool *is_nokey);
> +
>  #else /* CONFIG_FS_ENCRYPTION */
>  
>  static inline void ceph_fscrypt_set_ops(struct super_block *sb)
> @@ -55,6 +79,23 @@ static inline int ceph_fscrypt_prepare_context(struct inode *dir, struct inode *
>  	return 0;
>  }
>  
> +static inline int ceph_fname_alloc_buffer(struct inode *parent, struct fscrypt_str *fname)
> +{
> +	return 0;
> +}
> +
> +static inline void ceph_fname_free_buffer(struct inode *parent, struct fscrypt_str *fname)
> +{
> +}
> +
> +static inline int ceph_fname_to_usr(const struct ceph_fname *fname, struct fscrypt_str *tname,
> +				    struct fscrypt_str *oname, bool *is_nokey)
> +{
> +	oname->name = fname->name;
> +	oname->len = fname->name_len;
> +	return 0;
> +}
> +
>  #endif /* CONFIG_FS_ENCRYPTION */
>  
>  #endif
> diff --git a/fs/ceph/dir.c b/fs/ceph/dir.c
> index 20943769438c..236c381ab6bd 100644
> --- a/fs/ceph/dir.c
> +++ b/fs/ceph/dir.c
> @@ -9,6 +9,7 @@
>  
>  #include "super.h"
>  #include "mds_client.h"
> +#include "crypto.h"
>  
>  /*
>   * Directory operations: readdir, lookup, create, link, unlink,
> @@ -241,7 +242,9 @@ static int __dcache_readdir(struct file *file,  struct dir_context *ctx,
>  		di = ceph_dentry(dentry);
>  		if (d_unhashed(dentry) ||
>  		    d_really_is_negative(dentry) ||
> -		    di->lease_shared_gen != shared_gen) {
> +		    di->lease_shared_gen != shared_gen ||
> +		    ((dentry->d_flags & DCACHE_NOKEY_NAME) &&
> +		     fscrypt_has_encryption_key(dir))) {
>  			spin_unlock(&dentry->d_lock);
>  			dput(dentry);
>  			err = -EAGAIN;
> @@ -313,6 +316,8 @@ static int ceph_readdir(struct file *file, struct dir_context *ctx)
>  	int err;
>  	unsigned frag = -1;
>  	struct ceph_mds_reply_info_parsed *rinfo;
> +	struct fscrypt_str tname = FSTR_INIT(NULL, 0);
> +	struct fscrypt_str oname = FSTR_INIT(NULL, 0);
>  
>  	dout("readdir %p file %p pos %llx\n", inode, file, ctx->pos);
>  	if (dfi->file_info.flags & CEPH_F_ATEND)
> @@ -340,6 +345,10 @@ static int ceph_readdir(struct file *file, struct dir_context *ctx)
>  		ctx->pos = 2;
>  	}
>  
> +	err = fscrypt_prepare_readdir(inode);
> +	if (err)
> +		goto out;
> +
>  	spin_lock(&ci->i_ceph_lock);
>  	/* request Fx cap. if have Fx, we don't need to release Fs cap
>  	 * for later create/unlink. */
> @@ -360,6 +369,14 @@ static int ceph_readdir(struct file *file, struct dir_context *ctx)
>  		spin_unlock(&ci->i_ceph_lock);
>  	}
>  
> +	err = ceph_fname_alloc_buffer(inode, &tname);
> +	if (err < 0)
> +		goto out;
> +
> +	err = ceph_fname_alloc_buffer(inode, &oname);
> +	if (err < 0)
> +		goto out;
> +
>  	/* proceed with a normal readdir */
>  more:
>  	/* do we have the correct frag content buffered? */
> @@ -387,12 +404,14 @@ static int ceph_readdir(struct file *file, struct dir_context *ctx)
>  		dout("readdir fetching %llx.%llx frag %x offset '%s'\n",
>  		     ceph_vinop(inode), frag, dfi->last_name);
>  		req = ceph_mdsc_create_request(mdsc, op, USE_AUTH_MDS);
> -		if (IS_ERR(req))
> -			return PTR_ERR(req);
> +		if (IS_ERR(req)) {
> +			err = PTR_ERR(req);
> +			goto out;
> +		}
>  		err = ceph_alloc_readdir_reply_buffer(req, inode);
>  		if (err) {
>  			ceph_mdsc_put_request(req);
> -			return err;
> +			goto out;
>  		}
>  		/* hints to request -> mds selection code */
>  		req->r_direct_mode = USE_AUTH_MDS;
> @@ -405,7 +424,8 @@ static int ceph_readdir(struct file *file, struct dir_context *ctx)
>  			req->r_path2 = kstrdup(dfi->last_name, GFP_KERNEL);
>  			if (!req->r_path2) {
>  				ceph_mdsc_put_request(req);
> -				return -ENOMEM;
> +				err = -ENOMEM;
> +				goto out;
>  			}
>  		} else if (is_hash_order(ctx->pos)) {
>  			req->r_args.readdir.offset_hash =
> @@ -426,7 +446,7 @@ static int ceph_readdir(struct file *file, struct dir_context *ctx)
>  		err = ceph_mdsc_do_request(mdsc, NULL, req);
>  		if (err < 0) {
>  			ceph_mdsc_put_request(req);
> -			return err;
> +			goto out;
>  		}
>  		dout("readdir got and parsed readdir result=%d on "
>  		     "frag %x, end=%d, complete=%d, hash_order=%d\n",
> @@ -479,7 +499,7 @@ static int ceph_readdir(struct file *file, struct dir_context *ctx)
>  			err = note_last_dentry(dfi, rde->name, rde->name_len,
>  					       next_offset);
>  			if (err)
> -				return err;
> +				goto out;
>  		} else if (req->r_reply_info.dir_end) {
>  			dfi->next_offset = 2;
>  			/* keep last name */
> @@ -507,22 +527,37 @@ static int ceph_readdir(struct file *file, struct dir_context *ctx)
>  	}
>  	for (; i < rinfo->dir_nr; i++) {
>  		struct ceph_mds_reply_dir_entry *rde = rinfo->dir_entries + i;
> +		struct ceph_fname fname = { .dir	= inode,
> +					    .name	= rde->name,
> +					    .name_len	= rde->name_len,
> +					    .ctext	= rde->altname,
> +					    .ctext_len	= rde->altname_len };
> +		u32 olen = oname.len;
>  
>  		BUG_ON(rde->offset < ctx->pos);
> +		BUG_ON(!rde->inode.in);
>  
>  		ctx->pos = rde->offset;
>  		dout("readdir (%d/%d) -> %llx '%.*s' %p\n",
>  		     i, rinfo->dir_nr, ctx->pos,
>  		     rde->name_len, rde->name, &rde->inode.in);
>  
> -		BUG_ON(!rde->inode.in);
> +		err = ceph_fname_to_usr(&fname, &tname, &oname, NULL);
> +		if (err) {
> +			dout("Unable to decode %.*s. Skipping it.\n", rde->name_len, rde->name);
> +			continue;
> +		}
>  
> -		if (!dir_emit(ctx, rde->name, rde->name_len,
> +		if (!dir_emit(ctx, oname.name, oname.len,
>  			      ceph_present_ino(inode->i_sb, le64_to_cpu(rde->inode.in->ino)),
>  			      le32_to_cpu(rde->inode.in->mode) >> 12)) {
>  			dout("filldir stopping us...\n");
> -			return 0;
> +			err = 0;
> +			goto out;
>  		}
> +
> +		/* Reset the lengths to their original allocated vals */
> +		oname.len = olen;
>  		ctx->pos++;
>  	}
>  
> @@ -577,9 +612,12 @@ static int ceph_readdir(struct file *file, struct dir_context *ctx)
>  					dfi->dir_ordered_count);
>  		spin_unlock(&ci->i_ceph_lock);
>  	}
> -
> +	err = 0;
>  	dout("readdir %p file %p done.\n", inode, file);
> -	return 0;
> +out:
> +	ceph_fname_free_buffer(inode, &tname);
> +	ceph_fname_free_buffer(inode, &oname);
> +	return err;
>  }
>  
>  static void reset_readdir(struct ceph_dir_file_info *dfi)
> diff --git a/fs/ceph/inode.c b/fs/ceph/inode.c
> index 2854711e8988..9863a8818132 100644
> --- a/fs/ceph/inode.c
> +++ b/fs/ceph/inode.c
> @@ -1659,7 +1659,8 @@ int ceph_readdir_prepopulate(struct ceph_mds_request *req,
>  			     struct ceph_mds_session *session)
>  {
>  	struct dentry *parent = req->r_dentry;
> -	struct ceph_inode_info *ci = ceph_inode(d_inode(parent));
> +	struct inode *inode = d_inode(parent);
> +	struct ceph_inode_info *ci = ceph_inode(inode);
>  	struct ceph_mds_reply_info_parsed *rinfo = &req->r_reply_info;
>  	struct qstr dname;
>  	struct dentry *dn;
> @@ -1669,6 +1670,8 @@ int ceph_readdir_prepopulate(struct ceph_mds_request *req,
>  	u32 last_hash = 0;
>  	u32 fpos_offset;
>  	struct ceph_readdir_cache_control cache_ctl = {};
> +	struct fscrypt_str tname = FSTR_INIT(NULL, 0);
> +	struct fscrypt_str oname = FSTR_INIT(NULL, 0);
>  
>  	if (test_bit(CEPH_MDS_R_ABORTED, &req->r_req_flags))
>  		return readdir_prepopulate_inodes_only(req, session);
> @@ -1720,14 +1723,36 @@ int ceph_readdir_prepopulate(struct ceph_mds_request *req,
>  	cache_ctl.index = req->r_readdir_cache_idx;
>  	fpos_offset = req->r_readdir_offset;
>  
> +	err = ceph_fname_alloc_buffer(inode, &tname);
> +	if (err < 0)
> +		goto out;
> +
> +	err = ceph_fname_alloc_buffer(inode, &oname);
> +	if (err < 0)
> +		goto out;
> +
>  	/* FIXME: release caps/leases if error occurs */
>  	for (i = 0; i < rinfo->dir_nr; i++) {
> +		bool is_nokey = false;
>  		struct ceph_mds_reply_dir_entry *rde = rinfo->dir_entries + i;
>  		struct ceph_vino tvino;
> +		u32 olen = oname.len;
> +		struct ceph_fname fname = { .dir	= inode,
> +					    .name	= rde->name,
> +					    .name_len	= rde->name_len,
> +					    .ctext	= rde->altname,
> +					    .ctext_len	= rde->altname_len };
> +
> +		err = ceph_fname_to_usr(&fname, &tname, &oname, &is_nokey);
> +		if (err) {
> +			dout("Unable to decode %.*s. Skipping it.", rde->name_len, rde->name);
> +			continue;
> +		}
>  
> -		dname.name = rde->name;
> -		dname.len = rde->name_len;
> +		dname.name = oname.name;
> +		dname.len = oname.len;
>  		dname.hash = full_name_hash(parent, dname.name, dname.len);
> +		oname.len = olen;
>  
>  		tvino.ino = le64_to_cpu(rde->inode.in->ino);
>  		tvino.snap = le64_to_cpu(rde->inode.in->snapid);
> @@ -1758,6 +1783,11 @@ int ceph_readdir_prepopulate(struct ceph_mds_request *req,
>  				err = -ENOMEM;
>  				goto out;
>  			}
> +			if (is_nokey) {
> +				spin_lock(&dn->d_lock);
> +				dn->d_flags |= DCACHE_NOKEY_NAME;
> +				spin_unlock(&dn->d_lock);
> +			}
>  		} else if (d_really_is_positive(dn) &&
>  			   (ceph_ino(d_inode(dn)) != tvino.ino ||
>  			    ceph_snap(d_inode(dn)) != tvino.snap)) {
> @@ -1848,6 +1878,8 @@ int ceph_readdir_prepopulate(struct ceph_mds_request *req,
>  		req->r_readdir_cache_idx = cache_ctl.index;
>  	}
>  	ceph_readdir_cache_release(&cache_ctl);
> +	ceph_fname_free_buffer(inode, &tname);
> +	ceph_fname_free_buffer(inode, &oname);
>  	dout("readdir_prepopulate done\n");
>  	return err;
>  }
> -- 
>
> 2.29.2
>



[Index of Archives]     [Linux Ext4 Filesystem]     [Union Filesystem]     [Filesystem Testing]     [Ceph Users]     [Ecryptfs]     [AutoFS]     [Kernel Newbies]     [Share Photos]     [Security]     [Netfilter]     [Bugtraq]     [Yosemite News]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux Cachefs]     [Reiser Filesystem]     [Linux RAID]     [Samba]     [Device Mapper]     [CEPH Development]

  Powered by Linux