ceph: add support to readdir for encrypted names
authorXiubo Li <xiubli@redhat.com>
Mon, 14 Mar 2022 02:28:35 +0000 (10:28 +0800)
committerIlya Dryomov <idryomov@gmail.com>
Thu, 24 Aug 2023 09:24:34 +0000 (11:24 +0200)
To make it simpler to decrypt names in a readdir reply (i.e. before
we have a dentry), add a new ceph_encode_encrypted_fname()-like helper
that takes a qstr pointer instead of a dentry pointer.

Once we've decrypted the names in a readdir reply, we no longer need the
crypttext, so overwrite them in ceph_mds_reply_dir_entry with the
unencrypted names. Then in both ceph_readdir_prepopulate() and
ceph_readdir() we will use the dencrypted name directly.

[ jlayton: convert some BUG_ONs into error returns ]

Signed-off-by: Xiubo Li <xiubli@redhat.com>
Reviewed-by: Jeff Layton <jlayton@kernel.org>
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>
fs/ceph/crypto.c
fs/ceph/crypto.h
fs/ceph/dir.c
fs/ceph/inode.c
fs/ceph/mds_client.c
fs/ceph/mds_client.h

index e2a4619..a08978a 100644 (file)
@@ -192,15 +192,18 @@ 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)
+int ceph_encode_encrypted_dname(const struct inode *parent,
+                               struct qstr *d_name, char *buf)
 {
        u32 len;
        int elen;
        int ret;
        u8 *cryptbuf;
 
-       WARN_ON_ONCE(!fscrypt_has_encryption_key(parent));
+       if (!fscrypt_has_encryption_key(parent)) {
+               memcpy(buf, d_name->name, d_name->len);
+               return d_name->len;
+       }
 
        /*
         * Convert cleartext d_name to ciphertext. If result is longer than
@@ -208,8 +211,7 @@ int ceph_encode_encrypted_fname(const struct inode *parent,
         *
         * See: fscrypt_setup_filename
         */
-       if (!fscrypt_fname_encrypted_size(parent, dentry->d_name.len, NAME_MAX,
-                                         &len))
+       if (!fscrypt_fname_encrypted_size(parent, d_name->len, NAME_MAX, &len))
                return -ENAMETOOLONG;
 
        /* Allocate a buffer appropriate to hold the result */
@@ -218,7 +220,7 @@ int ceph_encode_encrypted_fname(const struct inode *parent,
        if (!cryptbuf)
                return -ENOMEM;
 
-       ret = fscrypt_fname_encrypt(parent, &dentry->d_name, cryptbuf, len);
+       ret = fscrypt_fname_encrypt(parent, d_name, cryptbuf, len);
        if (ret) {
                kfree(cryptbuf);
                return ret;
@@ -245,6 +247,14 @@ int ceph_encode_encrypted_fname(const struct inode *parent,
        return elen;
 }
 
+int ceph_encode_encrypted_fname(const struct inode *parent,
+                               struct dentry *dentry, char *buf)
+{
+       WARN_ON_ONCE(!fscrypt_has_encryption_key(parent));
+
+       return ceph_encode_encrypted_dname(parent, &dentry->d_name, buf);
+}
+
 /**
  * ceph_fname_to_usr - convert a filename for userland presentation
  * @fname: ceph_fname to be converted
@@ -286,7 +296,10 @@ int ceph_fname_to_usr(const struct ceph_fname *fname, struct fscrypt_str *tname,
         * generating a nokey name via fscrypt.
         */
        if (!fscrypt_has_encryption_key(fname->dir)) {
-               memcpy(oname->name, fname->name, fname->name_len);
+               if (fname->no_copy)
+                       oname->name = fname->name;
+               else
+                       memcpy(oname->name, fname->name, fname->name_len);
                oname->len = fname->name_len;
                if (is_nokey)
                        *is_nokey = true;
index a536451..3269fc8 100644 (file)
@@ -19,6 +19,7 @@ struct ceph_fname {
        unsigned char   *ctext;         // binary crypttext (if any)
        u32             name_len;       // length of name buffer
        u32             ctext_len;      // length of crypttext
+       bool            no_copy;
 };
 
 struct ceph_fscrypt_auth {
@@ -76,6 +77,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_dname(const struct inode *parent,
+                               struct qstr *d_name, char *buf);
 int ceph_encode_encrypted_fname(const struct inode *parent,
                                struct dentry *dentry, char *buf);
 
@@ -121,6 +124,13 @@ static inline void ceph_fscrypt_as_ctx_to_req(struct ceph_mds_request *req,
 {
 }
 
+static inline int ceph_encode_encrypted_dname(const struct inode *parent,
+                                             struct qstr *d_name, char *buf)
+{
+       memcpy(buf, d_name->name, d_name->len);
+       return d_name->len;
+}
+
 static inline int ceph_encode_encrypted_fname(const struct inode *parent,
                                              struct dentry *dentry, char *buf)
 {
index 9a03426..ee5c8b9 100644 (file)
@@ -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;
@@ -340,6 +343,10 @@ static int ceph_readdir(struct file *file, struct dir_context *ctx)
                ctx->pos = 2;
        }
 
+       err = fscrypt_prepare_readdir(inode);
+       if (err)
+               return err;
+
        spin_lock(&ci->i_ceph_lock);
        /* request Fx cap. if have Fx, we don't need to release Fs cap
         * for later create/unlink. */
@@ -389,6 +396,7 @@ more:
                req = ceph_mdsc_create_request(mdsc, op, USE_AUTH_MDS);
                if (IS_ERR(req))
                        return PTR_ERR(req);
+
                err = ceph_alloc_readdir_reply_buffer(req, inode);
                if (err) {
                        ceph_mdsc_put_request(req);
@@ -402,11 +410,21 @@ more:
                        req->r_inode_drop = CEPH_CAP_FILE_EXCL;
                }
                if (dfi->last_name) {
-                       req->r_path2 = kstrdup(dfi->last_name, GFP_KERNEL);
+                       struct qstr d_name = { .name = dfi->last_name,
+                                              .len = strlen(dfi->last_name) };
+
+                       req->r_path2 = kzalloc(NAME_MAX + 1, GFP_KERNEL);
                        if (!req->r_path2) {
                                ceph_mdsc_put_request(req);
                                return -ENOMEM;
                        }
+
+                       err = ceph_encode_encrypted_dname(inode, &d_name,
+                                                         req->r_path2);
+                       if (err < 0) {
+                               ceph_mdsc_put_request(req);
+                               return err;
+                       }
                } else if (is_hash_order(ctx->pos)) {
                        req->r_args.readdir.offset_hash =
                                cpu_to_le32(fpos_hash(ctx->pos));
@@ -511,15 +529,20 @@ more:
        for (; i < rinfo->dir_nr; i++) {
                struct ceph_mds_reply_dir_entry *rde = rinfo->dir_entries + i;
 
-               BUG_ON(rde->offset < ctx->pos);
+               if (rde->offset < ctx->pos) {
+                       pr_warn("%s: rde->offset 0x%llx ctx->pos 0x%llx\n",
+                               __func__, rde->offset, ctx->pos);
+                       return -EIO;
+               }
+
+               if (WARN_ON_ONCE(!rde->inode.in))
+                       return -EIO;
 
                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);
-
                if (!dir_emit(ctx, rde->name, rde->name_len,
                              ceph_present_ino(inode->i_sb, le64_to_cpu(rde->inode.in->ino)),
                              le32_to_cpu(rde->inode.in->mode) >> 12)) {
@@ -532,6 +555,8 @@ more:
                        dout("filldir stopping us...\n");
                        return 0;
                }
+
+               /* Reset the lengths to their original allocated vals */
                ctx->pos++;
        }
 
@@ -586,7 +611,6 @@ more:
                                        dfi->dir_ordered_count);
                spin_unlock(&ci->i_ceph_lock);
        }
-
        dout("readdir %p file %p done.\n", inode, file);
        return 0;
 }
index a6e9a41..0c33809 100644 (file)
@@ -1752,7 +1752,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;
@@ -1826,9 +1827,7 @@ int ceph_readdir_prepopulate(struct ceph_mds_request *req,
                tvino.snap = le64_to_cpu(rde->inode.in->snapid);
 
                if (rinfo->hash_order) {
-                       u32 hash = ceph_str_hash(ci->i_dir_layout.dl_dir_hash,
-                                                rde->name, rde->name_len);
-                       hash = ceph_frag_value(hash);
+                       u32 hash = ceph_frag_value(rde->raw_hash);
                        if (hash != last_hash)
                                fpos_offset = 2;
                        last_hash = hash;
@@ -1851,6 +1850,11 @@ retry_lookup:
                                err = -ENOMEM;
                                goto out;
                        }
+                       if (rde->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)) {
index 8709fc1..447acc4 100644 (file)
@@ -440,20 +440,87 @@ static int parse_reply_info_readdir(void **p, void *end,
 
        info->dir_nr = num;
        while (num) {
+               struct inode *inode = d_inode(req->r_dentry);
+               struct ceph_inode_info *ci = ceph_inode(inode);
                struct ceph_mds_reply_dir_entry *rde = info->dir_entries + i;
+               struct fscrypt_str tname = FSTR_INIT(NULL, 0);
+               struct fscrypt_str oname = FSTR_INIT(NULL, 0);
+               struct ceph_fname fname;
+               u32 altname_len, _name_len;
+               u8 *altname, *_name;
+
                /* dentry */
-               ceph_decode_32_safe(p, end, rde->name_len, bad);
-               ceph_decode_need(p, end, rde->name_len, bad);
-               rde->name = *p;
-               *p += rde->name_len;
-               dout("parsed dir dname '%.*s'\n", rde->name_len, rde->name);
+               ceph_decode_32_safe(p, end, _name_len, bad);
+               ceph_decode_need(p, end, _name_len, bad);
+               _name = *p;
+               *p += _name_len;
+               dout("parsed dir dname '%.*s'\n", _name_len, _name);
+
+               if (info->hash_order)
+                       rde->raw_hash = ceph_str_hash(ci->i_dir_layout.dl_dir_hash,
+                                                     _name, _name_len);
 
                /* dentry lease */
                err = parse_reply_info_lease(p, end, &rde->lease, features,
-                                            &rde->altname_len, &rde->altname);
+                                            &altname_len, &altname);
                if (err)
                        goto out_bad;
 
+               /*
+                * Try to dencrypt the dentry names and update them
+                * in the ceph_mds_reply_dir_entry struct.
+                */
+               fname.dir = inode;
+               fname.name = _name;
+               fname.name_len = _name_len;
+               fname.ctext = altname;
+               fname.ctext_len = altname_len;
+               /*
+                * The _name_len maybe larger than altname_len, such as
+                * when the human readable name length is in range of
+                * (CEPH_NOHASH_NAME_MAX, CEPH_NOHASH_NAME_MAX + SHA256_DIGEST_SIZE),
+                * then the copy in ceph_fname_to_usr will corrupt the
+                * data if there has no encryption key.
+                *
+                * Just set the no_copy flag and then if there has no
+                * encryption key the oname.name will be assigned to
+                * _name always.
+                */
+               fname.no_copy = true;
+               if (altname_len == 0) {
+                       /*
+                        * Set tname to _name, and this will be used
+                        * to do the base64_decode in-place. It's
+                        * safe because the decoded string should
+                        * always be shorter, which is 3/4 of origin
+                        * string.
+                        */
+                       tname.name = _name;
+
+                       /*
+                        * Set oname to _name too, and this will be
+                        * used to do the dencryption in-place.
+                        */
+                       oname.name = _name;
+                       oname.len = _name_len;
+               } else {
+                       /*
+                        * This will do the decryption only in-place
+                        * from altname cryptext directly.
+                        */
+                       oname.name = altname;
+                       oname.len = altname_len;
+               }
+               rde->is_nokey = false;
+               err = ceph_fname_to_usr(&fname, &tname, &oname, &rde->is_nokey);
+               if (err) {
+                       pr_err("%s unable to decode %.*s, got %d\n", __func__,
+                              _name_len, _name, err);
+                       goto out_bad;
+               }
+               rde->name = oname.name;
+               rde->name_len = oname.len;
+
                /* inode */
                err = parse_reply_info_in(p, end, &rde->inode, features);
                if (err < 0)
@@ -3671,7 +3738,7 @@ static void handle_reply(struct ceph_mds_session *session, struct ceph_msg *msg)
        if (err == 0) {
                if (result == 0 && (req->r_op == CEPH_MDS_OP_READDIR ||
                                    req->r_op == CEPH_MDS_OP_LSSNAP))
-                       ceph_readdir_prepopulate(req, req->r_session);
+                       err = ceph_readdir_prepopulate(req, req->r_session);
        }
        current->journal_info = NULL;
        mutex_unlock(&req->r_fill_mutex);
index a4a6f78..a8f6224 100644 (file)
@@ -96,10 +96,10 @@ struct ceph_mds_reply_info_in {
 };
 
 struct ceph_mds_reply_dir_entry {
+       bool                          is_nokey;
        char                          *name;
-       u8                            *altname;
        u32                           name_len;
-       u32                           altname_len;
+       u32                           raw_hash;
        struct ceph_mds_reply_lease   *lease;
        struct ceph_mds_reply_info_in inode;
        loff_t                        offset;