[PATCH 2/4] ecryptfs: add export_operations to ecryptfs

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



This patch add export_operations to ecryptfs in order to support nfs
export.

We have two approaches to implement it.

The first one is to implement encode_fh(), fh_to_dentry() and
get_parent() just like most of the other filesystems do.

The second one is the implementation of overlayfs. fh_to_dentry() returns
connected dir dentry so exportfs never needs to call get_parent() to
reconnect it. Connected non-dir file handles are not supported so
exportfs don't need fh_to_parent() to connect it to root. Therefore, we
don't have to implement fh_to_parent() and get_parent().

This patch implements the second one. The methods in export.c are similar
to overlayfs's. For encoding filehandle, we use the filehandle of lower
dentry as our filehandle. For decoding non-dir filhandle, we first decode
the lower dentry. Then we can obtain a disconnected ecryptfs dentry. For
dir entry, we first get a connected lower dentry by exportfs. Then, we
find a connected ancestor of target ecryptfs dentry in dcache. After
that, we can get the target dentry by forward lookup from lower path. The
dentry we found by forward lookup will be connected.

Because encoding connectable non-dir filehandle is not supported in this
patch, exporting whith 'subtree_check' configuration will cause failure.

The reasons for choosing this approach:
It is possible to rename/move directories in lower filesystem that
ecryptfs mounted. The race between reconnecting dentries and lower
filesystem rename should be handled carefully. The race has been
considered in the implementation of overlayfs and it is well reviewed.

Signed-off-by: Chieh Lin <jaycelin@xxxxxxxxxxxx>
---
 fs/ecryptfs/Makefile |   2 +-
 fs/ecryptfs/export.c | 409 +++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 410 insertions(+), 1 deletion(-)
 create mode 100644 fs/ecryptfs/export.c

diff --git a/fs/ecryptfs/Makefile b/fs/ecryptfs/Makefile
index 49678a69947d..e2b0d4d5ea6c 100644
--- a/fs/ecryptfs/Makefile
+++ b/fs/ecryptfs/Makefile
@@ -5,6 +5,6 @@
 obj-$(CONFIG_ECRYPT_FS) += ecryptfs.o
 
 ecryptfs-y := dentry.o file.o inode.o main.o super.o mmap.o read_write.o \
-	      crypto.o keystore.o kthread.o debug.o
+	      crypto.o keystore.o kthread.o debug.o export.o
 
 ecryptfs-$(CONFIG_ECRYPT_FS_MESSAGING) += messaging.o miscdev.o
diff --git a/fs/ecryptfs/export.c b/fs/ecryptfs/export.c
new file mode 100644
index 000000000000..8d7ec5edbc63
--- /dev/null
+++ b/fs/ecryptfs/export.c
@@ -0,0 +1,409 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * eCryptfs: Linux filesystem encryption layer
+ *
+ * Copyright (C) 1997-2004 Erez Zadok
+ * Copyright (C) 2001-2004 Stony Brook University
+ * Copyright (C) 2004-2007 International Business Machines Corp.
+ */
+#include <linux/fs.h>
+#include <linux/mount.h>
+#include <linux/exportfs.h>
+#include "ecryptfs_kernel.h"
+
+static int ecryptfs_encode_fh(struct inode *inode, u32 *fid, int *max_len,
+			      struct inode *parent)
+{
+	struct dentry *dentry;
+	int type;
+
+	if (parent)
+		return FILEID_INVALID;
+
+	dentry = d_find_any_alias(inode);
+	if (WARN_ON(!dentry))
+		return FILEID_INVALID;
+
+	type = exportfs_encode_fh(ecryptfs_dentry_to_lower(dentry),
+				  (struct fid *)fid, max_len, !!parent);
+
+	dput(dentry);
+	return type;
+}
+
+static int ecryptfs_acceptable(void *ctx, struct dentry *dentry)
+{
+	if (!d_is_dir(dentry))
+		return 1;
+
+	if (d_unhashed(dentry))
+		return 0;
+
+	/* Check if directory belongs to the layer we are decoding from */
+	return is_subdir(dentry, ((struct vfsmount *)ctx)->mnt_root);
+}
+
+/* Find or instantiate an disconnected ecryptfs dentry from lower_dentry */
+static struct dentry *ecryptfs_obtain_alias(struct super_block *sb,
+					    struct dentry *lower_dentry)
+{
+	struct dentry *dentry;
+	struct inode *inode;
+	struct ecryptfs_dentry_info *dentry_info;
+
+	if (d_is_dir(lower_dentry))
+		return ERR_PTR(-EIO);
+
+	inode = ecryptfs_get_inode(d_inode(lower_dentry), sb);
+	if (IS_ERR(inode))
+		return ERR_CAST(inode);
+
+	dentry = d_find_any_alias(inode);
+	if (!dentry) {
+		dentry = d_alloc_anon(inode->i_sb);
+		if (!dentry)
+			goto nomem;
+
+		dentry_info = kmem_cache_alloc(ecryptfs_dentry_info_cache,
+					       GFP_KERNEL);
+		if (!dentry_info)
+			goto nomem;
+
+		ecryptfs_set_dentry_private(dentry, dentry_info);
+		dentry_info->lower_path.dentry = dget(lower_dentry);
+		dentry_info->lower_path.mnt =
+			mntget(ecryptfs_superblock_to_lower_mnt(sb));
+	}
+
+	return d_instantiate_anon(dentry, inode);
+
+nomem:
+	iput(inode);
+	dput(dentry);
+	return ERR_PTR(-ENOMEM);
+}
+
+/* Test if data is the lower inode of input inode */
+static int ecryptfs_inode_test(struct inode *inode, void *data)
+{
+	return ecryptfs_inode_to_lower(inode) == data;
+}
+
+/* Lookup a ecryptfs dentry from cache, whose lower dentry is @lower_dentry */
+static struct dentry *ecryptfs_lookup_dentry(struct super_block *sb,
+					    struct dentry *lower_dentry)
+{
+	struct dentry *this = NULL;
+	struct inode *inode, *lower_inode = d_inode(lower_dentry);
+
+	inode = ilookup5(sb, (unsigned long)lower_inode,
+			 ecryptfs_inode_test, lower_inode);
+
+	if (!inode)
+		return NULL;
+
+	this = d_find_any_alias(inode);
+	iput(inode);
+
+	return this;
+}
+
+/* Lookup a child ecryptfs dentry whose lower dentry is @lower_dentry */
+static struct dentry *ecryptfs_lookup_one(struct super_block *sb,
+					  struct dentry *connected,
+					  struct dentry *lower_dentry)
+{
+	struct inode *dir = d_inode(connected);
+	struct dentry *this, *lower_parent = NULL;
+	struct name_snapshot lower_name;
+	char *decrypt_name = NULL;
+	size_t name_size;
+	int err;
+
+	/*
+	 * The dir mutex protects us from racing with rename.
+	 * If the ecryptfs dentry that is above @lower_dentry has been
+	 * moved to a parent that is not under the connected ecryptfs dir,
+	 * we return -ECHILD.
+	 */
+	inode_lock_nested(dir, I_MUTEX_PARENT);
+	err = -ECHILD;
+	lower_parent = dget_parent(lower_dentry);
+	if (ecryptfs_dentry_to_lower(connected) != lower_parent) {
+		dput(lower_parent);
+		inode_unlock(dir);
+		return ERR_PTR(err);
+	}
+
+	/*
+	 * We need to take a snapshot of 'lower_dentry' name to protect us
+	 * from racing with lower fs rename.
+	 */
+	take_dentry_name_snapshot(&lower_name, lower_dentry);
+	/* decrypt filename */
+	err = ecryptfs_decode_and_decrypt_filename(&decrypt_name, &name_size,
+						   sb, lower_name.name,
+						   strlen(lower_name.name));
+	if (err) {
+		ecryptfs_printk(KERN_WARNING,
+				"Error attempting to decode and decrypt filename [%s]; rc = [%d]\n",
+				lower_name.name, err);
+		goto fail;
+	}
+
+	/*
+	 * Lookup ecryptfs dentry by decrypted name
+	 * ecryptfs_lookup would encrypt the filename again and it is possible
+	 * to avoid that because we already have the lower filename and lower
+	 * dentry. In this version, we just do lookup_one_len to simplify
+	 * the implementation.
+	 */
+	this = lookup_one_len(decrypt_name, connected, name_size);
+	err = PTR_ERR(this);
+	if (IS_ERR(this)) {
+		goto fail;
+	} else if (!this || !this->d_inode) {
+		dput(this);
+		err = -ENOENT;
+		goto fail;
+	} else if (ecryptfs_dentry_to_lower(this) != lower_dentry) {
+		dput(this);
+		err = -ESTALE;
+		goto fail;
+	}
+
+	goto out;
+
+fail:
+	pr_warn_ratelimited("ecryptfs: failed to lookup one by lower dentry (%pd2, connected=%pd2, err=%i)\n",
+			    lower_dentry, connected, err);
+	this = ERR_PTR(err);
+
+out:
+	kfree(decrypt_name);
+	release_dentry_name_snapshot(&lower_name);
+	dput(lower_parent);
+	inode_unlock(dir);
+	return this;
+}
+
+/*
+ * Lookup a ecryptfs dentry from cache whose lower dentry is
+ * an ancestor of @lower_dentry. This dentry will be connected.
+ */
+static struct dentry *ecryptfs_lookup_ancestor(struct super_block *sb,
+					       struct dentry *lower_dentry)
+{
+	struct dentry *lower_root = ecryptfs_dentry_to_lower(sb->s_root);
+	struct dentry *next, *parent = NULL;	/* these are lower dentry */
+	struct dentry *ancestor = ERR_PTR(-EIO);
+
+	if (lower_root == lower_dentry)
+		return dget(sb->s_root);
+
+	next = dget(lower_dentry);
+	for (;;) {
+		parent = dget_parent(next);
+
+		ancestor = ecryptfs_lookup_dentry(sb, next);
+		if (ancestor)
+			break;
+
+		if (lower_root == parent) {
+			ancestor = dget(sb->s_root);
+			break;
+		}
+
+		if (parent == next) {
+			/*
+			 * We moved out of ecryptfs root and hit lower fs root.
+			 * This may happen if the dentry has been moved out of
+			 * ecryptfs, so we return ESTALE.
+			 */
+			ancestor = ERR_PTR(-ESTALE);
+			break;
+		}
+
+		dput(next);
+		next = parent;
+	}
+
+	dput(parent);
+	dput(next);
+	return ancestor;
+}
+
+/* lookup a connected ecryptfs dentry whose lower dentry is @lower_dentry */
+static struct dentry *ecryptfs_lookup_connected(struct super_block *sb,
+						struct dentry *lower_dentry)
+{
+	struct dentry *lower_root = ecryptfs_dentry_to_lower(sb->s_root);
+	struct dentry *connected;
+	int err = 0;
+
+	/*
+	 * Lookup a connected ecryptfs dentry whose lower dentry is
+	 * an ancestor of 'lower_dentry'.
+	 */
+	connected = ecryptfs_lookup_ancestor(sb, lower_dentry);
+	if (IS_ERR_OR_NULL(connected))
+		return connected;
+
+	while (!err) {
+		struct dentry *this;
+		struct dentry *next, *parent = NULL; /* these are lower */
+		struct dentry *lower_connected =
+			ecryptfs_dentry_to_lower(connected);
+
+		/* found it */
+		if (lower_connected == lower_dentry)
+			break;
+
+		/* find the topmost dentry not yet connected */
+		next = dget(lower_dentry);
+		for (;;) {
+			parent = dget_parent(next);
+
+			if (lower_connected == parent)
+				break;
+
+			/*
+			 * If @lower_dentry has been moved out of
+			 * @lower_connected, we will not find @lower_connected
+			 * and hit ecryptfs root.
+			 * In that case, we need to restart connecting.
+			 * This game can go on forever in the worst case. We
+			 * may want to consider taking s_vfs_rename_mutex if
+			 * this happens more than once.
+			 */
+			if (parent == lower_root) {
+				dput(connected);
+				connected = dget(sb->s_root);
+				break;
+			}
+
+			/*
+			 * We moved out of ecryptfs root and hit lower fs root.
+			 * This may happen if the dentry has been moved out of
+			 * ecryptfs, so we return ESTALE.
+			 */
+			if (parent == next) {
+				err = -ESTALE;
+				break;
+			}
+
+			dput(next);
+			next = parent;
+		}
+
+		if (!err) {
+			this = ecryptfs_lookup_one(sb, connected, next);
+			if (IS_ERR(this))
+				err = PTR_ERR(this);
+
+			/*
+			 * Lookup child of connected can fail when racing
+			 * with rename. If the ecryptfs dentry that is above
+			 * 'next' has already been moved to a parent that is
+			 * not under the 'connected' dir, we need to restart
+			 * the lookup from the top because we cannot trust that
+			 * 'lower_connected' is still an ancestor of
+			 * 'lower_dentry'.
+			 */
+			if (err == -ECHILD) {
+				this = ecryptfs_lookup_ancestor(sb, lower_dentry);
+				err = PTR_ERR_OR_ZERO(this);
+			}
+			if (!err) {
+				dput(connected);
+				connected = this;
+			}
+		}
+
+		dput(parent);
+		dput(next);
+	}
+	if (err)
+		goto fail;
+
+	return connected;
+
+fail:
+	pr_warn_ratelimited("ecryptfs: failed to lookup by lower_dentry (%pd2, connected=%pd2, err=%i)\n",
+			    lower_dentry, connected, err);
+
+	dput(connected);
+	return ERR_PTR(err);
+}
+
+static struct dentry *ecryptfs_get_dentry(struct super_block *sb,
+					  struct dentry *lower)
+{
+	/* Obtain a disconnected dentry. */
+	if (!d_is_dir(lower))
+		return ecryptfs_obtain_alias(sb, lower);
+
+	/* Removed empty directory? */
+	if ((lower->d_flags & DCACHE_DISCONNECTED) || d_unhashed(lower))
+		return ERR_PTR(-ENOENT);
+
+	return ecryptfs_lookup_connected(sb, lower);
+}
+
+static struct dentry *ecryptfs_fh_to_dentry(struct super_block *sb,
+					    struct fid *fid,
+					    int fh_len,
+					    int fh_type)
+{
+	struct vfsmount *lower_mnt = ecryptfs_superblock_to_lower_mnt(sb);
+	struct dentry *lower_dentry;
+	struct dentry *dentry;
+
+	lower_dentry = exportfs_decode_fh(lower_mnt, fid, fh_len, fh_type,
+					  ecryptfs_acceptable, lower_mnt);
+
+	if (IS_ERR_OR_NULL(lower_dentry))
+		return lower_dentry;
+
+	dentry = ecryptfs_get_dentry(sb, lower_dentry);
+	dput(lower_dentry);
+
+	return dentry;
+}
+
+static struct dentry *ecryptfs_fh_to_parent(struct super_block *sb,
+					    struct fid *fid,
+					    int fh_len, int fh_type)
+{
+	pr_warn_ratelimited("ecryptfs: connectable file handles not supported; use 'no_subtree_check' exportfs option.\n");
+	return ERR_PTR(-EACCES);
+}
+
+struct dentry *ecryptfs_get_parent(struct dentry *child)
+{
+	/*
+	 * ecryptfs_fh_to_dentry() returns connected dir dentries,
+	 * ecryptfs_fh_to_parent() is not implemented, so we sould not get here
+	 */
+	WARN_ON_ONCE(1);
+	return ERR_PTR(-EIO);
+}
+
+static int ecryptfs_get_name(struct dentry *parent, char *name,
+			     struct dentry *child)
+{
+	/*
+	 * ecryptfs_get_parent() is not implemented,
+	 * so we should not get here
+	 */
+	WARN_ON_ONCE(1);
+	return -EIO;
+}
+
+const struct export_operations ecryptfs_export_ops = {
+	.encode_fh	= ecryptfs_encode_fh,
+	.fh_to_dentry	= ecryptfs_fh_to_dentry,
+	.fh_to_parent	= ecryptfs_fh_to_parent,
+	.get_parent	= ecryptfs_get_parent,
+	.get_name	= ecryptfs_get_name
+};
-- 
2.17.1




[Index of Archives]     [Linux Crypto]     [Device Mapper Crypto]     [LARTC]     [Bugtraq]     [Yosemite Forum]

  Powered by Linux