#include <lustre_fid.h>
#include <lustre_lmv.h>
#include <lustre_idmap.h>
+#include <lustre_crypto.h>
+#include <uapi/linux/lustre/lgss.h>
#include "mdd_internal.h"
return rc;
}
+/* 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.
+ */
+/* Must be identical to ll_digest_filename in llite_internal.h */
+struct changelog_digest_filename {
+ struct lu_fid cdf_fid;
+ char cdf_excerpt[LL_CRYPTO_BLOCK_SIZE];
+};
+
+/**
+ * Utility function to process filename in changelog
+ *
+ * \param[in] name file name
+ * \param[in] namelen file name len
+ * \param[in] fid file FID
+ * \param[in] enc is object encrypted?
+ * \param[out]ln pointer to the struct lu_name to hold the real name
+ *
+ * If file is not encrypted, output name is just the file name.
+ * If file is encrypted, file name needs to be decoded then digested if the name
+ * is also encrypted. In this case a new buffer is allocated, and ln->ln_name
+ * needs to be freed by the caller.
+ *
+ * \retval 0, on success
+ * \retval -ve, on error
+ */
+static int changelog_name2digest(const char *name, int namelen,
+ const struct lu_fid *fid,
+ bool enc, struct lu_name *ln)
+{
+ struct changelog_digest_filename *digest = NULL;
+ char *buf = NULL, *bufout = NULL, *p, *q;
+ int len, bufoutlen;
+ int rc = 0;
+
+ ENTRY;
+
+ ln->ln_name = name;
+ ln->ln_namelen = namelen;
+
+ if (!enc)
+ GOTO(out, rc);
+
+ /* now we know file is encrypted */
+ if (strnchr(name, namelen, '=')) {
+ /* only proceed to critical decode if
+ * encrypted name contains espace char '='
+ */
+ buf = kmalloc(namelen, GFP_NOFS);
+ if (!buf)
+ GOTO(out, rc = -ENOMEM);
+
+ namelen = critical_decode(name, namelen, buf);
+ ln->ln_name = buf;
+ ln->ln_namelen = namelen;
+ }
+
+ p = (char *)ln->ln_name;
+ len = namelen;
+ while (len--) {
+ if (!isprint(*p++))
+ break;
+ }
+
+ /* len == -1 means we went through the whole decoded name without
+ * finding any non-printable character, so consider it is not encrypted
+ */
+ if (len == -1)
+ GOTO(out, rc);
+
+ /* now we know the name has some non-printable characters */
+ if (namelen > LL_CRYPTO_BLOCK_SIZE * 2) {
+ if (!fid)
+ GOTO(out, rc = -EPROTO);
+
+ OBD_ALLOC_PTR(digest);
+ if (!digest)
+ GOTO(out, rc = -ENOMEM);
+
+ digest->cdf_fid = *fid;
+ memcpy(digest->cdf_excerpt,
+ LLCRYPT_FNAME_DIGEST(ln->ln_name, ln->ln_namelen),
+ LL_CRYPTO_BLOCK_SIZE);
+ p = (char *)digest;
+ len = sizeof(*digest);
+ } else {
+ p = (char *)ln->ln_name;
+ len = ln->ln_namelen;
+ }
+
+ bufoutlen = BASE64URL_CHARS(len) + 2;
+ bufout = kmalloc(digest ? bufoutlen + 1 : bufoutlen, GFP_NOFS);
+ if (!bufout)
+ GOTO(free_digest, rc = -ENOMEM);
+
+ q = bufout;
+ if (digest)
+ *q++ = LLCRYPT_DIGESTED_CHAR;
+ /* beware that gss_base64url_encode adds a trailing space */
+ gss_base64url_encode(&q, &bufoutlen, (__u8 *)p, len);
+ if (bufoutlen == -1) {
+ kfree(bufout);
+ } else {
+ kfree(buf);
+ ln->ln_name = bufout;
+ ln->ln_namelen = q - bufout - 1;
+ }
+
+free_digest:
+ OBD_FREE_PTR(digest);
+out:
+ RETURN(rc);
+}
+
/** Store a namespace change changelog record
* If this fails, we must fail the whole transaction; we don't
* want the change to commit without the log entry.
const struct lu_name *sname,
struct thandle *handle)
{
+ struct mdd_thread_info *info = mdd_env_info(env);
+ struct lu_name *ltname = NULL, *lsname = NULL;
const struct lu_ucred *uc = lu_ucred(env);
struct llog_changelog_rec *rec;
+ __u64 xflags = CLFE_INVALID;
+ struct lu_fid *tfid = NULL;
struct lu_buf *buf;
int reclen;
- __u64 xflags = CLFE_INVALID;
- int rc;
+ bool enc;
+ int rc = 0;
ENTRY;
LASSERT(tname != NULL);
LASSERT(handle != NULL);
- reclen = mdd_llog_record_calc_size(env, tname, sname);
+ if (tname) {
+ OBD_ALLOC_PTR(ltname);
+ if (!ltname)
+ GOTO(out, rc = -ENOMEM);
+
+ if (sname) {
+ enc = info->mdi_tpattr.la_valid & LA_FLAGS &&
+ info->mdi_tpattr.la_flags & LUSTRE_ENCRYPT_FL;
+ tfid = (struct lu_fid *)sfid;
+ } else {
+ enc = info->mdi_pattr.la_valid & LA_FLAGS &&
+ info->mdi_pattr.la_flags & LUSTRE_ENCRYPT_FL;
+ tfid = (struct lu_fid *)mdd_object_fid(target);
+ }
+ rc = changelog_name2digest(tname->ln_name, tname->ln_namelen,
+ tfid, enc, ltname);
+ if (rc)
+ GOTO(out_ltname, rc);
+ }
+ if (sname) {
+ OBD_ALLOC_PTR(lsname);
+ if (!lsname)
+ GOTO(out_ltname, rc = -ENOMEM);
+
+ enc = info->mdi_pattr.la_valid & LA_FLAGS &&
+ info->mdi_pattr.la_flags & LUSTRE_ENCRYPT_FL;
+ rc = changelog_name2digest(sname->ln_name, sname->ln_namelen,
+ tfid, enc, lsname);
+ if (rc)
+ GOTO(out_lsname, rc);
+ }
+
+ reclen = mdd_llog_record_calc_size(env, ltname, lsname);
buf = lu_buf_check_and_alloc(&mdd_env_info(env)->mdi_chlg_buf, reclen);
if (buf->lb_buf == NULL)
- RETURN(-ENOMEM);
+ GOTO(out_lsname, rc = -ENOMEM);
rec = buf->lb_buf;
clf_flags &= CLF_FLAGMASK;
xflags |= CLFE_NID_BE;
}
- if (sname != NULL)
+ if (lsname != NULL)
clf_flags |= CLF_RENAME;
else
clf_flags |= CLF_VERSION;
rc = mdd_changelog_ns_pfid_set(env, mdd, parent, pattr,
&rec->cr.cr_pfid);
if (rc < 0)
- RETURN(rc);
+ GOTO(out_lsname, rc);
- rec->cr.cr_namelen = tname->ln_namelen;
- memcpy(changelog_rec_name(&rec->cr), tname->ln_name, tname->ln_namelen);
+ rec->cr.cr_namelen = ltname->ln_namelen;
+ memcpy(changelog_rec_name(&rec->cr), ltname->ln_name,
+ ltname->ln_namelen);
if (clf_flags & CLF_RENAME) {
struct lu_fid spfid;
rc = mdd_changelog_ns_pfid_set(env, mdd, sparent, spattr,
&spfid);
if (rc < 0)
- RETURN(rc);
+ GOTO(out_lsname, rc);
- mdd_changelog_rec_ext_rename(&rec->cr, sfid, &spfid, sname);
+ mdd_changelog_rec_ext_rename(&rec->cr, sfid, &spfid, lsname);
}
if (clf_flags & CLF_JOBID)
if (rc < 0) {
CERROR("%s: cannot store changelog record: type = %d, name = '%s', t = "
DFID", p = "DFID": rc = %d\n",
- mdd2obd_dev(mdd)->obd_name, type, tname->ln_name,
+ mdd2obd_dev(mdd)->obd_name, type, ltname->ln_name,
PFID(&rec->cr.cr_tfid), PFID(&rec->cr.cr_pfid), rc);
- return -EFAULT;
+ GOTO(out_lsname, rc = -EFAULT);
}
- return 0;
+out_lsname:
+ if (lsname && lsname->ln_name != sname->ln_name)
+ kfree(lsname->ln_name);
+ OBD_FREE_PTR(lsname);
+out_ltname:
+ if (ltname && ltname->ln_name != tname->ln_name)
+ kfree(ltname->ln_name);
+ OBD_FREE_PTR(ltname);
+out:
+ RETURN(rc);
}
static int __mdd_links_add(const struct lu_env *env,
}
run_test 72 "dynamic nodemap properties"
+test_73() {
+ local vaultdir1=$DIR/$tdir/vault1
+ local vaultdir2=$DIR/$tdir/vault2
+ local shortfname="short=a"
+ local longfname="longfilenamewitha=inthemiddletotestbehaviorregardingthedigestedform"
+ local fid
+ local digshort1
+ local digshort2
+ local diglong1
+ local diglong2
+
+ (( $MDS1_VERSION >= $(version_code 2.16.50) )) ||
+ skip "Need MDS version at least 2.16.50"
+
+ [[ $($LCTL get_param mdc.*.import) =~ client_encryption ]] ||
+ skip "need encryption support"
+ which fscrypt || skip_env "Need fscrypt"
+
+ mkdir -p $DIR/$tdir || error "mkdir $DIR/$tdir failed"
+
+ yes | fscrypt setup --force --verbose ||
+ echo "fscrypt global setup already done"
+ sed -i 's/\(.*\)policy_version\(.*\):\(.*\)\"[0-9]*\"\(.*\)/\1policy_version\2:\3"2"\4/' \
+ /etc/fscrypt.conf
+ yes | fscrypt setup --verbose $MOUNT ||
+ echo "fscrypt setup $MOUNT already done"
+ stack_trap "rm -rf $MOUNT/.fscrypt"
+
+ # enable_filename_encryption tunable only available for client
+ # built against embedded llcrypt. If client is built against in-kernel
+ # fscrypt, file names are always encrypted.
+ $LCTL get_param mdc.*.connect_flags | grep -q name_encryption &&
+ nameenc=$(lctl get_param -n llite.*.enable_filename_encryption |
+ head -n1)
+
+ # begin with non-encrypted names
+ if [ -n "$nameenc" ] && (( nameenc != 0 )); then
+ $LCTL set_param llite.*.enable_filename_encryption=0
+ [ $? -eq 0 ] ||
+ error "set_param \
+ llite.*.enable_filename_encryption=1 failed"
+ fi
+
+ mkdir -p $vaultdir1
+ stack_trap "rm -rf $vaultdir1"
+
+ echo -e 'mypass\nmypass' | fscrypt encrypt --verbose \
+ --source=custom_passphrase --name=protector_73a $vaultdir1 ||
+ error "fscrypt encrypt $vaultdir1 failed"
+
+ # activate changelogs
+ changelog_register || error "changelog_register failed"
+ local cl_user="${CL_USERS[$SINGLEMDS]%% *}"
+ changelog_users $SINGLEMDS | grep -q $cl_user ||
+ error "User $cl_user not found in changelog_users"
+ changelog_chmask ALL
+
+ touch $vaultdir1/$shortfname ||
+ error "touch $vaultdir1/$shortfname failed"
+ fid=$($LFS path2fid $vaultdir1/$shortfname)
+ fid="${fid:1:-1}"
+ fscrypt lock $vaultdir1 || error "fscrypt lock $vaultdir1 failed"
+ digshort1=$($LFS fid2path $MOUNT $fid)
+ digshort1=$(basename $digshort1)
+ echo mypass | fscrypt unlock $vaultdir1 ||
+ error "fscrypt unlock $vaultdir1 failed"
+ mrename $vaultdir1/$shortfname $vaultdir1/$longfname ||
+ error "mrename $vaultdir1/$shortfname failed"
+ fscrypt lock $vaultdir1 || error "fscrypt lock $vaultdir1 failed"
+ diglong1=$($LFS fid2path $MOUNT $fid)
+ diglong1=$(basename $diglong1)
+
+ # access changelogs
+ echo "changelogs dump"
+ changelog_dump || error "failed to dump changelogs"
+ digshort2=$(changelog_find -type CREAT -target-fid $fid |
+ awk '{print $12}')
+ [[ $digshort1 == $digshort2 ]] ||
+ error "name $digshort2 in CREAT is not $digshort1"
+ digshort2=$(changelog_find -type RENME -source-fid $fid |
+ awk '{print $15}')
+ [[ $digshort1 == $digshort2 ]] ||
+ error "name $digshort2 in RENME is not $digshort1"
+ diglong2=$(changelog_find -type RENME -source-fid $fid |
+ awk '{print $12}')
+ [[ $diglong1 == $diglong2 ]] ||
+ error "name $diglong2 in RENME is not $diglong1"
+
+ echo "changelogs clear"
+ changelog_clear 0 || error "failed to clear changelogs"
+
+ # now switch to encrypted names
+ if [ -n "$nameenc" ] && (( nameenc != 1 )); then
+ $LCTL set_param llite.*.enable_filename_encryption=1
+ [ $? -eq 0 ] ||
+ error "set_param \
+ llite.*.enable_filename_encryption=1 failed"
+ stack_trap \
+ "$LCTL set_param llite.*.enable_filename_encryption=0"
+ fi
+
+ $LFS mkdir -c1 -i $((MDSCOUNT-1)) $vaultdir2
+ stack_trap "rm -rf $vaultdir2"
+
+ echo -e 'mypass\nmypass' | fscrypt encrypt --verbose \
+ --source=custom_passphrase --name=protector_73b $vaultdir2 ||
+ error "fscrypt encrypt $vaultdir2 failed"
+
+ touch $vaultdir2/$shortfname ||
+ error "touch $vaultdir2/$shortfname failed"
+ fid=$($LFS path2fid $vaultdir2/$shortfname)
+ fid="${fid:1:-1}"
+ fscrypt lock $vaultdir2 || error "fscrypt lock $vaultdir2 failed"
+ digshort1=$($LFS fid2path $MOUNT $fid)
+ digshort1=$(basename $digshort1)
+ echo mypass | fscrypt unlock $vaultdir2 ||
+ error "fscrypt unlock $vaultdir2 failed"
+ mrename $vaultdir2/$shortfname $vaultdir2/$longfname ||
+ error "mrename $vaultdir2/$shortfname failed"
+ fscrypt lock $vaultdir2 || error "fscrypt lock $vaultdir2 failed"
+ diglong1=$($LFS fid2path $MOUNT $fid)
+ diglong1=$(basename $diglong1)
+
+ # access changelogs
+ echo "changelogs dump"
+ changelog_dump || error "failed to dump changelogs"
+ digshort2=$(changelog_find -type CREAT -target-fid $fid |
+ awk '{print $12}')
+ [[ $digshort1 == $digshort2 ]] ||
+ error "name $digshort2 in CREAT is not $digshort1"
+ digshort2=$(changelog_find -type RENME -source-fid $fid |
+ awk '{print $15}')
+ [[ $digshort1 == $digshort2 ]] ||
+ error "name $digshort2 in RENME is not $digshort1"
+ diglong2=$(changelog_find -type RENME -source-fid $fid |
+ awk '{print $12}')
+ [[ $diglong1 == $diglong2 ]] ||
+ error "name $diglong2 in RENME is not $diglong1"
+}
+run_test 73 "encrypted names in changelogs"
+
log "cleanup: ======================================================"
sec_unsetup() {