ceph: encode encrypted name in ceph_mdsc_build_path and dentry release
authorJeff Layton <jlayton@kernel.org>
Fri, 7 Aug 2020 13:28:31 +0000 (09:28 -0400)
committerIlya Dryomov <idryomov@gmail.com>
Thu, 24 Aug 2023 09:22:37 +0000 (11:22 +0200)
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>
fs/ceph/caps.c
fs/ceph/crypto.c
fs/ceph/crypto.h
fs/ceph/mds_client.c
fs/ceph/mds_client.h

index 1c62ef3..4738be5 100644 (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_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);
+               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;
+               }
+       } else {
+               spin_unlock(&dentry->d_lock);
        }
-       spin_unlock(&dentry->d_lock);
        return ret;
 }
 
index 0bb4d8e..6dc723e 100644 (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;
+}
index 44eb3f9..176731f 100644 (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
index c3927da..882c01a 100644 (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?
+ *
+ * Build a string that represents the path to the dentry. This is mostly called
+ * for two different purposes:
  *
- * If @stop_on_nosnap, generate path relative to the first non-snapped
- * inode.
+ * 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);
+                       }
+
+                       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);
                }
-               temp = cur;
-               spin_unlock(&temp->d_lock);
-               cur = dget_parent(temp);
-               dput(temp);
+               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) {
+               re= 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;
 }
 
 /*
index a2e85fb..1105baa 100644 (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,