[PATCH 3/4] ecryptfs: add export subtree_check support

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

 



If we found exportfs wants to encode filehandle with parent dentry, we
can make an assumption that exportfs will need a connected dentry while
decoding filehandle.

Base on this assumption, we encode a flag as well as lower filehandle
to tell us if lower filesystem encoded parent dentry for us or not.
Then in fh_to_dentry, if we found this flag is ON, we can get a connected
non-dir dentry just like we did for dir dentries. Exportfs calls
fh_to_parent() only if the dentry of this filehandle doesn't pass the
test of find_acceptable_alias(), so we can just return -EACCESS in
fh_to_parent().

Signed-off-by: Chieh Lin <jaycelin@xxxxxxxxxxxx>
---
 fs/ecryptfs/export.c | 106 +++++++++++++++++++++++++++++++++++++------
 1 file changed, 92 insertions(+), 14 deletions(-)

diff --git a/fs/ecryptfs/export.c b/fs/ecryptfs/export.c
index 8d7ec5edbc63..f1f1047a8b5d 100644
--- a/fs/ecryptfs/export.c
+++ b/fs/ecryptfs/export.c
@@ -11,21 +11,77 @@
 #include <linux/exportfs.h>
 #include "ecryptfs_kernel.h"
 
+/* Filehandle flags. Now we only have this one. */
+#define ECRYPTFS_FH_FLAG_CONNECTABLE	0x1 // parent dentry is encoded or not
+
+struct ecryptfs_decode_ctx {
+	u8 flags;	/* ECRYPTFS_FH_FLAG_* */
+	struct dentry *mnt_root;
+};
+
+struct ecryptfs_fh {
+	u8 len;		/* size of this header + size of fid in byte unit */
+	u8 flags;	/* ECRYPTFS_FH* */
+	u16 reserved;
+	u8 fid[0];	/* file identifier */
+} __packed;
+
+#define ECRYPTFS_FH_HEADER_SIZE (offsetof(struct ecryptfs_fh, fid))
+
+/**
+ * Encode filehandle to @fid from ecryptfs @dentry
+ * @dentry:      The ecryptfs dentry to encode
+ * @fid:         Where to store the file handle fragment
+ * @max_dwords:  Maximum length to store there (in 4 byte unit)
+ *               On error @max_len contains the min size needed to encode.
+ * @connectable: Encode file parent directory or not.
+ *
+ * @return:      the fileid_type on success, FILEID_INVALID on error
+ */
+static int ecryptfs_dentry_to_fh(struct dentry *dentry, u32 *fid,
+				 int *max_dwords, int connectable)
+{
+	struct ecryptfs_fh *fh = (struct ecryptfs_fh *)fid;
+	int lower_dwords = *max_dwords - (ECRYPTFS_FH_HEADER_SIZE >> 2);
+	int type;
+
+	/*
+	 * 'lower_dwords' may be negative if '*max_dword' is zero.
+	 * That is fine, we can just pass 0 to exportfs_encode_fh to get the
+	 * size of fid that we need.
+	 */
+	if (lower_dwords < 0)
+		lower_dwords = 0;
+
+	type = exportfs_encode_fh(ecryptfs_dentry_to_lower(dentry),
+				  (struct fid *)fh->fid,
+				  &lower_dwords,
+				  connectable);
+
+	BUILD_BUG_ON(0 != (ECRYPTFS_FH_HEADER_SIZE % 4));
+	*max_dwords = lower_dwords + (ECRYPTFS_FH_HEADER_SIZE >> 2);
+
+	if (type < 0 || type == FILEID_INVALID ||
+	    WARN_ON_ONCE((*max_dwords << 2) > MAX_HANDLE_SZ))
+		return FILEID_INVALID;
+
+	fh->len = *max_dwords << 2;
+	fh->flags = connectable ? ECRYPTFS_FH_FLAG_CONNECTABLE : 0;
+
+	return type;
+}
+
 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);
+	type = ecryptfs_dentry_to_fh(dentry, fid, max_len, !!parent);
 
 	dput(dentry);
 	return type;
@@ -33,14 +89,17 @@ static int ecryptfs_encode_fh(struct inode *inode, u32 *fid, int *max_len,
 
 static int ecryptfs_acceptable(void *ctx, struct dentry *dentry)
 {
-	if (!d_is_dir(dentry))
+	struct ecryptfs_decode_ctx *context = (struct ecryptfs_decode_ctx *)ctx;
+
+	if (!d_is_dir(dentry) &&
+	    !(context->flags & ECRYPTFS_FH_FLAG_CONNECTABLE))
 		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);
+	return is_subdir(dentry, context->mnt_root);
 }
 
 /* Find or instantiate an disconnected ecryptfs dentry from lower_dentry */
@@ -337,10 +396,11 @@ static struct dentry *ecryptfs_lookup_connected(struct super_block *sb,
 }
 
 static struct dentry *ecryptfs_get_dentry(struct super_block *sb,
-					  struct dentry *lower)
+					  struct dentry *lower,
+					  bool connected)
 {
 	/* Obtain a disconnected dentry. */
-	if (!d_is_dir(lower))
+	if (!d_is_dir(lower) && !connected)
 		return ecryptfs_obtain_alias(sb, lower);
 
 	/* Removed empty directory? */
@@ -355,17 +415,31 @@ static struct dentry *ecryptfs_fh_to_dentry(struct super_block *sb,
 					    int fh_len,
 					    int fh_type)
 {
+	struct ecryptfs_fh *fh = (struct ecryptfs_fh *)fid;
 	struct vfsmount *lower_mnt = ecryptfs_superblock_to_lower_mnt(sb);
 	struct dentry *lower_dentry;
 	struct dentry *dentry;
+	struct ecryptfs_decode_ctx ctx;
+	bool connected;
+
+	if (fh_len != (fh->len >> 2))
+		return ERR_PTR(-ESTALE);
+
+	/* now fh_len is length of lower fh */
+	fh_len -= ECRYPTFS_FH_HEADER_SIZE >> 2;
+	ctx.flags = fh->flags;
+	ctx.mnt_root = lower_mnt->mnt_root;
 
-	lower_dentry = exportfs_decode_fh(lower_mnt, fid, fh_len, fh_type,
-					  ecryptfs_acceptable, lower_mnt);
+	lower_dentry = exportfs_decode_fh(lower_mnt, (struct fid *)fh->fid,
+					  fh_len, fh_type,
+					  ecryptfs_acceptable, &ctx);
 
 	if (IS_ERR_OR_NULL(lower_dentry))
 		return lower_dentry;
 
-	dentry = ecryptfs_get_dentry(sb, lower_dentry);
+	connected = !!(fh->flags & ECRYPTFS_FH_FLAG_CONNECTABLE);
+
+	dentry = ecryptfs_get_dentry(sb, lower_dentry, connected);
 	dput(lower_dentry);
 
 	return dentry;
@@ -375,14 +449,18 @@ 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");
+	/*
+	 * All the dentries should be connected in ecryptfs_fh_to_dentry if
+	 * it is possible. If we get here, this file should be -EACCES because
+	 * it didn't pass the test of find_acceptable_alias.
+	 */
 	return ERR_PTR(-EACCES);
 }
 
 struct dentry *ecryptfs_get_parent(struct dentry *child)
 {
 	/*
-	 * ecryptfs_fh_to_dentry() returns connected dir dentries,
+	 * ecryptfs_fh_to_dentry() returns connected dentries,
 	 * ecryptfs_fh_to_parent() is not implemented, so we sould not get here
 	 */
 	WARN_ON_ONCE(1);
-- 
2.17.1




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

  Powered by Linux