| From 3a60a1686f0d51c99bd0df8ac93050fb6dfce647 Mon Sep 17 00:00:00 2001 |
| From: Tyler Hicks <tyhicks@linux.vnet.ibm.com> |
| Date: Mon, 22 Mar 2010 00:41:35 -0500 |
| Subject: eCryptfs: Decrypt symlink target for stat size |
| MIME-Version: 1.0 |
| Content-Type: text/plain; charset=UTF-8 |
| Content-Transfer-Encoding: 8bit |
| |
| From: Tyler Hicks <tyhicks@linux.vnet.ibm.com> |
| |
| commit 3a60a1686f0d51c99bd0df8ac93050fb6dfce647 upstream. |
| |
| Create a getattr handler for eCryptfs symlinks that is capable of |
| reading the lower target and decrypting its path. Prior to this patch, |
| a stat's st_size field would represent the strlen of the encrypted path, |
| while readlink() would return the strlen of the decrypted path. This |
| could lead to confusion in some userspace applications, since the two |
| values should be equal. |
| |
| https://bugs.launchpad.net/bugs/524919 |
| |
| Reported-by: Loïc Minier <loic.minier@canonical.com> |
| Signed-off-by: Tyler Hicks <tyhicks@linux.vnet.ibm.com> |
| Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> |
| |
| --- |
| fs/ecryptfs/inode.c | 100 +++++++++++++++++++++++++++------------------------- |
| 1 file changed, 52 insertions(+), 48 deletions(-) |
| |
| --- a/fs/ecryptfs/inode.c |
| +++ b/fs/ecryptfs/inode.c |
| @@ -638,38 +638,17 @@ out_lock: |
| return rc; |
| } |
| |
| -static int |
| -ecryptfs_readlink(struct dentry *dentry, char __user *buf, int bufsiz) |
| +static int ecryptfs_readlink_lower(struct dentry *dentry, char **buf, |
| + size_t *bufsiz) |
| { |
| + struct dentry *lower_dentry = ecryptfs_dentry_to_lower(dentry); |
| char *lower_buf; |
| - size_t lower_bufsiz; |
| - struct dentry *lower_dentry; |
| - struct ecryptfs_mount_crypt_stat *mount_crypt_stat; |
| - char *plaintext_name; |
| - size_t plaintext_name_size; |
| + size_t lower_bufsiz = PATH_MAX; |
| mm_segment_t old_fs; |
| int rc; |
| |
| - lower_dentry = ecryptfs_dentry_to_lower(dentry); |
| - if (!lower_dentry->d_inode->i_op->readlink) { |
| - rc = -EINVAL; |
| - goto out; |
| - } |
| - mount_crypt_stat = &ecryptfs_superblock_to_private( |
| - dentry->d_sb)->mount_crypt_stat; |
| - /* |
| - * If the lower filename is encrypted, it will result in a significantly |
| - * longer name. If needed, truncate the name after decode and decrypt. |
| - */ |
| - if (mount_crypt_stat->flags & ECRYPTFS_GLOBAL_ENCRYPT_FILENAMES) |
| - lower_bufsiz = PATH_MAX; |
| - else |
| - lower_bufsiz = bufsiz; |
| - /* Released in this function */ |
| lower_buf = kmalloc(lower_bufsiz, GFP_KERNEL); |
| - if (lower_buf == NULL) { |
| - printk(KERN_ERR "%s: Out of memory whilst attempting to " |
| - "kmalloc [%zd] bytes\n", __func__, lower_bufsiz); |
| + if (!lower_buf) { |
| rc = -ENOMEM; |
| goto out; |
| } |
| @@ -679,29 +658,31 @@ ecryptfs_readlink(struct dentry *dentry, |
| (char __user *)lower_buf, |
| lower_bufsiz); |
| set_fs(old_fs); |
| - if (rc >= 0) { |
| - rc = ecryptfs_decode_and_decrypt_filename(&plaintext_name, |
| - &plaintext_name_size, |
| - dentry, lower_buf, |
| - rc); |
| - if (rc) { |
| - printk(KERN_ERR "%s: Error attempting to decode and " |
| - "decrypt filename; rc = [%d]\n", __func__, |
| - rc); |
| - goto out_free_lower_buf; |
| - } |
| - /* Check for bufsiz <= 0 done in sys_readlinkat() */ |
| - rc = copy_to_user(buf, plaintext_name, |
| - min((size_t) bufsiz, plaintext_name_size)); |
| - if (rc) |
| - rc = -EFAULT; |
| - else |
| - rc = plaintext_name_size; |
| - kfree(plaintext_name); |
| - fsstack_copy_attr_atime(dentry->d_inode, lower_dentry->d_inode); |
| - } |
| -out_free_lower_buf: |
| + if (rc < 0) |
| + goto out; |
| + lower_bufsiz = rc; |
| + rc = ecryptfs_decode_and_decrypt_filename(buf, bufsiz, dentry, |
| + lower_buf, lower_bufsiz); |
| +out: |
| kfree(lower_buf); |
| + return rc; |
| +} |
| + |
| +static int |
| +ecryptfs_readlink(struct dentry *dentry, char __user *buf, int bufsiz) |
| +{ |
| + char *kbuf; |
| + size_t kbufsiz, copied; |
| + int rc; |
| + |
| + 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); |
| + fsstack_copy_attr_atime(dentry->d_inode, |
| + ecryptfs_dentry_to_lower(dentry)->d_inode); |
| out: |
| return rc; |
| } |
| @@ -971,6 +952,28 @@ out: |
| return rc; |
| } |
| |
| +int ecryptfs_getattr_link(struct vfsmount *mnt, struct dentry *dentry, |
| + struct kstat *stat) |
| +{ |
| + struct ecryptfs_mount_crypt_stat *mount_crypt_stat; |
| + int rc = 0; |
| + |
| + mount_crypt_stat = &ecryptfs_superblock_to_private( |
| + dentry->d_sb)->mount_crypt_stat; |
| + generic_fillattr(dentry->d_inode, stat); |
| + if (mount_crypt_stat->flags & ECRYPTFS_GLOBAL_ENCRYPT_FILENAMES) { |
| + char *target; |
| + size_t targetsiz; |
| + |
| + rc = ecryptfs_readlink_lower(dentry, &target, &targetsiz); |
| + if (!rc) { |
| + kfree(target); |
| + stat->size = targetsiz; |
| + } |
| + } |
| + return rc; |
| +} |
| + |
| int ecryptfs_getattr(struct vfsmount *mnt, struct dentry *dentry, |
| struct kstat *stat) |
| { |
| @@ -1088,6 +1091,7 @@ const struct inode_operations ecryptfs_s |
| .put_link = ecryptfs_put_link, |
| .permission = ecryptfs_permission, |
| .setattr = ecryptfs_setattr, |
| + .getattr = ecryptfs_getattr_link, |
| .setxattr = ecryptfs_setxattr, |
| .getxattr = ecryptfs_getxattr, |
| .listxattr = ecryptfs_listxattr, |