From 87c2a33e652dd39bb826e532ddf7deebc1ddee56 Mon Sep 17 00:00:00 2001 From: Christopher Hoffman Date: Thu, 19 Dec 2024 15:33:24 +0000 Subject: [PATCH] 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 --- src/client/Client.cc | 27 +++++++++- src/client/Client.h | 3 ++ src/client/Inode.cc | 6 ++- src/client/Inode.h | 13 ++++- src/client/fuse_ll.cc | 14 ++++++ src/include/cephfs/ceph_ll_client.h | 19 +++++++ src/include/cephfs/libcephfs.h | 9 ++++ src/libcephfs.cc | 7 +++ src/test/libcephfs/fscrypt.cc | 77 +++++++++++++++++++++++++++-- 9 files changed, 168 insertions(+), 7 deletions(-) diff --git a/src/client/Client.cc b/src/client/Client.cc index cf11c09fa29..77c845213f8 100644 --- a/src/client/Client.cc +++ b/src/client/Client.cc @@ -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) diff --git a/src/client/Client.h b/src/client/Client.h index 6e4d8802cd8..f34d7b16963 100644 --- a/src/client/Client.h +++ b/src/client/Client.h @@ -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( diff --git a/src/client/Inode.cc b/src/client/Inode.cc index 50dc3bff692..c0da9ccc758 100644 --- a/src/client/Inode.cc +++ b/src/client/Inode.cc @@ -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 *fsa) diff --git a/src/client/Inode.h b/src/client/Inode.h index 7bd223e0171..a0d2fa519da 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 16c86f6728c..37ed4fd9678 100644 --- a/src/client/fuse_ll.cc +++ b/src/client/fuse_ll.cc @@ -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); } diff --git a/src/include/cephfs/ceph_ll_client.h b/src/include/cephfs/ceph_ll_client.h index 458edce590b..2d2754d9473 100644 --- a/src/include/cephfs/ceph_ll_client.h +++ b/src/include/cephfs/ceph_ll_client.h @@ -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 diff --git a/src/include/cephfs/libcephfs.h b/src/include/cephfs/libcephfs.h index a3b65956681..b375cbfdabe 100644 --- a/src/include/cephfs/libcephfs.h +++ b/src/include/cephfs/libcephfs.h @@ -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); diff --git a/src/libcephfs.cc b/src/libcephfs.cc index 8ad2b9200e8..455016c5606 100644 --- a/src/libcephfs.cc +++ b/src/libcephfs.cc @@ -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); +} 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); } -- 2.47.3