]> git.karo-electronics.de Git - karo-tx-linux.git/commitdiff
ext4 crypto: Add symlink encryption
authorTheodore Ts'o <tytso@mit.edu>
Wed, 8 Apr 2015 04:20:21 +0000 (00:20 -0400)
committerTheodore Ts'o <tytso@mit.edu>
Wed, 8 Apr 2015 04:20:21 +0000 (00:20 -0400)
Change-Id: Ic92ebe4c615721650ccaf16b3175c2f4e931af2d
Signed-off-by: Uday Savagaonkar <savagaon@google.com>
Signed-off-by: Theodore Ts'o <tytso@mit.edu>
fs/ext4/ext4_crypto.h
fs/ext4/namei.c
fs/ext4/symlink.c

index bd1039e8ee444b6f22d8c4f0a70faf20ce34dc6a..e4cd2d01423a864a59b8e706b3271874f7043110 100644 (file)
@@ -115,4 +115,24 @@ struct ext4_fname_crypto_ctx {
        unsigned ctfm_key_is_ready : 1;
 };
 
+/**
+ * For encrypted symlinks, the ciphertext length is stored at the beginning
+ * of the string in little-endian format.
+ */
+struct ext4_encrypted_symlink_data {
+       __le32 len;
+       char encrypted_path[1];
+} __attribute__((__packed__));
+
+/**
+ * This function is used to calculate the disk space required to
+ * store a filename of length l in encrypted symlink format.
+ */
+static inline u32 encrypted_symlink_data_len(u32 l)
+{
+       return ((l + EXT4_CRYPTO_BLOCK_SIZE - 1) / EXT4_CRYPTO_BLOCK_SIZE)
+               * EXT4_CRYPTO_BLOCK_SIZE
+               + sizeof(struct ext4_encrypted_symlink_data) - 1;
+}
+
 #endif /* _EXT4_CRYPTO_H */
index 7ed83b73abf8bf16905e68c5dedfd7e9675f8459..7670777ac3b4348fc0dcedc473401eb57acf6040 100644 (file)
@@ -3200,14 +3200,31 @@ static int ext4_symlink(struct inode *dir,
        struct inode *inode;
        int l, err, retries = 0;
        int credits;
+       bool encryption_required = false;
+#ifdef CONFIG_EXT4_FS_ENCRYPTION
+       int l2;
+       struct ext4_fname_crypto_ctx *ctx = NULL;
+       struct qstr istr;
+       struct ext4_str ostr;
+       struct ext4_encrypted_symlink_data *sd = NULL;
+       struct ext4_sb_info *sbi = EXT4_SB(dir->i_sb);
+#endif
 
-       l = strlen(symname)+1;
+       l = strlen(symname) + 1;
+#ifdef CONFIG_EXT4_FS_ENCRYPTION
+       l2 = encrypted_symlink_data_len(l - 1);
+       encryption_required = (ext4_encrypted_inode(dir) ||
+                              unlikely(sbi->s_mount_flags &
+                                       EXT4_MF_TEST_DUMMY_ENCRYPTION));
+       if (encryption_required && l2 > dir->i_sb->s_blocksize)
+#else
        if (l > dir->i_sb->s_blocksize)
+#endif
                return -ENAMETOOLONG;
 
        dquot_initialize(dir);
 
-       if (l > EXT4_N_BLOCKS * 4) {
+       if ((l > EXT4_N_BLOCKS * 4) || encryption_required) {
                /*
                 * For non-fast symlinks, we just allocate inode and put it on
                 * orphan list in the first transaction => we need bitmap,
@@ -3235,7 +3252,7 @@ retry:
        if (IS_ERR(inode))
                goto out_stop;
 
-       if (l > EXT4_N_BLOCKS * 4) {
+       if ((l > EXT4_N_BLOCKS * 4) || encryption_required) {
                inode->i_op = &ext4_symlink_inode_operations;
                ext4_set_aops(inode);
                /*
@@ -3253,9 +3270,41 @@ retry:
                ext4_journal_stop(handle);
                if (err)
                        goto err_drop_inode;
-               err = __page_symlink(inode, symname, l, 1);
-               if (err)
-                       goto err_drop_inode;
+               if (!encryption_required) {
+                       err = __page_symlink(inode, symname, l, 1);
+                       if (err)
+                               goto err_drop_inode;
+               }
+#ifdef CONFIG_EXT4_FS_ENCRYPTION
+               else {
+                       sd = kmalloc(l2 + 1, GFP_NOFS);
+                       if (!sd) {
+                               err = -ENOMEM;
+                               goto err_drop_inode;
+                       }
+                       sd->encrypted_path[l2] = '\0';
+                       err = ext4_inherit_context(dir, inode);
+                       ctx = ext4_get_fname_crypto_ctx(
+                               inode, inode->i_sb->s_blocksize);
+                       if (IS_ERR_OR_NULL(ctx)) {
+                               /* We just set the policy, so ctx should
+                                  not be NULL */
+                               err = (ctx == NULL) ? -EIO : PTR_ERR(ctx);
+                               goto err_drop_inode;
+                       }
+                       istr.name = (const unsigned char *) symname;
+                       istr.len = l - 1;
+                       ostr.name = sd->encrypted_path;
+                       err = ext4_fname_usr_to_disk(ctx, &istr, &ostr);
+                       ext4_put_fname_crypto_ctx(&ctx);
+                       if (err < 0)
+                               goto err_drop_inode;
+                       sd->len = cpu_to_le32(ostr.len);
+                       err = __page_symlink(inode, (char *)sd, l2 + 1, 1);
+                       kfree(sd);
+                       sd = NULL;
+               }
+#endif
                /*
                 * Now inode is being linked into dir (EXT4_DATA_TRANS_BLOCKS
                 * + EXT4_INDEX_EXTRA_TRANS_BLOCKS), inode is also modified
@@ -3295,6 +3344,9 @@ out_stop:
 err_drop_inode:
        unlock_new_inode(inode);
        iput(inode);
+#ifdef CONFIG_EXT4_FS_ENCRYPTION
+       kfree(sd);
+#endif
        return err;
 }
 
index 489b34333ea4b4848be9de1f7948182968400b76..839f71e941a9d2f0d5043d0099a21feadf9f05a0 100644 (file)
 #include <linux/namei.h>
 #include "ext4.h"
 #include "xattr.h"
+#include "ext4_crypto.h"
 
+#ifdef CONFIG_EXT4_FS_ENCRYPTION
 static void *ext4_follow_link(struct dentry *dentry, struct nameidata *nd)
+{
+       struct page *cpage = NULL;
+       char *caddr, *paddr;
+       struct ext4_str cstr, pstr;
+       struct inode *inode = dentry->d_inode;
+       struct ext4_fname_crypto_ctx *ctx = NULL;
+       struct ext4_encrypted_symlink_data *sd;
+       loff_t size = min(inode->i_size, (loff_t) PAGE_SIZE-1);
+       int res;
+       u32 plen, plen2;
+
+       ctx = ext4_get_fname_crypto_ctx(inode, inode->i_sb->s_blocksize);
+       if (IS_ERR(ctx))
+               return ctx;
+
+       cpage = read_mapping_page(inode->i_mapping, 0, NULL);
+       if (IS_ERR(cpage)) {
+               ext4_put_fname_crypto_ctx(&ctx);
+               return cpage;
+       }
+       caddr = kmap(cpage);
+       caddr[size] = 0;
+
+       if (!ctx) {
+               /* Symlink is unencrypted */
+               plen = strnlen((char *)caddr, inode->i_sb->s_blocksize);
+               plen2 = (plen < inode->i_sb->s_blocksize) ? plen + 1 : plen;
+               paddr = kmalloc(plen2, GFP_NOFS);
+               if (!paddr) {
+                       ext4_put_fname_crypto_ctx(&ctx);
+                       kunmap(cpage);
+                       page_cache_release(cpage);
+                       return ERR_PTR(-ENOMEM);
+               }
+               memcpy(paddr, caddr, plen);
+               if (plen < inode->i_sb->s_blocksize)
+                       paddr[plen] = '\0';
+       } else {
+               /* Symlink is encrypted */
+               sd = (struct ext4_encrypted_symlink_data *)caddr;
+               cstr.name = sd->encrypted_path;
+               cstr.len  = le32_to_cpu(sd->len);
+               if ((cstr.len + sizeof(struct ext4_encrypted_symlink_data) - 1)
+                       > inode->i_sb->s_blocksize) {
+                       /* Symlink data on the disk is corrupted */
+                       kunmap(cpage);
+                       page_cache_release(cpage);
+                       return ERR_PTR(-EIO);
+               }
+               plen = (cstr.len < EXT4_FNAME_CRYPTO_DIGEST_SIZE*2)
+                       ? EXT4_FNAME_CRYPTO_DIGEST_SIZE*2
+                       : cstr.len;
+               paddr = kmalloc(plen + 1, GFP_NOFS);
+               if (!paddr) {
+                       ext4_put_fname_crypto_ctx(&ctx);
+                       kunmap(cpage);
+                       page_cache_release(cpage);
+                       return ERR_PTR(-ENOMEM);
+               }
+               pstr.name = paddr;
+               res = _ext4_fname_disk_to_usr(ctx, &cstr, &pstr);
+               if (res < 0) {
+                       ext4_put_fname_crypto_ctx(&ctx);
+                       kunmap(cpage);
+                       page_cache_release(cpage);
+                       kfree(paddr);
+                       return ERR_PTR(res);
+               }
+               /* Null-terminate the name */
+               if (res <= plen)
+                       paddr[res] = '\0';
+       }
+       nd_set_link(nd, paddr);
+       ext4_put_fname_crypto_ctx(&ctx);
+       return cpage;
+}
+
+static void ext4_put_link(struct dentry *dentry, struct nameidata *nd,
+                         void *cookie)
+{
+       struct page *page = cookie;
+       char *buf = nd_get_link(nd);
+
+       if (page) {
+               kunmap(page);
+               page_cache_release(page);
+       }
+       if (buf) {
+               nd_set_link(nd, NULL);
+               kfree(buf);
+       }
+}
+#endif
+
+static void *ext4_follow_fast_link(struct dentry *dentry, struct nameidata *nd)
 {
        struct ext4_inode_info *ei = EXT4_I(dentry->d_inode);
        nd_set_link(nd, (char *) ei->i_data);
@@ -31,8 +128,13 @@ static void *ext4_follow_link(struct dentry *dentry, struct nameidata *nd)
 
 const struct inode_operations ext4_symlink_inode_operations = {
        .readlink       = generic_readlink,
+#ifdef CONFIG_EXT4_FS_ENCRYPTION
+       .follow_link    = ext4_follow_link,
+       .put_link       = ext4_put_link,
+#else
        .follow_link    = page_follow_link_light,
        .put_link       = page_put_link,
+#endif
        .setattr        = ext4_setattr,
        .setxattr       = generic_setxattr,
        .getxattr       = generic_getxattr,
@@ -42,7 +144,7 @@ const struct inode_operations ext4_symlink_inode_operations = {
 
 const struct inode_operations ext4_fast_symlink_inode_operations = {
        .readlink       = generic_readlink,
-       .follow_link    = ext4_follow_link,
+       .follow_link    = ext4_follow_fast_link,
        .setattr        = ext4_setattr,
        .setxattr       = generic_setxattr,
        .getxattr       = generic_getxattr,