Whamcloud - gitweb
LU-13717 sec: filename encryption 90/43390/15
authorSebastien Buisson <sbuisson@ddn.com>
Tue, 23 Mar 2021 13:58:50 +0000 (22:58 +0900)
committerOleg Drokin <green@whamcloud.com>
Sat, 11 Sep 2021 06:35:03 +0000 (06:35 +0000)
On client side, call the appropriate llcrypt primitives from llite,
to proceed with filename encryption before sending requests to servers
and filename decryption upon request receipt.
Note we need specific overlay functions to handle encoding and
decoding of encrypted filenames, as we do not want server side to deal
with binary names before they reach the backend file system layer.

On server side, mainly the OSD layer, we need to know the encryption
status of files being processed.
If an object belongs to an encrypted file, the filename has been
encoded by the client because it is binary, so it needs to be decoded
before being handed over to the backend file system layer.
And conversely, the filename of an encrypted file has to be encoded
before being sent over the wire.
Note server side is osd-ldiskfs only for now.

Signed-off-by: Sebastien Buisson <sbuisson@ddn.com>
Change-Id: I7ac9047f5a046b8bc63afdbbb1f28e78aa5c8c7e
Reviewed-on: https://review.whamcloud.com/43390
Tested-by: jenkins <devops@whamcloud.com>
Tested-by: Maloo <maloo@whamcloud.com>
Reviewed-by: Andreas Dilger <adilger@whamcloud.com>
Reviewed-by: Patrick Farrell <pfarrell@whamcloud.com>
Reviewed-by: James Simmons <jsimmons@infradead.org>
14 files changed:
lustre/include/obd.h
lustre/llite/crypto.c
lustre/llite/dcache.c
lustre/llite/dir.c
lustre/llite/file.c
lustre/llite/llite_internal.h
lustre/llite/llite_lib.c
lustre/llite/namei.c
lustre/llite/statahead.c
lustre/mdc/mdc_lib.c
lustre/mdd/mdd_dir.c
lustre/osd-ldiskfs/osd_handler.c
lustre/ptlrpc/layout.c
lustre/tests/sanity-sec.sh

index 9bc1b13..6185f4e 100644 (file)
@@ -840,6 +840,7 @@ enum md_op_flags {
        MF_GETATTR_BY_FID       = BIT(5),
        MF_QOS_MKDIR            = BIT(6),
        MF_RR_MKDIR             = BIT(7),
+       MF_OPNAME_KMALLOCED     = BIT(8),
 };
 
 enum md_cli_flags {
@@ -857,6 +858,9 @@ enum md_op_code {
        LUSTRE_OPC_MKNOD,
        LUSTRE_OPC_CREATE,
        LUSTRE_OPC_ANY,
+       LUSTRE_OPC_LOOKUP,
+       LUSTRE_OPC_OPEN,
+       LUSTRE_OPC_MIGR,
 };
 
 /**
index 3f69d39..17c42d0 100644 (file)
@@ -29,6 +29,7 @@
 #include "llite_internal.h"
 
 #ifdef HAVE_LUSTRE_CRYPTO
+#include <libcfs/libcfs_crypto.h>
 
 static int ll_get_context(struct inode *inode, void *ctx, size_t len)
 {
@@ -158,6 +159,150 @@ static bool ll_empty_dir(struct inode *inode)
        return true;
 }
 
+/**
+ * ll_setup_filename() - overlay to llcrypt_setup_filename
+ * @dir: the directory that will be searched
+ * @iname: the user-provided filename being searched for
+ * @lookup: 1 if we're allowed to proceed without the key because it's
+ *     ->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
+ *
+ * This overlay function is necessary to properly encode @fname after
+ * encryption, as it will be sent over the wire.
+ */
+int ll_setup_filename(struct inode *dir, const struct qstr *iname,
+                     int lookup, struct llcrypt_name *fname)
+{
+       int rc;
+
+       rc = llcrypt_setup_filename(dir, iname, lookup, fname);
+       if (rc)
+               return rc;
+
+       if (IS_ENCRYPTED(dir) &&
+           !name_is_dot_or_dotdot(fname->disk_name.name,
+                                  fname->disk_name.len)) {
+               int presented_len = critical_chars(fname->disk_name.name,
+                                                  fname->disk_name.len);
+               char *buf;
+
+               buf = kmalloc(presented_len + 1, GFP_NOFS);
+               if (!buf) {
+                       rc = -ENOMEM;
+                       goto out_free;
+               }
+
+               if (presented_len == fname->disk_name.len)
+                       memcpy(buf, fname->disk_name.name, presented_len);
+               else
+                       critical_encode(fname->disk_name.name,
+                                       fname->disk_name.len, buf);
+               buf[presented_len] = '\0';
+               kfree(fname->crypto_buf.name);
+               fname->crypto_buf.name = buf;
+               fname->crypto_buf.len = presented_len;
+               fname->disk_name.name = fname->crypto_buf.name;
+               fname->disk_name.len = fname->crypto_buf.len;
+       }
+
+       return rc;
+
+out_free:
+       llcrypt_free_filename(fname);
+       return rc;
+}
+
+/**
+ * ll_fname_disk_to_usr() - overlay to llcrypt_fname_disk_to_usr
+ * @inode: the inode to convert name
+ * @hash: major hash for inode
+ * @minor_hash: minor hash for inode
+ * @iname: the user-provided filename needing conversion
+ * @oname: the filename information to be filled in
+ *
+ * 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.
+ */
+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 lltr = LLTR_INIT(iname->name, iname->len);
+       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;
+       }
+
+       rc = llcrypt_fname_disk_to_usr(inode, hash, minor_hash, &lltr, oname);
+
+       kfree(buf);
+
+       return rc;
+}
+
+/* Copied from llcrypt_d_revalidate, as it is not exported */
+/*
+ * Validate dentries in encrypted directories to make sure we aren't potentially
+ * caching stale dentries after a key has been added.
+ */
+int ll_revalidate_d_crypto(struct dentry *dentry, unsigned int flags)
+{
+       struct dentry *dir;
+       int err;
+       int valid;
+
+       /*
+        * Plaintext names are always valid, since llcrypt doesn't support
+        * reverting to ciphertext names without evicting the directory's inode
+        * -- which implies eviction of the dentries in the directory.
+        */
+       if (!(dentry->d_flags & DCACHE_ENCRYPTED_NAME))
+               return 1;
+
+       /*
+        * Ciphertext name; valid if the directory's key is still unavailable.
+        *
+        * Although llcrypt forbids rename() on ciphertext names, we still must
+        * use dget_parent() here rather than use ->d_parent directly.  That's
+        * because a corrupted fs image may contain directory hard links, which
+        * the VFS handles by moving the directory's dentry tree in the dcache
+        * each time ->lookup() finds the directory and it already has a dentry
+        * elsewhere.  Thus ->d_parent can be changing, and we must safely grab
+        * a reference to some ->d_parent to prevent it from being freed.
+        */
+
+       if (flags & LOOKUP_RCU)
+               return -ECHILD;
+
+       dir = dget_parent(dentry);
+       err = llcrypt_get_encryption_info(d_inode(dir));
+       valid = !llcrypt_has_encryption_key(d_inode(dir));
+       dput(dir);
+
+       if (err < 0)
+               return err;
+
+       return valid;
+}
+
 const struct llcrypt_operations lustre_cryptops = {
        .key_prefix             = "lustre:",
        .get_context            = ll_get_context,
@@ -190,5 +335,23 @@ bool ll_sbi_has_encrypt(struct ll_sb_info *sbi)
 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)
+{
+       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)
+{
+       return llcrypt_fname_disk_to_usr(inode, hash, minor_hash, iname, oname);
+}
+
+int ll_revalidate_d_crypto(struct dentry *dentry, unsigned int flags)
+{
+       return 1;
+}
 #endif
 
index a1ca583..eeffcdb 100644 (file)
@@ -293,10 +293,15 @@ static int ll_revalidate_dentry(struct dentry *dentry,
                                unsigned int lookup_flags)
 {
        struct inode *dir = dentry->d_parent->d_inode;
+       int rc;
 
        CDEBUG(D_VFSTRACE, "VFS Op:name=%s, flags=%u\n",
               dentry->d_name.name, lookup_flags);
 
+       rc = ll_revalidate_d_crypto(dentry, lookup_flags);
+       if (rc != 1)
+               return rc;
+
        /* If this is intermediate component path lookup and we were able to get
         * to this dentry, then its lock has not been revoked and the
         * path component is valid. */
index 1a06c0f..2e78b3b 100644 (file)
@@ -55,6 +55,7 @@
 #include <lustre_fid.h>
 #include <lustre_kernelcomm.h>
 #include <lustre_swab.h>
+#include <libcfs/libcfs_crypto.h>
 
 #include "llite_internal.h"
 
@@ -186,15 +187,22 @@ int ll_dir_read(struct inode *inode, __u64 *ppos, struct md_op_data *op_data,
                void *cookie, filldir_t filldir)
 {
 #endif
-       struct ll_sb_info    *sbi        = ll_i2sbi(inode);
-       __u64                 pos        = *ppos;
-       bool                  is_api32 = ll_need_32bit_api(sbi);
-       bool                  is_hash64 = sbi->ll_flags & LL_SBI_64BIT_HASH;
-       struct page          *page;
-       bool                  done = false;
-       int                   rc = 0;
+       struct ll_sb_info *sbi = ll_i2sbi(inode);
+       __u64 pos = *ppos;
+       bool is_api32 = ll_need_32bit_api(sbi);
+       bool is_hash64 = sbi->ll_flags & LL_SBI_64BIT_HASH;
+       struct page *page;
+       bool done = false;
+       struct llcrypt_str lltr = LLTR_INIT(NULL, 0);
+       int rc = 0;
        ENTRY;
 
+       if (IS_ENCRYPTED(inode)) {
+               rc = llcrypt_fname_alloc_buffer(inode, NAME_MAX, &lltr);
+               if (rc < 0)
+                       RETURN(rc);
+       }
+
        page = ll_get_dir_page(inode, op_data, pos);
 
        while (rc == 0 && !done) {
@@ -238,9 +246,31 @@ int ll_dir_read(struct inode *inode, __u64 *ppos, struct md_op_data *op_data,
                         * for 'filldir()' must be part of the 'ent'. */
 #ifdef HAVE_DIR_CONTEXT
                        ctx->pos = lhash;
-                       done = !dir_emit(ctx, ent->lde_name, namelen, ino,
-                                        type);
+                       if (!IS_ENCRYPTED(inode)) {
+                               done = !dir_emit(ctx, ent->lde_name, namelen,
+                                                ino, type);
+                       } else {
+                               /* Directory is encrypted */
+                               int save_len = lltr.len;
+                               struct llcrypt_str de_name =
+                                       LLTR_INIT(ent->lde_name, namelen);
+
+                               rc = ll_fname_disk_to_usr(inode, 0, 0, &de_name,
+                                                         &lltr);
+                               de_name = lltr;
+                               lltr.len = save_len;
+                               if (rc) {
+                                       done = 1;
+                                       break;
+                               }
+                               done = !dir_emit(ctx, de_name.name, de_name.len,
+                                                ino, type);
+                       }
 #else
+                       /* HAVE_DIR_CONTEXT is defined from kernel 3.11, whereas
+                        * IS_ENCRYPTED is brought by kernel 4.14.
+                        * So there is no need to handle encryption case here.
+                        */
                        done = filldir(cookie, ent->lde_name, namelen, lhash,
                                       ino, type);
 #endif
@@ -277,6 +307,7 @@ int ll_dir_read(struct inode *inode, __u64 *ppos, struct md_op_data *op_data,
 #else
        *ppos = pos;
 #endif
+       llcrypt_fname_free_buffer(&lltr);
        RETURN(rc);
 }
 
@@ -308,6 +339,12 @@ static int ll_readdir(struct file *filp, void *cookie, filldir_t filldir)
               PFID(ll_inode2fid(inode)),
               inode, (unsigned long)pos, i_size_read(inode), api32);
 
+       if (IS_ENCRYPTED(inode)) {
+               rc = llcrypt_get_encryption_info(inode);
+               if (rc && rc != -ENOKEY)
+                       GOTO(out, rc);
+       }
+
        if (pos == MDS_DIR_END_OFF)
                /*
                 * end-of-file.
index 38d23fc..a611212 100644 (file)
@@ -628,7 +628,7 @@ retry:
        }
 
        op_data = ll_prep_md_op_data(NULL, parent->d_inode, de->d_inode,
-                                    name, len, 0, LUSTRE_OPC_ANY, NULL);
+                                    name, len, 0, LUSTRE_OPC_OPEN, NULL);
        if (IS_ERR(op_data)) {
                kfree(name);
                RETURN(PTR_ERR(op_data));
@@ -2283,7 +2283,7 @@ int ll_lov_getstripe_ea_info(struct inode *inode, const char *filename,
                              struct ptlrpc_request **request)
 {
        struct ll_sb_info *sbi = ll_i2sbi(inode);
-       struct mdt_body *body;
+       struct mdt_body  *body;
        struct lov_mds_md *lmm = NULL;
        struct ptlrpc_request *req = NULL;
        struct md_op_data *op_data;
@@ -2305,8 +2305,8 @@ int ll_lov_getstripe_ea_info(struct inode *inode, const char *filename,
        rc = md_getattr_name(sbi->ll_md_exp, op_data, &req);
        ll_finish_md_op_data(op_data);
        if (rc < 0) {
-               CDEBUG(D_INFO, "md_getattr_name failed "
-                      "on %s: rc %d\n", filename, rc);
+               CDEBUG(D_INFO, "md_getattr_name failed on %s: rc %d\n",
+                      filename, rc);
                GOTO(out, rc);
        }
 
@@ -4734,10 +4734,10 @@ int ll_get_fid_by_name(struct inode *parent, const char *name,
                       int namelen, struct lu_fid *fid,
                       struct inode **inode)
 {
-       struct md_op_data       *op_data = NULL;
-       struct mdt_body         *body;
-       struct ptlrpc_request   *req;
-       int                     rc;
+       struct md_op_data *op_data = NULL;
+       struct mdt_body *body;
+       struct ptlrpc_request *req;
+       int rc;
        ENTRY;
 
        op_data = ll_prep_md_op_data(NULL, parent, NULL, name, namelen, 0,
@@ -4845,7 +4845,8 @@ int ll_migrate(struct inode *parent, struct file *file, struct lmv_user_md *lum,
        }
 
        op_data = ll_prep_md_op_data(NULL, parent, NULL, name, namelen,
-                                    child_inode->i_mode, LUSTRE_OPC_ANY, NULL);
+                                    child_inode->i_mode, LUSTRE_OPC_MIGR,
+                                    NULL);
        if (IS_ERR(op_data))
                GOTO(out_iput, rc = PTR_ERR(op_data));
 
@@ -4886,8 +4887,9 @@ again:
                spin_unlock(&och->och_mod->mod_open_req->rq_lock);
        }
 
-       rc = md_rename(ll_i2sbi(parent)->ll_md_exp, op_data, name, namelen,
-                      name, namelen, &request);
+       rc = md_rename(ll_i2sbi(parent)->ll_md_exp, op_data,
+                      op_data->op_name, op_data->op_namelen,
+                      op_data->op_name, op_data->op_namelen, &request);
        if (rc == 0) {
                LASSERT(request != NULL);
                ll_update_times(request, parent);
index 854ca79..d07afb0 100644 (file)
@@ -1722,10 +1722,17 @@ static inline struct pcc_super *ll_info2pccs(struct ll_inode_info *lli)
        return ll_i2pccs(ll_info2i(lli));
 }
 
-#ifdef HAVE_LUSTRE_CRYPTO
 /* crypto.c */
+int ll_setup_filename(struct inode *dir, const struct qstr *iname,
+                     int lookup, struct llcrypt_name *fname);
+int ll_fname_disk_to_usr(struct inode *inode,
+                        u32 hash, u32 minor_hash,
+                        struct llcrypt_str *iname, struct llcrypt_str *oname);
+int ll_revalidate_d_crypto(struct dentry *dentry, unsigned int flags);
+#ifdef HAVE_LUSTRE_CRYPTO
 extern const struct llcrypt_operations lustre_cryptops;
 #endif
+
 /* llite/llite_foreign.c */
 int ll_manage_foreign(struct inode *inode, struct lustre_md *lmd);
 bool ll_foreign_is_openable(struct dentry *dentry, unsigned int flags);
index 906b530..38c1fa0 100644 (file)
@@ -3104,6 +3104,9 @@ struct md_op_data *ll_prep_md_op_data(struct md_op_data *op_data,
                                      __u32 mode, enum md_op_code opc,
                                      void *data)
 {
+       struct llcrypt_name fname = { 0 };
+       int rc;
+
        LASSERT(i1 != NULL);
 
        if (name == NULL) {
@@ -3128,7 +3131,6 @@ struct md_op_data *ll_prep_md_op_data(struct md_op_data *op_data,
 
        ll_i2gids(op_data->op_suppgids, i1, i2);
        op_data->op_fid1 = *ll_inode2fid(i1);
-       op_data->op_code = opc;
 
        if (S_ISDIR(i1->i_mode)) {
                down_read_non_owner(&ll_i2info(i1)->lli_lsm_sem);
@@ -3160,8 +3162,46 @@ struct md_op_data *ll_prep_md_op_data(struct md_op_data *op_data,
        if (ll_need_32bit_api(ll_i2sbi(i1)))
                op_data->op_cli_flags |= CLI_API32;
 
-       op_data->op_name = name;
-       op_data->op_namelen = namelen;
+       if (opc == LUSTRE_OPC_LOOKUP || opc == LUSTRE_OPC_CREATE) {
+               /* In case of lookup, ll_setup_filename() has already been
+                * called in ll_lookup_it(), so just take provided name.
+                */
+               fname.disk_name.name = (unsigned char *)name;
+               fname.disk_name.len = namelen;
+       } else if (name && namelen) {
+               struct qstr dname = QSTR_INIT(name, namelen);
+               struct inode *dir;
+               int lookup;
+
+               if (!S_ISDIR(i1->i_mode) && i2 && S_ISDIR(i2->i_mode)) {
+                       /* special case when called from ll_link() */
+                       dir = i2;
+                       lookup = 0;
+               } else {
+                       dir = i1;
+                       lookup = (int)(opc == LUSTRE_OPC_ANY);
+               }
+               rc = ll_setup_filename(dir, &dname, lookup, &fname);
+               if (rc) {
+                       ll_finish_md_op_data(op_data);
+                       return ERR_PTR(rc);
+               }
+               if (fname.disk_name.name &&
+                   fname.disk_name.name != (unsigned char *)name)
+                       /* op_data->op_name must be freed after use */
+                       op_data->op_flags |= MF_OPNAME_KMALLOCED;
+       }
+
+       /* In fact LUSTRE_OPC_LOOKUP, LUSTRE_OPC_OPEN, LUSTRE_OPC_MIGR
+        * are LUSTRE_OPC_ANY
+        */
+       if (opc == LUSTRE_OPC_LOOKUP || opc == LUSTRE_OPC_OPEN ||
+           opc == LUSTRE_OPC_MIGR)
+               op_data->op_code = LUSTRE_OPC_ANY;
+       else
+               op_data->op_code = opc;
+       op_data->op_name = fname.disk_name.name;
+       op_data->op_namelen = fname.disk_name.len;
        op_data->op_mode = mode;
        op_data->op_mod_time = ktime_get_real_seconds();
        op_data->op_fsuid = from_kuid(&init_user_ns, current_fsuid());
@@ -3182,6 +3222,11 @@ void ll_finish_md_op_data(struct md_op_data *op_data)
        ll_unlock_md_op_lsm(op_data);
        ll_security_release_secctx(op_data->op_file_secctx,
                                   op_data->op_file_secctx_size);
+       if (op_data->op_flags & MF_OPNAME_KMALLOCED)
+               /* allocated via ll_setup_filename called
+                * from ll_prep_md_op_data
+                */
+               kfree(op_data->op_name);
        llcrypt_free_ctx(op_data->op_file_encctx, op_data->op_file_encctx_size);
        OBD_FREE_PTR(op_data);
 }
index 6b56934..951619f 100644 (file)
@@ -822,6 +822,7 @@ static struct dentry *ll_lookup_it(struct inode *parent, struct dentry *dentry,
        __u32 opc;
        int rc;
        char secctx_name[XATTR_NAME_MAX + 1];
+       struct llcrypt_name fname;
 
        ENTRY;
 
@@ -850,12 +851,32 @@ static struct dentry *ll_lookup_it(struct inode *parent, struct dentry *dentry,
        if (it->it_op & IT_CREAT)
                opc = LUSTRE_OPC_CREATE;
        else
-               opc = LUSTRE_OPC_ANY;
+               opc = LUSTRE_OPC_LOOKUP;
+
+       /* Here we should be calling llcrypt_prepare_lookup(). But it installs a
+        * custom ->d_revalidate() method, so we lose ll_d_ops.
+        * To workaround this, call ll_setup_filename() and do the rest
+        * manually. Also make a copy of llcrypt_d_revalidate() (unfortunately
+        * 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);
+       if ((!rc || rc == -ENOENT) && fname.is_ciphertext_name) {
+               spin_lock(&dentry->d_lock);
+               dentry->d_flags |= DCACHE_ENCRYPTED_NAME;
+               spin_unlock(&dentry->d_lock);
+       }
+       if (rc == -ENOENT)
+               RETURN(NULL);
+       if (rc)
+               RETURN(ERR_PTR(rc));
 
-       op_data = ll_prep_md_op_data(NULL, parent, NULL, dentry->d_name.name,
-                                    dentry->d_name.len, 0, opc, NULL);
-       if (IS_ERR(op_data))
-               GOTO(out, retval = ERR_CAST(op_data));
+       op_data = ll_prep_md_op_data(NULL, parent, NULL, fname.disk_name.name,
+                                    fname.disk_name.len, 0, opc, NULL);
+       if (IS_ERR(op_data)) {
+               llcrypt_free_filename(&fname);
+               RETURN(ERR_CAST(op_data));
+       }
 
        /* enforce umask if acl disabled or MDS doesn't support umask */
        if (!IS_POSIXACL(parent) || !exp_connect_umask(ll_i2mdexp(parent)))
@@ -1083,6 +1104,7 @@ out:
                        op_data->op_file_encctx = NULL;
                        op_data->op_file_encctx_size = 0;
                }
+               llcrypt_free_filename(&fname);
                ll_finish_md_op_data(op_data);
        }
 
@@ -1983,13 +2005,12 @@ static int ll_rename(struct inode *src, struct dentry *src_dchild,
 #endif
                     )
 {
-       struct qstr *src_name = &src_dchild->d_name;
-       struct qstr *tgt_name = &tgt_dchild->d_name;
        struct ptlrpc_request *request = NULL;
        struct ll_sb_info *sbi = ll_i2sbi(src);
        struct md_op_data *op_data;
        ktime_t kstart = ktime_get();
        umode_t mode = 0;
+       struct llcrypt_name foldname, fnewname;
        int err;
        ENTRY;
 
@@ -2036,9 +2057,20 @@ 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);
+       if (err)
+               RETURN(err);
+       err = ll_setup_filename(tgt, &tgt_dchild->d_name, 1, &fnewname);
+       if (err) {
+               llcrypt_free_filename(&foldname);
+               RETURN(err);
+       }
        err = md_rename(sbi->ll_md_exp, op_data,
-                       src_name->name, src_name->len,
-                       tgt_name->name, tgt_name->len, &request);
+                       foldname.disk_name.name, foldname.disk_name.len,
+                       fnewname.disk_name.name, fnewname.disk_name.len,
+                       &request);
+       llcrypt_free_filename(&foldname);
+       llcrypt_free_filename(&fnewname);
        ll_finish_md_op_data(op_data);
        if (!err) {
                ll_update_times(request, src);
index 1238b17..e0aef8b 100644 (file)
@@ -343,6 +343,11 @@ __sa_make_ready(struct ll_statahead_info *sai, struct sa_entry *entry, int ret)
 /* finish async stat RPC arguments */
 static void sa_fini_data(struct md_enqueue_info *minfo)
 {
+       struct md_op_data *op_data = &minfo->mi_data;
+
+       if (op_data->op_flags & MF_OPNAME_KMALLOCED)
+               /* allocated via ll_setup_filename called from sa_prep_data */
+               kfree(op_data->op_name);
        ll_unlock_md_op_lsm(&minfo->mi_data);
        iput(minfo->mi_dir);
        OBD_FREE_PTR(minfo);
@@ -1058,6 +1063,7 @@ static int ll_statahead_thread(void *arg)
                        int namelen;
                        char *name;
                        struct lu_fid fid;
+                       struct llcrypt_str lltr = LLTR_INIT(NULL, 0);
 
                        hash = le64_to_cpu(ent->lde_hash);
                        if (unlikely(hash < pos))
@@ -1132,7 +1138,27 @@ static int ll_statahead_thread(void *arg)
                        }
                        __set_current_state(TASK_RUNNING);
 
+                       if (IS_ENCRYPTED(dir)) {
+                               struct llcrypt_str de_name =
+                                       LLTR_INIT(ent->lde_name, namelen);
+
+                               rc = llcrypt_fname_alloc_buffer(dir, NAME_MAX,
+                                                               &lltr);
+                               if (rc < 0)
+                                       continue;
+
+                               if (ll_fname_disk_to_usr(dir, 0, 0, &de_name,
+                                                        &lltr)) {
+                                       llcrypt_fname_free_buffer(&lltr);
+                                       continue;
+                               }
+
+                               name = lltr.name;
+                               namelen = lltr.len;
+                       }
+
                        sa_statahead(parent, name, namelen, &fid);
+                       llcrypt_fname_free_buffer(&lltr);
                }
 
                pos = le64_to_cpu(dp->ldp_hash_end);
@@ -1275,12 +1301,13 @@ enum {
 /* file is first dirent under @dir */
 static int is_first_dirent(struct inode *dir, struct dentry *dentry)
 {
-       struct qstr          *target = &dentry->d_name;
-       struct md_op_data    *op_data;
-       int                   dot_de;
-       struct page          *page = NULL;
-       int                   rc = LS_NOT_FIRST_DE;
-       __u64                 pos = 0;
+       struct qstr *target = &dentry->d_name;
+       struct md_op_data *op_data;
+       int dot_de;
+       struct page *page = NULL;
+       int rc = LS_NOT_FIRST_DE;
+       __u64 pos = 0;
+       struct llcrypt_str lltr = LLTR_INIT(NULL, 0);
 
        ENTRY;
 
@@ -1288,6 +1315,14 @@ static int is_first_dirent(struct inode *dir, struct dentry *dentry)
                                     LUSTRE_OPC_ANY, dir);
        if (IS_ERR(op_data))
                RETURN(PTR_ERR(op_data));
+
+       if (IS_ENCRYPTED(dir)) {
+               int rc2 = llcrypt_fname_alloc_buffer(dir, NAME_MAX, &lltr);
+
+               if (rc2 < 0)
+                       RETURN(rc2);
+       }
+
        /**
         *FIXME choose the start offset of the readdir
         */
@@ -1356,6 +1391,17 @@ static int is_first_dirent(struct inode *dir, struct dentry *dentry)
                                continue;
                        }
 
+                       if (IS_ENCRYPTED(dir)) {
+                               struct llcrypt_str de_name =
+                                       LLTR_INIT(ent->lde_name, namelen);
+
+                               if (ll_fname_disk_to_usr(dir, 0, 0, &de_name,
+                                                         &lltr))
+                                       continue;
+                               name = lltr.name;
+                               namelen = lltr.len;
+                       }
+
                        if (target->len != namelen ||
                            memcmp(target->name, name, namelen) != 0)
                                rc = LS_NOT_FIRST_DE;
@@ -1386,6 +1432,7 @@ static int is_first_dirent(struct inode *dir, struct dentry *dentry)
        }
        EXIT;
 out:
+       llcrypt_fname_free_buffer(&lltr);
        ll_finish_md_op_data(op_data);
 
        return rc;
index 40f2349..9f59b36 100644 (file)
@@ -107,9 +107,12 @@ static void mdc_pack_name(struct req_capsule *pill,
        buf = req_capsule_client_get(pill, field);
        buf_size = req_capsule_get_size(pill, field, RCL_CLIENT);
 
-       LASSERT(name != NULL && name_len != 0 &&
-               buf != NULL && buf_size == name_len + 1);
+       LASSERT(buf != NULL && buf_size == name_len + 1);
 
+       if (!name) {
+               buf[name_len] = '\0';
+               return;
+       }
        cpy_len = strlcpy(buf, name, buf_size);
 
        LASSERT(lu_name_is_valid_2(buf, cpy_len));
index 497366a..cb6fbea 100644 (file)
@@ -55,11 +55,16 @@ static struct lu_name lname_dotdot = {
 };
 
 static inline int
-mdd_name_check(struct mdd_device *m, const struct lu_name *ln)
+mdd_name_check(const struct lu_env *env, struct mdd_device *m,
+              const struct lu_name *ln)
 {
+       struct mdd_thread_info *info = mdd_env_info(env);
+       bool enc = info->mdi_pattr.la_valid & LA_FLAGS &&
+               info->mdi_pattr.la_flags & LUSTRE_ENCRYPT_FL;
+
        if (!lu_name_is_valid(ln))
                return -EINVAL;
-       else if (ln->ln_namelen > m->mdd_dt_conf.ddp_max_name_len)
+       else if (!enc && ln->ln_namelen > m->mdd_dt_conf.ddp_max_name_len)
                return -ENAMETOOLONG;
        else
                return 0;
@@ -628,7 +633,7 @@ static int mdd_link_sanity_check(const struct lu_env *env,
                 RETURN(-ESTALE);
 
         /* Local ops, no lookup before link, check filename length here. */
-       rc = mdd_name_check(m, lname);
+       rc = mdd_name_check(env, m, lname);
        if (rc < 0)
                RETURN(rc);
 
@@ -2109,7 +2114,7 @@ static int mdd_create_sanity_check(const struct lu_env *env,
                cattr->la_valid |= LA_PROJID;
        }
 
-       rc = mdd_name_check(m, lname);
+       rc = mdd_name_check(env, m, lname);
        if (rc < 0)
                RETURN(rc);
 
@@ -3068,7 +3073,7 @@ static int mdd_rename(const struct lu_env *env,
        if (rc)
                GOTO(out_pending, rc);
 
-       rc = mdd_name_check(mdd, ltname);
+       rc = mdd_name_check(env, mdd, ltname);
        if (rc < 0)
                GOTO(out_pending, rc);
 
@@ -3515,11 +3520,9 @@ static int mdd_update_link(const struct lu_env *env,
 
        ENTRY;
 
-       LASSERT(lu_name_is_valid(lname));
-
        /* ignore tobj */
        if (lu_fid_eq(tpfid, fid) && tname->ln_namelen == lname->ln_namelen &&
-           !strncmp(tname->ln_name, lname->ln_name, lname->ln_namelen))
+           !memcmp(tname->ln_name, lname->ln_name, lname->ln_namelen))
                RETURN(0);
 
        CDEBUG(D_INFO, "update "DFID"/"DNAME":"DFID"\n",
@@ -3602,7 +3605,7 @@ static int mdd_is_link_on_source_mdt(const struct lu_env *env,
 
        /* ignore tobj */
        if (lu_fid_eq(tpfid, fid) && tname->ln_namelen == lname->ln_namelen &&
-           !strcmp(tname->ln_name, lname->ln_name))
+           !memcmp(tname->ln_name, lname->ln_name, lname->ln_namelen))
                return 0;
 
        rc = mdd_fld_lookup(env, mdd, fid, &link_mdt_index);
index 05dc11b..f56d53c 100644 (file)
@@ -75,6 +75,9 @@
 
 #include <lustre_linkea.h>
 
+/* encoding routines */
+#include <lustre_crypto.h>
+
 /* Maximum EA size is limited by LNET_MTU for remote objects */
 #define OSD_MAX_EA_SIZE 1048364
 
@@ -5358,6 +5361,50 @@ static void osd_take_care_of_agent(const struct lu_env *env,
 }
 
 /**
+ * Utility function to get real name from object name
+ *
+ * \param[in] obj      pointer to the object to be handled
+ * \param[in] name     object name
+ * \param[in] len      object name len
+ * \param[out]ln       pointer to the struct lu_name to hold the real name
+ *
+ * If file is not encrypted, real name is just the object name.
+ * If file is encrypted, object name needs to be decoded. 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 obj_name2lu_name(struct osd_object *obj, const char *name,
+                           int len, struct lu_name *ln)
+{
+       struct lu_fid namefid;
+
+       fid_zero(&namefid);
+
+       if (name[0] == '[')
+               sscanf(name + 1, SFID, RFID(&namefid));
+
+       if (fid_is_sane(&namefid) || name_is_dot_or_dotdot(name, len) ||
+           !(obj->oo_lma_flags & LUSTRE_ENCRYPT_FL)) {
+               ln->ln_name = name;
+               ln->ln_namelen = len;
+       } else {
+               char *buf = kmalloc(len, GFP_NOFS);
+
+               if (!buf)
+                       return -ENOMEM;
+
+               len = critical_decode(name, len, buf);
+               ln->ln_name = buf;
+               ln->ln_namelen = len;
+       }
+
+       return 0;
+}
+
+/**
  * Index delete function for interoperability mode (b11826).
  * It will remove the directory entry added by osd_index_ea_insert().
  * This entry is needed to maintain name->fid mapping.
@@ -5378,6 +5425,7 @@ static int osd_index_ea_delete(const struct lu_env *env, struct dt_object *dt,
        struct buffer_head *bh;
        struct htree_lock *hlock = NULL;
        struct osd_device *osd = osd_dev(dt->do_lu.lo_dev);
+       struct lu_name ln;
        int rc;
 
        ENTRY;
@@ -5389,6 +5437,10 @@ static int osd_index_ea_delete(const struct lu_env *env, struct dt_object *dt,
        LASSERT(!dt_object_remote(dt));
        LASSERT(handle != NULL);
 
+       rc = obj_name2lu_name(obj, (char *)key, strlen((char *)key), &ln);
+       if (rc)
+               RETURN(rc);
+
        osd_trans_exec_op(env, handle, OSD_OT_DELETE);
 
        oh = container_of(handle, struct osd_thandle, ot_super);
@@ -5396,8 +5448,7 @@ static int osd_index_ea_delete(const struct lu_env *env, struct dt_object *dt,
        LASSERT(oh->ot_handle->h_transaction != NULL);
 
        dquot_initialize(dir);
-       dentry = osd_child_dentry_get(env, obj,
-                                     (char *)key, strlen((char *)key));
+       dentry = osd_child_dentry_get(env, obj, ln.ln_name, ln.ln_namelen);
 
        if (obj->oo_hl_head != NULL) {
                hlock = osd_oti_get(env)->oti_hlock;
@@ -5451,6 +5502,8 @@ static int osd_index_ea_delete(const struct lu_env *env, struct dt_object *dt,
 out:
        LASSERT(osd_invariant(obj));
        osd_trans_exec_check(env, handle, OSD_OT_DELETE);
+       if (ln.ln_name != (char *)key)
+               kfree(ln.ln_name);
        RETURN(rc);
 }
 
@@ -5625,6 +5678,7 @@ static int __osd_ea_add_rec(struct osd_thread_info *info,
        struct ldiskfs_dentry_param *ldp;
        struct dentry *child;
        struct osd_thandle *oth;
+       struct lu_name ln;
        int rc;
 
        oth = container_of(th, struct osd_thandle, ot_super);
@@ -5632,12 +5686,17 @@ static int __osd_ea_add_rec(struct osd_thread_info *info,
        LASSERT(oth->ot_handle->h_transaction != NULL);
        LASSERT(pobj->oo_inode);
 
+       rc = obj_name2lu_name(pobj, name, strlen(name), &ln);
+       if (rc)
+               RETURN(rc);
+
        ldp = (struct ldiskfs_dentry_param *)info->oti_ldp;
        if (unlikely(osd_object_is_root(pobj)))
                ldp->edp_magic = 0;
        else
                osd_get_ldiskfs_dirent_param(ldp, fid);
-       child = osd_child_dentry_get(info->oti_env, pobj, name, strlen(name));
+       child = osd_child_dentry_get(info->oti_env, pobj,
+                                    ln.ln_name, ln.ln_namelen);
        child->d_fsdata = (void *)ldp;
        dquot_initialize(pobj->oo_inode);
        rc = osd_ldiskfs_add_entry(info, osd_obj2dev(pobj), oth->ot_handle,
@@ -5666,6 +5725,8 @@ static int __osd_ea_add_rec(struct osd_thread_info *info,
                }
        }
 
+       if (ln.ln_name != name)
+               kfree(ln.ln_name);
        RETURN(rc);
 }
 
@@ -6068,6 +6129,7 @@ static int osd_ea_lookup_rec(const struct lu_env *env, struct osd_object *obj,
        struct buffer_head *bh;
        struct lu_fid *fid = (struct lu_fid *)rec;
        struct htree_lock *hlock = NULL;
+       struct lu_name ln;
        int ino;
        int rc;
 
@@ -6076,8 +6138,11 @@ static int osd_ea_lookup_rec(const struct lu_env *env, struct osd_object *obj,
        LASSERT(dir->i_op != NULL);
        LASSERT(dir->i_op->lookup != NULL);
 
-       dentry = osd_child_dentry_get(env, obj,
-                                     (char *)key, strlen((char *)key));
+       rc = obj_name2lu_name(obj, (char *)key, strlen((char *)key), &ln);
+       if (rc)
+               RETURN(rc);
+
+       dentry = osd_child_dentry_get(env, obj, ln.ln_name, ln.ln_namelen);
 
        if (obj->oo_hl_head != NULL) {
                hlock = osd_oti_get(env)->oti_hlock;
@@ -6153,7 +6218,9 @@ out:
                ldiskfs_htree_unlock(hlock);
        else
                up_read(&obj->oo_ext_idx_sem);
-       return rc;
+       if (ln.ln_name != (char *)key)
+               kfree(ln.ln_name);
+       RETURN(rc);
 }
 
 static int osd_index_declare_ea_insert(const struct lu_env *env,
@@ -6785,6 +6852,7 @@ static int osd_ldiskfs_filldir(void *buf,
        struct osd_it_ea_dirent *ent = it->oie_dirent;
        struct lu_fid *fid = &ent->oied_fid;
        struct osd_fid_pack *rec;
+       struct lu_fid namefid;
 
        ENTRY;
 
@@ -6798,6 +6866,8 @@ static int osd_ldiskfs_filldir(void *buf,
            OSD_IT_EA_BUFSIZE)
                RETURN(1);
 
+       fid_zero(&namefid);
+
        /* "." is just the object itself. */
        if (namelen == 1 && name[0] == '.') {
                if (obj != NULL)
@@ -6818,13 +6888,28 @@ static int osd_ldiskfs_filldir(void *buf,
                *fid = obj->oo_dt.do_lu.lo_header->loh_fid;
        }
 
+       if (name[0] == '[')
+               sscanf(name + 1, SFID, RFID(&namefid));
+       if (fid_is_sane(&namefid) || name_is_dot_or_dotdot(name, namelen) ||
+           !obj || !(obj->oo_lma_flags & LUSTRE_ENCRYPT_FL)) {
+               memcpy(ent->oied_name, name, namelen);
+       } else {
+               int presented_len = critical_chars(name, namelen);
+
+               if (presented_len == namelen)
+                       memcpy(ent->oied_name, name, namelen);
+               else
+                       namelen = critical_encode(name, namelen,
+                                                 ent->oied_name);
+
+               ent->oied_name[namelen] = '\0';
+       }
+
        ent->oied_ino     = ino;
        ent->oied_off     = offset;
        ent->oied_namelen = namelen;
        ent->oied_type    = d_type;
 
-       memcpy(ent->oied_name, name, namelen);
-
        it->oie_rd_dirent++;
        it->oie_dirent = (void *)ent + cfs_size_round(sizeof(*ent) + namelen);
        RETURN(0);
@@ -7076,6 +7161,7 @@ osd_dirent_check_repair(const struct lu_env *env, struct osd_object *obj,
        int rc;
        bool dotdot = false;
        bool dirty = false;
+       struct lu_name ln;
 
        ENTRY;
 
@@ -7110,8 +7196,11 @@ osd_dirent_check_repair(const struct lu_env *env, struct osd_object *obj,
                RETURN(rc);
        }
 
-       dentry = osd_child_dentry_by_inode(env, dir, ent->oied_name,
-                                          ent->oied_namelen);
+       rc = obj_name2lu_name(obj, ent->oied_name, ent->oied_namelen, &ln);
+       if (rc)
+               RETURN(rc);
+
+       dentry = osd_child_dentry_by_inode(env, dir, ln.ln_name, ln.ln_namelen);
        rc = osd_get_lma(info, inode, dentry, &info->oti_ost_attrs);
        if (rc == -ENODATA || !fid_is_sane(&lma->lma_self_fid))
                lma = NULL;
@@ -7384,6 +7473,8 @@ out_inode:
        iput(inode);
        if (rc >= 0 && !dirty)
                dev->od_dirent_journal = 0;
+       if (ln.ln_name != ent->oied_name)
+               kfree(ln.ln_name);
 
        return rc;
 }
index 7abd631..2c68ea3 100644 (file)
@@ -1037,7 +1037,7 @@ struct req_msg_field RMF_FID_ARRAY =
 EXPORT_SYMBOL(RMF_FID_ARRAY);
 
 struct req_msg_field RMF_SYMTGT =
-        DEFINE_MSGF("symtgt", RMF_F_STRING, -1, NULL, NULL);
+       DEFINE_MSGF("symtgt", 0, -1, NULL, NULL);
 EXPORT_SYMBOL(RMF_SYMTGT);
 
 struct req_msg_field RMF_TGTUUID =
index e95536e..09cb9b0 100755 (executable)
@@ -3252,13 +3252,12 @@ 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 lsfile=$TMP/lsfile
        local scrambleddir
        local scrambledfile
 
-       local testfile2=$DIR/$tdir/${tfile}.2
-       local tmpfile=$DIR/junk
-
        $LCTL get_param mdc.*.import | grep -q client_encryption ||
                skip "client encryption not supported"
 
@@ -3268,24 +3267,32 @@ test_46() {
        stack_trap cleanup_for_enc_tests EXIT
        setup_for_enc_tests
 
-       touch $DIR/onefile
        touch $DIR/$tdir/$tfile
        mkdir $testdir
        echo test > $testfile
+       echo othertest > $testfile2
        sync ; echo 3 > /proc/sys/vm/drop_caches
 
        # remove fscrypt key from keyring
        keyctl revoke $(keyctl show | awk '$7 ~ "^fscrypt:" {print $1}')
        keyctl reap
+       cancel_lru_locks
 
        scrambleddir=$(find $DIR/$tdir/ -maxdepth 1 -mindepth 1 -type d)
-       ls -1 $scrambleddir > $lsfile || error "ls $testdir failed"
+       ls -1 $scrambleddir > $lsfile || error "ls $testdir failed (1)"
 
        scrambledfile=$scrambleddir/$(head -n 1 $lsfile)
-       stat $scrambledfile || error "stat $scrambledfile failed"
+       stat $scrambledfile || error "stat $scrambledfile failed (1)"
        rm -f $lsfile
 
-       cat $scrambledfile && error "cat $scrambledfile should have failed"
+       cat $scrambledfile && error "cat $scrambledfile should have failed (1)"
+       rm -f $scrambledfile || error "rm $scrambledfile failed (1)"
+
+       ls -1 $scrambleddir > $lsfile || error "ls $testdir failed (2)"
+       scrambledfile=$scrambleddir/$(head -n 1 $lsfile)
+       stat $scrambledfile || error "stat $scrambledfile failed (2)"
+       rm -f $lsfile
+       cat $scrambledfile && error "cat $scrambledfile should have failed (2)"
 
        touch $scrambleddir/otherfile &&
                error "touch otherfile should have failed"
@@ -3294,10 +3301,10 @@ test_46() {
                error "mkdir otherdir should have failed"
        ls -d $scrambleddir/otherdir && error "otherdir should not exist"
 
-       rm -f $scrambledfile || error "rm $scrambledfile failed"
+       ls -R $DIR
+       rm -f $scrambledfile || error "rm $scrambledfile failed (2)"
        rmdir $scrambleddir || error "rmdir $scrambleddir failed"
-
-       rm -f $DIR/onefile
+       ls -R $DIR
 }
 run_test 46 "encrypted file access semantics without key"
 
@@ -3322,7 +3329,7 @@ test_47() {
                error "rename from unencrypted to encrypted dir should fail"
 
        ln $tmpfile $testfile &&
-               error "link from unencrypted to encrypted dir should fail"
+               error "link from encrypted to unencrypted dir should fail"
 
        cp $tmpfile $testfile ||
                error "cp from unencrypted to encrypted dir should succeed"
@@ -3336,7 +3343,7 @@ test_47() {
        rm -f $testfile
 
        ln $testfile2 $tmpfile ||
-               error "link from encrypted to unencrypted dir should succeed"
+               error "link from unencrypted to encrypted dir should succeed"
        rm -f $tmpfile
 
        # check we are limited in the number of hard links
@@ -3345,9 +3352,11 @@ test_47() {
                ln $testfile2 ${testfile}_$i || break
        done
        [ $i -lt 160 ] || error "hard link $i should fail"
+       rm -f ${testfile}_*
 
        mrename $testfile2 $tmpfile &&
                error "rename from encrypted to unencrypted dir should fail"
+       rm -f $testfile2
        touch $tmpfile
 
        dd if=/dev/zero of=$testfile bs=512K count=1
@@ -3357,6 +3366,7 @@ test_47() {
        # remove fscrypt key from keyring
        keyctl revoke $(keyctl show | awk '$7 ~ "^fscrypt:" {print $1}')
        keyctl reap
+       cancel_lru_locks
 
        scrambleddir=$(find $DIR/$tdir/ -maxdepth 1 -mindepth 1 -type d)
        scrambledfile=$(find $DIR/$tdir/ -maxdepth 1 -type f)
@@ -4038,8 +4048,9 @@ test_54() {
        local filecount=$($RUNAS find $testdir -type f | wc -l)
        [ $filecount -eq 3 ] || error "found $filecount files"
 
-       $RUNAS hexdump -C $testfile &&
-               error "reading $testfile should have failed without key"
+       scrambledfiles=( $(find $testdir/ -maxdepth 1 -type f) )
+       $RUNAS hexdump -C ${scrambledfiles[0]} &&
+               error "reading ${scrambledfiles[0]} should fail without key"
 
        $RUNAS touch ${testfile}.nokey &&
                error "touch ${testfile}.nokey should have failed without key"
@@ -4055,8 +4066,8 @@ test_54() {
        $RUNAS fscrypt lock --verbose $testdir ||
                error "fscrypt lock $testdir failed (2)"
 
-       $RUNAS hexdump -C $testfile2 &&
-               error "reading $testfile2 should have failed without key"
+       $RUNAS hexdump -C ${scrambledfiles[1]} &&
+               error "reading ${scrambledfiles[1]} should fail without key"
 
        echo mypass | $RUNAS fscrypt unlock --verbose $testdir ||
                error "fscrypt unlock $testdir failed (2)"