ecryptfs-devel team mailing list archive
-
ecryptfs-devel team
-
Mailing list archive
-
Message #00131
[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