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 >