← Back to team overview

ecryptfs-devel team mailing list archive

[PATCH] EcryptFS: Add support for file names that are too long after being encrypted

 

BugLink:  https://bugs.launchpad.net/ecryptfs/+bug/344878

Fix Bug#344878 which occurs when a file is created with a file name that
would be valid before encrypting and encoding but after being encrypted and
encoded is too long for the underlying filesytem.
---
 fs/ecryptfs/crypto.c          |   13 +-
 fs/ecryptfs/ecryptfs_kernel.h |   23 +++
 fs/ecryptfs/file.c            |   43 +++++
 fs/ecryptfs/inode.c           |  401 ++++++++++++++++++++++++++++++++++++++++-
 fs/ecryptfs/longname.txt      |  230 +++++++++++++++++++++++
 fs/ecryptfs/main.c            |   67 +++++++
 fs/ecryptfs/super.c           |    3 +-
 7 files changed, 771 insertions(+), 9 deletions(-)
 create mode 100644 fs/ecryptfs/longname.txt

diff --git a/fs/ecryptfs/crypto.c b/fs/ecryptfs/crypto.c
index bfd8b68..e1a6a66 100644
--- a/fs/ecryptfs/crypto.c
+++ b/fs/ecryptfs/crypto.c
@@ -92,9 +92,8 @@ void ecryptfs_from_hex(char *dst, char *src, int dst_size)
  * Uses the allocated crypto context that crypt_stat references to
  * generate the MD5 sum of the contents of src.
  */
-static int ecryptfs_calculate_md5(char *dst,
-				  struct ecryptfs_crypt_stat *crypt_stat,
-				  char *src, int len)
+int ecryptfs_calculate_md5(char *dst, struct ecryptfs_crypt_stat *crypt_stat,
+			   const char *src, int len)
 {
 	struct scatterlist sg;
 	struct hash_desc desc = {
@@ -914,6 +913,9 @@ static void ecryptfs_copy_mount_wide_flags_to_inode_flags(
 		else if (mount_crypt_stat->flags
 			 & ECRYPTFS_GLOBAL_ENCFN_USE_FEK)
 			crypt_stat->flags |= ECRYPTFS_ENCFN_USE_FEK;
+		if (mount_crypt_stat->flags &
+		    ECRYPTFS_GLOBAL_FNE_LONGNAME_XATTR)
+			crypt_stat->flags |= ECRYPTFS_FNE_LONGNAME_XATTR;
 	}
 }
 
@@ -1056,7 +1058,8 @@ static struct ecryptfs_flag_map_elem ecryptfs_flag_map[] = {
 	{0x00000001, ECRYPTFS_ENABLE_HMAC},
 	{0x00000002, ECRYPTFS_ENCRYPTED},
 	{0x00000004, ECRYPTFS_METADATA_IN_XATTR},
-	{0x00000008, ECRYPTFS_ENCRYPT_FILENAMES}
+	{0x00000008, ECRYPTFS_ENCRYPT_FILENAMES},
+	{0x00000010, ECRYPTFS_FNE_LONGNAME_XATTR}
 };
 
 /**
@@ -1648,7 +1651,7 @@ out:
  *
  * Returns zero on success; non-zero otherwise
  */
-static int
+int
 ecryptfs_encrypt_filename(struct ecryptfs_filename *filename,
 			  struct ecryptfs_crypt_stat *crypt_stat,
 			  struct ecryptfs_mount_crypt_stat *mount_crypt_stat)
diff --git a/fs/ecryptfs/ecryptfs_kernel.h b/fs/ecryptfs/ecryptfs_kernel.h
index dbc84ed..c93b911 100644
--- a/fs/ecryptfs/ecryptfs_kernel.h
+++ b/fs/ecryptfs/ecryptfs_kernel.h
@@ -85,6 +85,8 @@
 #define ECRYPTFS_DEFAULT_NUM_USERS 4
 #define ECRYPTFS_MAX_NUM_USERS 32768
 #define ECRYPTFS_XATTR_NAME "user.ecryptfs"
+#define ECRYPTFS_LONGNAME_PREFIX "trusted.ecryptfs."
+#define ECRYPTFS_LONGNAME_PREFIX_SIZE (sizeof(ECRYPTFS_LONGNAME_PREFIX) - 1)
 
 #define RFC2440_CIPHER_DES3_EDE 0x02
 #define RFC2440_CIPHER_CAST_5 0x03
@@ -230,6 +232,9 @@ ecryptfs_get_key_payload_data(struct key *key)
 #define ECRYPTFS_FNEK_ENCRYPTED_FILENAME_PREFIX "ECRYPTFS_FNEK_ENCRYPTED."
 #define ECRYPTFS_FNEK_ENCRYPTED_FILENAME_PREFIX_SIZE 24
 #define ECRYPTFS_ENCRYPTED_DENTRY_NAME_LEN (18 + 1 + 4 + 1 + 32)
+#define ECRYPTFS_ENCODED_MD5_SIZE 24
+#define ECRYPTFS_SHORTNAME_SIZE (ECRYPTFS_FNEK_ENCRYPTED_FILENAME_PREFIX_SIZE +\
+				 ECRYPTFS_ENCODED_MD5_SIZE)
 
 struct ecryptfs_key_sig {
 	struct list_head crypt_stat_list;
@@ -270,6 +275,7 @@ struct ecryptfs_crypt_stat {
 #define ECRYPTFS_ENCFN_USE_MOUNT_FNEK 0x00001000
 #define ECRYPTFS_ENCFN_USE_FEK        0x00002000
 #define ECRYPTFS_UNLINK_SIGS	      0x00004000
+#define ECRYPTFS_FNE_LONGNAME_XATTR   0x00008000
 	u32 flags;
 	unsigned int file_version;
 	size_t iv_bytes;
@@ -306,6 +312,7 @@ struct ecryptfs_inode_info {
 struct ecryptfs_dentry_info {
 	struct path lower_path;
 	struct ecryptfs_crypt_stat *crypt_stat;
+	int lower_longname;
 };
 
 /**
@@ -377,7 +384,9 @@ struct ecryptfs_mount_crypt_stat {
 #define ECRYPTFS_GLOBAL_ENCFN_USE_MOUNT_FNEK   0x00000020
 #define ECRYPTFS_GLOBAL_ENCFN_USE_FEK          0x00000040
 #define ECRYPTFS_GLOBAL_MOUNT_AUTH_TOK_ONLY    0x00000080
+#define ECRYPTFS_GLOBAL_FNE_LONGNAME_XATTR     0x00000100
 	u32 flags;
+	long f_namelen;
 	struct list_head global_auth_tok_list;
 	struct mutex global_auth_tok_list_mutex;
 	size_t num_global_auth_toks;
@@ -775,5 +784,19 @@ ecryptfs_parse_tag_70_packet(char **filename, size_t *filename_size,
 			     char *data, size_t max_packet_size);
 int ecryptfs_derive_iv(char *iv, struct ecryptfs_crypt_stat *crypt_stat,
 		       loff_t offset);
+void ecryptfs_encode_for_filename(unsigned char *dst, size_t *dst_size,
+				  unsigned char *src, size_t src_size);
+int
+ecryptfs_encrypt_filename(struct ecryptfs_filename *filename,
+			  struct ecryptfs_crypt_stat *crypt_stat,
+			  struct ecryptfs_mount_crypt_stat *mount_crypt_stat);
+int ecryptfs_calculate_md5(char *dst, struct ecryptfs_crypt_stat *crypt_stat,
+			   const char *src, int len);
+
+int ecryptfs_get_longname(struct ecryptfs_mount_crypt_stat *mount_crypt_stat,
+			  struct dentry *lower_dentry, char **long_name,
+			  size_t *long_name_size);
+
+int ecryptfs_is_shortname(const char *name, const char *lower_name);
 
 #endif /* #ifndef ECRYPTFS_KERNEL_H */
diff --git a/fs/ecryptfs/file.c b/fs/ecryptfs/file.c
index 81e10e6..af8ad88 100644
--- a/fs/ecryptfs/file.c
+++ b/fs/ecryptfs/file.c
@@ -80,6 +80,7 @@ static int
 ecryptfs_filldir(void *dirent, const char *lower_name, int lower_namelen,
 		 loff_t offset, u64 ino, unsigned int d_type)
 {
+	struct ecryptfs_mount_crypt_stat *mount_crypt_stat;
 	struct ecryptfs_getdents_callback *buf =
 	    (struct ecryptfs_getdents_callback *)dirent;
 	size_t name_size;
@@ -96,6 +97,48 @@ ecryptfs_filldir(void *dirent, const char *lower_name, int lower_namelen,
 		       rc);
 		goto out;
 	}
+
+	mount_crypt_stat = &ecryptfs_superblock_to_private(
+		buf->dentry->d_sb)->mount_crypt_stat;
+	if ((mount_crypt_stat->flags & ECRYPTFS_GLOBAL_FNE_LONGNAME_XATTR) &&
+	    ecryptfs_is_shortname(name, lower_name)) {
+		/* we have a short name find the matching longname, which
+		 * means finding the file it is stored on
+		 */
+		struct dentry *lower_dir_dentry =
+			ecryptfs_dentry_to_lower(buf->dentry);
+		struct dentry *lower_dentry =
+			lookup_one_len(lower_name, lower_dir_dentry,
+				       lower_namelen);
+		char *longname;
+		size_t longname_size;
+
+		if (!lower_dentry) {
+			printk(KERN_ERR "%s: Error attempting to retrieve "
+			       " dentry for longname for filename [%s]; rc = [%d]\n",
+			       __func__, lower_name, rc);
+			goto out;
+
+		}
+
+		rc = ecryptfs_get_longname(mount_crypt_stat, lower_dentry,
+					   &longname, &longname_size);
+		if (rc) {
+			printk(KERN_ERR "%s: Error attempting to retrieve "
+			       " longname for filename [%s], using [%s]; "
+			       "rc = [%d]\n",
+			       __func__, lower_name, name, rc);
+			/* TODO: add option to fail instead of falling back
+			 * to shortname
+			 */
+			rc = 0;
+		} else {
+			kfree(name);
+			name = longname;
+			name_size = longname_size;
+		}
+
+	}
 	rc = buf->filldir(buf->dirent, name, name_size, offset, ino, d_type);
 	kfree(name);
 	if (rc >= 0)
diff --git a/fs/ecryptfs/inode.c b/fs/ecryptfs/inode.c
index bd33f87..14e6cb0 100644
--- a/fs/ecryptfs/inode.c
+++ b/fs/ecryptfs/inode.c
@@ -33,9 +33,276 @@
 #include <linux/fs_stack.h>
 #include <linux/slab.h>
 #include <linux/xattr.h>
+#include <linux/statfs.h>
 #include <asm/unaligned.h>
 #include "ecryptfs_kernel.h"
 
+/**
+ * ecryptfs_gen_xattr_name - build the corresponding xattr name from @shortname
+ * @shortname: the encrypted and encoded shortname to create the xattr name for
+ *
+ * Returns: an xattr name or NULL if out of memory
+ *
+ * caller must free the generated xattr
+ */
+static char *ecryptfs_gen_xattr_name(const char *shortname)
+{
+	char *xattr_name;
+	int size;
+
+	/* Skip storing ecryptfs prefix as part of xattr name */
+	shortname += ECRYPTFS_FNEK_ENCRYPTED_FILENAME_PREFIX_SIZE;
+
+	size = ECRYPTFS_LONGNAME_PREFIX_SIZE + strlen(shortname) + 1;
+	xattr_name = kmalloc(size, GFP_KERNEL);
+	if (!xattr_name)
+		return NULL;
+
+	strcpy(xattr_name, ECRYPTFS_LONGNAME_PREFIX);
+	strcat(xattr_name, shortname);
+
+	return xattr_name;
+}
+
+/**
+ * ecryptfs_get_longname - get the longname
+ * @mount_crypt_stat:
+ * @lower_dentry:
+ * @longname: Returns retrieved long name
+ * @longname_size: Returns size of retrieved long name
+ *
+ * Returns: 0 on success else error code
+ */
+int ecryptfs_get_longname(struct ecryptfs_mount_crypt_stat *mount_crypt_stat,
+			  struct dentry *lower_dentry, char **longname,
+			  size_t *longname_size)
+{
+	struct inode *inode = lower_dentry->d_inode;
+	size_t packet_size;
+	char *xattr_name = NULL, *value = NULL;
+	size_t xattr_size;
+	int rc = 0;
+
+	xattr_name = ecryptfs_gen_xattr_name(lower_dentry->d_name.name);
+	if (!xattr_name)
+		return -ENOMEM;
+
+	/* get the size of the xattr */
+	rc = inode->i_op->getxattr(lower_dentry, xattr_name, NULL, 0);
+	if (rc <= 0)
+		goto out;
+
+	xattr_size = rc;
+	value = kmalloc(xattr_size, GFP_KERNEL);
+	if (!value) {
+		printk(KERN_INFO "%s: Out of memory whilst attempting to "
+		       "kmalloc [%zd] bytes\n", __func__, xattr_size);
+		rc = -ENOMEM;
+		goto out;
+	}
+
+	rc = inode->i_op->getxattr(lower_dentry, xattr_name, value, xattr_size);
+	if (rc <= 0) {
+		printk(KERN_INFO "%s: Error getting longname xattr [%s]; "
+		       "rc = [%d]\n", __func__, xattr_name, rc);
+		goto out;
+	}
+
+	rc = ecryptfs_parse_tag_70_packet(longname, longname_size,
+					  &packet_size, mount_crypt_stat,
+					  value, xattr_size);
+	if (rc) {
+		printk(KERN_INFO "%s: Could not parse tag 70 packet from "
+		       "filename\n", __func__);
+		goto out;
+	}
+
+	if (!longname)
+		goto out;
+
+	BUG_ON((*longname)[(*longname_size) - 1] != 0);
+
+out:
+	kfree(xattr_name);
+	kfree(value);
+	return rc;
+}
+
+/**
+ * ecryptfs_set_longname - set the long name xattr for @ecryptfs_dentry
+ * @ecryptfs_dentry: upper dentry to set a long name for
+ * @name: name to set
+ * @value: value for @name
+ * @value_len: length of value
+ *
+ * Returns: 0 on success else error code
+ *
+ * Set the upper (ecryptfs) dentry name on the corresponding inode using
+ * the encrypted and encoded shortname from the lower dentry as the
+ * key.
+ */
+
+static int ecryptfs_set_longname(struct dentry *ecryptfs_dentry,
+				 const char *name, const char *value,
+				 size_t value_len)
+{
+	struct ecryptfs_mount_crypt_stat *mount_crypt_stat =
+		&ecryptfs_superblock_to_private(
+			ecryptfs_dentry->d_sb)->mount_crypt_stat;
+	struct dentry *lower_dentry = ecryptfs_dentry_to_lower(ecryptfs_dentry);
+	struct inode *inode = lower_dentry->d_inode;
+	struct ecryptfs_filename filename;
+
+	int rc = 0;
+
+	filename.filename = kmalloc(value_len + 1, GFP_KERNEL);
+	if (!filename.filename) {
+		printk(KERN_ERR "%s: Error attempting to allocate "
+		       "filename buffer; rc = [%d]\n", __func__, -ENOMEM);
+		return -ENOMEM;
+	}
+	strncpy(filename.filename, value, value_len);
+	filename.filename[value_len] = 0;
+	/* include terminating null */
+	filename.filename_size = value_len + 1;
+
+	rc = ecryptfs_encrypt_filename(&filename, NULL, mount_crypt_stat);
+	if (rc) {
+		printk(KERN_ERR "%s: Error attempting to encrypt "
+		       "filename; rc = [%d]\n", __func__, rc);
+		goto out;
+	}
+
+	rc = inode->i_op->setxattr(lower_dentry, name,
+				   filename.encrypted_filename,
+				   filename.encrypted_filename_size, 0);
+
+	if (rc < 0) {
+		printk(KERN_INFO "%s: Error setting longname xattr [%s] to "
+		       "[%s == %s]; rc = [%d]\n", __func__, name,
+		       filename.encrypted_filename, value,
+		       rc);
+		goto out;
+	}
+
+out:
+	kfree(filename.encrypted_filename);
+	return rc;
+}
+
+static int ecryptfs_dentry_set_longname(struct dentry *ecryptfs_dentry)
+{
+	int rc = -ENOMEM;
+
+	struct dentry *lower_dentry = ecryptfs_dentry_to_lower(ecryptfs_dentry);
+	char *xattr_name = NULL;
+
+	xattr_name = ecryptfs_gen_xattr_name(lower_dentry->d_name.name);
+	if (!xattr_name)
+		goto out;
+
+	rc = ecryptfs_set_longname(ecryptfs_dentry, xattr_name,
+				   ecryptfs_dentry->d_name.name,
+				   ecryptfs_dentry->d_name.len);
+out:
+	kfree(xattr_name);
+	return rc;
+}
+
+/**
+ * ecryptfs_remove_longname - remove the matching long name xattr from an inode
+ * @lower_dentry: dentry with short name to remove matching long name xattr for
+ *
+ * Returns: 0 on success else error code
+ */
+static int ecryptfs_remove_longname(struct dentry *lower_dentry)
+{
+	struct inode *inode = lower_dentry->d_inode;
+	char *xattr_name;
+	int rc = -ENOMEM;
+
+	xattr_name = ecryptfs_gen_xattr_name(lower_dentry->d_name.name);
+	if (!xattr_name)
+		goto out;
+
+	rc = inode->i_op->removexattr(lower_dentry, xattr_name);
+
+out:
+	kfree(xattr_name);
+	return rc;
+}
+
+
+int ecryptfs_is_shortname(const char *name, const char *lower_name)
+{
+	return (strncmp(name, ECRYPTFS_FNEK_ENCRYPTED_FILENAME_PREFIX,
+			ECRYPTFS_FNEK_ENCRYPTED_FILENAME_PREFIX_SIZE) == 0 &&
+		strncmp(lower_name, ECRYPTFS_FNEK_ENCRYPTED_FILENAME_PREFIX,
+			ECRYPTFS_FNEK_ENCRYPTED_FILENAME_PREFIX_SIZE) == 0);
+}
+
+/**
+ * ecryptfs_gen_shortname - generate a shortname hash from name
+ * @ecryptfs_dentry: dentry with original name that needs a short name
+ * @short_name: buffer containing the encrypted & encoded short name
+ * @short_size: size of the short name
+ *
+ * Returns: 0 if successful else error code
+ *
+ * Generate a unique short name for a filename that was found to be too long.
+ *
+ * NOTES:
+ * currently the short name consists of the ECRYPTFS_FNEK_ENCRYPTED
+ * prefix followed by an encoded md5 hash of the file name that is too long
+ * eg. ECRYPTFS_FNEK_ENCRYPTED.R3-j1H3386OUcEiLNK7nlk--
+ *
+ * This is then encrypted and encoded to get the final name.
+ */
+static int ecryptfs_gen_shortname(struct dentry *ecryptfs_dentry,
+				  char **short_name, size_t *short_size)
+{
+	struct ecryptfs_crypt_stat * crypt_stat =
+		&ecryptfs_inode_to_private(
+			ecryptfs_dentry->d_parent->d_inode)->crypt_stat;
+	struct ecryptfs_mount_crypt_stat *mount_crypt_stat =
+		&ecryptfs_superblock_to_private(
+			ecryptfs_dentry->d_sb)->mount_crypt_stat;
+	char md5[16];
+	char unencoded_name[ECRYPTFS_SHORTNAME_SIZE + 1];
+	size_t unencoded_size;
+	int rc;
+
+	rc = ecryptfs_calculate_md5(md5, crypt_stat,
+				    ecryptfs_dentry->d_name.name,
+				    ecryptfs_dentry->d_name.len);
+	if (rc) {
+		printk(KERN_ERR
+		       "%s: Error md5 hashing name; rc = [%d]\n",
+		       __func__, rc);
+		goto out;
+	}
+	ecryptfs_encode_for_filename(unencoded_name +
+				     ECRYPTFS_FNEK_ENCRYPTED_FILENAME_PREFIX_SIZE,
+				     &unencoded_size, md5, 16);
+	strncpy(unencoded_name, ECRYPTFS_FNEK_ENCRYPTED_FILENAME_PREFIX,
+		ECRYPTFS_FNEK_ENCRYPTED_FILENAME_PREFIX_SIZE);
+	unencoded_name[ECRYPTFS_SHORTNAME_SIZE] = 0;
+
+	rc = ecryptfs_encrypt_and_encode_filename(short_name, short_size,
+						  NULL,
+						  mount_crypt_stat,
+						  unencoded_name,
+						  ECRYPTFS_SHORTNAME_SIZE);
+	if (rc) {
+		printk(KERN_ERR "%s: Error attempting to encrypt and encode "
+		       "filename; rc = [%d]\n", __func__, rc);
+		goto out;
+	}
+out:
+	return rc;
+}
+
+
 static struct dentry *lock_parent(struct dentry *dentry)
 {
 	struct dentry *dir;
@@ -124,6 +391,17 @@ ecryptfs_do_create(struct inode *directory_inode,
 		       "rc = [%d]\n", __func__, rc);
 		goto out_lock;
 	}
+
+	if (ecryptfs_dentry_to_private(ecryptfs_dentry)->lower_longname) {
+		/* This dentry was marked as having a longname during lookup */
+		rc = ecryptfs_dentry_set_longname(ecryptfs_dentry);
+		if (rc) {
+			ecryptfs_printk(KERN_ERR, "%s: Failure to set "
+					"longname; rc = [%d]\n", __func__, rc);
+			goto out_lock;
+		}
+	}
+
 	rc = ecryptfs_interpose(lower_dentry, ecryptfs_dentry,
 				directory_inode->i_sb, 0);
 	if (rc) {
@@ -260,8 +538,8 @@ int ecryptfs_lookup_and_interpose_lower(struct dentry *ecryptfs_dentry,
 	fsstack_copy_attr_atime(ecryptfs_dir_inode, lower_dir_dentry->d_inode);
 	BUG_ON(!lower_dentry->d_count);
 	ecryptfs_set_dentry_private(ecryptfs_dentry,
-				    kmem_cache_alloc(ecryptfs_dentry_info_cache,
-						     GFP_KERNEL));
+				    kmem_cache_zalloc(ecryptfs_dentry_info_cache,
+						      GFP_KERNEL));
 	if (!ecryptfs_dentry_to_private(ecryptfs_dentry)) {
 		rc = -ENOMEM;
 		printk(KERN_ERR "%s: Out of memory whilst attempting "
@@ -435,6 +713,7 @@ static struct dentry *ecryptfs_lookup(struct inode *ecryptfs_dir_inode,
 	struct ecryptfs_mount_crypt_stat *mount_crypt_stat = NULL;
 	struct dentry *lower_dir_dentry, *lower_dentry;
 	struct qstr lower_name;
+	int set_longname_mark = 0;
 	int rc = 0;
 
 	if ((ecryptfs_dentry->d_name.len == 1
@@ -479,6 +758,35 @@ static struct dentry *ecryptfs_lookup(struct inode *ecryptfs_dir_inode,
 		       "filename; rc = [%d]\n", __func__, rc);
 		goto out_d_drop;
 	}
+	/* check for lower filename limit and if the name before encryption
+	 * and encoding is within that limit, truncate and "hash" the true
+	 * name will be stored as encrypted metadata.
+	 *
+	 * TODO:
+	 *   handle collision of long name and other name that is same
+	 *   after encryption, encode and if necessary truncation
+	 *
+	 *   Should handle current long encrypted names that aren't broken
+	 *   ie. match limit, so test that path first
+	 */
+	if ((mount_crypt_stat->flags & ECRYPTFS_GLOBAL_FNE_LONGNAME_XATTR) &&
+	    encrypted_and_encoded_name_size > mount_crypt_stat->f_namelen) {
+		rc = -ENAMETOOLONG;
+		if (ecryptfs_dentry->d_name.len > mount_crypt_stat->f_namelen)
+			goto out_d_drop;
+
+		kfree(encrypted_and_encoded_name);
+		rc = ecryptfs_gen_shortname(ecryptfs_dentry,
+					    &encrypted_and_encoded_name,
+					    &encrypted_and_encoded_name_size);
+		if (rc) {
+			printk(KERN_ERR "%s: Error creating valid short place "
+			       "holder name for long file name; rc = [%d]\n",
+			       __func__, rc);
+			goto out_d_drop;
+		}
+		set_longname_mark = 1;
+	}
 	lower_name.name = encrypted_and_encoded_name;
 	lower_name.len = encrypted_and_encoded_name_size;
 	lower_name.hash = full_name_hash(lower_name.name, lower_name.len);
@@ -501,6 +809,9 @@ lookup_and_interpose:
 	rc = ecryptfs_lookup_and_interpose_lower(ecryptfs_dentry, lower_dentry,
 						 ecryptfs_dir_inode,
 						 ecryptfs_nd);
+	if (set_longname_mark)
+		ecryptfs_dentry_to_private(ecryptfs_dentry)->lower_longname = 1;
+
 	goto out;
 out_d_drop:
 	d_drop(ecryptfs_dentry);
@@ -595,6 +906,17 @@ static int ecryptfs_symlink(struct inode *dir, struct dentry *dentry,
 	kfree(encoded_symname);
 	if (rc || !lower_dentry->d_inode)
 		goto out_lock;
+
+	if (ecryptfs_dentry_to_private(dentry)->lower_longname) {
+		/* This dentry was marked as having a longname during lookup */
+		rc = ecryptfs_dentry_set_longname(dentry);
+		if (rc) {
+			ecryptfs_printk(KERN_ERR, "%s: Failure to set "
+					"longname; rc = [%d]\n", __func__, rc);
+			goto out_lock;
+		}
+	}
+
 	rc = ecryptfs_interpose(lower_dentry, dentry, dir->i_sb, 0);
 	if (rc)
 		goto out_lock;
@@ -619,6 +941,17 @@ static int ecryptfs_mkdir(struct inode *dir, struct dentry *dentry, int mode)
 	rc = vfs_mkdir(lower_dir_dentry->d_inode, lower_dentry, mode);
 	if (rc || !lower_dentry->d_inode)
 		goto out;
+
+	if (ecryptfs_dentry_to_private(dentry)->lower_longname) {
+		/* This dentry was marked as having a longname during lookup */
+		rc = ecryptfs_dentry_set_longname(dentry);
+		if (rc) {
+			ecryptfs_printk(KERN_ERR, "%s: Failure to set "
+					"longname; rc = [%d]\n", __func__, rc);
+			goto out;
+		}
+	}
+
 	rc = ecryptfs_interpose(lower_dentry, dentry, dir->i_sb, 0);
 	if (rc)
 		goto out;
@@ -667,6 +1000,17 @@ ecryptfs_mknod(struct inode *dir, struct dentry *dentry, int mode, dev_t dev)
 	rc = vfs_mknod(lower_dir_dentry->d_inode, lower_dentry, mode, dev);
 	if (rc || !lower_dentry->d_inode)
 		goto out;
+
+	if (ecryptfs_dentry_to_private(dentry)->lower_longname) {
+		/* This dentry was marked as having a longname during lookup */
+		rc = ecryptfs_dentry_set_longname(dentry);
+		if (rc) {
+			ecryptfs_printk(KERN_ERR, "%s: Failure to set "
+					"longname; rc = [%d]\n", __func__, rc);
+			goto out;
+		}
+	}
+
 	rc = ecryptfs_interpose(lower_dentry, dentry, dir->i_sb, 0);
 	if (rc)
 		goto out;
@@ -689,6 +1033,7 @@ ecryptfs_rename(struct inode *old_dir, struct dentry *old_dentry,
 	struct dentry *lower_old_dir_dentry;
 	struct dentry *lower_new_dir_dentry;
 	struct dentry *trap = NULL;
+	const char *old_name = NULL, *new_name = NULL;
 
 	lower_old_dentry = ecryptfs_dentry_to_lower(old_dentry);
 	lower_new_dentry = ecryptfs_dentry_to_lower(new_dentry);
@@ -707,10 +1052,54 @@ ecryptfs_rename(struct inode *old_dir, struct dentry *old_dentry,
 		rc = -ENOTEMPTY;
 		goto out_lock;
 	}
+
+	/* is there a long name xattr to remove for old_dentry */
+	if (ecryptfs_dentry_to_private(old_dentry)->lower_longname) {
+		old_name = ecryptfs_gen_xattr_name(
+			lower_old_dentry->d_name.name);
+		if (!old_name) {
+			rc = -ENOMEM;
+			goto out_lock;
+		}
+	}
+	if (ecryptfs_dentry_to_private(new_dentry)->lower_longname) {
+		/* make sure a new longname can be set before actual rename */
+		new_name = ecryptfs_gen_xattr_name(
+			lower_new_dentry->d_name.name);
+		if (!new_name) {
+			rc = -ENOMEM;
+			goto out_lock;
+		}
+
+		rc = ecryptfs_set_longname(old_dentry, new_name,
+					   new_dentry->d_name.name,
+					   new_dentry->d_name.len);
+		if (rc) {
+			ecryptfs_printk(KERN_ERR, "%s: Failure to set "
+					"longname; rc = [%d]\n", __func__, rc);
+			goto out_lock;
+		}
+	}
+
 	rc = vfs_rename(lower_old_dir_dentry->d_inode, lower_old_dentry,
 			lower_new_dir_dentry->d_inode, lower_new_dentry);
-	if (rc)
+	if (rc) {
+		if (new_name)
+			lower_old_dentry->d_inode->i_op->removexattr(
+				lower_old_dentry, new_name);
 		goto out_lock;
+	}
+
+	if (old_name) {
+		rc = lower_old_dentry->d_inode->i_op->removexattr(
+			lower_old_dentry, old_name);
+
+		if (rc) {
+			printk("%s: xattr old_name %s\n", __func__, old_name);
+			rc = 0;
+		}
+	}
+
 	fsstack_copy_attr_all(new_dir, lower_new_dir_dentry->d_inode);
 	if (new_dir != old_dir)
 		fsstack_copy_attr_all(old_dir, lower_old_dir_dentry->d_inode);
@@ -720,6 +1109,8 @@ out_lock:
 	dput(lower_old_dentry->d_parent);
 	dput(lower_new_dentry);
 	dput(lower_old_dentry);
+	kfree(old_name);
+	kfree(new_name);
 	return rc;
 }
 
@@ -763,6 +1154,7 @@ ecryptfs_readlink(struct dentry *dentry, char __user *buf, int bufsiz)
 	rc = ecryptfs_readlink_lower(dentry, &kbuf, &kbufsiz);
 	if (rc)
 		goto out;
+
 	copied = min_t(size_t, bufsiz, kbufsiz);
 	rc = copy_to_user(buf, kbuf, copied) ? -EFAULT : copied;
 	kfree(kbuf);
@@ -1138,6 +1530,7 @@ static ssize_t
 ecryptfs_getxattr(struct dentry *dentry, const char *name, void *value,
 		  size_t size)
 {
+	/* TODO screen off requests for trusted.ecryptfs ???? */
 	return ecryptfs_getxattr_lower(ecryptfs_dentry_to_lower(dentry), name,
 				       value, size);
 }
@@ -1154,6 +1547,7 @@ ecryptfs_listxattr(struct dentry *dentry, char *list, size_t size)
 		goto out;
 	}
 	mutex_lock(&lower_dentry->d_inode->i_mutex);
+	/* TODO screen off requests for trusted.ecryptfs ??? */
 	rc = lower_dentry->d_inode->i_op->listxattr(lower_dentry, list, size);
 	mutex_unlock(&lower_dentry->d_inode->i_mutex);
 out:
@@ -1171,6 +1565,7 @@ static int ecryptfs_removexattr(struct dentry *dentry, const char *name)
 		goto out;
 	}
 	mutex_lock(&lower_dentry->d_inode->i_mutex);
+	/* TODO screen off requests for trusted.ecryptfs ??? */
 	rc = lower_dentry->d_inode->i_op->removexattr(lower_dentry, name);
 	mutex_unlock(&lower_dentry->d_inode->i_mutex);
 out:
diff --git a/fs/ecryptfs/longname.txt b/fs/ecryptfs/longname.txt
new file mode 100644
index 0000000..dbbc160
--- /dev/null
+++ b/fs/ecryptfs/longname.txt
@@ -0,0 +1,230 @@
+The following patch is a first pass at addressing the bug
+  "file name too long when creating new file"
+  https://bugs.launchpad.net/ecryptfs/+bug/344878
+
+Which occurs when a file is created with a file name that would be valid
+before encrypting and encoding but after being encrypted and encoded is
+too long for the underlying filesytem.
+
+
+Overview:
+To support file names that are too long when encrypted and encoded the patch
+stores the long file name (longname) in an xattr on the file and creates a
+"unique" short file name (shortname) which is stored in the underlying
+filesystem.  The shortname is never seen when accessing files from the
+ecryptfs view, but it is what will be found when accessing the lower
+filesystem directly.
+
+While the patch currently uses xattrs it is possible to convert to storing
+the longname in the ecryptfs file header (see below for some notes about
+advantanges and disadvantages), or even allow for both options.
+
+
+Current State:
+- Use xattrs to store longname on the file
+- Detects xattr support at mount time
+- Uses a mount flag for longname support
+  - currently the mount flag is inverted.  Longname support is enabled
+    by default and the flag is used to disable it.
+  - current method is some what hacky in that it was assumed this
+    would be inverted, back to requiring a flag but if not this can
+    be cleaned up.
+- Currently the code is does not have a Kconfig to disable at compile
+  time.  Is this desired?
+- the longname xattr is stored in the trusted namespace using the
+  trusted.ecryptfs. prefix
+- the longname is encrypted using the same tag70 packet encoding as any
+  other encrypted file name.  It is not encoded to reduce the size of the
+  xattr.
+- a file can have multiple longnames (hardlinks)
+- each longname is stored as a single xattr name, value pair.
+  - the xattr name is based off of the encrypted and encoded shortname
+    without the ECRYPTFS_FNEK prefix
+    eg.
+       if the encrypted and encoded shortname is
+          ECRYPTFS_FNEK_ENCRYPTED.FZYwryMXdKVUQZfN26kvrVp30Yif
+       then the xattr name will be
+          trusted.ecryptfs.FZYwryMXdKVUQZfN26kvrVp30Yif
+    + it would be possible to reduce the size of the xattr name if it was
+      based on the unencrypted and unecoded shortname
+  - the value contains the encrypted long filename
+- if the expected longname is missing, the current code falls back to
+  using the shortname.
+  + a mount option could be added to force failure instead of trying to
+    gracefully fallback
++ the patch extends the ecryptfs private dentry field with a longname flag
+  that is used to indicate that the underlying dentry has a longname
+- a unique shortname is used as a place holder for the long file name in
+  the lower filesystem.
+  + the current encoding of the shortname will most like change a least some
+  + the shortname generated is always the same for the same name, this
+    leaks more information than it should and can result in collisions
+    if the same name is used from different directories.
+  + the current shortname generation doesn't deal with potential collision
+    between encrypted and encoded file names (this seems pretty unlikely),
+    nor with name collisions of filenames that hash to the same md5 (again
+    unlikely)
+  - currently the shortname is created from combining the the
+    ECRYPTFS_FNEK_ENCRYPTED. prefix with the encoded md5 hash of the long
+    file name.
+    eg.
+      ECRYPTFS_FNEK_ENCRYPTED.sdfjyo34n2lkh2lknlkafa--
+  - the shortname is encrypted and encoded just like any other filename
+  - both the shortname and the encrypted and encoded shortname must have
+    the ECRYPTFS_FNEK_ENCRYPTED. for a file name to be considered a valid
+    shortname
+  - This design allows for the shortname to "work" to some degree, with
+    older versions of ecryptfs.  Name lookups based off of the long file
+    name won't work but the shortname can be used so that files can
+    be copied/moved without losing data.
+- only the symlink name can be give a long name currently.  The
+  symlink target encryption hasn't changed.
+  - this means symlinks don't use the shortname when being accessed
+    by older versions of ecryptfs.  So even if the long name file
+    they reference exists they won't resolve to a long name file.
+    - it is possible to have the target to use shortnames
+  - it is possible to add support for long name targets, that after
+    encrypting and encoding are too long.  By using short names and
+    an extra xattr for the long target name on the symlink.
+
+
+
+
+
+
+
+= Supportting long file names =
+
+Since encrypting and encoding expand the length of the dentry, we need to
+either cancel out the expansion or store the extra information for the
+long name else where.  This also necessitates putting a shorter place
+holder name as the name in the file system.
+
+Each method of dealing with long names have their own advantages and
+disadvantages.
+
+== compression ==
+Little gain, certainly not enough for all possible long file names.  Several
+applications make random large file names, etc.  Would also have to cope
+with language encoding etc.
+
+== reducing ECRYPTFS_FNEK_ENCRYPTED prefix ==
+Some gain in size, but loses any potential backwards compatibility.  Also
+doesn't deal with expansion caused by encoding, nor the tag70 packet header
+expanding the encrypted value.
+
+
+== long file names with xattrs ==
+
+Disadvatanges
+- requires lower file system to support xattrs
+
+- long file name information can be lost by copies, taring, backups, etc
+  made on the lower file system that are unaware of xattrs
+
+- xattrs can be manipulated directly through the lower file system
+
+Advantages
+- supports multiple names with space only limited by xattrs limits
+
+- no extra code to manage name value paris, if multiple long names are
+  to be supported.
+
+- provides for partial backwards compatibility
+
+  The ecryptfs header doesn't need to be modified, so previous versions
+  can still read/write the file data.  However version that don't support
+  long names via xattrs, will see the short name, and will not update
+  the long name xattrs.
+
+- allows for long directory and symlink names
+
+- can allow for long symlink targets
+  If the encoded symlink target is to long an extra xattr containing the
+  target can be stored, and a short name style encoding can be performed
+  on the symlink target data.
+
+ 
+== long file names in the ecryptfs header ==
+
+Disadvantages
+- the space to store long file names is more limited than with xattrs.
+
+  In practice this shouldn't be a problem as just supporting a single long
+  file name would cover the majority of use cases, if multiple shorter
+  name links are allowed.
+
+  Even when storing multiple long names, being able to store 2 or 3
+  should cover almost all use cases.
+
+- requires extra code to manage name value pairs, if multiple long names
+  are to be supported.
+
+  This is just a matter of code. Xattrs provide support for name value pairs,
+  and supporting multiple long file names in the ecryptfs header would
+  require creating some addition code.
+
+  If however only a single long file name is supported then there is
+  no extra code required.  Though storing the long name as a name value
+  pair is still advisable as it will allow catching rename operations
+  that are done on the lower filesystem so that the stored long name is
+  not properly updated.
+
+
+- is not backwards compatible.
+
+  Storing long file names in xattrs allows for some degraded backwards
+  compatibily with older versions of ecryptfs.  But storing long names in
+  the ecryptfs header will prevent older version from being able to
+  access the stored data.
+
+  How important is this?  Not very, while being able to access the data
+  with an older version may be nice for data recovering it also risks
+  losing the longer specially stored longer names.
+
+- requires header to be updated, for renames or hardlinks with long names
+
+  This is mostly a non issue.  It may even be faster than storing an xattr.
+
+- can not be used for directories, symlinks
+
+  Storing the long file name in the ecryptfs header will only work for
+  encrypted files, it won't work for directories or symlinks as they
+  don't have a header.
+
+- can not work around the symlink target being to long.
+
+  This is fs dependent but if the name for the symlink target is too long
+  after encrypting and encoding, creation of the symlink may fail, and
+  since symlinks have no header there is no place to store the extra
+  information.
+
+Advantages
+- the lower filesystem does not require xattr support
+
+- long name information will not be lost by copies, taring, or backups
+  made on the lower file system that don't store xattrs.
+
+== special dentry file ==
+
+
+
+
+
+
+
+
+
+XAttrs Notes
+- requires fs have xattr support
+- 4 namespaces (security, system, trusted, user)
+  - security: used by smack/selinux not appropriate to use
+  - system: is tied to acls for some filesystems, so affected by mount flags
+  - user: can't be trusted, can't be set on symlinks, device files
+  - trusted: need cap_sys_admin to see/set
+- not the same space restrictions as ecryptfs header, can use multiple xattrs
+- xattr can be ecrypted separate from file, so error in name encryption leaks
+  name instead of data.  Does this matter if relying on current encryption?
+- having longname xattr leaks that the file has a longname
+  - is this anyworse than directory walking would leak
+- use trusted.ecryptfs.<name>
diff --git a/fs/ecryptfs/main.c b/fs/ecryptfs/main.c
index 758323a..e83aad8 100644
--- a/fs/ecryptfs/main.c
+++ b/fs/ecryptfs/main.c
@@ -37,6 +37,7 @@
 #include <linux/fs_stack.h>
 #include <linux/slab.h>
 #include <linux/magic.h>
+#include <linux/statfs.h>
 #include "ecryptfs_kernel.h"
 
 /**
@@ -218,6 +219,7 @@ enum { ecryptfs_opt_sig, ecryptfs_opt_ecryptfs_sig,
        ecryptfs_opt_encrypted_view, ecryptfs_opt_fnek_sig,
        ecryptfs_opt_fn_cipher, ecryptfs_opt_fn_cipher_key_bytes,
        ecryptfs_opt_unlink_sigs, ecryptfs_opt_mount_auth_tok_only,
+       ecryptfs_opt_fne_no_longname_xattr,
        ecryptfs_opt_err };
 
 static const match_table_t tokens = {
@@ -234,6 +236,7 @@ static const match_table_t tokens = {
 	{ecryptfs_opt_fn_cipher_key_bytes, "ecryptfs_fn_key_bytes=%u"},
 	{ecryptfs_opt_unlink_sigs, "ecryptfs_unlink_sigs"},
 	{ecryptfs_opt_mount_auth_tok_only, "ecryptfs_mount_auth_tok_only"},
+	{ecryptfs_opt_fne_no_longname_xattr, "ecryptfs_no_longname_xattr"},
 	{ecryptfs_opt_err, NULL}
 };
 
@@ -271,6 +274,7 @@ static void ecryptfs_init_mount_crypt_stat(
 	INIT_LIST_HEAD(&mount_crypt_stat->global_auth_tok_list);
 	mutex_init(&mount_crypt_stat->global_auth_tok_list_mutex);
 	mount_crypt_stat->flags |= ECRYPTFS_MOUNT_CRYPT_STAT_INITIALIZED;
+	mount_crypt_stat->flags |= ECRYPTFS_GLOBAL_FNE_LONGNAME_XATTR;
 }
 
 /**
@@ -395,6 +399,9 @@ static int ecryptfs_parse_options(struct ecryptfs_sb_info *sbi, char *options)
 				(ECRYPTFS_GLOBAL_ENCRYPT_FILENAMES
 				 | ECRYPTFS_GLOBAL_ENCFN_USE_MOUNT_FNEK);
 			break;
+		case ecryptfs_opt_fne_no_longname_xattr:
+			mount_crypt_stat->flags &= ~ECRYPTFS_GLOBAL_FNE_LONGNAME_XATTR;
+			break;
 		case ecryptfs_opt_fn_cipher:
 			fn_cipher_name_src = args[0].from;
 			fn_cipher_name_dst =
@@ -500,6 +507,62 @@ out:
 struct kmem_cache *ecryptfs_sb_info_cache;
 static struct file_system_type ecryptfs_fs_type;
 
+static int ecryptfs_mount_supports_longname(struct super_block *sb,
+					    struct dentry *lower_dentry)
+{
+	struct ecryptfs_mount_crypt_stat *mount_crypt_stat;
+	struct kstatfs buf;
+	int rc = 0;
+
+	mount_crypt_stat = &ecryptfs_superblock_to_private(
+		sb)->mount_crypt_stat;
+	if (mount_crypt_stat->flags & ECRYPTFS_GLOBAL_FNE_LONGNAME_XATTR) {
+		rc = statfs_by_dentry(lower_dentry, &buf);
+		/* TODO these should probably result in mount failure not just
+		 * disabling of the flag
+		 */
+		if (rc) {
+			printk(KERN_WARNING "lower filename limit "
+					"unknown, disabling encrypted "
+					"longname in xattr\n");
+			mount_crypt_stat->flags &=
+				~ECRYPTFS_GLOBAL_FNE_LONGNAME_XATTR;
+			goto out;
+		}
+		mount_crypt_stat->f_namelen = buf.f_namelen;
+		if (!lower_dentry->d_inode->i_op->getxattr) {
+			printk(KERN_WARNING "lower filesystem does"
+					"not support getxattr, disabling "
+					"encrypted longname in xattr\n");
+			//rc = -EOPNOTSUPP;
+			mount_crypt_stat->flags &=
+				~ECRYPTFS_GLOBAL_FNE_LONGNAME_XATTR;
+			goto out;
+		}
+		if (!lower_dentry->d_inode->i_op->setxattr) {
+			printk(KERN_WARNING "lower filesystem does "
+					"not support setxattr, disabling "
+					"encrypted longname in xattr\n");
+			//rc = -ENAMETOOLONG;
+			mount_crypt_stat->flags &=
+				~ECRYPTFS_GLOBAL_FNE_LONGNAME_XATTR;
+			goto out;
+		}
+		if (!lower_dentry->d_inode->i_op->removexattr) {
+			printk(KERN_WARNING "lower filesystem does "
+					"not support setxattr, disabling "
+					"encrypted longname in xattr\n");
+			//rc = -ENAMETOOLONG;
+			mount_crypt_stat->flags &=
+				~ECRYPTFS_GLOBAL_FNE_LONGNAME_XATTR;
+			goto out;
+		}
+	}
+
+out:
+	return rc;
+}
+
 /**
  * ecryptfs_get_sb
  * @fs_type
@@ -589,6 +652,10 @@ static struct dentry *ecryptfs_mount(struct file_system_type *fs_type, int flags
 	ecryptfs_set_dentry_lower(s->s_root, path.dentry);
 	ecryptfs_set_dentry_lower_mnt(s->s_root, path.mnt);
 
+	rc = ecryptfs_mount_supports_longname(s, path.dentry);
+	if (rc)
+		goto out_free;
+
 	s->s_flags |= MS_ACTIVE;
 	return dget(s->s_root);
 
diff --git a/fs/ecryptfs/super.c b/fs/ecryptfs/super.c
index 3042fe1..fefb71e 100644
--- a/fs/ecryptfs/super.c
+++ b/fs/ecryptfs/super.c
@@ -191,7 +191,8 @@ static int ecryptfs_show_options(struct seq_file *m, struct vfsmount *mnt)
 		seq_printf(m, ",ecryptfs_unlink_sigs");
 	if (mount_crypt_stat->flags & ECRYPTFS_GLOBAL_MOUNT_AUTH_TOK_ONLY)
 		seq_printf(m, ",ecryptfs_mount_auth_tok_only");
-
+	if (mount_crypt_stat->flags & ECRYPTFS_GLOBAL_FNE_LONGNAME_XATTR)
+		seq_printf(m, ",ecryptfs_longname_xattr");
 	return 0;
 }
 
-- 
1.7.1




References