]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph-ci.git/commitdiff
fuse client, fscrypt, test: Implement and create tests for S_ENCRYPTED in inode i_flags
authorChristopher Hoffman <choffman@redhat.com>
Thu, 19 Dec 2024 15:33:24 +0000 (15:33 +0000)
committerChristopher Hoffman <choffman@redhat.com>
Thu, 14 Aug 2025 20:09:46 +0000 (20:09 +0000)
This PR adds test for S_ENCRYPTED bit in the i_flags field of Inode.
The test implements 2 quering methods: using FS_IOC_GETFLAGS and STATX_ATTR_ENCRYPTED

Fixes: https://tracker.ceph.com/issues/64129
Author: Igor Golikov <igolikov@ibm.com>
Signed-off-by: Christopher Hoffman <choffman@redhat.com>
src/client/Client.cc
src/client/Client.h
src/client/Inode.cc
src/client/Inode.h
src/client/fuse_ll.cc
src/include/cephfs/ceph_ll_client.h
src/include/cephfs/libcephfs.h
src/libcephfs.cc
src/test/libcephfs/fscrypt.cc

index cf11c09fa2903cab1f9b373c31c91d5697d23a6f..77c845213f88af384d1eb19126be3f80523d0634 100644 (file)
@@ -9121,6 +9121,10 @@ void Client::fill_statx(Inode *in, unsigned int mask, struct ceph_statx *stx)
     stx->stx_mask |= (CEPH_STATX_CTIME|CEPH_STATX_VERSION);
   }
 
+  // Check that the flag has no garbage, if is_encrypted() is false
+  bool en = in->is_encrypted();
+  ceph_assert(en || !(stx->stx_attributes & STATX_ATTR_ENCRYPTED));
+  stx->stx_attributes |= en ? STATX_ATTR_ENCRYPTED : 0;
 }
 
 void Client::touch_dn(Dentry *dn)
@@ -11675,7 +11679,7 @@ void Client::C_Read_Async_Finisher::finish(int r)
 int Client::_read_async(Fh *f, uint64_t off, uint64_t len, bufferlist *bl,
                        Context *onfinish)
 {
-  ceph_assert(ceph_mutex_is_locked_by_me(client_lock));
+  (ceph_mutex_is_locked_by_me(client_lock));
 
   const auto& conf = cct->_conf;
   Inode *in = f->inode.get();
@@ -18707,6 +18711,27 @@ mds_rank_t Client::_get_random_up_mds() const
   return *p;
 }
 
+int  Client::get_inode_flags(const Inode* in, int* file_attr_out) {
+  if (!file_attr_out)
+    return -CEPHFS_EINVAL;
+
+  *file_attr_out = 0;
+    // set or clear the encryption flag depending on the inode status
+  if (in->is_encrypted()) {
+    *file_attr_out |= FS_ENCRYPT_FL;
+  } else {
+    *file_attr_out &= ~FS_ENCRYPT_FL;
+  }
+  return 0;
+}
+
+int Client::get_inode_flags(int fd, int* file_attr_out) {
+  Fh *fh = get_filehandle(fd);
+  if (!fh) {
+    return -CEPHFS_EBADF;
+  }
+  return get_inode_flags(fh->inode.get(), file_attr_out);
+}
 
 StandaloneClient::StandaloneClient(Messenger *m, MonClient *mc,
                                   boost::asio::io_context& ictx)
index 6e4d8802cd8b1adc367f9f0441db0bd4b4972f7e..f34d7b16963760456b322da131966930d2e6241b 100644 (file)
@@ -346,6 +346,9 @@ public:
   int remove_fscrypt_key(fscrypt_remove_key_arg* kid, int user = 0);
   int get_fscrypt_key_status(fscrypt_get_key_status_arg* arg);
 
+  int get_inode_flags(const Inode* in, int* file_attr_out);
+  int get_inode_flags(int fd, int* file_attr_out);
+
   int set_fscrypt_policy_v2(int fd, const struct fscrypt_policy_v2& policy);
 
   int mds_command(
index 50dc3bff6924419047fca104243f8c6bfe4fdbcc..c0da9ccc758001a58272f1484bc36b4a23760123 100644 (file)
@@ -836,7 +836,11 @@ void Inode::mark_caps_clean()
 
 FSCryptContextRef Inode::init_fscrypt_ctx(FSCrypt *fscrypt)
 {
-  return fscrypt->init_ctx(fscrypt_auth);
+  auto res = fscrypt->init_ctx(fscrypt_auth);
+  // at this point, if fscrypt is enabled, the fscrypt_auth is not empty
+  // and we can set the S_ENCRYPTED flag
+  set_is_encrypted_flag();
+  return res;
 }
 
 void Inode::gen_inherited_fscrypt_auth(std::vector<uint8_t> *fsa)
index 7bd223e01715021dd4490d54ae5f7a061df3cdba..a0d2fa519daa7855f5bcdcec10efd4ddd6383603 100644 (file)
@@ -140,7 +140,7 @@ struct Inode : RefCountedObject {
   uint32_t   mode = 0;
   uid_t      uid = 0;
   gid_t      gid = 0;
-  uint32_t i_flags;
+  uint32_t   i_flags = 0;
 
   // nlink
   int32_t    nlink = 0;
@@ -184,6 +184,9 @@ struct Inode : RefCountedObject {
   uint64_t effective_size() const;
   void set_effective_size(uint64_t size);
 
+  // this method returns true if inode is de facto ecrypted.
+  // semantics of "enabled" is a bit confusing since it may mean
+  // "enabled but not encrypted de facto".
   bool is_fscrypt_enabled() {
     return !!fscrypt_auth.size();
   }
@@ -199,6 +202,14 @@ struct Inode : RefCountedObject {
 
   // use i_flags as 1 << 14 will overlap with other mode bits.
   bool is_encrypted() const { return (i_flags & S_ENCRYPTED) == S_ENCRYPTED; }
+  // this function sets S_ENCRYPTED bit in i_flag
+  // is called when the is_fscrypt_enabled
+  void set_is_encrypted_flag() {
+    bool en = is_fscrypt_enabled();
+    // just to make sure that no garbage is set in the flag, if fscrypt is disabled
+    ceph_assert(en || !(i_flags & S_ENCRYPTED));
+    i_flags |= en ? S_ENCRYPTED : 0;
+  }
 
   bool has_dir_layout() const {
     return layout != file_layout_t();
index 16c86f6728c63f30103784cd95f4bc0e938477fb..37ed4fd96782ae16bddd5137f623468c1cb0c392 100644 (file)
@@ -1120,6 +1120,20 @@ static void fuse_ll_ioctl(fuse_req_t req, fuse_ino_t ino,
       fuse_reply_ioctl(req, 0, arg, sizeof(*arg));
     }
     break;
+    case FS_IOC_GETFLAGS: {
+      generic_dout(0) << __FILE__ << ":" << __LINE__ << ": FS_IOC_GETFLAGS ioctl" << dendl;
+
+      int file_attr = 0;
+      if (out_bufsz < sizeof(file_attr)) {
+          fuse_reply_err(req, ERANGE);
+          break;
+      }
+
+      Fh *fh = (Fh*)fi->fh;
+      cfuse->client->get_inode_flags(fh->inode.get(), &file_attr);
+      fuse_reply_ioctl(req, 0, &file_attr, sizeof(file_attr));
+}
+break;
     default:
       fuse_reply_err(req, EINVAL);
   }
index 458edce590bfeb78d14c30f7766b008c886eaf6b..2d2754d947373320c438fe7a28b88a685be52830 100644 (file)
@@ -71,6 +71,7 @@ struct ceph_statx {
        struct timespec stx_mtime;
        struct timespec stx_btime;
        uint64_t        stx_version;
+       uint64_t        stx_attributes;
 };
 
 #define CEPH_STATX_MODE                0x00000001U     /* Want/got stx_mode */
@@ -89,6 +90,13 @@ struct ceph_statx {
 #define CEPH_STATX_VERSION     0x00001000U     /* Want/got stx_version */
 #define CEPH_STATX_ALL_STATS   0x00001fffU     /* All supported stats */
 
+// the value of STATX_ATTR_ENCRYPTED on most systems is equal to FS_ENCRYPT_FL
+// to identify encrypted files uniformly,
+// simplifying operations that need to check encryption status.
+#ifndef STATX_ATTR_ENCRYPTED
+#define STATX_ATTR_ENCRYPTED   0x00000800 /* File requires key to decrypt in fs */
+#endif
+
 /*
  * Compatibility macros until these defines make their way into glibc
  */
@@ -122,6 +130,17 @@ struct ceph_statx {
 #define FALLOC_FL_PUNCH_HOLE 0x02
 #endif
 
+/* fscrypt IOCTL flags*/
+// the value of FS_ENCRYPT_FL on most systems is equal to STATX_ATTR_ENCRYPTED
+// to identify encrypted files uniformly,
+// simplifying operations that need to check encryption status.
+#ifndef FS_ENCRYPT_FL
+#define FS_ENCRYPT_FL  0x00000800 /* Encrypted file */
+#endif
+#ifndef FS_IOC_GETFLAGS
+#define FS_IOC_GETFLAGS _IOR('f', 1, int32_t)
+#endif
+
 /** ceph_deleg_cb_t: Delegation recalls
  *
  * Called when there is an outstanding Delegation and there is conflicting
index a3b6595668123a9a9f5814e91b5421877f49299c..b375cbfdabe6a4c913576519008f67b2b6dc4579 100644 (file)
@@ -2037,6 +2037,15 @@ int ceph_remove_fscrypt_key(struct ceph_mount_info *cmount,
 int ceph_set_fscrypt_policy_v2(struct ceph_mount_info *cmount,
                                int fd, const struct fscrypt_policy_v2 *policy);
 
+/**
+ * Fill file_attr_out with content of i_flags
+ * @param cmount the ceph mount handle to use.
+ * @param fd open directory file descriptor
+ * @param file_attr_out will have result bits set
+ * @returns zero on success, other returns a negative error code.
+ */
+int get_inode_flags(struct ceph_mount_info *cmount, int fd, int* file_attr_out);
+
 /* Low Level */
 struct Inode *ceph_ll_get_inode(struct ceph_mount_info *cmount,
                                vinodeno_t vino);
index 8ad2b9200e8eabd3c571ffa0946afbee380408ab..455016c5606da68afbc94afbc5ca62749b7a2bf3 100644 (file)
@@ -2613,3 +2613,10 @@ extern "C" int ceph_get_perf_counters(struct ceph_mount_info *cmount, char **per
   do_out_buffer(outbl, perf_dump, NULL);
   return outbl.length();
 }
+
+extern "C" int get_inode_flags(struct ceph_mount_info *cmount, int fd, int* file_attr_out) {
+  if (!cmount->is_mounted())
+    return -CEPHFS_ENOTCONN;
+
+  return cmount->get_client()->get_inode_flags(fd, file_attr_out);
+}
index a7ce1c1001d19d9a3bf59a2e8f6ad703bb7d5c24..fb544481acfae421e29b9171766881eca73102e1 100644 (file)
@@ -306,7 +306,6 @@ TEST(FSCrypt, UnlockKeyUserDNE) {
   r = ceph_remove_fscrypt_key(cmount, &arg, 1299);
   ASSERT_EQ(0, r);
   ASSERT_EQ(2, arg.removal_status_flags);
-
   ceph_shutdown(cmount);
 }
 
@@ -330,7 +329,6 @@ TEST(FSCrypt, UnlockKeyDNE) {
   r = ceph_remove_fscrypt_key(cmount, &arg, 1299);
   ASSERT_EQ(-ENOKEY, r);
   ASSERT_EQ(0, arg.removal_status_flags);
-
   ceph_shutdown(cmount);
 }
 
@@ -352,7 +350,6 @@ TEST(FSCrypt, SetPolicyEmptyDir) {
   r = ceph_remove_fscrypt_key(cmount, &arg, 1299);
   ASSERT_EQ(0, r);
   ASSERT_EQ(0, arg.removal_status_flags);
-
   ceph_shutdown(cmount);
 }
 
@@ -468,7 +465,6 @@ TEST(FSCrypt, SetPolicyNonDir) {
   r = ceph_set_fscrypt_policy_v2(cmount, fd, &policy);
   ASSERT_EQ(-ENOTDIR, r);
   ceph_close(cmount, fd);
-
   ceph_shutdown(cmount);
 }
 
@@ -947,6 +943,79 @@ TEST(FSCrypt, RemoveBusyCreate) {
 
   ceph_unlink(cmount, src_path.c_str());
   ceph_rmdir(cmount, dir_path.c_str());
+  ceph_unmount(cmount);
+  ceph_shutdown(cmount);
+}
+
+TEST(FSCrypt, QuerySEncryptFlag) {
+  struct ceph_fscrypt_key_identifier kid;
+
+  struct ceph_mount_info *cmount;
+  int r = init_mount(&cmount);
+  ASSERT_EQ(0, r);
+
+  //add dir
+  string dir_path = "sencrypt_test_dir1";
+  ceph_mkdir(cmount, dir_path.c_str(), 0777);
+  int fd1 = ceph_open(cmount, dir_path.c_str(), O_DIRECTORY, 0);
+
+  //query S_ENCRYPTED flag with statx on non encrypted directory
+  struct ceph_statx stx;
+  r = ceph_statx(cmount, dir_path.c_str(), &stx,
+                     CEPH_STATX_ALL_STATS,
+                     0);
+  ASSERT_EQ(0, r);
+  ASSERT_EQ(0, stx.stx_attributes & STATX_ATTR_ENCRYPTED);
+
+  //query S_ENCRYPTED flag with ioctl on non encr directory
+  int file_attr_out;
+  r = get_inode_flags(cmount, fd1, &file_attr_out);
+  ASSERT_EQ(0, r);
+  ASSERT_EQ(0, file_attr_out & FS_ENCRYPT_FL);
+
+  // enable fscrypt
+  r = ceph_add_fscrypt_key(cmount, fscrypt_key, sizeof(fscrypt_key), &kid, 1299);
+  ASSERT_EQ(0, r);
+  struct fscrypt_policy_v2 policy;
+  policy.version = 2;
+  policy.contents_encryption_mode = FSCRYPT_MODE_AES_256_XTS;
+  policy.filenames_encryption_mode = FSCRYPT_MODE_AES_256_CTS;
+  policy.flags = FSCRYPT_POLICY_FLAGS_PAD_32;
+  memcpy(policy.master_key_identifier, kid.raw, FSCRYPT_KEY_IDENTIFIER_SIZE);
+  r = ceph_set_fscrypt_policy_v2(cmount, fd1, &policy);
+  ASSERT_EQ(0, r);
+
+  //add file to encrypted directory
+  string file_path = "sencrypt_test_file";
+  string path = "";
+  path.append(dir_path);
+  path.append("/");
+  path.append(file_path);
+  int fd2 = ceph_open(cmount, path.c_str(), O_RDWR|O_CREAT|O_TRUNC, 0600);
+  r = ceph_write(cmount, fd2, fscrypt_key, sizeof(fscrypt_key), 0);
+  ASSERT_EQ(32, r);
+  ceph_close(cmount, fd1);
+
+  //query S_ENCRYPTED flag with statx on encrypted file
+  r = ceph_statx(cmount, path.c_str(), &stx,
+                     CEPH_STATX_ALL_STATS,
+                     0);
+  ASSERT_EQ(0, r);
+  ASSERT_EQ(STATX_ATTR_ENCRYPTED, stx.stx_attributes & STATX_ATTR_ENCRYPTED);
+
+  //query S_ENCRYPTED flag with ioctl on encrypted file
+  r = get_inode_flags(cmount, fd2, &file_attr_out);
+  ASSERT_EQ(FS_ENCRYPT_FL, file_attr_out & FS_ENCRYPT_FL);
+
+  // cleanup
+  ceph_close(cmount, fd2);
+  ceph_unlink(cmount, file_path.c_str());
+  ceph_rmdir(cmount, dir_path.c_str());
+  fscrypt_remove_key_arg arg;
+  generate_remove_key_arg(kid, &arg);
+  r = ceph_remove_fscrypt_key(cmount, &arg, 1299);
+  ASSERT_EQ(0, r);
+  ASSERT_EQ(0, arg.removal_status_flags);
   ceph_shutdown(cmount);
 }