From ed4a625d88567a2498c3fe32fd340ae7985e6ad0 Mon Sep 17 00:00:00 2001 From: Sebastien Buisson Date: Fri, 22 Jan 2021 21:06:50 +0900 Subject: [PATCH] LU-13717 sec: filename encryption - digest support A number of operations are allowed on encrypted files without the key: - read file metadata (stat); - list directories; - remove files and directories. In order to present valid names to users, cipher text names are base64 encoded if they are short. Otherwise we compute a digested form of the cipher text, made of the FID (16 bytes) followed by the second-to-last cipher block (16 bytes), and we base64 encode this digested form for presentation to user. These transformations are carried out in the specific overlay functions, that now need to know the fid of the file. As the digested form does not contain the whole cipher text name, server side needs to proceed to an operation by FID for requests such as lookup and getattr. It also relies on the content of the LinkEA to verify the digested form as received from client side. Signed-off-by: Sebastien Buisson Change-Id: I45d10a426373c2cfe0b92a58c351da452d085d7d Reviewed-on: https://review.whamcloud.com/43392 Tested-by: jenkins Tested-by: Maloo Reviewed-by: Andreas Dilger Reviewed-by: Patrick Farrell --- lustre/include/uapi/linux/lustre/lustre_idl.h | 4 + lustre/include/uapi/linux/lustre/lustre_user.h | 3 +- lustre/llite/crypto.c | 131 +++++++++++++++++---- lustre/llite/dir.c | 2 +- lustre/llite/llite_internal.h | 15 ++- lustre/llite/llite_lib.c | 11 +- lustre/llite/namei.c | 20 +++- lustre/llite/statahead.c | 8 +- lustre/mdc/mdc_lib.c | 2 + lustre/mdc/mdc_locks.c | 4 +- lustre/mdc/mdc_request.c | 9 ++ lustre/mdt/mdt_handler.c | 157 +++++++++++++++++++++++-- lustre/mdt/mdt_internal.h | 3 + lustre/mdt/mdt_lib.c | 4 + lustre/mdt/mdt_reint.c | 69 +++++++---- lustre/tests/sanity-sec.sh | 16 ++- 16 files changed, 389 insertions(+), 69 deletions(-) diff --git a/lustre/include/uapi/linux/lustre/lustre_idl.h b/lustre/include/uapi/linux/lustre/lustre_idl.h index 19cce1c..f1e8652 100644 --- a/lustre/include/uapi/linux/lustre/lustre_idl.h +++ b/lustre/include/uapi/linux/lustre/lustre_idl.h @@ -1329,6 +1329,9 @@ lov_mds_md_max_stripe_count(__kernel_size_t buf_size, __u32 lmm_magic) #define OBD_MD_FLLAZYBLOCKS (0x0800000000000000ULL) /* Lazy blocks */ #define OBD_MD_FLBTIME (0x1000000000000000ULL) /* birth time */ #define OBD_MD_ENCCTX (0x2000000000000000ULL) /* embed encryption ctx */ +#define OBD_MD_NAMEHASH (0x4000000000000000ULL) /* use hash instead of name + * in case of encryption + */ #define OBD_MD_FLALLQUOTA (OBD_MD_FLUSRQUOTA | \ OBD_MD_FLGRPQUOTA | \ @@ -1958,6 +1961,7 @@ enum mds_op_bias { MDS_CLOSE_UPDATE_TIMES = 1 << 20, /* setstripe create only, don't restripe if target exists */ MDS_SETSTRIPE_CREATE = 1 << 21, + MDS_FID_OP = 1 << 22, }; #define MDS_CLOSE_INTENT (MDS_HSM_RELEASE | MDS_CLOSE_LAYOUT_SWAP | \ diff --git a/lustre/include/uapi/linux/lustre/lustre_user.h b/lustre/include/uapi/linux/lustre/lustre_user.h index d64e888f..c5f5c91 100644 --- a/lustre/include/uapi/linux/lustre/lustre_user.h +++ b/lustre/include/uapi/linux/lustre/lustre_user.h @@ -1537,13 +1537,14 @@ enum la_valid { #define MDS_OPEN_RESYNC 04000000000000ULL /* FLR: file resync */ #define MDS_OPEN_PCC 010000000000000ULL /* PCC: auto RW-PCC cache attach * for newly created file */ +#define MDS_OP_WITH_FID 020000000000000ULL /* operation carried out by FID */ /* lustre internal open flags, which should not be set from user space */ #define MDS_OPEN_FL_INTERNAL (MDS_OPEN_HAS_EA | MDS_OPEN_HAS_OBJS | \ MDS_OPEN_OWNEROVERRIDE | MDS_OPEN_LOCK | \ MDS_OPEN_BY_FID | MDS_OPEN_LEASE | \ MDS_OPEN_RELEASE | MDS_OPEN_RESYNC | \ - MDS_OPEN_PCC) + MDS_OPEN_PCC | MDS_OP_WITH_FID) /********* Changelogs **********/ diff --git a/lustre/llite/crypto.c b/lustre/llite/crypto.c index 17c42d0..89917c7 100644 --- a/lustre/llite/crypto.c +++ b/lustre/llite/crypto.c @@ -167,19 +167,64 @@ static bool ll_empty_dir(struct inode *inode) * ->lookup() or we're finding the dir_entry for deletion; 0 if we cannot * proceed without the key because we're going to create the dir_entry. * @fname: the filename information to be filled in + * @fid: fid retrieved from user-provided filename * * This overlay function is necessary to properly encode @fname after * encryption, as it will be sent over the wire. + * This overlay function is also necessary to handle the case of operations + * carried out without the key. Normally llcrypt makes use of digested names in + * that case. Having a digested name works for local file systems that can call + * llcrypt_match_name(), but Lustre server side is not aware of encryption. + * So for keyless @lookup operations on long names, for Lustre we choose to + * present to users the encoded struct ll_digest_filename, instead of a digested + * name. FID and name hash can then easily be extracted and put into the + * requests sent to servers. */ int ll_setup_filename(struct inode *dir, const struct qstr *iname, - int lookup, struct llcrypt_name *fname) + int lookup, struct llcrypt_name *fname, + struct lu_fid *fid) { + int digested = 0; + struct qstr dname; int rc; - rc = llcrypt_setup_filename(dir, iname, lookup, fname); + if (fid && IS_ENCRYPTED(dir) && !llcrypt_has_encryption_key(dir) && + iname->name[0] == '_') + digested = 1; + + dname.name = iname->name + digested; + dname.len = iname->len - digested; + + if (fid) { + fid->f_seq = 0; + fid->f_oid = 0; + fid->f_ver = 0; + } + rc = llcrypt_setup_filename(dir, &dname, lookup, fname); if (rc) return rc; + if (digested) { + /* Without the key, for long names user should have struct + * ll_digest_filename representation of the dentry instead of + * the name. So make sure it is valid, return fid and put + * excerpt of cipher text name in disk_name. + */ + struct ll_digest_filename *digest; + + if (fname->crypto_buf.len < sizeof(struct ll_digest_filename)) { + rc = -EINVAL; + goto out_free; + } + digest = (struct ll_digest_filename *)fname->crypto_buf.name; + *fid = digest->ldf_fid; + if (!fid_is_sane(fid)) { + rc = -EINVAL; + goto out_free; + } + fname->disk_name.name = digest->ldf_excerpt; + fname->disk_name.len = LLCRYPT_FNAME_DIGEST_SIZE; + } if (IS_ENCRYPTED(dir) && !name_is_dot_or_dotdot(fname->disk_name.name, fname->disk_name.len)) { @@ -220,40 +265,78 @@ out_free: * @minor_hash: minor hash for inode * @iname: the user-provided filename needing conversion * @oname: the filename information to be filled in + * @fid: the user-provided fid for filename * * The caller must have allocated sufficient memory for the @oname string. * * This overlay function is necessary to properly decode @iname before * decryption, as it comes from the wire. + * This overlay function is also necessary to handle the case of operations + * carried out without the key. Normally llcrypt makes use of digested names in + * that case. Having a digested name works for local file systems that can call + * llcrypt_match_name(), but Lustre server side is not aware of encryption. + * So for keyless @lookup operations on long names, for Lustre we choose to + * present to users the encoded struct ll_digest_filename, instead of a digested + * name. FID and name hash can then easily be extracted and put into the + * requests sent to servers. */ int ll_fname_disk_to_usr(struct inode *inode, u32 hash, u32 minor_hash, - struct llcrypt_str *iname, struct llcrypt_str *oname) + struct llcrypt_str *iname, struct llcrypt_str *oname, + struct lu_fid *fid) { struct llcrypt_str lltr = LLTR_INIT(iname->name, iname->len); + struct ll_digest_filename digest; + int digested = 0; char *buf = NULL; int rc; - if (IS_ENCRYPTED(inode) && - !name_is_dot_or_dotdot(lltr.name, lltr.len) && - strnchr(lltr.name, lltr.len, '=')) { - /* Only proceed to critical decode if - * iname contains espace char '='. - */ - int len = lltr.len; - - buf = kmalloc(len, GFP_NOFS); - if (!buf) - return -ENOMEM; - - len = critical_decode(lltr.name, len, buf); - lltr.name = buf; - lltr.len = len; + if (IS_ENCRYPTED(inode)) { + if (!name_is_dot_or_dotdot(lltr.name, lltr.len) && + strnchr(lltr.name, lltr.len, '=')) { + /* Only proceed to critical decode if + * iname contains espace char '='. + */ + int len = lltr.len; + + buf = kmalloc(len, GFP_NOFS); + if (!buf) + return -ENOMEM; + + len = critical_decode(lltr.name, len, buf); + lltr.name = buf; + lltr.len = len; + } + if (lltr.len > LLCRYPT_FNAME_MAX_UNDIGESTED_SIZE && + !llcrypt_has_encryption_key(inode) && + likely(llcrypt_policy_has_filename_enc(inode))) { + digested = 1; + /* Without the key for long names, set the dentry name + * to the representing struct ll_digest_filename. It + * will be encoded by llcrypt for display, and will + * enable further lookup requests. + */ + if (!fid) + return -EINVAL; + digest.ldf_fid = *fid; + memcpy(digest.ldf_excerpt, + LLCRYPT_FNAME_DIGEST(lltr.name, lltr.len), + LLCRYPT_FNAME_DIGEST_SIZE); + + lltr.name = (char *)&digest; + lltr.len = sizeof(digest); + + oname->name[0] = '_'; + oname->name = oname->name + 1; + oname->len--; + } } rc = llcrypt_fname_disk_to_usr(inode, hash, minor_hash, &lltr, oname); kfree(buf); + oname->name = oname->name - digested; + oname->len = oname->len + digested; return rc; } @@ -337,14 +420,22 @@ void ll_sbi_set_encrypt(struct ll_sb_info *sbi, bool set) } int ll_setup_filename(struct inode *dir, const struct qstr *iname, - int lookup, struct llcrypt_name *fname) + int lookup, struct llcrypt_name *fname, + struct lu_fid *fid) { + if (fid) { + fid->f_seq = 0; + fid->f_oid = 0; + fid->f_ver = 0; + } + return llcrypt_setup_filename(dir, iname, lookup, fname); } int ll_fname_disk_to_usr(struct inode *inode, u32 hash, u32 minor_hash, - struct llcrypt_str *iname, struct llcrypt_str *oname) + struct llcrypt_str *iname, struct llcrypt_str *oname, + struct lu_fid *fid) { return llcrypt_fname_disk_to_usr(inode, hash, minor_hash, iname, oname); } diff --git a/lustre/llite/dir.c b/lustre/llite/dir.c index 2e78b3b..c0ceecc 100644 --- a/lustre/llite/dir.c +++ b/lustre/llite/dir.c @@ -256,7 +256,7 @@ int ll_dir_read(struct inode *inode, __u64 *ppos, struct md_op_data *op_data, LLTR_INIT(ent->lde_name, namelen); rc = ll_fname_disk_to_usr(inode, 0, 0, &de_name, - &lltr); + &lltr, &fid); de_name = lltr; lltr.len = save_len; if (rc) { diff --git a/lustre/llite/llite_internal.h b/lustre/llite/llite_internal.h index d07afb0..f567d16 100644 --- a/lustre/llite/llite_internal.h +++ b/lustre/llite/llite_internal.h @@ -1723,11 +1723,22 @@ static inline struct pcc_super *ll_info2pccs(struct ll_inode_info *lli) } /* crypto.c */ +/* The digested form is made of a FID (16 bytes) followed by the second-to-last + * ciphertext block (16 bytes), so a total length of 32 bytes. + * That way, llcrypt does not compute a digested form of this digest. + */ +struct ll_digest_filename { + struct lu_fid ldf_fid; + char ldf_excerpt[LLCRYPT_FNAME_DIGEST_SIZE]; +}; + int ll_setup_filename(struct inode *dir, const struct qstr *iname, - int lookup, struct llcrypt_name *fname); + int lookup, struct llcrypt_name *fname, + struct lu_fid *fid); int ll_fname_disk_to_usr(struct inode *inode, u32 hash, u32 minor_hash, - struct llcrypt_str *iname, struct llcrypt_str *oname); + struct llcrypt_str *iname, struct llcrypt_str *oname, + struct lu_fid *fid); int ll_revalidate_d_crypto(struct dentry *dentry, unsigned int flags); #ifdef HAVE_LUSTRE_CRYPTO extern const struct llcrypt_operations lustre_cryptops; diff --git a/lustre/llite/llite_lib.c b/lustre/llite/llite_lib.c index 38c1fa0..5c88bb2 100644 --- a/lustre/llite/llite_lib.c +++ b/lustre/llite/llite_lib.c @@ -3171,6 +3171,8 @@ struct md_op_data *ll_prep_md_op_data(struct md_op_data *op_data, } else if (name && namelen) { struct qstr dname = QSTR_INIT(name, namelen); struct inode *dir; + struct lu_fid *pfid = NULL; + struct lu_fid fid; int lookup; if (!S_ISDIR(i1->i_mode) && i2 && S_ISDIR(i2->i_mode)) { @@ -3181,11 +3183,18 @@ struct md_op_data *ll_prep_md_op_data(struct md_op_data *op_data, dir = i1; lookup = (int)(opc == LUSTRE_OPC_ANY); } - rc = ll_setup_filename(dir, &dname, lookup, &fname); + if (opc == LUSTRE_OPC_ANY && lookup) + pfid = &fid; + rc = ll_setup_filename(dir, &dname, lookup, &fname, pfid); if (rc) { ll_finish_md_op_data(op_data); return ERR_PTR(rc); } + if (pfid && !fid_is_zero(pfid)) { + if (i2 == NULL) + op_data->op_fid2 = fid; + op_data->op_bias = MDS_FID_OP; + } if (fname.disk_name.name && fname.disk_name.name != (unsigned char *)name) /* op_data->op_name must be freed after use */ diff --git a/lustre/llite/namei.c b/lustre/llite/namei.c index 951619f..ed37f0c 100644 --- a/lustre/llite/namei.c +++ b/lustre/llite/namei.c @@ -823,7 +823,7 @@ static struct dentry *ll_lookup_it(struct inode *parent, struct dentry *dentry, int rc; char secctx_name[XATTR_NAME_MAX + 1]; struct llcrypt_name fname; - + struct lu_fid fid; ENTRY; if (dentry->d_name.len > ll_i2sbi(parent)->ll_namelen) @@ -860,7 +860,7 @@ static struct dentry *ll_lookup_it(struct inode *parent, struct dentry *dentry, * not exported function) and call it from ll_revalidate_dentry(), to * ensure we do not cache stale dentries after a key has been added. */ - rc = ll_setup_filename(parent, &dentry->d_name, 1, &fname); + rc = ll_setup_filename(parent, &dentry->d_name, 1, &fname, &fid); if ((!rc || rc == -ENOENT) && fname.is_ciphertext_name) { spin_lock(&dentry->d_lock); dentry->d_flags |= DCACHE_ENCRYPTED_NAME; @@ -877,6 +877,12 @@ static struct dentry *ll_lookup_it(struct inode *parent, struct dentry *dentry, llcrypt_free_filename(&fname); RETURN(ERR_CAST(op_data)); } + if (!fid_is_zero(&fid)) { + op_data->op_fid2 = fid; + op_data->op_bias = MDS_FID_OP; + if (it->it_op & IT_OPEN) + it->it_flags |= MDS_OPEN_BY_FID; + } /* enforce umask if acl disabled or MDS doesn't support umask */ if (!IS_POSIXACL(parent) || !exp_connect_umask(ll_i2mdexp(parent))) @@ -1882,7 +1888,8 @@ static int ll_rmdir(struct inode *dir, struct dentry *dchild) if (dchild->d_inode != NULL) op_data->op_fid3 = *ll_inode2fid(dchild->d_inode); - op_data->op_fid2 = op_data->op_fid3; + if (fid_is_zero(&op_data->op_fid2)) + op_data->op_fid2 = op_data->op_fid3; rc = md_unlink(ll_i2sbi(dir)->ll_md_exp, op_data, &request); ll_finish_md_op_data(op_data); if (!rc) { @@ -1974,7 +1981,8 @@ static int ll_unlink(struct inode *dir, struct dentry *dchild) ll_i2info(dchild->d_inode)->lli_clob && dirty_cnt(dchild->d_inode)) op_data->op_cli_flags |= CLI_DIRTY_DATA; - op_data->op_fid2 = op_data->op_fid3; + if (fid_is_zero(&op_data->op_fid2)) + op_data->op_fid2 = op_data->op_fid3; rc = md_unlink(ll_i2sbi(dir)->ll_md_exp, op_data, &request); ll_finish_md_op_data(op_data); if (rc) @@ -2057,10 +2065,10 @@ static int ll_rename(struct inode *src, struct dentry *src_dchild, if (tgt_dchild->d_inode) op_data->op_fid4 = *ll_inode2fid(tgt_dchild->d_inode); - err = ll_setup_filename(src, &src_dchild->d_name, 1, &foldname); + err = ll_setup_filename(src, &src_dchild->d_name, 1, &foldname, NULL); if (err) RETURN(err); - err = ll_setup_filename(tgt, &tgt_dchild->d_name, 1, &fnewname); + err = ll_setup_filename(tgt, &tgt_dchild->d_name, 1, &fnewname, NULL); if (err) { llcrypt_free_filename(&foldname); RETURN(err); diff --git a/lustre/llite/statahead.c b/lustre/llite/statahead.c index e0aef8b..edac2cf 100644 --- a/lustre/llite/statahead.c +++ b/lustre/llite/statahead.c @@ -1141,14 +1141,16 @@ static int ll_statahead_thread(void *arg) if (IS_ENCRYPTED(dir)) { struct llcrypt_str de_name = LLTR_INIT(ent->lde_name, namelen); + struct lu_fid fid; rc = llcrypt_fname_alloc_buffer(dir, NAME_MAX, &lltr); if (rc < 0) continue; + fid_le_to_cpu(&fid, &ent->lde_fid); if (ll_fname_disk_to_usr(dir, 0, 0, &de_name, - &lltr)) { + &lltr, &fid)) { llcrypt_fname_free_buffer(&lltr); continue; } @@ -1394,9 +1396,11 @@ static int is_first_dirent(struct inode *dir, struct dentry *dentry) if (IS_ENCRYPTED(dir)) { struct llcrypt_str de_name = LLTR_INIT(ent->lde_name, namelen); + struct lu_fid fid; + fid_le_to_cpu(&fid, &ent->lde_fid); if (ll_fname_disk_to_usr(dir, 0, 0, &de_name, - &lltr)) + &lltr, &fid)) continue; name = lltr.name; namelen = lltr.len; diff --git a/lustre/mdc/mdc_lib.c b/lustre/mdc/mdc_lib.c index 9f59b36..1dea486 100644 --- a/lustre/mdc/mdc_lib.c +++ b/lustre/mdc/mdc_lib.c @@ -632,6 +632,8 @@ void mdc_getattr_pack(struct req_capsule *pill, __u64 valid, __u32 flags, b->mbo_valid = valid; if (op_data->op_bias & MDS_CROSS_REF) b->mbo_valid |= OBD_MD_FLCROSSREF; + if (op_data->op_bias & MDS_FID_OP) + b->mbo_valid |= OBD_MD_NAMEHASH; b->mbo_eadatasize = ea_size; b->mbo_flags = flags; __mdc_pack_body(b, op_data->op_suppgids[0]); diff --git a/lustre/mdc/mdc_locks.c b/lustre/mdc/mdc_locks.c index ac4574c..f0ee92e 100644 --- a/lustre/mdc/mdc_locks.c +++ b/lustre/mdc/mdc_locks.c @@ -1327,8 +1327,10 @@ int mdc_intent_lock(struct obd_export *exp, struct md_op_data *op_data, it->it_flags); lockh.cookie = 0; + /* MDS_FID_OP is not a revalidate case */ if (fid_is_sane(&op_data->op_fid2) && - (it->it_op & (IT_LOOKUP | IT_GETATTR | IT_READDIR))) { + (it->it_op & (IT_LOOKUP | IT_GETATTR | IT_READDIR)) && + !(op_data->op_bias & MDS_FID_OP)) { /* We could just return 1 immediately, but since we should only * be called in revalidate_it if we already have a lock, let's * verify that. diff --git a/lustre/mdc/mdc_request.c b/lustre/mdc/mdc_request.c index 7629a00..cea198f 100644 --- a/lustre/mdc/mdc_request.c +++ b/lustre/mdc/mdc_request.c @@ -292,6 +292,15 @@ again: op_data->op_mode); req_capsule_set_size(&req->rq_pill, &RMF_ACL, RCL_SERVER, acl_bufsize); ptlrpc_request_set_replen(req); + if (op_data->op_bias & MDS_FID_OP) { + struct mdt_body *b = req_capsule_client_get(&req->rq_pill, + &RMF_MDT_BODY); + + if (b) { + b->mbo_valid |= OBD_MD_NAMEHASH; + b->mbo_fid2 = op_data->op_fid2; + } + } rc = mdc_getattr_common(exp, req); if (rc) { diff --git a/lustre/mdt/mdt_handler.c b/lustre/mdt/mdt_handler.c index 47b97cf..7f54b59 100644 --- a/lustre/mdt/mdt_handler.c +++ b/lustre/mdt/mdt_handler.c @@ -63,6 +63,7 @@ #include #include #include +#include #include "mdt_internal.h" @@ -1902,6 +1903,97 @@ lookup: RETURN(rc); } +/** + * Find name matching hash + * + * We search \a child LinkEA for a name whose hash matches \a lname + * (it contains an encoded hash). + * + * \param info mdt thread info + * \param lname encoded hash to find + * \param parent parent object + * \param child object to search with LinkEA + * \param force_check true to check hash even if LinkEA has only one entry + * + * \retval 1 match found + * \retval 0 no match found + * \retval -ev negative errno upon error + */ +int find_name_matching_hash(struct mdt_thread_info *info, struct lu_name *lname, + struct mdt_object *parent, struct mdt_object *child, + bool force_check) +{ + /* Here, lname is an encoded hash of on-disk name, and + * client is doing access without encryption key. + * So we need to get LinkEA, check parent fid is correct and + * compare name hash with the one in the request. + */ + struct lu_buf *buf = &info->mti_big_buf; + struct lu_name name; + struct lu_fid pfid; + struct linkea_data ldata = { NULL }; + struct link_ea_header *leh; + struct link_ea_entry *lee; + struct lu_buf link = { 0 }; + char *hash = NULL; + int reclen, count, rc; + + ENTRY; + + if (lname->ln_namelen < LLCRYPT_FNAME_DIGEST_SIZE) + RETURN(-EINVAL); + + buf = lu_buf_check_and_alloc(buf, PATH_MAX); + if (!buf->lb_buf) + RETURN(-ENOMEM); + + ldata.ld_buf = buf; + rc = mdt_links_read(info, child, &ldata); + if (rc < 0) + RETURN(rc); + + leh = buf->lb_buf; + if (force_check || leh->leh_reccount > 1) { + hash = kmalloc(lname->ln_namelen, GFP_NOFS); + if (!hash) + RETURN(-ENOMEM); + rc = critical_decode(lname->ln_name, lname->ln_namelen, hash); + } + lee = (struct link_ea_entry *)(leh + 1); + for (count = 0; count < leh->leh_reccount; count++) { + linkea_entry_unpack(lee, &reclen, &name, &pfid); + if (!force_check && leh->leh_reccount == 1) { + /* if there is only one rec, it has to be it */ + *lname = name; + break; + } + if (!parent || lu_fid_eq(&pfid, mdt_object_fid(parent))) { + lu_buf_check_and_alloc(&link, name.ln_namelen); + if (!link.lb_buf) + GOTO(out_match, rc = -ENOMEM); + rc = critical_decode(name.ln_name, name.ln_namelen, + link.lb_buf); + + if (memcmp(LLCRYPT_FNAME_DIGEST(link.lb_buf, rc), + hash, LLCRYPT_FNAME_DIGEST_SIZE) == 0) { + *lname = name; + break; + } + } + lee = (struct link_ea_entry *) ((char *)lee + reclen); + } + if (count == leh->leh_reccount) + rc = 0; + else + rc = 1; + +out_match: + lu_buf_free(&link); + kfree(hash); + + RETURN(rc); +} + /* * UPDATE lock should be taken against parent, and be released before exit; * child_bits lock should be taken against child, and be returned back: @@ -1998,7 +2090,30 @@ static int mdt_getattr_name_lock(struct mdt_thread_info *info, lname = &info->mti_name; mdt_name_unpack(pill, &RMF_NAME, lname, MNF_FIX_ANON); - if (lu_name_is_valid(lname)) { + if (info->mti_body->mbo_valid & OBD_MD_NAMEHASH) { + reqbody = req_capsule_client_get(pill, &RMF_MDT_BODY); + if (unlikely(reqbody == NULL)) + RETURN(err_serious(-EPROTO)); + + *child_fid = reqbody->mbo_fid2; + if (unlikely(!fid_is_sane(child_fid))) + RETURN(err_serious(-EINVAL)); + + if (lu_fid_eq(mdt_object_fid(parent), child_fid)) { + mdt_object_get(info->mti_env, parent); + child = parent; + } else { + child = mdt_object_find(info->mti_env, info->mti_mdt, + child_fid); + if (IS_ERR(child)) + RETURN(PTR_ERR(child)); + } + + CDEBUG(D_INODE, "getattr with lock for "DFID"/"DFID", " + "ldlm_rep = %p\n", + PFID(mdt_object_fid(parent)), + PFID(&reqbody->mbo_fid2), ldlm_rep); + } else if (lu_name_is_valid(lname)) { if (mdt_object_remote(parent)) { CERROR("%s: parent "DFID" is on remote target\n", mdt_obd_name(info->mti_mdt), @@ -2050,14 +2165,17 @@ static int mdt_getattr_name_lock(struct mdt_thread_info *info, mdt_set_disposition(info, ldlm_rep, DISP_LOOKUP_EXECD); - if (unlikely(!mdt_object_exists(parent)) && lu_name_is_valid(lname)) { + if (unlikely(!mdt_object_exists(parent)) && + !(info->mti_body->mbo_valid & OBD_MD_NAMEHASH) && + lu_name_is_valid(lname)) { LU_OBJECT_DEBUG(D_INODE, info->mti_env, &parent->mot_obj, "Parent doesn't exist!"); GOTO(out_child, rc = -ESTALE); } - if (lu_name_is_valid(lname)) { + if (!(info->mti_body->mbo_valid & OBD_MD_NAMEHASH) && + lu_name_is_valid(lname)) { if (info->mti_body->mbo_valid == OBD_MD_FLID) { rc = mdt_raw_lookup(info, parent, lname); @@ -2095,6 +2213,19 @@ static int mdt_getattr_name_lock(struct mdt_thread_info *info, /* step 3: lock child regardless if it is local or remote. */ LASSERT(child); + if (info->mti_body->mbo_valid & OBD_MD_NAMEHASH) { + /* Here, lname is an encoded hash of on-disk name, and + * client is doing access without encryption key. + * So we need to compare name hash with the one in the request. + */ + if (!find_name_matching_hash(info, lname, parent, + child, true)) { + mdt_set_disposition(info, ldlm_rep, DISP_LOOKUP_NEG); + mdt_clear_disposition(info, ldlm_rep, DISP_LOOKUP_POS); + GOTO(out_child, rc = -ENOENT); + } + } + OBD_FAIL_TIMEOUT(OBD_FAIL_MDS_RESEND, obd_timeout * 2); if (!mdt_object_exists(child)) { LU_OBJECT_DEBUG(D_INODE, info->mti_env, @@ -7002,12 +7133,24 @@ static int mdt_fid2path(struct mdt_thread_info *info, RETURN(rc); } - if (mdt_object_remote(obj)) + if (mdt_object_remote(obj)) { rc = -EREMOTE; - else if (!mdt_object_exists(obj)) + } else if (!mdt_object_exists(obj)) { rc = -ENOENT; - else - rc = 0; + } else { + struct lu_attr la = { 0 }; + struct dt_object *dt = mdt_obj2dt(obj); + + if (dt && dt->do_ops && dt->do_ops->do_attr_get) + dt_attr_get(info->mti_env, mdt_obj2dt(obj), &la); + if (la.la_valid & LA_FLAGS && la.la_flags & LUSTRE_ENCRYPT_FL) + /* path resolution cannot be carried out on server + * side for encrypted files + */ + rc = -ENODATA; + else + rc = 0; + } if (rc < 0) { mdt_object_put(info->mti_env, obj); diff --git a/lustre/mdt/mdt_internal.h b/lustre/mdt/mdt_internal.h index 4e09c2a..7b3e302 100644 --- a/lustre/mdt/mdt_internal.h +++ b/lustre/mdt/mdt_internal.h @@ -920,6 +920,9 @@ void mdt_reconstruct_open(struct mdt_thread_info *, struct mdt_lock_handle *); int mdt_layout_change(struct mdt_thread_info *info, struct mdt_object *obj, struct mdt_lock_handle *lhc, struct md_layout_change *spec); +int find_name_matching_hash(struct mdt_thread_info *info, struct lu_name *lname, + struct mdt_object *parent, struct mdt_object *child, + bool force_check); int mdt_device_sync(const struct lu_env *env, struct mdt_device *mdt); struct lu_buf *mdt_buf(const struct lu_env *env, void *area, ssize_t len); diff --git a/lustre/mdt/mdt_lib.c b/lustre/mdt/mdt_lib.c index 76a80aa..e06fd77 100644 --- a/lustre/mdt/mdt_lib.c +++ b/lustre/mdt/mdt_lib.c @@ -1425,6 +1425,10 @@ static int mdt_unlink_unpack(struct mdt_thread_info *info) attr->la_mtime = rec->ul_time; attr->la_mode = rec->ul_mode; attr->la_valid = LA_UID | LA_GID | LA_CTIME | LA_MTIME | LA_MODE; + if (rec->ul_bias & MDS_FID_OP) + info->mti_spec.sp_cr_flags |= MDS_OP_WITH_FID; + else + info->mti_spec.sp_cr_flags &= ~MDS_OP_WITH_FID; rc = mdt_name_unpack(pill, &RMF_NAME, &rr->rr_name, 0); if (rc < 0) diff --git a/lustre/mdt/mdt_reint.c b/lustre/mdt/mdt_reint.c index 44b9638..6090ff0 100644 --- a/lustre/mdt/mdt_reint.c +++ b/lustre/mdt/mdt_reint.c @@ -44,6 +44,7 @@ #include #include "mdt_internal.h" #include +#include static inline void mdt_reint_init_ma(struct mdt_thread_info *info, struct md_attr *ma) @@ -1097,31 +1098,36 @@ relock: if (rc != 0) GOTO(put_parent, rc); - /* lookup child object along with version checking */ - fid_zero(child_fid); - rc = mdt_lookup_version_check(info, mp, &rr->rr_name, child_fid, 1); - if (rc != 0) { - /* Name might not be able to find during resend of - * remote unlink, considering following case. - * dir_A is a remote directory, the name entry of - * dir_A is on MDT0, the directory is on MDT1, - * - * 1. client sends unlink req to MDT1. - * 2. MDT1 sends name delete update to MDT0. - * 3. name entry is being deleted in MDT0 synchronously. - * 4. MDT1 is restarted. - * 5. client resends unlink req to MDT1. So it can not - * find the name entry on MDT0 anymore. - * In this case, MDT1 only needs to destory the local - * directory. - */ - if (mdt_object_remote(mp) && rc == -ENOENT && - !fid_is_zero(rr->rr_fid2) && - lustre_msg_get_flags(req->rq_reqmsg) & MSG_RESENT) { - no_name = 1; - *child_fid = *rr->rr_fid2; - } else { - GOTO(unlock_parent, rc); + if (info->mti_spec.sp_cr_flags & MDS_OP_WITH_FID) { + *child_fid = *rr->rr_fid2; + } else { + /* lookup child object along with version checking */ + fid_zero(child_fid); + rc = mdt_lookup_version_check(info, mp, &rr->rr_name, child_fid, + 1); + if (rc != 0) { + /* Name might not be able to find during resend of + * remote unlink, considering following case. + * dir_A is a remote directory, the name entry of + * dir_A is on MDT0, the directory is on MDT1, + * + * 1. client sends unlink req to MDT1. + * 2. MDT1 sends name delete update to MDT0. + * 3. name entry is being deleted in MDT0 synchronously. + * 4. MDT1 is restarted. + * 5. client resends unlink req to MDT1. So it can not + * find the name entry on MDT0 anymore. + * In this case, MDT1 only needs to destory the local + * directory. + */ + if (mdt_object_remote(mp) && rc == -ENOENT && + !fid_is_zero(rr->rr_fid2) && + lustre_msg_get_flags(req->rq_reqmsg) & MSG_RESENT) { + no_name = 1; + *child_fid = *rr->rr_fid2; + } else { + GOTO(unlock_parent, rc); + } } } @@ -1133,6 +1139,16 @@ relock: if (IS_ERR(mc)) GOTO(unlock_parent, rc = PTR_ERR(mc)); + if (info->mti_spec.sp_cr_flags & MDS_OP_WITH_FID) { + /* In this case, child fid is embedded in the request, and we do + * not have a proper name as rr_name contains an encoded + * hash. So find name that matches provided hash. + */ + if (!find_name_matching_hash(info, &rr->rr_name, + NULL, mc, false)) + GOTO(put_child, rc = -ENOENT); + } + if (!cos_incompat) { rc = mdt_object_striped(info, mc); if (rc < 0) @@ -1274,6 +1290,9 @@ out_stat: unlock_child: mdt_reint_striped_unlock(info, mc, child_lh, einfo, rc); put_child: + if (info->mti_spec.sp_cr_flags & MDS_OP_WITH_FID && + info->mti_big_buf.lb_buf) + lu_buf_free(&info->mti_big_buf); mdt_object_put(info->mti_env, mc); unlock_parent: mdt_object_unlock(info, mp, parent_lh, rc); diff --git a/lustre/tests/sanity-sec.sh b/lustre/tests/sanity-sec.sh index 09cb9b0..8938082 100755 --- a/lustre/tests/sanity-sec.sh +++ b/lustre/tests/sanity-sec.sh @@ -3252,8 +3252,8 @@ run_test 45 "encrypted file access semantics: MMAP" test_46() { local testdir=$DIR/$tdir/mydir local testfile=$testdir/myfile - local testdir2=$DIR/$tdir/mydir2 - local testfile2=$testdir/myfile2 + local testdir2=$DIR/$tdir/mydirwithaveryverylongnametotestcodebehaviour0 + local testfile2=$testdir/myfilewithaveryverylongnametotestcodebehaviour0 local lsfile=$TMP/lsfile local scrambleddir local scrambledfile @@ -3271,6 +3271,11 @@ test_46() { mkdir $testdir echo test > $testfile echo othertest > $testfile2 + if [[ $MDSCOUNT -gt 1 ]]; then + $LFS setdirstripe -c1 -i1 $testdir2 + else + mkdir $testdir2 + fi sync ; echo 3 > /proc/sys/vm/drop_caches # remove fscrypt key from keyring @@ -3278,6 +3283,11 @@ test_46() { keyctl reap cancel_lru_locks + # this is $testdir2 + scrambleddir=$(find $DIR/$tdir/ -maxdepth 1 -mindepth 1 -type d| grep _) + stat $scrambleddir || error "stat $scrambleddir failed" + rmdir $scrambleddir || error "rmdir $scrambleddir failed" + scrambleddir=$(find $DIR/$tdir/ -maxdepth 1 -mindepth 1 -type d) ls -1 $scrambleddir > $lsfile || error "ls $testdir failed (1)" @@ -3998,7 +4008,7 @@ run_test 53 "Mixed PAGE_SIZE clients" test_54() { local testdir=$DIR/$tdir/$ID0 local testfile=$testdir/$tfile - local testfile2=$testdir/${tfile}2 + local testfile2=$testdir/${tfile}withveryverylongnametoexercisecode local tmpfile=$TMP/${tfile}.tmp local resfile=$TMP/${tfile}.res -- 1.8.3.1