ceph: handle fscrypt fields in cap messages from MDS
authorJeff Layton <jlayton@kernel.org>
Thu, 25 Aug 2022 13:31:09 +0000 (09:31 -0400)
committerIlya Dryomov <idryomov@gmail.com>
Thu, 24 Aug 2023 09:24:35 +0000 (11:24 +0200)
Handle the new fscrypt_file and fscrypt_auth fields in cap messages. Use
them to populate new fields in cap_extra_info and update the inode with
those values.

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

index aff2562..ee61b53 100644 (file)
@@ -3383,6 +3383,9 @@ struct cap_extra_info {
        /* currently issued */
        int issued;
        struct timespec64 btime;
+       u8 *fscrypt_auth;
+       u32 fscrypt_auth_len;
+       u64 fscrypt_file_size;
 };
 
 /*
@@ -3415,6 +3418,14 @@ static void handle_cap_grant(struct inode *inode,
        bool deleted_inode = false;
        bool fill_inline = false;
 
+       /*
+        * If there is at least one crypto block then we'll trust
+        * fscrypt_file_size. If the real length of the file is 0, then
+        * ignore it (it has probably been truncated down to 0 by the MDS).
+        */
+       if (IS_ENCRYPTED(inode) && size)
+               size = extra_info->fscrypt_file_size;
+
        dout("handle_cap_grant inode %p cap %p mds%d seq %d %s\n",
             inode, cap, session->s_mds, seq, ceph_cap_string(newcaps));
        dout(" size %llu max_size %llu, i_size %llu\n", size, max_size,
@@ -3481,6 +3492,14 @@ static void handle_cap_grant(struct inode *inode,
                dout("%p mode 0%o uid.gid %d.%d\n", inode, inode->i_mode,
                     from_kuid(&init_user_ns, inode->i_uid),
                     from_kgid(&init_user_ns, inode->i_gid));
+#if IS_ENABLED(CONFIG_FS_ENCRYPTION)
+               if (ci->fscrypt_auth_len != extra_info->fscrypt_auth_len ||
+                   memcmp(ci->fscrypt_auth, extra_info->fscrypt_auth,
+                          ci->fscrypt_auth_len))
+                       pr_warn_ratelimited("%s: cap grant attempt to change fscrypt_auth on non-I_NEW inode (old len %d new len %d)\n",
+                               __func__, ci->fscrypt_auth_len,
+                               extra_info->fscrypt_auth_len);
+#endif
        }
 
        if ((newcaps & CEPH_CAP_LINK_SHARED) &&
@@ -3897,7 +3916,8 @@ static void handle_cap_flushsnap_ack(struct inode *inode, u64 flush_tid,
  */
 static bool handle_cap_trunc(struct inode *inode,
                             struct ceph_mds_caps *trunc,
-                            struct ceph_mds_session *session)
+                            struct ceph_mds_session *session,
+                            struct cap_extra_info *extra_info)
 {
        struct ceph_inode_info *ci = ceph_inode(inode);
        int mds = session->s_mds;
@@ -3914,6 +3934,14 @@ static bool handle_cap_trunc(struct inode *inode,
 
        issued |= implemented | dirty;
 
+       /*
+        * If there is at least one crypto block then we'll trust
+        * fscrypt_file_size. If the real length of the file is 0, then
+        * ignore it (it has probably been truncated down to 0 by the MDS).
+        */
+       if (IS_ENCRYPTED(inode) && size)
+               size = extra_info->fscrypt_file_size;
+
        dout("handle_cap_trunc inode %p mds%d seq %d to %lld seq %d\n",
             inode, mds, seq, truncate_size, truncate_seq);
        queue_trunc = ceph_fill_file_size(inode, issued,
@@ -4135,6 +4163,52 @@ retry:
        *target_cap = cap;
 }
 
+#ifdef CONFIG_FS_ENCRYPTION
+static int parse_fscrypt_fields(void **p, void *end,
+                               struct cap_extra_info *extra)
+{
+       u32 len;
+
+       ceph_decode_32_safe(p, end, extra->fscrypt_auth_len, bad);
+       if (extra->fscrypt_auth_len) {
+               ceph_decode_need(p, end, extra->fscrypt_auth_len, bad);
+               extra->fscrypt_auth = kmalloc(extra->fscrypt_auth_len,
+                                             GFP_KERNEL);
+               if (!extra->fscrypt_auth)
+                       return -ENOMEM;
+               ceph_decode_copy_safe(p, end, extra->fscrypt_auth,
+                                       extra->fscrypt_auth_len, bad);
+       }
+
+       ceph_decode_32_safe(p, end, len, bad);
+       if (len >= sizeof(u64)) {
+               ceph_decode_64_safe(p, end, extra->fscrypt_file_size, bad);
+               len -= sizeof(u64);
+       }
+       ceph_decode_skip_n(p, end, len, bad);
+       return 0;
+bad:
+       return -EIO;
+}
+#else
+static int parse_fscrypt_fields(void **p, void *end,
+                               struct cap_extra_info *extra)
+{
+       u32 len;
+
+       /* Don't care about these fields unless we're encryption-capable */
+       ceph_decode_32_safe(p, end, len, bad);
+       if (len)
+               ceph_decode_skip_n(p, end, len, bad);
+       ceph_decode_32_safe(p, end, len, bad);
+       if (len)
+               ceph_decode_skip_n(p, end, len, bad);
+       return 0;
+bad:
+       return -EIO;
+}
+#endif
+
 /*
  * Handle a caps message from the MDS.
  *
@@ -4255,6 +4329,11 @@ void ceph_handle_caps(struct ceph_mds_session *session,
                ceph_decode_64_safe(&p, end, extra_info.nsubdirs, bad);
        }
 
+       if (msg_version >= 12) {
+               if (parse_fscrypt_fields(&p, end, &extra_info))
+                       goto bad;
+       }
+
        /* lookup ino */
        inode = ceph_find_inode(mdsc->fsc->sb, vino);
        dout(" op %s ino %llx.%llx inode %p\n", ceph_cap_op_name(op), vino.ino,
@@ -4352,7 +4431,8 @@ void ceph_handle_caps(struct ceph_mds_session *session,
                break;
 
        case CEPH_CAP_OP_TRUNC:
-               queue_trunc = handle_cap_trunc(inode, h, session);
+               queue_trunc = handle_cap_trunc(inode, h, session,
+                                               &extra_info);
                spin_unlock(&ci->i_ceph_lock);
                if (queue_trunc)
                        ceph_queue_vmtruncate(inode);
@@ -4375,6 +4455,7 @@ out:
        if (close_sessions)
                ceph_mdsc_close_sessions(mdsc);
 
+       kfree(extra_info.fscrypt_auth);
        return;
 
 flush_cap_releases: