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