From fdbf2ffd41fa5660782d5ca8489ec2eb644c8113 Mon Sep 17 00:00:00 2001 From: Sebastien Buisson Date: Thu, 17 Jun 2021 15:31:44 +0200 Subject: [PATCH] LU-14677 sec: no encryption key migrate/extend/resync/split Allow some layout operations on encrypted files, even when the encryption key is not available: - lfs migrate - lfs mirror extend - lfs mirror resync - lfs mirror verify - lfs mirror split We allow these access patterns to applications that know what they are doing, by using the specific flag O_FILE_ENC and O_DIRECT. Also add sanity-sec test_59a,b,c to exercise these access patterns. Signed-off-by: Sebastien Buisson Change-Id: Ieaeee0e5bf7643f18d775fe6daa5e31c2f349f8c Reviewed-on: https://review.whamcloud.com/44024 Reviewed-by: Andreas Dilger Tested-by: jenkins Tested-by: Maloo Reviewed-by: Patrick Farrell Reviewed-by: Oleg Drokin --- lustre/doc/lfs-migrate.1 | 5 + lustre/doc/lfs-mirror-split.1 | 10 + lustre/include/lustre/lustreapi.h | 7 + lustre/include/obd.h | 1 - lustre/include/uapi/linux/lustre/lustre_user.h | 4 + lustre/llite/crypto.c | 44 +++++ lustre/llite/dir.c | 12 +- lustre/llite/file.c | 65 +++++-- lustre/llite/llite_internal.h | 3 + lustre/llite/llite_lib.c | 105 +++++++++- lustre/llite/namei.c | 57 +++--- lustre/osc/osc_request.c | 42 +++- lustre/tests/sanity-sec.sh | 256 ++++++++++++++++++++++++- lustre/utils/lfs.c | 55 ++++-- lustre/utils/liblustreapi_layout.c | 24 ++- lustre/utils/lustreapi_internal.h | 5 + 16 files changed, 603 insertions(+), 92 deletions(-) diff --git a/lustre/doc/lfs-migrate.1 b/lustre/doc/lfs-migrate.1 index 3991c9f..1ee23fd 100644 --- a/lustre/doc/lfs-migrate.1 +++ b/lustre/doc/lfs-migrate.1 @@ -72,6 +72,11 @@ write operations synchronous. Using the .B --non-direct option uses buffered read/write operations, which may improve migration speed at the cost of more CPU and memory overhead. +.br +This option cannot be used on encrypted files when the encryption key is not +available. It will result in +.B +-ENOKEY. .TP .BR -v , --verbose Print each filename as it is migrated. diff --git a/lustre/doc/lfs-mirror-split.1 b/lustre/doc/lfs-mirror-split.1 index fb74f18..9dd5daa 100644 --- a/lustre/doc/lfs-mirror-split.1 +++ b/lustre/doc/lfs-mirror-split.1 @@ -40,10 +40,20 @@ The pool storing a component contained within a mirror. .TP .BR \-\-destroy\fR|\fB\-d\fR This option indicates the split mirror will be destroyed. +.br +This option is mandatory on encrypted files when the encryption key is not +available. Otherwise operation results in +.B +-ENOKEY. .TP .BR \-f\fR\ <\fInew_file\fR> This option indicates the layout of the split mirror will be stored into <\fInew_file\fR>. +.br +This option cannot be used on encrypted files when the encryption key is not +available. It will result in +.B +-ENOKEY. .SH EXAMPLES .TP .B lfs mirror split --mirror-id 1 /mnt/lustre/file1 diff --git a/lustre/include/lustre/lustreapi.h b/lustre/include/lustre/lustreapi.h index 2cf1669..2ce4bf1 100644 --- a/lustre/include/lustre/lustreapi.h +++ b/lustre/include/lustre/lustreapi.h @@ -148,6 +148,13 @@ struct llapi_stripe_param { #define lsp_tgts lsp_osts +enum { + LLAPI_MIGRATION_NONBLOCK = 0x0001, + LLAPI_MIGRATION_MIRROR = 0x0002, + LLAPI_MIGRATION_NONDIRECT = 0x0004, + LLAPI_MIGRATION_VERBOSE = 0x0008, +}; + __u32 llapi_pattern_to_lov(uint64_t pattern); int llapi_file_open_param(const char *name, int flags, mode_t mode, diff --git a/lustre/include/obd.h b/lustre/include/obd.h index a2c2a40..9ebd53f 100644 --- a/lustre/include/obd.h +++ b/lustre/include/obd.h @@ -866,7 +866,6 @@ enum md_op_code { LUSTRE_OPC_ANY, LUSTRE_OPC_LOOKUP, LUSTRE_OPC_OPEN, - LUSTRE_OPC_MIGR, }; /** diff --git a/lustre/include/uapi/linux/lustre/lustre_user.h b/lustre/include/uapi/linux/lustre/lustre_user.h index bb5d3e6..49f0b4c 100644 --- a/lustre/include/uapi/linux/lustre/lustre_user.h +++ b/lustre/include/uapi/linux/lustre/lustre_user.h @@ -683,6 +683,10 @@ struct fsxattr { #define O_LOV_DELAY_CREATE_MASK (O_NOCTTY | FASYNC) #define O_LOV_DELAY_CREATE (O_LOV_DELAY_CREATE_1_8 | \ O_LOV_DELAY_CREATE_MASK) +/* O_FILE_ENC principle is similar to O_LOV_DELAY_CREATE above, + * for access to encrypted files without the encryption key. + */ +#define O_FILE_ENC (O_NOCTTY | O_NDELAY) #define LL_FILE_IGNORE_LOCK 0x00000001 #define LL_FILE_GROUP_LOCKED 0x00000002 diff --git a/lustre/llite/crypto.c b/lustre/llite/crypto.c index 7a073c9..cc80aea 100644 --- a/lustre/llite/crypto.c +++ b/lustre/llite/crypto.c @@ -131,6 +131,34 @@ static int ll_set_context(struct inode *inode, const void *ctx, size_t len, return ll_set_encflags(inode, (void *)ctx, len, false); } +/** + * ll_file_open_encrypt() - overlay to llcrypt_file_open + * @inode: the inode being opened + * @filp: the struct file being set up + * + * This overlay function is necessary to handle encrypted file open without + * the key. We allow this access pattern to applications that know what they + * are doing, by using the specific flag O_FILE_ENC. + * This flag is only compatible with O_DIRECT IOs, to make sure ciphertext + * data is wiped from page cache once IOs are finished. + */ +int ll_file_open_encrypt(struct inode *inode, struct file *filp) +{ + int rc; + + rc = llcrypt_file_open(inode, filp); + if (likely(rc != -ENOKEY)) + return rc; + + if (rc == -ENOKEY && + (filp->f_flags & O_FILE_ENC) == O_FILE_ENC && + filp->f_flags & O_DIRECT) + /* allow file open with O_FILE_ENC flag when we have O_DIRECT */ + rc = 0; + + return rc; +} + void llcrypt_free_ctx(void *encctx, __u32 size) { if (encctx) @@ -218,6 +246,17 @@ int ll_setup_filename(struct inode *dir, const struct qstr *iname, fid->f_ver = 0; } rc = llcrypt_setup_filename(dir, &dname, lookup, fname); + if (rc == -ENOENT && lookup && + !llcrypt_has_encryption_key(dir) && + unlikely(filename_is_volatile(iname->name, iname->len, NULL))) { + /* For purpose of migration or mirroring without enc key, we + * allow lookup of volatile file without enc context. + */ + memset(fname, 0, sizeof(struct llcrypt_name)); + fname->disk_name.name = (unsigned char *)iname->name; + fname->disk_name.len = iname->len; + rc = 0; + } if (rc) return rc; @@ -418,6 +457,11 @@ int ll_set_encflags(struct inode *inode, void *encctx, __u32 encctxlen, return 0; } +int ll_file_open_encrypt(struct inode *inode, struct file *filp) +{ + return llcrypt_file_open(inode, filp); +} + void llcrypt_free_ctx(void *encctx, __u32 size) { } diff --git a/lustre/llite/dir.c b/lustre/llite/dir.c index ad94e5a..6294d23 100644 --- a/lustre/llite/dir.c +++ b/lustre/llite/dir.c @@ -1847,7 +1847,11 @@ out_rmdir: st.st_uid = body->mbo_uid; st.st_gid = body->mbo_gid; st.st_rdev = body->mbo_rdev; - st.st_size = body->mbo_size; + if (llcrypt_require_key(inode) == -ENOKEY) + st.st_size = round_up(st.st_size, + LUSTRE_ENCRYPTION_UNIT_SIZE); + else + st.st_size = body->mbo_size; st.st_blksize = PAGE_SIZE; st.st_blocks = body->mbo_blocks; st.st_atime = body->mbo_atime; @@ -1870,7 +1874,11 @@ out_rmdir: stx.stx_mode = body->mbo_mode; stx.stx_ino = cl_fid_build_ino(&body->mbo_fid1, api32); - stx.stx_size = body->mbo_size; + if (llcrypt_require_key(inode) == -ENOKEY) + stx.stx_size = round_up(stx.stx_size, + LUSTRE_ENCRYPTION_UNIT_SIZE); + else + stx.stx_size = body->mbo_size; stx.stx_blocks = body->mbo_blocks; stx.stx_atime.tv_sec = body->mbo_atime; stx.stx_ctime.tv_sec = body->mbo_ctime; diff --git a/lustre/llite/file.c b/lustre/llite/file.c index 21fb0b4..b5e11ca 100644 --- a/lustre/llite/file.c +++ b/lustre/llite/file.c @@ -105,7 +105,16 @@ static void ll_prepare_close(struct inode *inode, struct md_op_data *op_data, op_data->op_attr.ia_atime = inode->i_atime; op_data->op_attr.ia_mtime = inode->i_mtime; op_data->op_attr.ia_ctime = inode->i_ctime; - op_data->op_attr.ia_size = i_size_read(inode); + /* In case of encrypted file without the key, visible size was rounded + * up to next LUSTRE_ENCRYPTION_UNIT_SIZE, and clear text size was + * stored into lli_lazysize in ll_merge_attr(), so set proper file size + * now that we are closing. + */ + if (llcrypt_require_key(inode) == -ENOKEY && + ll_i2info(inode)->lli_attr_valid & OBD_MD_FLLAZYSIZE) + op_data->op_attr.ia_size = ll_i2info(inode)->lli_lazysize; + else + op_data->op_attr.ia_size = i_size_read(inode); op_data->op_attr.ia_valid |= (ATTR_MODE | ATTR_ATIME | ATTR_ATIME_SET | ATTR_MTIME | ATTR_MTIME_SET | ATTR_CTIME); @@ -816,9 +825,12 @@ int ll_file_open(struct inode *inode, struct file *file) file->private_data = NULL; /* prevent ll_local_open assertion */ if (S_ISREG(inode->i_mode)) { - rc = llcrypt_file_open(inode, file); - if (rc) + rc = ll_file_open_encrypt(inode, file); + if (rc) { + if (it && it->it_disposition) + ll_release_openhandle(file_dentry(file), it); GOTO(out_nofiledata, rc); + } } fd = ll_file_data_get(); @@ -1467,6 +1479,16 @@ int ll_merge_attr(const struct lu_env *env, struct inode *inode) CDEBUG(D_VFSTRACE, DFID" updating i_size %llu\n", PFID(&lli->lli_fid), attr->cat_size); + if (llcrypt_require_key(inode) == -ENOKEY) { + /* Without the key, round up encrypted file size to next + * LUSTRE_ENCRYPTION_UNIT_SIZE. Clear text size is put in + * lli_lazysize for proper file size setting at close time. + */ + lli->lli_attr_valid |= OBD_MD_FLLAZYSIZE; + lli->lli_lazysize = attr->cat_size; + attr->cat_size = round_up(attr->cat_size, + LUSTRE_ENCRYPTION_UNIT_SIZE); + } i_size_write(inode, attr->cat_size); inode->i_blocks = attr->cat_blocks; @@ -4420,6 +4442,12 @@ loff_t ll_lseek(struct file *file, loff_t offset, int whence) cl_env_put(env, &refcheck); + /* Without the key, SEEK_HOLE return value has to be + * rounded up to next LUSTRE_ENCRYPTION_UNIT_SIZE. + */ + if (llcrypt_require_key(inode) == -ENOKEY && whence == SEEK_HOLE) + retval = round_up(retval, LUSTRE_ENCRYPTION_UNIT_SIZE); + RETURN(retval); } @@ -4802,6 +4830,7 @@ int ll_migrate(struct inode *parent, struct file *file, struct lmv_user_md *lum, __u64 data_version = 0; size_t namelen = strlen(name); int lumlen = lmv_user_md_size(lum->lum_stripe_count, lum->lum_magic); + bool oldformat = false; int rc; ENTRY; @@ -4852,26 +4881,22 @@ int ll_migrate(struct inode *parent, struct file *file, struct lmv_user_md *lum, if (is_root_inode(child_inode)) GOTO(out_iput, rc = -EINVAL); - if (IS_ENCRYPTED(child_inode)) { - rc = llcrypt_get_encryption_info(child_inode); - if (rc) - GOTO(out_iput, rc); - if (!llcrypt_has_encryption_key(child_inode)) { - CDEBUG(D_SEC, "no enc key for "DFID"\n", - PFID(ll_inode2fid(child_inode))); - GOTO(out_iput, rc = -ENOKEY); - } - if (unlikely(!llcrypt_policy_has_filename_enc(child_inode))) { - CDEBUG(D_SEC, - "cannot migrate old format encrypted "DFID", please move to new enc dir first\n", - PFID(ll_inode2fid(child_inode))); - GOTO(out_iput, rc = -EUCLEAN); - } + if (IS_ENCRYPTED(parent)) { + if (unlikely(!llcrypt_policy_has_filename_enc(parent))) + oldformat = true; + } else if (IS_ENCRYPTED(child_inode) && + unlikely(!llcrypt_policy_has_filename_enc(child_inode))) { + oldformat = true; + } + if (unlikely(oldformat)) { + CDEBUG(D_SEC, + "cannot migrate old format encrypted "DFID", please move to new enc dir first\n", + PFID(ll_inode2fid(child_inode))); + GOTO(out_iput, rc = -EUCLEAN); } op_data = ll_prep_md_op_data(NULL, parent, NULL, name, namelen, - child_inode->i_mode, LUSTRE_OPC_MIGR, - NULL); + child_inode->i_mode, LUSTRE_OPC_ANY, NULL); if (IS_ERR(op_data)) GOTO(out_iput, rc = PTR_ERR(op_data)); diff --git a/lustre/llite/llite_internal.h b/lustre/llite/llite_internal.h index 1a8ff18..97ec1d2 100644 --- a/lustre/llite/llite_internal.h +++ b/lustre/llite/llite_internal.h @@ -1190,6 +1190,8 @@ void ll_kill_super(struct super_block *sb); struct inode *ll_inode_from_resource_lock(struct ldlm_lock *lock); void ll_dir_clear_lsm_md(struct inode *inode); void ll_clear_inode(struct inode *inode); +int volatile_ref_file(const char *volatile_name, int volatile_len, + struct file **ref_file); int ll_setattr_raw(struct dentry *dentry, struct iattr *attr, enum op_xvalid xvalid, bool hsm_import); int ll_setattr(struct dentry *de, struct iattr *attr); @@ -1718,6 +1720,7 @@ int ll_fname_disk_to_usr(struct inode *inode, struct llcrypt_str *iname, struct llcrypt_str *oname, struct lu_fid *fid); int ll_revalidate_d_crypto(struct dentry *dentry, unsigned int flags); +int ll_file_open_encrypt(struct inode *inode, struct file *filp); #ifdef HAVE_LUSTRE_CRYPTO extern const struct llcrypt_operations lustre_cryptops; #endif diff --git a/lustre/llite/llite_lib.c b/lustre/llite/llite_lib.c index a60739e..d5c3c94 100644 --- a/lustre/llite/llite_lib.c +++ b/lustre/llite/llite_lib.c @@ -40,6 +40,7 @@ #include #include #include +#include #include #include #include @@ -2025,6 +2026,44 @@ putenv: RETURN(rc); } +/** + * Get reference file from volatile file name. + * Volatile file name may look like: + * /LUSTRE_VOLATILE_HDR:::fd= + * where fd is opened descriptor of reference file. + * + * \param[in] volatile_name volatile file name + * \param[in] volatile_len volatile file name length + * \param[out] ref_file pointer to struct file of reference file + * + * \retval 0 on success + * \retval negative errno on failure + */ +int volatile_ref_file(const char *volatile_name, int volatile_len, + struct file **ref_file) +{ + char *p, *q, *fd_str; + int fd, rc; + + p = strnstr(volatile_name, ":fd=", volatile_len); + if (!p || strlen(p + 4) == 0) + return -EINVAL; + + q = strchrnul(p + 4, ':'); + fd_str = kstrndup(p + 4, q - p - 4, GFP_NOFS); + if (!fd_str) + return -ENOMEM; + rc = kstrtouint(fd_str, 10, &fd); + kfree(fd_str); + if (rc) + return -EINVAL; + + *ref_file = fget(fd); + if (!(*ref_file)) + return -EINVAL; + return 0; +} + /* If this inode has objects allocated to it (lsm != NULL), then the OST * object(s) determine the file size and mtime. Otherwise, the MDS will * keep these values until such a time that objects are allocated for it. @@ -2188,6 +2227,55 @@ int ll_setattr_raw(struct dentry *dentry, struct iattr *attr, if (rc) GOTO(out, rc); } + /* If encrypted volatile file without the key, + * we need to fetch size from reference file, + * and set it on OST objects. This happens when + * migrating or extending an encrypted file + * without the key. + */ + if (filename_is_volatile(dentry->d_name.name, + dentry->d_name.len, + NULL) && + llcrypt_require_key(inode) == -ENOKEY) { + struct file *ref_file; + struct inode *ref_inode; + struct ll_inode_info *ref_lli; + struct cl_object *ref_obj; + struct cl_attr ref_attr = { 0 }; + struct lu_env *env; + __u16 refcheck; + + rc = volatile_ref_file( + dentry->d_name.name, + dentry->d_name.len, + &ref_file); + if (rc) + GOTO(out, rc); + + ref_inode = file_inode(ref_file); + if (!ref_inode) { + fput(ref_file); + GOTO(out, rc = -EINVAL); + } + + env = cl_env_get(&refcheck); + if (IS_ERR(env)) + GOTO(out, rc = PTR_ERR(env)); + + ref_lli = ll_i2info(ref_inode); + ref_obj = ref_lli->lli_clob; + cl_object_attr_lock(ref_obj); + rc = cl_object_attr_get(env, ref_obj, + &ref_attr); + cl_object_attr_unlock(ref_obj); + cl_env_put(env, &refcheck); + fput(ref_file); + if (rc) + GOTO(out, rc); + + attr->ia_valid |= ATTR_SIZE; + attr->ia_size = ref_attr.cat_size; + } } rc = cl_setattr_ost(lli->lli_clob, attr, xvalid, flags); } @@ -2246,7 +2334,7 @@ out: LPROC_LL_TRUNC : LPROC_LL_SETATTR, ktime_us_delta(ktime_get(), kstart)); - return rc; + RETURN(rc); } int ll_setattr(struct dentry *de, struct iattr *attr) @@ -2552,7 +2640,15 @@ int ll_update_inode(struct inode *inode, struct lustre_md *md) LASSERT(fid_seq(&lli->lli_fid) != 0); - lli->lli_attr_valid = body->mbo_valid; + /* In case of encrypted file without the key, please do not lose + * clear text size stored into lli_lazysize in ll_merge_attr(), + * we will need it in ll_prepare_close(). + */ + if (lli->lli_attr_valid & OBD_MD_FLLAZYSIZE && lli->lli_lazysize && + llcrypt_require_key(inode) == -ENOKEY) + lli->lli_attr_valid = body->mbo_valid | OBD_MD_FLLAZYSIZE; + else + lli->lli_attr_valid = body->mbo_valid; if (body->mbo_valid & OBD_MD_FLSIZE) { i_size_write(inode, body->mbo_size); @@ -3212,11 +3308,10 @@ struct md_op_data *ll_prep_md_op_data(struct md_op_data *op_data, op_data->op_flags |= MF_OPNAME_KMALLOCED; } - /* In fact LUSTRE_OPC_LOOKUP, LUSTRE_OPC_OPEN, LUSTRE_OPC_MIGR + /* In fact LUSTRE_OPC_LOOKUP, LUSTRE_OPC_OPEN * are LUSTRE_OPC_ANY */ - if (opc == LUSTRE_OPC_LOOKUP || opc == LUSTRE_OPC_OPEN || - opc == LUSTRE_OPC_MIGR) + if (opc == LUSTRE_OPC_LOOKUP || opc == LUSTRE_OPC_OPEN) op_data->op_code = LUSTRE_OPC_ANY; else op_data->op_code = opc; diff --git a/lustre/llite/namei.c b/lustre/llite/namei.c index 44d0666..6f372c7 100644 --- a/lustre/llite/namei.c +++ b/lustre/llite/namei.c @@ -49,7 +49,7 @@ static int ll_create_it(struct inode *dir, struct dentry *dentry, struct lookup_intent *it, void *secctx, __u32 secctxlen, bool encrypt, - void *encctx, __u32 encctxlen); + void *encctx, __u32 encctxlen, unsigned int open_flags); /* called from iget5_locked->find_inode() under inode_lock spinlock */ static int ll_test_inode(struct inode *inode, void *opaque) @@ -908,37 +908,20 @@ static struct dentry *ll_lookup_it(struct inode *parent, struct dentry *dentry, *secctxlen = 0; } if (it->it_op & IT_CREAT && encrypt) { - /* Volatile file name may look like: - * /LUSTRE_VOLATILE_HDR:::fd= - * where fd is opened descriptor of reference file. - */ if (unlikely(filename_is_volatile(dentry->d_name.name, dentry->d_name.len, NULL))) { + /* get encryption context from reference file */ int ctx_size = LLCRYPT_ENC_CTX_SIZE; struct lustre_sb_info *lsi; struct file *ref_file; struct inode *ref_inode; - char *p, *q, *fd_str; void *ctx; - int fd; - - p = strnstr(dentry->d_name.name, ":fd=", - dentry->d_name.len); - if (!p || strlen(p + 4) == 0) - GOTO(out, retval = ERR_PTR(-EINVAL)); - q = strchrnul(p + 4, ':'); - fd_str = kstrndup(p + 4, q - p - 4, GFP_NOFS); - if (!fd_str) - GOTO(out, retval = ERR_PTR(-ENOMEM)); - rc = kstrtouint(fd_str, 10, &fd); - kfree(fd_str); + rc = volatile_ref_file(dentry->d_name.name, + dentry->d_name.len, + &ref_file); if (rc) - GOTO(inherit, rc = -EINVAL); - - ref_file = fget(fd); - if (!ref_file) - GOTO(inherit, rc = -EINVAL); + GOTO(out, retval = ERR_PTR(rc)); ref_inode = file_inode(ref_file); if (!ref_inode) { @@ -984,7 +967,6 @@ getctx: op_data->op_file_encctx_size); OBD_FREE(ctx, ctx_size); } - } else { inherit: rc = llcrypt_inherit_context(parent, NULL, op_data, @@ -1252,7 +1234,14 @@ static int ll_atomic_open(struct inode *dir, struct dentry *dentry, if (rc) GOTO(out_release, rc); if (open_flags & O_CREAT) { - if (!llcrypt_has_encryption_key(dir)) + /* For migration or mirroring without enc key, we still + * need to be able to create a volatile file. + */ + if (!llcrypt_has_encryption_key(dir) && + (!filename_is_volatile(dentry->d_name.name, + dentry->d_name.len, NULL) || + (open_flags & O_FILE_ENC) != O_FILE_ENC || + !(open_flags & O_DIRECT))) GOTO(out_release, rc = -ENOKEY); encrypt = true; } @@ -1283,7 +1272,8 @@ static int ll_atomic_open(struct inode *dir, struct dentry *dentry, if (it_disposition(it, DISP_OPEN_CREATE)) { /* Dentry instantiated in ll_create_it. */ rc = ll_create_it(dir, dentry, it, secctx, secctxlen, - encrypt, encctx, encctxlen); + encrypt, encctx, encctxlen, + open_flags); ll_security_release_secctx(secctx, secctxlen); llcrypt_free_ctx(encctx, encctxlen); if (rc) { @@ -1409,7 +1399,7 @@ static struct inode *ll_create_node(struct inode *dir, struct lookup_intent *it) static int ll_create_it(struct inode *dir, struct dentry *dentry, struct lookup_intent *it, void *secctx, __u32 secctxlen, bool encrypt, - void *encctx, __u32 encctxlen) + void *encctx, __u32 encctxlen, unsigned int open_flags) { struct inode *inode; __u64 bits = 0; @@ -1444,7 +1434,18 @@ static int ll_create_it(struct inode *dir, struct dentry *dentry, d_instantiate(dentry, inode); if (encrypt) { - rc = ll_set_encflags(inode, encctx, encctxlen, true); + bool preload = true; + + /* For migration or mirroring without enc key, we + * create a volatile file without enc context. + */ + if (!llcrypt_has_encryption_key(dir) && + filename_is_volatile(dentry->d_name.name, + dentry->d_name.len, NULL) && + (open_flags & O_FILE_ENC) == O_FILE_ENC && + open_flags & O_DIRECT) + preload = false; + rc = ll_set_encflags(inode, encctx, encctxlen, preload); if (rc) RETURN(rc); } diff --git a/lustre/osc/osc_request.c b/lustre/osc/osc_request.c index fcac713..0d34360 100644 --- a/lustre/osc/osc_request.c +++ b/lustre/osc/osc_request.c @@ -1441,7 +1441,8 @@ osc_brw_prep_request(int cmd, struct client_obd *cli, struct obdo *oa, if (req == NULL) RETURN(-ENOMEM); - if (opc == OST_WRITE && inode && IS_ENCRYPTED(inode)) { + if (opc == OST_WRITE && inode && IS_ENCRYPTED(inode) && + llcrypt_has_encryption_key(inode)) { for (i = 0; i < page_count; i++) { struct brw_page *pg = pga[i]; struct page *data_page = NULL; @@ -1452,9 +1453,7 @@ osc_brw_prep_request(int cmd, struct client_obd *cli, struct obdo *oa, pgoff_t index_orig; retry_encrypt: - if (nunits & ~LUSTRE_ENCRYPTION_MASK) - nunits = (nunits & LUSTRE_ENCRYPTION_MASK) + - LUSTRE_ENCRYPTION_UNIT_SIZE; + nunits = round_up(nunits, LUSTRE_ENCRYPTION_UNIT_SIZE); /* The page can already be locked when we arrive here. * This is possible when cl_page_assume/vvp_page_assume * is stuck on wait_on_page_writeback with page lock @@ -1511,14 +1510,38 @@ retry_encrypt: pg->bp_off_diff = pg->off & ~PAGE_MASK; pg->off = pg->off & PAGE_MASK; } - } else if (opc == OST_READ && inode && IS_ENCRYPTED(inode)) { + } else if (opc == OST_WRITE && inode && IS_ENCRYPTED(inode)) { + struct osc_async_page *oap = brw_page2oap(pga[0]); + struct cl_page *clpage = oap2cl_page(oap); + struct cl_object *clobj = clpage->cp_obj; + struct cl_attr attr = { 0 }; + struct lu_env *env; + __u16 refcheck; + + env = cl_env_get(&refcheck); + if (IS_ERR(env)) { + rc = PTR_ERR(env); + ptlrpc_request_free(req); + RETURN(rc); + } + + cl_object_attr_lock(clobj); + rc = cl_object_attr_get(env, clobj, &attr); + cl_object_attr_unlock(clobj); + cl_env_put(env, &refcheck); + if (rc != 0) { + ptlrpc_request_free(req); + RETURN(rc); + } + if (attr.cat_size) + oa->o_size = attr.cat_size; + } else if (opc == OST_READ && inode && IS_ENCRYPTED(inode) && + llcrypt_has_encryption_key(inode)) { for (i = 0; i < page_count; i++) { struct brw_page *pg = pga[i]; u32 nunits = (pg->off & ~PAGE_MASK) + pg->count; - if (nunits & ~LUSTRE_ENCRYPTION_MASK) - nunits = (nunits & LUSTRE_ENCRYPTION_MASK) + - LUSTRE_ENCRYPTION_UNIT_SIZE; + nunits = round_up(nunits, LUSTRE_ENCRYPTION_UNIT_SIZE); /* count/off are forced to cover the whole encryption * unit size so that all encrypted data is stored on the * OST, so adjust bp_{count,off}_diff for the size of @@ -1544,7 +1567,8 @@ retry_encrypt: for (i = 0; i < page_count; i++) { short_io_size += pga[i]->count; - if (!inode || !IS_ENCRYPTED(inode)) { + if (!inode || !IS_ENCRYPTED(inode) || + !llcrypt_has_encryption_key(inode)) { pga[i]->bp_count_diff = 0; pga[i]->bp_off_diff = 0; } diff --git a/lustre/tests/sanity-sec.sh b/lustre/tests/sanity-sec.sh index a7c3074..0f38097 100755 --- a/lustre/tests/sanity-sec.sh +++ b/lustre/tests/sanity-sec.sh @@ -2700,6 +2700,18 @@ test_35() { } run_test 35 "Check permissions when accessing changelogs" +setup_dummy_key() { + local mode='\x00\x00\x00\x00' + local raw="$(printf ""\\\\x%02x"" {0..63})" + local size + local key + + [[ $(lscpu) =~ Byte\ Order.*Little ]] && size='\x40\x00\x00\x00' || + size='\x00\x00\x00\x40' + key="${mode}${raw}${size}" + echo -n -e "${key}" | keyctl padd logon fscrypt:4242424242424242 @s +} + setup_for_enc_tests() { # remount client with test_dummy_encryption option if is_mounted $MOUNT; then @@ -2713,7 +2725,7 @@ setup_for_enc_tests() { } cleanup_for_enc_tests() { - rm -rf $DIR/$tdir + rm -rf $DIR/$tdir $* # remount client normally if is_mounted $MOUNT; then @@ -2913,6 +2925,10 @@ test_40() { local tmpfile=$TMP/abc local tmpfile2=$TMP/abc2 local seek + local filesz + #define LUSTRE_ENCRYPTION_UNIT_SIZE (1 << 12) + local UNIT_SIZE=$((1 << 12)) + local scrambledfile $LCTL get_param mdc.*.import | grep -q client_encryption || skip "client encryption not supported" @@ -3037,6 +3053,19 @@ test_40() { cmp -bl $tmpfile $testfile || error "file $testfile is corrupted on server (5)" + filesz=$(stat --format=%s $testfile) + filesz=$(((filesz+UNIT_SIZE-1)/UNIT_SIZE * UNIT_SIZE)) + + sync ; echo 3 > /proc/sys/vm/drop_caches + + # remove fscrypt key from keyring + keyctl revoke $(keyctl show | awk '$7 ~ "^fscrypt:" {print $1}') + keyctl reap + + scrambledfile=$(find $DIR/$tdir/ -maxdepth 1 -mindepth 1 -type f) + [ $(stat --format=%s $scrambledfile) -eq $filesz ] || + error "file size without key should be rounded up" + rm -f $tmpfile } run_test 40 "exercise size of encrypted file" @@ -3712,34 +3741,44 @@ test_49() { mkdir $dirname trace_cmd stat $dirname - trace_cmd touch $dirname/f1 + trace_cmd echo a > $dirname/f1 + sync ; sync ; echo 3 > /proc/sys/vm/drop_caches trace_cmd stat $dirname/f1 + sync ; sync ; echo 3 > /proc/sys/vm/drop_caches trace_cmd cat $dirname/f1 dd if=/dev/zero of=$dirname/f1 bs=1M count=10 conv=fsync + sync ; sync ; echo 3 > /proc/sys/vm/drop_caches MATCHING_STRING="get xattr 'security.c'" \ trace_cmd $TRUNCATE $dirname/f1 10240 trace_cmd $LFS setstripe -E -1 -S 4M $dirname/f2 + sync ; sync ; echo 3 > /proc/sys/vm/drop_caches trace_cmd $LFS migrate -E -1 -S 256K $dirname/f2 if [[ $MDSCOUNT -gt 1 ]]; then trace_cmd $LFS setdirstripe -i 1 $dirname/d2 + sync ; sync ; echo 3 > /proc/sys/vm/drop_caches trace_cmd $LFS migrate -m 0 $dirname/d2 - touch $dirname/d2/subf + echo b > $dirname/d2/subf + sync ; sync ; echo 3 > /proc/sys/vm/drop_caches # migrate a non-empty encrypted dir trace_cmd $LFS migrate -m 1 $dirname/d2 $LFS setdirstripe -i 1 -c 1 $dirname/d3 dirname=$dirname/d3/subdir mkdir $dirname - + sync ; sync ; echo 3 > /proc/sys/vm/drop_caches trace_cmd stat $dirname - trace_cmd touch $dirname/f1 + trace_cmd echo c > $dirname/f1 + sync ; sync ; echo 3 > /proc/sys/vm/drop_caches trace_cmd stat $dirname/f1 + sync ; sync ; echo 3 > /proc/sys/vm/drop_caches trace_cmd cat $dirname/f1 dd if=/dev/zero of=$dirname/f1 bs=1M count=10 conv=fsync + sync ; sync ; echo 3 > /proc/sys/vm/drop_caches MATCHING_STRING="get xattr 'security.c'" \ trace_cmd $TRUNCATE $dirname/f1 10240 trace_cmd $LFS setstripe -E -1 -S 4M $dirname/f2 + sync ; sync ; echo 3 > /proc/sys/vm/drop_caches trace_cmd $LFS migrate -E -1 -S 256K $dirname/f2 else skip_noexit "2nd part needs >= 2 MDTs" @@ -3937,7 +3976,7 @@ test_52() { [[ $OSTCOUNT -lt 2 ]] && skip_env "needs >= 2 OSTs" - stack_trap cleanup_for_enc_tests EXIT + stack_trap "cleanup_for_enc_tests $tmpfile $mirror1 $mirror2" EXIT setup_for_enc_tests dd if=/dev/urandom of=$tmpfile bs=5000 count=1 conv=fsync @@ -4036,8 +4075,6 @@ test_52() { cancel_lru_locks cmp -bl $tmpfile $testfile || error "extended/split file is corrupted" - - rm -f $tmpfile $mirror1 $mirror2 } run_test 52 "Mirrored encrypted file" @@ -4448,6 +4485,209 @@ test_58() { } run_test 58 "access to enc file's xattrs" +insert_enc_key() { + sync ; echo 3 > /proc/sys/vm/drop_caches + setup_dummy_key +} + +remove_env_key() { + cancel_lru_locks + sync ; echo 3 > /proc/sys/vm/drop_caches + keyctl revoke $(keyctl show | awk '$7 ~ "^fscrypt:" {print $1}') + keyctl reap +} + +verify_mirror() { + local mirror1=$TMP/$tfile.mirror1 + local mirror2=$TMP/$tfile.mirror2 + local testfile=$1 + local reffile=$2 + + $LFS mirror verify -vvv $testfile || + error "verifying mirror failed (1)" + if [ $($LFS mirror verify -v $testfile 2>&1 | + grep -ci "only valid") -ne 0 ]; then + error "verifying mirror failed (2)" + fi + + $LFS mirror read -N 1 -o $mirror1 $testfile || + error "read from mirror 1 failed" + cmp -bl $reffile $mirror1 || + error "corruption of mirror 1" + $LFS mirror read -N 2 -o $mirror2 $testfile || + error "read from mirror 2 failed" + cmp -bl $reffile $mirror2 || + error "corruption of mirror 2" +} + +test_59a() { + local testfile=$DIR/$tdir/$tfile + local tmpfile=$TMP/$tfile + local mirror1=$TMP/$tfile.mirror1 + local mirror2=$TMP/$tfile.mirror2 + local scrambledfile + + $LCTL get_param mdc.*.import | grep -q client_encryption || + skip "client encryption not supported" + + mount.lustre --help |& grep -q "test_dummy_encryption:" || + skip "need dummy encryption support" + + [[ $OSTCOUNT -lt 2 ]] && skip_env "needs >= 2 OSTs" + + stack_trap "cleanup_for_enc_tests $tmpfile $mirror1 $mirror2" EXIT + setup_for_enc_tests + + dd if=/dev/urandom of=$tmpfile bs=5000 count=1 conv=fsync + + $LFS mirror create -N -i0 -N -i1 $testfile || + error "could not create mirror" + dd if=$tmpfile of=$testfile bs=5000 count=1 conv=fsync || + error "could not write to $testfile" + $LFS getstripe $testfile + + # now, without the key + remove_env_key + scrambledfile=$(find $DIR/$tdir/ -maxdepth 1 -mindepth 1 -type f) + $LFS mirror resync $scrambledfile || + error "could not resync mirror" + + $LFS mirror verify -vvv $scrambledfile || + error "mirror verify failed (1)" + if [ $($LFS mirror verify -v $scrambledfile 2>&1 | + grep -ci "only valid") -ne 0 ]; then + error "mirror verify failed (2)" + fi + + $LFS mirror read -N 1 -o $mirror1 $scrambledfile && + error "read from mirror should fail" + + # now, with the key + insert_enc_key + verify_mirror $testfile $tmpfile +} +run_test 59a "mirror resync of encrypted files without key" + +test_59b() { + local testfile=$DIR/$tdir/$tfile + local tmpfile=$TMP/$tfile + local mirror1=$TMP/$tfile.mirror1 + local mirror2=$TMP/$tfile.mirror2 + local scrambledfile + + $LCTL get_param mdc.*.import | grep -q client_encryption || + skip "client encryption not supported" + + mount.lustre --help |& grep -q "test_dummy_encryption:" || + skip "need dummy encryption support" + + [[ $OSTCOUNT -lt 2 ]] && skip_env "needs >= 2 OSTs" + + stack_trap "cleanup_for_enc_tests $tmpfile $mirror1 $mirror2" EXIT + setup_for_enc_tests + + tr '\0' '2' < /dev/zero | + dd of=$tmpfile bs=1 count=9000 conv=fsync + + $LFS setstripe -c1 -i0 $testfile + dd if=$tmpfile of=$testfile bs=9000 count=1 conv=fsync || + error "write to $testfile failed" + $LFS getstripe $testfile + + # now, without the key + remove_env_key + scrambledfile=$(find $DIR/$tdir/ -maxdepth 1 -mindepth 1 -type f) + $LFS migrate -i1 $scrambledfile || + error "migrate $scrambledfile failed" + $LFS getstripe $scrambledfile + stripe=$($LFS getstripe -i $scrambledfile) + [ $stripe -eq 1 ] || error "migrate file $scrambledfile failed" + cancel_lru_locks + + # now, with the key + insert_enc_key + cmp -bl $tmpfile $testfile || + error "migrated file is corrupted" + + # now, without the key + remove_env_key + $LFS mirror extend -N -i0 $scrambledfile || + error "mirror extend $scrambledfile failed (1)" + $LFS getstripe $scrambledfile + mirror_count=$($LFS getstripe -N $scrambledfile) + [ $mirror_count -eq 2 ] || + error "mirror extend file $scrambledfile failed (2)" + stripe=$($LFS getstripe --mirror-id=1 -i $scrambledfile) + [ $stripe -eq 1 ] || + error "mirror extend file $scrambledfile failed (3)" + stripe=$($LFS getstripe --mirror-id=2 -i $scrambledfile) + [ $stripe -eq 0 ] || + error "mirror extend file $scrambledfile failed (4)" + + $LFS mirror verify -vvv $scrambledfile || + error "mirror verify failed (1)" + if [ $($LFS mirror verify -v $scrambledfile 2>&1 | + grep -ci "only valid") -ne 0 ]; then + error "mirror verify failed (2)" + fi + + # now, with the key + insert_enc_key + verify_mirror $testfile $tmpfile + + # now, without the key + remove_env_key + $LFS mirror split --mirror-id 1 -d $scrambledfile || + error "mirror split file $scrambledfile failed (1)" + $LFS getstripe $scrambledfile + mirror_count=$($LFS getstripe -N $scrambledfile) + [ $mirror_count -eq 1 ] || + error "mirror split file $scrambledfile failed (2)" + stripe=$($LFS getstripe --mirror-id=1 -i $scrambledfile) + [ -z "$stripe" ] || error "mirror split file $scrambledfile failed (3)" + stripe=$($LFS getstripe --mirror-id=2 -i $scrambledfile) + [ $stripe -eq 0 ] || error "mirror split file $scrambledfile failed (4)" + + # now, with the key + insert_enc_key + cancel_lru_locks + cmp -bl $tmpfile $testfile || + error "extended/split file is corrupted" +} +run_test 59b "migrate/extend/split of encrypted files without key" + +test_59c() { + local dirname=$DIR/$tdir/subdir + local scrambleddir + + $LCTL get_param mdc.*.import | grep -q client_encryption || + skip "client encryption not supported" + + mount.lustre --help |& grep -q "test_dummy_encryption:" || + skip "need dummy encryption support" + + [[ $MDSCOUNT -ge 2 ]] || skip_env "needs >= 2 MDTs" + + stack_trap "cleanup_for_enc_tests" EXIT + setup_for_enc_tests + + $LFS setdirstripe -i 0 $dirname + echo b > $dirname/subf + + # now, without the key + remove_env_key + scrambleddir=$(find $DIR/$tdir/ -maxdepth 1 -mindepth 1 -type d) + + # migrate a non-empty encrypted dir + $LFS migrate -m 1 $scrambleddir || + error "migrate $scrambleddir between MDTs failed (1)" + + stripe=$($LFS getdirstripe -i $scrambleddir) + [ $stripe -eq 1 ] || + error "migrate $scrambleddir between MDTs failed (2)" +} +run_test 59c "MDT migrate of encrypted files without key" + log "cleanup: ======================================================" sec_unsetup() { diff --git a/lustre/utils/lfs.c b/lustre/utils/lfs.c index 213dc09..fb630fa 100644 --- a/lustre/utils/lfs.c +++ b/lustre/utils/lfs.c @@ -624,12 +624,10 @@ static uint32_t check_foreign_type_name(const char *foreign_type_name) static const char *error_loc = "syserror"; -enum { - MIGRATION_NONBLOCK = 0x0001, - MIGRATION_MIRROR = 0x0002, - MIGRATION_NONDIRECT = 0x0004, - MIGRATION_VERBOSE = 0x0008, -}; +#define MIGRATION_NONBLOCK LLAPI_MIGRATION_NONBLOCK +#define MIGRATION_MIRROR LLAPI_MIGRATION_MIRROR +#define MIGRATION_NONDIRECT LLAPI_MIGRATION_NONDIRECT +#define MIGRATION_VERBOSE LLAPI_MIGRATION_VERBOSE static int migrate_open_files(const char *name, __u64 migration_flags, @@ -672,15 +670,24 @@ migrate_open_files(const char *name, __u64 migration_flags, *ptr = '\0'; } - /* open file, direct io */ /* even if the file is only read, WR mode is nedeed to allow * layout swap on fd */ - rflags = O_RDWR | O_NOATIME; + /* Allow migrating even without the key on encrypted files */ + rflags = O_RDWR | O_NOATIME | O_FILE_ENC; if (!(migration_flags & MIGRATION_NONDIRECT)) rflags |= O_DIRECT; +source_open: fd = open(name, rflags); if (fd < 0) { + /* If encrypted file without the key, + * retry mirror extend in O_DIRECT. + */ + if (errno == ENOKEY && !(rflags & O_DIRECT) && + migration_flags & MIGRATION_MIRROR) { + rflags |= O_DIRECT; + goto source_open; + } rc = -errno; error_loc = "cannot open source file"; return rc; @@ -693,9 +700,13 @@ migrate_open_files(const char *name, __u64 migration_flags, } do { - int open_flags = O_WRONLY | O_CREAT | O_EXCL | O_NOFOLLOW; + int open_flags = O_WRONLY | O_CREAT | O_EXCL | O_NOFOLLOW | + /* Allow migrating without the key on encrypted files */ + O_FILE_ENC; mode_t open_mode = S_IRUSR | S_IWUSR; + if (rflags & O_DIRECT) + open_flags |= O_DIRECT; random_value = random(); rc = snprintf(volatile_file, sizeof(volatile_file), "%s/%s:%.4X:%.4X:fd=%.2d", parent, @@ -1831,8 +1842,8 @@ static int mirror_extend_layout(char *name, struct llapi_layout *m_layout, } } llapi_layout_comp_flags_set(m_layout, flags); - rc = migrate_open_files(name, MIGRATION_NONDIRECT, NULL, m_layout, &fd, - &fdv); + rc = migrate_open_files(name, MIGRATION_NONDIRECT | MIGRATION_MIRROR, + NULL, m_layout, &fd, &fdv); if (rc < 0) goto out; @@ -2153,7 +2164,15 @@ static int mirror_split(const char *fname, __u32 id, const char *pool, } } - fd = open(fname, O_RDWR); + if (!victim_file && mflags & MF_DESTROY) + /* Allow mirror split even without the key on encrypted files, + * and in this case of a 'split -d', open file with O_DIRECT + * (no IOs will be done). + */ + fd = open(fname, O_RDWR | O_DIRECT | O_FILE_ENC); + else + fd = open(fname, O_RDWR); + if (fd < 0) { fprintf(stderr, "error %s: open file '%s' failed: %s\n", @@ -2237,7 +2256,11 @@ again: open_flags = O_RDWR | (O_LOV_DELAY_CREATE & ~O_ACCMODE) | - O_CREAT | O_EXCL | O_NOFOLLOW; + O_CREAT | O_EXCL | O_NOFOLLOW | + /* O_DIRECT for mirror split -d */ + O_DIRECT | + /* Allow split without the key */ + O_FILE_ENC; fdv = open(file_path, open_flags, S_IRUSR | S_IWUSR); if (fdv < 0) @@ -10582,7 +10605,8 @@ int lfs_mirror_resync_file(const char *fname, struct ll_ioc_lease *ioc, goto error; } - fd = open(fname, O_DIRECT | O_RDWR); + /* Allow mirror resync even without the key on encrypted files */ + fd = open(fname, O_DIRECT | O_RDWR | O_FILE_ENC); if (fd < 0) { fprintf(stderr, "%s: cannot open '%s': %s.\n", progname, fname, strerror(errno)); @@ -11880,7 +11904,8 @@ int lfs_mirror_verify_file(const char *fname, __u16 *mirror_ids, int ids_nr, goto error; } - fd = open(fname, O_DIRECT | O_RDONLY); + /* Allow mirror verify even without the key on encrypted files */ + fd = open(fname, O_DIRECT | O_RDONLY | O_FILE_ENC); if (fd < 0) { fprintf(stderr, "%s: cannot open '%s': %s.\n", progname, fname, strerror(errno)); diff --git a/lustre/utils/liblustreapi_layout.c b/lustre/utils/liblustreapi_layout.c index f107d27..c90972b 100644 --- a/lustre/utils/liblustreapi_layout.c +++ b/lustre/utils/liblustreapi_layout.c @@ -1069,15 +1069,27 @@ struct llapi_layout *llapi_layout_get_by_path(const char *path, enum llapi_layout_get_flags flags) { struct llapi_layout *layout = NULL; + bool failed = false; + int open_flags; int fd; int tmp; if (flags & LLAPI_LAYOUT_GET_EXPECTED) return llapi_layout_expected(path); - fd = open(path, O_RDONLY); - if (fd < 0) - return layout; + /* Always get layout in O_DIRECT */ + /* Allow fetching layout even without the key on encrypted files */ + open_flags = O_RDONLY | O_DIRECT | O_FILE_ENC; +do_open: + fd = open(path, open_flags); + if (fd < 0) { + if (errno != EINVAL || failed) + return layout; + /* EINVAL is because a directory cannot be opened in O_DIRECT */ + open_flags = O_RDONLY | O_FILE_ENC; + failed = true; + goto do_open; + } layout = llapi_layout_get_by_fd(fd, flags); tmp = errno; @@ -3110,7 +3122,11 @@ do_read: if (comp_array[i].lrc_synced && pos & (page_size - 1)) { rc = llapi_mirror_truncate(fd, comp_array[i].lrc_mirror_id, pos); - if (rc < 0) + /* Ignore truncate error on encrypted file without the + * key if tried on LUSTRE_ENCRYPTION_UNIT_SIZE boundary. + */ + if (rc < 0 && (rc != -ENOKEY || + pos & ~LUSTRE_ENCRYPTION_MASK)) comp_array[i].lrc_synced = false; } } diff --git a/lustre/utils/lustreapi_internal.h b/lustre/utils/lustreapi_internal.h index 522b8c3..3d291dc 100644 --- a/lustre/utils/lustreapi_internal.h +++ b/lustre/utils/lustreapi_internal.h @@ -51,6 +51,11 @@ #define WANT_INDEX 0x8 #define WANT_ERROR 0x10 +/* Define a fixed 4096-byte encryption unit size */ +#define LUSTRE_ENCRYPTION_BLOCKBITS 12 +#define LUSTRE_ENCRYPTION_UNIT_SIZE ((size_t)1 << LUSTRE_ENCRYPTION_BLOCKBITS) +#define LUSTRE_ENCRYPTION_MASK (~(LUSTRE_ENCRYPTION_UNIT_SIZE - 1)) + /* mount point listings in /proc/mounts */ #ifndef PROC_MOUNTS #define PROC_MOUNTS "/proc/mounts" -- 1.8.3.1