]> git.apps.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>
Wed, 5 Nov 2025 13:59:34 +0000 (13:59 +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 eba7edcca7347998535fef3f1b0fc1b3853bded3..5e47e6766b4e31f806c508130a6ed8e1bb18c7f9 100644 (file)
@@ -9187,6 +9187,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)
@@ -11739,7 +11743,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();
@@ -18943,6 +18947,28 @@ SubvolumeMetricTracker::aggregate(bool clean) {
 }
 // --- subvolume metrics tracking --- //
 
+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)
   : Client(m, mc, new Objecter(m->cct, m, mc, ictx))
index 43926b4d98e583a2988b95930ea129b20f05c126..deb1c98066d1e69cf9a352dc42cec913244f30af 100644 (file)
@@ -384,6 +384,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 0c2348825b50e22eb46383fb5f9e81f6e1ad6e69..0d3aeebeb1d6424dbfe5203ed7f5467e4b53bec2 100644 (file)
@@ -851,7 +851,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 b2f77bce38570256ef338f538f92ca7e5cb8d7f9..adae48030bbbe61be9417f71f6c199e5c19ad2fe 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 13db5595b4883a3b87b95f721e296485700a38e7..0379edf86f0ce304302b4aa398dd71c8a39639d5 100644 (file)
@@ -1123,6 +1123,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 0bc08d221b47b400161936eccffa93f4db8ee83d..ca793b3743439d6895299ee30829ea1c970e25ec 100644 (file)
@@ -72,6 +72,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 */
@@ -90,6 +91,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
  */
@@ -123,6 +131,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 a304b361be6c2dd82c4505d41d4b5319d4c27901..c839f0f55c886a8145d0101e46325d98bec43699 100644 (file)
@@ -2038,6 +2038,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 e35053dbb68423a025ab6eb685b68ccd3f8e18c3..98f63185c83da6c3c71f02e97bc61167a62fddb3 100644 (file)
@@ -2623,3 +2623,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);
 }