From: Christopher Hoffman Date: Thu, 19 Dec 2024 15:33:24 +0000 (+0000) Subject: fuse client, fscrypt, test: Implement and create tests for S_ENCRYPTED in inode i_flags X-Git-Tag: testing/wip-pdonnell-testing-20251117.182723-debug~69^2~94 X-Git-Url: http://git.apps.os.sepia.ceph.com/?a=commitdiff_plain;h=208c1e5d672a15101b9d986383b6320f0433167a;p=ceph-ci.git fuse client, fscrypt, test: Implement and create tests for S_ENCRYPTED in inode i_flags 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 Signed-off-by: Christopher Hoffman --- diff --git a/src/client/Client.cc b/src/client/Client.cc index eba7edcca73..5e47e6766b4 100644 --- a/src/client/Client.cc +++ b/src/client/Client.cc @@ -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)) diff --git a/src/client/Client.h b/src/client/Client.h index 43926b4d98e..deb1c98066d 100644 --- a/src/client/Client.h +++ b/src/client/Client.h @@ -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( diff --git a/src/client/Inode.cc b/src/client/Inode.cc index 0c2348825b5..0d3aeebeb1d 100644 --- a/src/client/Inode.cc +++ b/src/client/Inode.cc @@ -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 *fsa) diff --git a/src/client/Inode.h b/src/client/Inode.h index b2f77bce385..adae48030bb 100644 --- a/src/client/Inode.h +++ b/src/client/Inode.h @@ -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(); diff --git a/src/client/fuse_ll.cc b/src/client/fuse_ll.cc index 13db5595b48..0379edf86f0 100644 --- a/src/client/fuse_ll.cc +++ b/src/client/fuse_ll.cc @@ -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); } diff --git a/src/include/cephfs/ceph_ll_client.h b/src/include/cephfs/ceph_ll_client.h index 0bc08d221b4..ca793b37434 100644 --- a/src/include/cephfs/ceph_ll_client.h +++ b/src/include/cephfs/ceph_ll_client.h @@ -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 diff --git a/src/include/cephfs/libcephfs.h b/src/include/cephfs/libcephfs.h index a304b361be6..c839f0f55c8 100644 --- a/src/include/cephfs/libcephfs.h +++ b/src/include/cephfs/libcephfs.h @@ -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); diff --git a/src/libcephfs.cc b/src/libcephfs.cc index e35053dbb68..98f63185c83 100644 --- a/src/libcephfs.cc +++ b/src/libcephfs.cc @@ -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); +} diff --git a/src/test/libcephfs/fscrypt.cc b/src/test/libcephfs/fscrypt.cc index a7ce1c1001d..fb544481acf 100644 --- a/src/test/libcephfs/fscrypt.cc +++ b/src/test/libcephfs/fscrypt.cc @@ -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); }