linux-cifs.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
From: David Howells <dhowells@redhat.com>
To: linux-fsdevel@vger.kernel.org
Cc: dhowells@redhat.com, Jeff Layton <jlayton@redhat.com>,
	"Matthew Wilcox (Oracle)" <willy@infradead.org>,
	Anna Schumaker <anna.schumaker@netapp.com>,
	Steve French <sfrench@samba.org>,
	Dominique Martinet <asmadeus@codewreck.org>,
	Mike Marshall <hubcap@omnibond.com>,
	David Wysochanski <dwysocha@redhat.com>,
	Shyam Prasad N <nspmangalore@gmail.com>,
	Miklos Szeredi <miklos@szeredi.hu>,
	Linus Torvalds <torvalds@linux-foundation.org>,
	linux-cachefs@redhat.com, linux-afs@lists.infradead.org,
	linux-nfs@vger.kernel.org, linux-cifs@vger.kernel.org,
	ceph-devel@vger.kernel.org, v9fs-developer@lists.sourceforge.net,
	devel@lists.orangefs.org, linux-mm@kvack.org,
	linux-kernel@vger.kernel.org
Subject: [RFC PATCH 10/12] netfs: Do encryption in write preparatory phase
Date: Wed, 21 Jul 2021 14:46:48 +0100	[thread overview]
Message-ID: <162687520852.276387.2868702028972631448.stgit@warthog.procyon.org.uk> (raw)
In-Reply-To: <162687506932.276387.14456718890524355509.stgit@warthog.procyon.org.uk>

When dealing with an encrypted or compressed file, we gather together
sufficient pages from the pagecache to constitute a logical
crypto/compression block, allocate a bounce buffer and then ask the
filesystem to encrypt/compress between the buffers.  The bounce buffer is
then passed to the filesystem to upload.

The network filesystem must set a flag to indicate what service is desired
and when the logical blocksize will be.

The netfs library iterates through each block to be processed, providing a
pair of scatterlists to describe the start and end buffers.

Note that it should be possible in future to encrypt/compress DIO writes
also by this same mechanism.

A mock-up block-encryption function for afs is included for illustration.

Signed-off-by: David Howells <dhowells@redhat.com>
---

 fs/afs/file.c         |    1 
 fs/afs/inode.c        |    6 ++
 fs/afs/internal.h     |    5 ++
 fs/afs/super.c        |    7 ++
 fs/afs/write.c        |   49 +++++++++++++++
 fs/netfs/Makefile     |    3 +
 fs/netfs/internal.h   |    5 ++
 fs/netfs/write_back.c |    6 ++
 fs/netfs/write_prep.c |  160 +++++++++++++++++++++++++++++++++++++++++++++++++
 include/linux/netfs.h |    6 ++
 10 files changed, 246 insertions(+), 2 deletions(-)
 create mode 100644 fs/netfs/write_prep.c

diff --git a/fs/afs/file.c b/fs/afs/file.c
index 22030d5191cd..8a6be8d2b426 100644
--- a/fs/afs/file.c
+++ b/fs/afs/file.c
@@ -404,6 +404,7 @@ const struct netfs_request_ops afs_req_ops = {
 	.update_i_size		= afs_update_i_size,
 	.init_wreq		= afs_init_wreq,
 	.add_write_streams	= afs_add_write_streams,
+	.encrypt_block		= afs_encrypt_block,
 };
 
 int afs_write_inode(struct inode *inode, struct writeback_control *wbc)
diff --git a/fs/afs/inode.c b/fs/afs/inode.c
index a6ae031461c7..7cad099c3bb1 100644
--- a/fs/afs/inode.c
+++ b/fs/afs/inode.c
@@ -452,10 +452,16 @@ static void afs_get_inode_cache(struct afs_vnode *vnode)
 static void afs_set_netfs_context(struct afs_vnode *vnode)
 {
 	struct netfs_i_context *ctx = netfs_i_context(&vnode->vfs_inode);
+	struct afs_super_info *as = AFS_FS_S(vnode->vfs_inode.i_sb);
 
 	netfs_i_context_init(&vnode->vfs_inode, &afs_req_ops);
 	ctx->n_wstreams = 1;
 	ctx->bsize = PAGE_SIZE;
+	if (as->fscrypt) {
+		kdebug("ENCRYPT!");
+		ctx->crypto_bsize = ilog2(4096);
+		__set_bit(NETFS_ICTX_ENCRYPTED, &ctx->flags);
+	}
 }
 
 /*
diff --git a/fs/afs/internal.h b/fs/afs/internal.h
index 32a36b96cc9b..b5f7c3659a0a 100644
--- a/fs/afs/internal.h
+++ b/fs/afs/internal.h
@@ -51,6 +51,7 @@ struct afs_fs_context {
 	bool			autocell;	/* T if set auto mount operation */
 	bool			dyn_root;	/* T if dynamic root */
 	bool			no_cell;	/* T if the source is "none" (for dynroot) */
+	bool			fscrypt;	/* T if content encryption is engaged */
 	enum afs_flock_mode	flock_mode;	/* Partial file-locking emulation mode */
 	afs_voltype_t		type;		/* type of volume requested */
 	unsigned int		volnamesz;	/* size of volume name */
@@ -230,6 +231,7 @@ struct afs_super_info {
 	struct afs_volume	*volume;	/* volume record */
 	enum afs_flock_mode	flock_mode:8;	/* File locking emulation mode */
 	bool			dyn_root;	/* True if dynamic root */
+	bool			fscrypt;	/* T if content encryption is engaged */
 };
 
 static inline struct afs_super_info *AFS_FS_S(struct super_block *sb)
@@ -1518,6 +1520,9 @@ extern void afs_prune_wb_keys(struct afs_vnode *);
 extern int afs_launder_page(struct page *);
 extern ssize_t afs_file_direct_write(struct kiocb *, struct iov_iter *);
 extern void afs_add_write_streams(struct netfs_write_request *);
+extern bool afs_encrypt_block(struct netfs_write_request *, loff_t, size_t,
+			      struct scatterlist *, unsigned int,
+			      struct scatterlist *, unsigned int);
 
 /*
  * xattr.c
diff --git a/fs/afs/super.c b/fs/afs/super.c
index 29c1178beb72..53f35ec7b17b 100644
--- a/fs/afs/super.c
+++ b/fs/afs/super.c
@@ -71,6 +71,7 @@ enum afs_param {
 	Opt_autocell,
 	Opt_dyn,
 	Opt_flock,
+	Opt_fscrypt,
 	Opt_source,
 };
 
@@ -86,6 +87,7 @@ static const struct fs_parameter_spec afs_fs_parameters[] = {
 	fsparam_flag  ("autocell",	Opt_autocell),
 	fsparam_flag  ("dyn",		Opt_dyn),
 	fsparam_enum  ("flock",		Opt_flock, afs_param_flock),
+	fsparam_flag  ("fscrypt",	Opt_fscrypt),
 	fsparam_string("source",	Opt_source),
 	{}
 };
@@ -342,6 +344,10 @@ static int afs_parse_param(struct fs_context *fc, struct fs_parameter *param)
 		ctx->flock_mode = result.uint_32;
 		break;
 
+	case Opt_fscrypt:
+		ctx->fscrypt = true;
+		break;
+
 	default:
 		return -EINVAL;
 	}
@@ -516,6 +522,7 @@ static struct afs_super_info *afs_alloc_sbi(struct fs_context *fc)
 			as->cell = afs_use_cell(ctx->cell, afs_cell_trace_use_sbi);
 			as->volume = afs_get_volume(ctx->volume,
 						    afs_volume_trace_get_alloc_sbi);
+			as->fscrypt = ctx->fscrypt;
 		}
 	}
 	return as;
diff --git a/fs/afs/write.c b/fs/afs/write.c
index 0668389f3466..d2b7cb1a4668 100644
--- a/fs/afs/write.c
+++ b/fs/afs/write.c
@@ -13,6 +13,7 @@
 #include <linux/pagevec.h>
 #include <linux/netfs.h>
 #include <linux/fscache.h>
+#include <crypto/skcipher.h>
 #include <trace/events/netfs.h>
 #include "internal.h"
 
@@ -293,6 +294,54 @@ void afs_add_write_streams(struct netfs_write_request *wreq)
 				  afs_upload_to_server_worker);
 }
 
+/*
+ * Encrypt part of a write for fscrypt.
+ */
+bool afs_encrypt_block(struct netfs_write_request *wreq, loff_t pos, size_t len,
+		       struct scatterlist *source_sg, unsigned int n_source,
+		       struct scatterlist *dest_sg, unsigned int n_dest)
+{
+	struct crypto_sync_skcipher *ci;
+	struct crypto_skcipher *tfm;
+	struct skcipher_request *req;
+	u8 session_key[8], iv[8];
+	int ret;
+
+	kenter("%llx", pos);
+
+	ci = crypto_alloc_sync_skcipher("pcbc(fcrypt)", 0, 0);
+	if (IS_ERR(ci)) {
+		_debug("no cipher");
+		ret = PTR_ERR(ci);
+		goto error;
+	}
+	tfm= &ci->base;
+
+	ret = crypto_sync_skcipher_setkey(ci, session_key, sizeof(session_key));
+	if (ret < 0)
+		goto error_ci;
+
+	ret = -ENOMEM;
+	req = skcipher_request_alloc(tfm, GFP_NOFS);
+	if (!req)
+		goto error_ci;
+
+	memset(iv, 0, sizeof(iv));
+	skcipher_request_set_sync_tfm(req, ci);
+	skcipher_request_set_callback(req, 0, NULL, NULL);
+	skcipher_request_set_crypt(req, source_sg, dest_sg, len, iv);
+	ret = crypto_skcipher_encrypt(req);
+
+	skcipher_request_free(req);
+error_ci:
+	crypto_free_sync_skcipher(ci);
+error:
+	if (ret < 0)
+		wreq->error = ret;
+	kleave(" = %d", ret);
+	return ret == 0;
+}
+
 /*
  * Extend the region to be written back to include subsequent contiguously
  * dirty pages if possible, but don't sleep while doing so.
diff --git a/fs/netfs/Makefile b/fs/netfs/Makefile
index a201fd7b22cf..a7c3a9173ac0 100644
--- a/fs/netfs/Makefile
+++ b/fs/netfs/Makefile
@@ -4,7 +4,8 @@ netfs-y := \
 	objects.o \
 	read_helper.o \
 	write_back.o \
-	write_helper.o
+	write_helper.o \
+	write_prep.o
 # dio_helper.o
 
 netfs-$(CONFIG_NETFS_STATS) += stats.o
diff --git a/fs/netfs/internal.h b/fs/netfs/internal.h
index 6fdf9e5663f7..381ca64062eb 100644
--- a/fs/netfs/internal.h
+++ b/fs/netfs/internal.h
@@ -65,6 +65,11 @@ void netfs_flush_region(struct netfs_i_context *ctx,
 			struct netfs_dirty_region *region,
 			enum netfs_dirty_trace why);
 
+/*
+ * write_prep.c
+ */
+bool netfs_prepare_wreq(struct netfs_write_request *wreq);
+
 /*
  * stats.c
  */
diff --git a/fs/netfs/write_back.c b/fs/netfs/write_back.c
index 15cc0e1b9acf..7363c3324602 100644
--- a/fs/netfs/write_back.c
+++ b/fs/netfs/write_back.c
@@ -254,7 +254,9 @@ static void netfs_writeback(struct netfs_write_request *wreq)
 
 	kenter("");
 
-	/* TODO: Encrypt or compress the region as appropriate */
+	if (test_bit(NETFS_ICTX_ENCRYPTED, &ctx->flags) &&
+	    !netfs_prepare_wreq(wreq))
+		goto out;
 
 	/* ->outstanding > 0 carries a ref */
 	netfs_get_write_request(wreq, netfs_wreq_trace_get_for_outstanding);
@@ -262,6 +264,8 @@ static void netfs_writeback(struct netfs_write_request *wreq)
 	if (test_bit(NETFS_WREQ_WRITE_TO_CACHE, &wreq->flags))
 		netfs_set_up_write_to_cache(wreq);
 	ctx->ops->add_write_streams(wreq);
+
+out:
 	if (atomic_dec_and_test(&wreq->outstanding))
 		netfs_write_completed(wreq, false);
 }
diff --git a/fs/netfs/write_prep.c b/fs/netfs/write_prep.c
new file mode 100644
index 000000000000..f0a9dfd92a18
--- /dev/null
+++ b/fs/netfs/write_prep.c
@@ -0,0 +1,160 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* Network filesystem high-level write support.
+ *
+ * Copyright (C) 2021 Red Hat, Inc. All Rights Reserved.
+ * Written by David Howells (dhowells@redhat.com)
+ */
+
+#include <linux/fs.h>
+#include <linux/mm.h>
+#include <linux/pagemap.h>
+#include <linux/slab.h>
+#include "internal.h"
+
+/*
+ * Allocate a bunch of pages and add them into the xarray buffer starting at
+ * the given index.
+ */
+static int netfs_alloc_buffer(struct xarray *xa, pgoff_t index, unsigned int nr_pages)
+{
+	struct page *page;
+	unsigned int n;
+	int ret;
+	LIST_HEAD(list);
+
+	kenter("");
+
+	n = alloc_pages_bulk_list(GFP_NOIO, nr_pages, &list);
+	if (n < nr_pages) {
+		ret = -ENOMEM;
+	}
+
+	while ((page = list_first_entry_or_null(&list, struct page, lru))) {
+		list_del(&page->lru);
+		ret = xa_insert(xa, index++, page, GFP_NOIO);
+		if (ret < 0)
+			break;
+	}
+
+	while ((page = list_first_entry_or_null(&list, struct page, lru))) {
+		list_del(&page->lru);
+		__free_page(page);
+	}
+	return ret;
+}
+
+/*
+ * Populate a scatterlist from pages in an xarray.
+ */
+static int netfs_xarray_to_sglist(struct xarray *xa, loff_t pos, size_t len,
+				  struct scatterlist *sg, unsigned int n_sg)
+{
+	struct scatterlist *p = sg;
+	struct page *head = NULL;
+	size_t seg, offset, skip = 0;
+	loff_t start = pos;
+	pgoff_t index = start >> PAGE_SHIFT;
+	int j;
+
+	XA_STATE(xas, xa, index);
+
+	sg_init_table(sg, n_sg);
+
+	rcu_read_lock();
+
+	xas_for_each(&xas, head, ULONG_MAX) {
+		kdebug("LOAD %lx %px", head->index, head);
+		if (xas_retry(&xas, head))
+			continue;
+		if (WARN_ON(xa_is_value(head)) || WARN_ON(PageHuge(head)))
+			break;
+		for (j = (head->index < index) ? index - head->index : 0;
+		     j < thp_nr_pages(head); j++
+		     ) {
+			offset = (pos + skip) & ~PAGE_MASK;
+			seg = min(len, PAGE_SIZE - offset);
+
+			kdebug("[%zx] %lx %zx @%zx", p - sg, (head + j)->index, seg, offset);
+			sg_set_page(p++, head + j, seg, offset);
+
+			len -= seg;
+			skip += seg;
+			if (len == 0)
+				break;
+		}
+		if (len == 0)
+			break;
+	}
+
+	rcu_read_unlock();
+	if (len > 0) {
+		WARN_ON(len > 0);
+		return -EIO;
+	}
+
+	sg_mark_end(p - 1);
+	kleave(" = %zd", p - sg);
+	return p - sg;
+}
+
+/*
+ * Perform content encryption on the data to be written before we write it to
+ * the server and the cache.
+ */
+static bool netfs_prepare_encrypt(struct netfs_write_request *wreq)
+{
+	struct netfs_i_context *ctx = netfs_i_context(wreq->inode);
+	struct scatterlist source_sg[16], dest_sg[16];
+	unsigned int bsize = 1 << ctx->crypto_bsize, n_source, n_dest;
+	loff_t pos;
+	size_t n;
+	int ret;
+
+	ret = netfs_alloc_buffer(&wreq->buffer, wreq->first, wreq->last - wreq->first + 1);
+	if (ret < 0)
+		goto error;
+
+	pos = round_down(wreq->start, bsize);
+	n = round_up(wreq->start + wreq->len, bsize) - pos;
+	for (; n > 0; n -= bsize, pos += bsize) {
+		ret = netfs_xarray_to_sglist(&wreq->mapping->i_pages, pos, bsize,
+					     source_sg, ARRAY_SIZE(source_sg));
+		if (ret < 0)
+			goto error;
+		n_source = ret;
+
+		ret = netfs_xarray_to_sglist(&wreq->buffer, pos, bsize,
+					     dest_sg, ARRAY_SIZE(dest_sg));
+		if (ret < 0)
+			goto error;
+		n_dest = ret;
+
+		ret = ctx->ops->encrypt_block(wreq, pos, bsize,
+					      source_sg, n_source, dest_sg, n_dest);
+		if (ret < 0)
+			goto error;
+	}
+
+	iov_iter_xarray(&wreq->source, WRITE, &wreq->buffer, wreq->start, wreq->len);
+	kleave(" = t");
+	return true;
+
+error:
+	wreq->error = ret;
+	kleave(" = f [%d]", ret);
+	return false;
+}
+
+/*
+ * Prepare a write request for writing.  All the pages in the bounding box have
+ * had a ref taken on them and those covering the dirty region have been marked
+ * as being written back and their dirty bits provisionally cleared.
+ */
+bool netfs_prepare_wreq(struct netfs_write_request *wreq)
+{
+	struct netfs_i_context *ctx = netfs_i_context(wreq->inode);
+
+	if (test_bit(NETFS_ICTX_ENCRYPTED, &ctx->flags))
+		return netfs_prepare_encrypt(wreq);
+	return true;
+}
diff --git a/include/linux/netfs.h b/include/linux/netfs.h
index 9d50c2933863..6acf3fb170c3 100644
--- a/include/linux/netfs.h
+++ b/include/linux/netfs.h
@@ -19,6 +19,7 @@
 #include <linux/pagemap.h>
 #include <linux/uio.h>
 
+struct scatterlist;
 enum netfs_wreq_trace;
 
 /*
@@ -177,12 +178,14 @@ struct netfs_i_context {
 #endif
 	unsigned long		flags;
 #define NETFS_ICTX_NEW_CONTENT	0		/* Set if file has new content (create/trunc-0) */
+#define NETFS_ICTX_ENCRYPTED	1		/* The file contents are encrypted */
 	spinlock_t		lock;
 	unsigned int		rsize;		/* Maximum read size */
 	unsigned int		wsize;		/* Maximum write size */
 	unsigned int		bsize;		/* Min block size for bounding box */
 	unsigned int		inval_counter;	/* Number of invalidations made */
 	unsigned char		n_wstreams;	/* Number of write streams to allocate */
+	unsigned char		crypto_bsize;	/* log2 of crypto block size */
 };
 
 /*
@@ -358,6 +361,9 @@ struct netfs_request_ops {
 	void (*init_wreq)(struct netfs_write_request *wreq);
 	void (*add_write_streams)(struct netfs_write_request *wreq);
 	void (*invalidate_cache)(struct netfs_write_request *wreq);
+	bool (*encrypt_block)(struct netfs_write_request *wreq, loff_t pos,  size_t len,
+			      struct scatterlist *source_sg, unsigned int n_source,
+			      struct scatterlist *dest_sg, unsigned int n_dest);
 };
 
 /*



  parent reply	other threads:[~2021-07-21 13:47 UTC|newest]

Thread overview: 22+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2021-07-21 13:44 David Howells
2021-07-21 13:44 ` [RFC PATCH 01/12] afs: Sort out symlink reading David Howells
2021-07-21 16:20   ` Jeff Layton
2021-07-26  9:44   ` David Howells
2021-07-21 13:44 ` [RFC PATCH 02/12] netfs: Add an iov_iter to the read subreq for the network fs/cache to use David Howells
2021-07-21 17:16   ` Jeff Layton
2021-07-21 17:20   ` David Howells
2021-07-21 13:45 ` [RFC PATCH 03/12] netfs: Remove netfs_read_subrequest::transferred David Howells
2021-07-21 17:43   ` Jeff Layton
2021-07-21 18:54   ` David Howells
2021-07-21 19:00     ` Jeff Layton
2021-07-21 13:45 ` [RFC PATCH 04/12] netfs: Use a buffer in netfs_read_request and add pages to it David Howells
2021-07-21 13:45 ` [RFC PATCH 05/12] netfs: Add a netfs inode context David Howells
2021-07-21 13:46 ` [RFC PATCH 06/12] netfs: Keep lists of pending, active, dirty and flushed regions David Howells
2021-07-21 13:46 ` [RFC PATCH 07/12] netfs: Initiate write request from a dirty region David Howells
2021-07-21 13:46 ` [RFC PATCH 08/12] netfs: Keep dirty mark for pages with more than one " David Howells
2021-07-21 13:46 ` [RFC PATCH 09/12] netfs: Send write request to multiple destinations David Howells
2021-07-21 13:46 ` David Howells [this message]
2021-07-21 13:47 ` [RFC PATCH 11/12] netfs: Put a list of regions in /proc/fs/netfs/regions David Howells
2021-07-21 13:47 ` [RFC PATCH 12/12] netfs: Export some read-request ref functions David Howells
2021-07-21 14:00 ` [RFC PATCH 00/12] netfs: Experimental write helpers, fscrypt and compression David Howells
2021-07-21 18:42 ` [RFC PATCH 13/12] netfs: Do copy-to-cache-on-read through VM writeback David Howells

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=162687520852.276387.2868702028972631448.stgit@warthog.procyon.org.uk \
    --to=dhowells@redhat.com \
    --cc=anna.schumaker@netapp.com \
    --cc=asmadeus@codewreck.org \
    --cc=ceph-devel@vger.kernel.org \
    --cc=devel@lists.orangefs.org \
    --cc=dwysocha@redhat.com \
    --cc=hubcap@omnibond.com \
    --cc=jlayton@redhat.com \
    --cc=linux-afs@lists.infradead.org \
    --cc=linux-cachefs@redhat.com \
    --cc=linux-cifs@vger.kernel.org \
    --cc=linux-fsdevel@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-mm@kvack.org \
    --cc=linux-nfs@vger.kernel.org \
    --cc=miklos@szeredi.hu \
    --cc=nspmangalore@gmail.com \
    --cc=sfrench@samba.org \
    --cc=torvalds@linux-foundation.org \
    --cc=v9fs-developer@lists.sourceforge.net \
    --cc=willy@infradead.org \
    --subject='Re: [RFC PATCH 10/12] netfs: Do encryption in write preparatory phase' \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).