diff mbox

[13/22] ext4 crypto: implement the ext4 decryption read path

Message ID 1428012659-12709-14-git-send-email-tytso@mit.edu
State Superseded, archived
Headers show

Commit Message

Theodore Ts'o April 2, 2015, 10:10 p.m. UTC
From: Michael Halcrow <mhalcrow@google.com>

Change-Id: Ie9c043a132a01da60d1617662cd30307639f5599
Signed-off-by: Michael Halcrow <mhalcrow@google.com>
Signed-off-by: Ildar Muslukhov <ildarm@google.com>
Signed-off-by: Theodore Ts'o <tytso@mit.edu>
---
 fs/ext4/file.c     | 22 +++++++++++++++----
 fs/ext4/inode.c    | 10 +++++++++
 fs/ext4/readpage.c | 62 +++++++++++++++++++++++++++++++++++++++++++++++++++++-
 3 files changed, 89 insertions(+), 5 deletions(-)

Comments

Andreas Dilger April 8, 2015, 6:51 p.m. UTC | #1
On Apr 2, 2015, at 4:10 PM, Theodore Ts'o <tytso@mit.edu> wrote:
> 
> From: Michael Halcrow <mhalcrow@google.com>
> 
> Change-Id: Ie9c043a132a01da60d1617662cd30307639f5599
> Signed-off-by: Michael Halcrow <mhalcrow@google.com>
> Signed-off-by: Ildar Muslukhov <ildarm@google.com>
> Signed-off-by: Theodore Ts'o <tytso@mit.edu>
> ---
> fs/ext4/file.c     | 22 +++++++++++++++----
> fs/ext4/inode.c    | 10 +++++++++
> fs/ext4/readpage.c | 62 +++++++++++++++++++++++++++++++++++++++++++++++++++++-
> 3 files changed, 89 insertions(+), 5 deletions(-)
> 
> diff --git a/fs/ext4/file.c b/fs/ext4/file.c
> index 8131be8..4cacc30 100644
> --- a/fs/ext4/file.c
> +++ b/fs/ext4/file.c
> @@ -28,6 +28,7 @@
> #include <linux/pagevec.h>
> #include "ext4.h"
> #include "ext4_jbd2.h"
> +#include "ext4_crypto.h"
> #include "xattr.h"
> #include "acl.h"
> 
> @@ -200,9 +201,15 @@ static const struct vm_operations_struct ext4_file_vm_ops = {
> 
> static int ext4_file_mmap(struct file *file, struct vm_area_struct *vma)
> {
> +	int res = 0;

This function is using "res", the one below "ret".  It would be nice to
have some consistency.  "rc" is probably the most common and would be
my preference for new/modified code.

> +	struct inode *inode = file->f_mapping->host;
> +
> +	if (ext4_encrypted_inode(inode))
> +		res = ext4_generate_encryption_key(inode);
> 	file_accessed(file);
> -	vma->vm_ops = &ext4_file_vm_ops;
> -	return 0;
> +	if (!res)
> +		vma->vm_ops = &ext4_file_vm_ops;
> +	return res;
> }
> 
> static int ext4_file_open(struct inode * inode, struct file * filp)
> @@ -212,6 +219,7 @@ static int ext4_file_open(struct inode * inode, struct file * filp)
> 	struct vfsmount *mnt = filp->f_path.mnt;
> 	struct path path;
> 	char buf[64], *cp;
> +	int ret;
> 
> 	if (unlikely(!(sbi->s_mount_flags & EXT4_MF_MNTDIR_SAMPLED) &&
> 		     !(sb->s_flags & MS_RDONLY))) {
> @@ -250,11 +258,17 @@ static int ext4_file_open(struct inode * inode, struct file * filp)
> 	 * writing and the journal is present
> 	 */
> 	if (filp->f_mode & FMODE_WRITE) {
> -		int ret = ext4_inode_attach_jinode(inode);
> +		ret = ext4_inode_attach_jinode(inode);
> 		if (ret < 0)
> 			return ret;
> 	}
> -	return dquot_file_open(inode, filp);
> +	ret = dquot_file_open(inode, filp);
> +	if (!ret && ext4_encrypted_inode(inode)) {
> +		ret = ext4_generate_encryption_key(inode);
> +		if (ret)
> +			ret = -EACCES;
> +	}
> +	return ret;
> }
> 
> /*
> diff --git a/fs/ext4/inode.c b/fs/ext4/inode.c
> index dcc836c..b033405 100644
> --- a/fs/ext4/inode.c
> +++ b/fs/ext4/inode.c
> @@ -39,6 +39,7 @@
> #include <linux/ratelimit.h>
> #include <linux/aio.h>
> #include <linux/bitops.h>
> +#include <linux/prefetch.h>
> 
> #include "ext4_jbd2.h"
> #include "ext4_crypto.h"
> @@ -3363,6 +3364,15 @@ static int ext4_block_zero_page_range(handle_t *handle,
> 		/* Uhhuh. Read error. Complain and punt. */
> 		if (!buffer_uptodate(bh))
> 			goto unlock;
> +#ifdef CONFIG_EXT4_FS_ENCRYPTION
> +		if (S_ISREG(inode->i_mode) &&
> +		    ext4_encrypted_inode(inode)) {
> +			/* We expect the key to be set. */
> +			BUG_ON(!ext4_has_encryption_key(inode));
> +			BUG_ON(blocksize != PAGE_CACHE_SIZE);
> +			WARN_ON_ONCE(ext4_decrypt_one(inode, page));
> +		}
> +#endif

This could avoid the #ifdef since ext4_encrypted_inode() is declared (0)
in the !CONFIG_EXT4_FS_ENCRYPTION case.  The compiler will optimize out
the resulting unreachable code in that case and make this code easier
to read.

> 	}
> 	if (ext4_should_journal_data(inode)) {
> 		BUFFER_TRACE(bh, "get write access");
> diff --git a/fs/ext4/readpage.c b/fs/ext4/readpage.c
> index 9ca4dfc..8978b1d 100644
> --- a/fs/ext4/readpage.c
> +++ b/fs/ext4/readpage.c
> @@ -43,6 +43,35 @@
> 
> #include "ext4.h"
> 
> +#ifdef CONFIG_EXT4_FS_ENCRYPTION
> +/*
> + * Call ext4_decrypt on every single page, reusing the encryption
> + * context.
> + */
> +static void completion_pages(struct work_struct *work)
> +{
> +	struct ext4_crypto_ctx *ctx =
> +		container_of(work, struct ext4_crypto_ctx, work);
> +	struct bio	*bio	= ctx->bio;
> +	struct bio_vec	*bv;
> +	int		i;
> +
> +	bio_for_each_segment_all(bv, bio, i) {
> +		struct page *page = bv->bv_page;
> +
> +		int ret = ext4_decrypt(ctx, page);
> +		if (ret) {
> +			WARN_ON_ONCE(1);
> +			SetPageError(page);
> +		} else
> +			SetPageUptodate(page);
> +		unlock_page(page);
> +	}
> +	ext4_release_crypto_ctx(ctx);
> +	bio_put(bio);
> +}
> +#endif
> +
> /*
>  * I/O completion handler for multipage BIOs.
>  *
> @@ -60,6 +89,20 @@ static void mpage_end_io(struct bio *bio, int err)
> 	struct bio_vec *bv;
> 	int i;
> 
> +#ifdef CONFIG_EXT4_FS_ENCRYPTION
> +	if (bio->bi_private) {

If the (bio->bi_private != NULL) check was moved to a helper function:

static inline bool ext4_bio_encrypted(struct bio *bio)
{
#ifdef CONFIG_EXT4_FS_ENCRYPTION
	return unlikely(bio->bi_private != NULL);
#else
	return false;
#endif
}

Then the inline #ifdefs could be removed here, since the resulting

	if (false) {
		...
	}

block would be optimized away by the caller.  It would also simplify
future changes if there is some other reason why bio->bi_private is
non-NULL besides crypto.

> +		struct ext4_crypto_ctx *ctx = bio->bi_private;
> +
> +		if (err)
> +			ext4_release_crypto_ctx(ctx);
> +		else {

The if/else should have matching {} blocks.

> +			INIT_WORK(&ctx->work, completion_pages);
> +			ctx->bio = bio;
> +			queue_work(ext4_read_workqueue, &ctx->work);
> +			return;
> +		}
> +	}
> +#endif
> 	bio_for_each_segment_all(bv, bio, i) {
> 		struct page *page = bv->bv_page;
> 
> @@ -94,9 +137,15 @@ int ext4_mpage_readpages(struct address_space *mapping,
> 	unsigned page_block;
> 	struct block_device *bdev = inode->i_sb->s_bdev;
> 	int length;
> +	int do_decryption = 0;

Can be bool instead of int.

> 	unsigned relative_block = 0;
> 	struct ext4_map_blocks map;
> 
> +#ifdef CONFIG_EXT4_FS_ENCRYPTION
> +	if (ext4_encrypted_inode(inode) && S_ISREG(inode->i_mode))
> +		do_decryption = 1;
> +#endif

#ifdef can be removed.

> +
> 	map.m_pblk = 0;
> 	map.m_lblk = 0;
> 	map.m_len = 0;
> @@ -220,13 +269,24 @@ int ext4_mpage_readpages(struct address_space *mapping,
> 			bio = NULL;
> 		}
> 		if (bio == NULL) {
> +			struct ext4_crypto_ctx *ctx = NULL;
> +
> +			if (do_decryption) {
> +				ctx = ext4_get_crypto_ctx(inode);
> +				if (IS_ERR(ctx))
> +					goto set_error_page;
> +			}
> 			bio = bio_alloc(GFP_KERNEL,
> 				min_t(int, nr_pages, bio_get_nr_vecs(bdev)));
> -			if (!bio)
> +			if (!bio) {
> +				if (ctx)
> +					ext4_release_crypto_ctx(ctx);
> 				goto set_error_page;
> +			}
> 			bio->bi_bdev = bdev;
> 			bio->bi_iter.bi_sector = blocks[0] << (blkbits - 9);
> 			bio->bi_end_io = mpage_end_io;
> +			bio->bi_private = ctx;
> 		}
> 
> 		length = first_hole << blkbits;
> -- 
> 2.3.0
> 
> --
> To unsubscribe from this list: send the line "unsubscribe linux-ext4" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html


Cheers, Andreas





--
To unsubscribe from this list: send the line "unsubscribe linux-ext4" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Theodore Ts'o April 11, 2015, 1:38 p.m. UTC | #2
On Wed, Apr 08, 2015 at 12:51:04PM -0600, Andreas Dilger wrote:
> > @@ -200,9 +201,15 @@ static const struct vm_operations_struct ext4_file_vm_ops = {
> > 
> > static int ext4_file_mmap(struct file *file, struct vm_area_struct *vma)
> > {
> > +	int res = 0;
> 
> This function is using "res", the one below "ret".  It would be nice to
> have some consistency.  "rc" is probably the most common and would be
> my preference for new/modified code.

Good point; I've restructured this code to make it simpler (and to
avoid conflicting with the DAX patches).  So it now looks like this:

	if (ext4_encrypted_inode(inode)) {
		int err = ext4_generate_encryption_key(inode);
		if (err)
			return 0;
	}

(I'm not a fan of rc, myself, but this makes the declaration and use
of the function much closer together than what we had before, which
makes it much easier to understand.  I'm guessing Microsoft had a
coding policy which stated that all functions have to only have one
return function at the end of the function, since I've noticed that
both Ildar and Michael seem to code that way, even when it requires
using goto statements and/or making the code less clear.)

> > +#ifdef CONFIG_EXT4_FS_ENCRYPTION
> > +		if (S_ISREG(inode->i_mode) &&
> > +		    ext4_encrypted_inode(inode)) {
> > +			/* We expect the key to be set. */
> > +			BUG_ON(!ext4_has_encryption_key(inode));
> > +			BUG_ON(blocksize != PAGE_CACHE_SIZE);
> > +			WARN_ON_ONCE(ext4_decrypt_one(inode, page));
> > +		}
> > +#endif
> 
> This could avoid the #ifdef since ext4_encrypted_inode() is declared (0)
> in the !CONFIG_EXT4_FS_ENCRYPTION case.  The compiler will optimize out
> the resulting unreachable code in that case and make this code easier
> to read.

Good point, done.

> If the (bio->bi_private != NULL) check was moved to a helper function:
> 
> static inline bool ext4_bio_encrypted(struct bio *bio)
> {
> #ifdef CONFIG_EXT4_FS_ENCRYPTION
> 	return unlikely(bio->bi_private != NULL);
> #else
> 	return false;
> #endif
> }
> 
> Then the inline #ifdefs could be removed here, since the resulting

Sure, that makes the code a bit more readable, thanks.

> > +		if (err)
> > +			ext4_release_crypto_ctx(ctx);
> > +		else {
> 
> The if/else should have matching {} blocks.

Meh, I don't really think it's that important, but sure.

> > @@ -94,9 +137,15 @@ int ext4_mpage_readpages(struct address_space *mapping,
> > 	unsigned page_block;
> > 	struct block_device *bdev = inode->i_sb->s_bdev;
> > 	int length;
> > +	int do_decryption = 0;
> 
> Can be bool instead of int.

Actually, since we only use do_decryption in one place now, I'll just remove this and this:

> > +#ifdef CONFIG_EXT4_FS_ENCRYPTION
> > +	if (ext4_encrypted_inode(inode) && S_ISREG(inode->i_mode))
> > +		do_decryption = 1;
> > +#endif
> #ifdef can be removed.

And move the condition to the one place where we test for do_decryption.

Thanks,

					- Ted

    
--
To unsubscribe from this list: send the line "unsubscribe linux-ext4" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
diff mbox

Patch

diff --git a/fs/ext4/file.c b/fs/ext4/file.c
index 8131be8..4cacc30 100644
--- a/fs/ext4/file.c
+++ b/fs/ext4/file.c
@@ -28,6 +28,7 @@ 
 #include <linux/pagevec.h>
 #include "ext4.h"
 #include "ext4_jbd2.h"
+#include "ext4_crypto.h"
 #include "xattr.h"
 #include "acl.h"
 
@@ -200,9 +201,15 @@  static const struct vm_operations_struct ext4_file_vm_ops = {
 
 static int ext4_file_mmap(struct file *file, struct vm_area_struct *vma)
 {
+	int res = 0;
+	struct inode *inode = file->f_mapping->host;
+
+	if (ext4_encrypted_inode(inode))
+		res = ext4_generate_encryption_key(inode);
 	file_accessed(file);
-	vma->vm_ops = &ext4_file_vm_ops;
-	return 0;
+	if (!res)
+		vma->vm_ops = &ext4_file_vm_ops;
+	return res;
 }
 
 static int ext4_file_open(struct inode * inode, struct file * filp)
@@ -212,6 +219,7 @@  static int ext4_file_open(struct inode * inode, struct file * filp)
 	struct vfsmount *mnt = filp->f_path.mnt;
 	struct path path;
 	char buf[64], *cp;
+	int ret;
 
 	if (unlikely(!(sbi->s_mount_flags & EXT4_MF_MNTDIR_SAMPLED) &&
 		     !(sb->s_flags & MS_RDONLY))) {
@@ -250,11 +258,17 @@  static int ext4_file_open(struct inode * inode, struct file * filp)
 	 * writing and the journal is present
 	 */
 	if (filp->f_mode & FMODE_WRITE) {
-		int ret = ext4_inode_attach_jinode(inode);
+		ret = ext4_inode_attach_jinode(inode);
 		if (ret < 0)
 			return ret;
 	}
-	return dquot_file_open(inode, filp);
+	ret = dquot_file_open(inode, filp);
+	if (!ret && ext4_encrypted_inode(inode)) {
+		ret = ext4_generate_encryption_key(inode);
+		if (ret)
+			ret = -EACCES;
+	}
+	return ret;
 }
 
 /*
diff --git a/fs/ext4/inode.c b/fs/ext4/inode.c
index dcc836c..b033405 100644
--- a/fs/ext4/inode.c
+++ b/fs/ext4/inode.c
@@ -39,6 +39,7 @@ 
 #include <linux/ratelimit.h>
 #include <linux/aio.h>
 #include <linux/bitops.h>
+#include <linux/prefetch.h>
 
 #include "ext4_jbd2.h"
 #include "ext4_crypto.h"
@@ -3363,6 +3364,15 @@  static int ext4_block_zero_page_range(handle_t *handle,
 		/* Uhhuh. Read error. Complain and punt. */
 		if (!buffer_uptodate(bh))
 			goto unlock;
+#ifdef CONFIG_EXT4_FS_ENCRYPTION
+		if (S_ISREG(inode->i_mode) &&
+		    ext4_encrypted_inode(inode)) {
+			/* We expect the key to be set. */
+			BUG_ON(!ext4_has_encryption_key(inode));
+			BUG_ON(blocksize != PAGE_CACHE_SIZE);
+			WARN_ON_ONCE(ext4_decrypt_one(inode, page));
+		}
+#endif
 	}
 	if (ext4_should_journal_data(inode)) {
 		BUFFER_TRACE(bh, "get write access");
diff --git a/fs/ext4/readpage.c b/fs/ext4/readpage.c
index 9ca4dfc..8978b1d 100644
--- a/fs/ext4/readpage.c
+++ b/fs/ext4/readpage.c
@@ -43,6 +43,35 @@ 
 
 #include "ext4.h"
 
+#ifdef CONFIG_EXT4_FS_ENCRYPTION
+/*
+ * Call ext4_decrypt on every single page, reusing the encryption
+ * context.
+ */
+static void completion_pages(struct work_struct *work)
+{
+	struct ext4_crypto_ctx *ctx =
+		container_of(work, struct ext4_crypto_ctx, work);
+	struct bio	*bio	= ctx->bio;
+	struct bio_vec	*bv;
+	int		i;
+
+	bio_for_each_segment_all(bv, bio, i) {
+		struct page *page = bv->bv_page;
+
+		int ret = ext4_decrypt(ctx, page);
+		if (ret) {
+			WARN_ON_ONCE(1);
+			SetPageError(page);
+		} else
+			SetPageUptodate(page);
+		unlock_page(page);
+	}
+	ext4_release_crypto_ctx(ctx);
+	bio_put(bio);
+}
+#endif
+
 /*
  * I/O completion handler for multipage BIOs.
  *
@@ -60,6 +89,20 @@  static void mpage_end_io(struct bio *bio, int err)
 	struct bio_vec *bv;
 	int i;
 
+#ifdef CONFIG_EXT4_FS_ENCRYPTION
+	if (bio->bi_private) {
+		struct ext4_crypto_ctx *ctx = bio->bi_private;
+
+		if (err)
+			ext4_release_crypto_ctx(ctx);
+		else {
+			INIT_WORK(&ctx->work, completion_pages);
+			ctx->bio = bio;
+			queue_work(ext4_read_workqueue, &ctx->work);
+			return;
+		}
+	}
+#endif
 	bio_for_each_segment_all(bv, bio, i) {
 		struct page *page = bv->bv_page;
 
@@ -94,9 +137,15 @@  int ext4_mpage_readpages(struct address_space *mapping,
 	unsigned page_block;
 	struct block_device *bdev = inode->i_sb->s_bdev;
 	int length;
+	int do_decryption = 0;
 	unsigned relative_block = 0;
 	struct ext4_map_blocks map;
 
+#ifdef CONFIG_EXT4_FS_ENCRYPTION
+	if (ext4_encrypted_inode(inode) && S_ISREG(inode->i_mode))
+		do_decryption = 1;
+#endif
+
 	map.m_pblk = 0;
 	map.m_lblk = 0;
 	map.m_len = 0;
@@ -220,13 +269,24 @@  int ext4_mpage_readpages(struct address_space *mapping,
 			bio = NULL;
 		}
 		if (bio == NULL) {
+			struct ext4_crypto_ctx *ctx = NULL;
+
+			if (do_decryption) {
+				ctx = ext4_get_crypto_ctx(inode);
+				if (IS_ERR(ctx))
+					goto set_error_page;
+			}
 			bio = bio_alloc(GFP_KERNEL,
 				min_t(int, nr_pages, bio_get_nr_vecs(bdev)));
-			if (!bio)
+			if (!bio) {
+				if (ctx)
+					ext4_release_crypto_ctx(ctx);
 				goto set_error_page;
+			}
 			bio->bi_bdev = bdev;
 			bio->bi_iter.bi_sector = blocks[0] << (blkbits - 9);
 			bio->bi_end_io = mpage_end_io;
+			bio->bi_private = ctx;
 		}
 
 		length = first_hole << blkbits;