afs: [DON'T MERGE] Do content encryption with an in-file trailer
To do content encryption, the data at the end of the file needs padding out
to crypto block size (e.g. 16 or 32 bytes for AES types), but that means we
need somewhere to store the actual file length with the padding removed.
Typically, this is stored in an xattr, though it could also be stored in
the file as an additional block at the front or end of the file.
With AFS, there are no xattrs and nowhere to store additional metadata, so
this information must be stored in the file. Now, padding out the last
block to the fscrypt block size (4KiB, say) and then sticking a trailer
after it with the real size recorded therein is a problem for stat(). We
have to be able to read the file to be able to return the correct value in
st_size; further, if the trailer is at the end of the file rather than the
beginning, we can't guess where real size is recorded and speculatively use
the FetchData RPC rather than the FetchStatus RPC as we need to know
i_size. Yet further, this doesn't work for bulk status fetch.
However, lacking a vectored StoreData RPC, we really want to store extra
metadata at the end of the file as StoreData "atomically" combines a
truncate and a data write which would allow us to update the metadata too.
With this in mind, implement another alternative:
(1) Store zero-length files as zero-length on the server. These are
hereafter ignored in this description.
(2) Place zeroed padding of one crypto block size (typically 16 or 32
bytes) after the real EOF and always constant size for any particular
file. No information is stored here, though it may get partially
encrypted.
(3) Simply subtract the crypto block size from the server's file size to
derive i_size. This can be done when initialising/updating the inode
and will work with bulk status fetch.
(4) When encrypting the EOF block in a file, if partial, encrypt into the
padding.
(5) When decrypting the EOF block in a file, if partial, decrypt part of
the padding.
Signed-off-by: David Howells <dhowells@redhat.com>
cc: Marc Dionne <marc.dionne@auristor.com>
cc: linux-afs@lists.infradead.org
cc: netfs@lists.linux.dev
diff --git a/fs/afs/file.c b/fs/afs/file.c
index d2e75f0..d51e9c4 100644
--- a/fs/afs/file.c
+++ b/fs/afs/file.c
@@ -144,6 +144,12 @@ int afs_open(struct inode *inode, struct file *file)
if (ret < 0)
goto error_af;
+ if (test_bit(NETFS_ICTX_ENCRYPTED, &vnode->netfs.flags) && !vnode->content_ci) {
+ ret = afs_open_crypto(vnode);
+ if (ret < 0)
+ goto error_af;
+ }
+
if (file->f_mode & FMODE_WRITE) {
ret = afs_cache_wb_key(vnode, af);
if (ret < 0)
@@ -387,12 +393,21 @@ static void afs_issue_read(struct netfs_io_subrequest *subreq)
static int afs_init_request(struct netfs_io_request *rreq, struct file *file)
{
+ struct afs_super_info *as = AFS_FS_S(rreq->inode->i_sb);
struct afs_vnode *vnode = AFS_FS_I(rreq->inode);
if (file)
rreq->netfs_priv = key_get(afs_file_key(file));
- rreq->rsize = 256 * 1024;
- rreq->wsize = 256 * 1024 * 1024;
+ if (test_bit(NETFS_RREQ_CONTENT_ENCRYPTION, &rreq->flags) &&
+ S_ISREG(rreq->inode->i_mode)) {
+ rreq->rsize = 64 * 1024;
+ rreq->wsize = 64 * 1024;
+ rreq->crypto_asize = as->crypto_asize;
+ rreq->crypto_bsize = as->crypto_bsize;
+ } else {
+ rreq->rsize = 256 * 1024;
+ rreq->wsize = 256 * 1024 * 1024;
+ }
switch (rreq->origin) {
case NETFS_READ_SINGLE:
@@ -478,6 +493,8 @@ const struct netfs_request_ops afs_req_ops = {
.estimate_write = afs_estimate_write,
.issue_write = afs_issue_write,
.retry_request = afs_retry_request,
+ .encrypt_block = afs_encrypt_block,
+ .decrypt_block = afs_decrypt_block,
};
static void afs_add_open_mmap(struct afs_vnode *vnode)
diff --git a/fs/afs/fs_operation.c b/fs/afs/fs_operation.c
index c0dbbc6..9f1925b 100644
--- a/fs/afs/fs_operation.c
+++ b/fs/afs/fs_operation.c
@@ -58,7 +58,7 @@ struct afs_io_locker {
/*
* Unlock the I/O lock on a vnode.
*/
-static void afs_unlock_for_io(struct afs_vnode *vnode)
+void afs_unlock_for_io(struct afs_vnode *vnode)
{
struct afs_io_locker *locker;
@@ -80,7 +80,7 @@ static void afs_unlock_for_io(struct afs_vnode *vnode)
* Lock the I/O lock on a vnode uninterruptibly. We can't use an ordinary
* mutex as lockdep will complain if we unlock it in the wrong thread.
*/
-static void afs_lock_for_io(struct afs_vnode *vnode)
+void afs_lock_for_io(struct afs_vnode *vnode)
{
struct afs_io_locker myself = { .task = current, };
@@ -107,7 +107,7 @@ static void afs_lock_for_io(struct afs_vnode *vnode)
* Lock the I/O lock on a vnode interruptibly. We can't use an ordinary mutex
* as lockdep will complain if we unlock it in the wrong thread.
*/
-static int afs_lock_for_io_interruptible(struct afs_vnode *vnode)
+int afs_lock_for_io_interruptible(struct afs_vnode *vnode)
{
struct afs_io_locker myself = { .task = current, };
int ret = 0;
diff --git a/fs/afs/inode.c b/fs/afs/inode.c
index 1e7bfde..983b4f8 100644
--- a/fs/afs/inode.c
+++ b/fs/afs/inode.c
@@ -22,6 +22,7 @@
#include <linux/mount.h>
#include <linux/namei.h>
#include <linux/iversion.h>
+#include <crypto/skcipher.h>
#include "internal.h"
#include "afs_fs.h"
@@ -54,7 +55,13 @@ static noinline void dump_vnode(struct afs_vnode *vnode, struct afs_vnode *paren
*/
static void afs_set_netfs_context(struct afs_vnode *vnode)
{
+ struct netfs_inode *ictx = &vnode->netfs;
+
netfs_inode_init(&vnode->netfs, &afs_req_ops, true);
+ if (vnode->status.type == AFS_FTYPE_FILE && IS_ENCRYPTED(&vnode->netfs.inode)) {
+ __set_bit(NETFS_ICTX_ENCRYPTED, &ictx->flags);
+ __set_bit(NETFS_ICTX_NO_SPARSE, &ictx->flags);
+ }
}
/*
@@ -66,7 +73,9 @@ static int afs_inode_init_from_status(struct afs_operation *op,
{
struct afs_file_status *status = &vp->scb.status;
struct inode *inode = AFS_VNODE_TO_I(vnode);
+ struct afs_super_info *as = AFS_FS_S(inode->i_sb);
struct timespec64 t;
+ unsigned long long size = status->size;
_enter("{%llx:%llu.%u} %s",
vp->fid.vid, vp->fid.vnode, vp->fid.unique,
@@ -79,6 +88,23 @@ static int afs_inode_init_from_status(struct afs_operation *op,
status->data_version,
status->mode);
+ if (as->fscrypt &&
+ status->type == AFS_FTYPE_FILE) {
+ /* There must be a trailer of exactly crypt_asize bytes unless
+ * the file is zero-length, in which case the file size should
+ * be zero.
+ */
+ if (size > 0) {
+ if (size <= as->crypto_asize) {
+ pr_err("Bad fscrypt size %llx (%llx:%llx.%x)\n",
+ size, vp->fid.vid, vp->fid.vnode, vp->fid.unique);
+ return -EIO;
+ }
+ size -= as->crypto_asize;
+ }
+ inode->i_flags |= S_ENCRYPTED;
+ }
+
write_seqlock(&vnode->cb_lock);
vnode->cb_v_check = op->cb_v_break;
@@ -134,8 +160,8 @@ static int afs_inode_init_from_status(struct afs_operation *op,
return afs_protocol_error(NULL, afs_eproto_file_type);
}
- i_size_write(inode, status->size);
- inode_set_bytes(inode, status->size);
+ i_size_write(inode, size);
+ inode_set_bytes(inode, size);
afs_set_netfs_context(vnode);
vnode->invalid_before = status->data_version;
@@ -166,7 +192,9 @@ static void afs_apply_status(struct afs_operation *op,
struct afs_vnode *vnode = vp->vnode;
struct netfs_inode *ictx = &vnode->netfs;
struct inode *inode = &ictx->inode;
+ struct afs_super_info *as = AFS_FS_S(inode->i_sb);
struct timespec64 t;
+ unsigned long long size = status->size;
umode_t mode;
bool unexpected_jump = false;
bool data_changed = false;
@@ -178,6 +206,19 @@ static void afs_apply_status(struct afs_operation *op,
BUG_ON(test_bit(AFS_VNODE_UNSET, &vnode->flags));
+ if (test_bit(NETFS_ICTX_ENCRYPTED, &vnode->netfs.flags)) {
+ if (size == 0) {
+ size = 0;
+ } else if (size <= as->crypto_asize) {
+ pr_err("Bad fscrypt size %llx (%llx:%llx.%x)\n",
+ size, vp->fid.vid, vp->fid.vnode, vp->fid.unique);
+ size = 0;
+ } else {
+ size -= as->crypto_asize;
+ }
+ kdebug("apply enc size %llx -> %llx", status->size, size);
+ }
+
if (status->type != vnode->status.type) {
pr_warn("Vnode %llx:%llx:%x changed type %u to %u\n",
vnode->fid.vid,
@@ -688,6 +729,9 @@ void afs_evict_inode(struct inode *inode)
if (vnode->symlink)
afs_evict_symlink(vnode);
+ if (vnode->content_ci)
+ crypto_free_skcipher(vnode->content_ci);
+
afs_set_cache_aux(vnode, &aux);
netfs_clear_inode_writeback(inode, &aux);
clear_inode(inode);
diff --git a/fs/afs/internal.h b/fs/afs/internal.h
index f201260..766c077 100644
--- a/fs/afs/internal.h
+++ b/fs/afs/internal.h
@@ -52,6 +52,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 */
@@ -251,6 +252,9 @@ 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 */
+ u8 crypto_asize; /* Crypto algo blocksize for fscrypt */
+ u16 crypto_bsize; /* Crypto blocksize for fscrypt */
};
static inline struct afs_super_info *AFS_FS_S(struct super_block *sb)
@@ -694,6 +698,7 @@ struct afs_vnode {
struct rw_semaphore validate_lock; /* lock for validating this vnode */
struct rw_semaphore rmdir_lock; /* Lock for rmdir vs sillyrename */
struct key *silly_key; /* Silly rename key */
+ struct crypto_skcipher *content_ci; /* Content crypto cipher */
spinlock_t wb_lock; /* lock for wb_keys */
spinlock_t lock; /* waitqueue/flags lock */
unsigned long flags;
@@ -1213,6 +1218,9 @@ extern void afs_fs_store_acl(struct afs_operation *);
/*
* fs_operation.c
*/
+void afs_unlock_for_io(struct afs_vnode *vnode);
+void afs_lock_for_io(struct afs_vnode *vnode);
+int afs_lock_for_io_interruptible(struct afs_vnode *vnode);
extern struct afs_operation *afs_alloc_operation(struct key *, struct afs_volume *);
extern int afs_put_operation(struct afs_operation *);
extern bool afs_begin_vnode_operation(struct afs_operation *);
@@ -1708,6 +1716,16 @@ extern int afs_writepages(struct address_space *, struct writeback_control *);
extern int afs_fsync(struct file *, loff_t, loff_t, int);
extern vm_fault_t afs_page_mkwrite(struct vm_fault *vmf);
extern void afs_prune_wb_keys(struct afs_vnode *);
+int afs_open_crypto(struct afs_vnode *vnode);
+int afs_encrypt_block(struct netfs_io_request *wreq,
+ unsigned long long start,
+ struct scatterlist *src_sg,
+ struct scatterlist *dst_sg,
+ gfp_t gfp);
+int afs_decrypt_block(struct netfs_io_request *rreq,
+ unsigned long long start, size_t len,
+ struct scatterlist *src_sg, unsigned int n_src,
+ struct scatterlist *dst_sg, unsigned int n_dst);
/*
* xattr.c
diff --git a/fs/afs/super.c b/fs/afs/super.c
index 942f3e9..1556820 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),
{}
};
@@ -194,6 +196,8 @@ static int afs_show_options(struct seq_file *m, struct dentry *root)
if (as->dyn_root)
seq_puts(m, ",dyn");
+ if (as->fscrypt)
+ seq_puts(m, ",fscrypt");
switch (as->flock_mode) {
case afs_flock_mode_unset: break;
case afs_flock_mode_local: p = "local"; break;
@@ -341,6 +345,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;
}
@@ -512,6 +520,14 @@ 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);
+ if (ctx->fscrypt) {
+ as->fscrypt = true;
+ as->crypto_asize = 16;
+ as->crypto_bsize = 4096;
+ } else {
+ as->crypto_asize = 1;
+ as->crypto_bsize = 1;
+ }
}
}
return as;
@@ -687,6 +703,7 @@ static struct inode *afs_alloc_inode(struct super_block *sb)
vnode->permit_cache = NULL;
vnode->directory = NULL;
vnode->directory_size = 0;
+ vnode->content_ci = NULL;
vnode->flags = 1 << AFS_VNODE_UNSET;
vnode->lock_state = AFS_VNODE_LOCK_NONE;
diff --git a/fs/afs/write.c b/fs/afs/write.c
index 6a50b3c..6e33455 100644
--- a/fs/afs/write.c
+++ b/fs/afs/write.c
@@ -11,6 +11,8 @@
#include <linux/pagemap.h>
#include <linux/writeback.h>
#include <linux/netfs.h>
+#include <crypto/skcipher.h>
+#include <crypto/sha2.h>
#include <trace/events/netfs.h>
#include "internal.h"
@@ -92,6 +94,9 @@ int afs_estimate_write(struct netfs_io_request *wreq,
unsigned long long limit = ULLONG_MAX - stream->issue_from;
unsigned long long max_len = 256 * 1024 * 1024;
+ if (test_bit(NETFS_RREQ_CONTENT_ENCRYPTION, &wreq->flags))
+ max_len = 64 * 1024;
+
//if (test_bit(NETFS_SREQ_RETRYING, &subreq->flags))
// max_len = 512 * 1024;
@@ -100,6 +105,80 @@ int afs_estimate_write(struct netfs_io_request *wreq,
}
/*
+ * Add a trailer to adjust the write to a content-encrypted file. We have to
+ * round up the block with the EOF in it to the algo block size at a minimum;
+ * we then tag on sufficient zeros to pad the server's file size to our file
+ * size + 16 (or whatever the algo block size is).
+ *
+ * Note that *_len is already padded out to a full fscrypt block size.
+ */
+static int afs_add_content_encryption_trailer(struct afs_vnode *vnode,
+ struct netfs_io_request *wreq,
+ struct netfs_io_subrequest *subreq,
+ size_t *_len)
+{
+ struct bvecq *bq;
+ unsigned long long remote_i_size, eof;
+ size_t asize = wreq->crypto_asize; /* Algo block size */
+ size_t tlen = asize;
+ size_t len = *_len;
+
+ kenter("%llx,%zx", subreq->start, len);
+
+ eof = subreq->start + len;
+ remote_i_size = netfs_read_remote_i_size(&vnode->netfs.inode);
+ if (eof < remote_i_size) {
+ kleave(" = 0");
+ return 0;
+ }
+
+ /* If the target file size is at or beyond the end of this subreq, then
+ * we write all of it with a full-sized trailer. Subsequent subreqs
+ * will overwrite the trailer, but as long as FS.StoreData is atomic
+ * and gets rolled back on failure, the file should never be seen in a
+ * state where it has a corrupt length.
+ */
+ if (eof <= wreq->i_size)
+ goto add_trailer;
+
+ /* Reduce the length so that we don't write beyond the final algo
+ * block. If i_size aligns with the algo block, then just add a whole
+ * trailer.
+ */
+ eof = round_up(wreq->i_size, asize);
+ *_len = eof - subreq->start;
+ if (eof == wreq->i_size)
+ goto add_trailer;
+
+ /* Otherwise, just add sufficient zeros to pad from the end of the algo
+ * block to EOF + algo block size.
+ */
+ tlen = asize - (eof - wreq->i_size);
+
+add_trailer:
+ for (bq = subreq->content.bvecq; bq->next; bq = bq->next)
+ ;
+ if (bvecq_is_full(bq) || bq->mem_type == BVECQ_MEM_ALLOCED) {
+ struct bvecq *p = bvecq_alloc_one(1, GFP_NOFS);
+
+ if (!p) {
+ kleave(" = -ENOMEM");
+ return -ENOMEM;
+ }
+ p->fpos = eof;
+ p->prev = bq;
+ bq->next = p;
+ bq = p;
+ }
+ bvec_set_page(&bq->bv[bq->nr_slots], ZERO_PAGE(0), PAGE_SIZE, 0);
+ bq->nr_slots++;
+ subreq->back_excess = asize;
+ *_len += tlen;
+ kleave(" = 0 [%zx]", *_len);
+ return 0;
+}
+
+/*
* Issue a subrequest to write to the server.
*/
static void afs_issue_write_worker(struct work_struct *work)
@@ -112,7 +191,7 @@ static void afs_issue_write_worker(struct work_struct *work)
size_t len = subreq->len - subreq->transferred;
int ret = -ENOKEY;
- _enter("R=%x[%x],%s{%llx:%llu.%u},%llx,%zx",
+ kenter("R=%x[%x],%s{%llx:%llu.%u},%llx,%zx",
wreq->debug_id, subreq->debug_index,
vnode->volume->name,
vnode->fid.vid,
@@ -130,6 +209,19 @@ static void afs_issue_write_worker(struct work_struct *work)
}
#endif
+ if (test_bit(NETFS_RREQ_CONTENT_ENCRYPTION, &wreq->flags)) {
+ /* We need to include a fixed-length trailer if this is at the
+ * end of the file so that we can work out later how big the
+ * file is as we need to retain the entire end crypto block.
+ */
+ ret = afs_add_content_encryption_trailer(vnode, wreq, subreq, &len);
+ if (ret < 0)
+ return netfs_write_subrequest_terminated(subreq, ret);
+ }
+
+ kdebug("-- content %zx --", len);
+ bvecq_dump(subreq->content.bvecq);
+
op = afs_alloc_operation(wreq->netfs_priv, vnode->volume);
if (IS_ERR(op))
return netfs_write_subrequest_terminated(subreq, -EAGAIN);
@@ -149,7 +241,7 @@ static void afs_issue_write_worker(struct work_struct *work)
op->mtime = inode_get_mtime(&vnode->netfs.inode);
iov_iter_bvec_queue(&op->store.write_iter, ITER_SOURCE, subreq->content.bvecq,
- subreq->content.slot, subreq->content.offset, subreq->len);
+ subreq->content.slot, subreq->content.offset, len);
afs_wait_for_operation(op);
ret = afs_put_operation(op);
@@ -171,16 +263,24 @@ static void afs_issue_write_worker(struct work_struct *work)
break;
}
+ kdebug("excess %zx %x", subreq->len, subreq->back_excess);
+ //subreq->len -= subreq->back_excess;
+ kleave(" = %zd", ret < 0 ? ret : subreq->len);
netfs_write_subrequest_terminated(subreq, ret < 0 ? ret : subreq->len);
}
void afs_issue_write(struct netfs_io_subrequest *subreq)
{
+ struct netfs_io_request *wreq = subreq->rreq;
+ unsigned long long wsize = 256 * 1024 * 1024;
+ bool enc = test_bit(NETFS_RREQ_CONTENT_ENCRYPTION, &wreq->flags);
int ret;
- if (subreq->len > 256 * 1024 * 1024)
- subreq->len = 256 * 1024 * 1024;
- ret = netfs_prepare_write_buffer(subreq, INT_MAX, false);
+ if (enc)
+ wsize = 64 * 1024;
+ if (subreq->len > wsize)
+ subreq->len = wsize;
+ ret = netfs_prepare_write_buffer(subreq, INT_MAX, enc);
if (ret < 0)
return netfs_write_subrequest_terminated(subreq, ret);
@@ -322,3 +422,165 @@ void afs_prune_wb_keys(struct afs_vnode *vnode)
afs_put_wb_key(wbk);
}
}
+
+static void netfs_dump_sg(const char *prefix, struct scatterlist *sg, unsigned int n_sg)
+{
+ unsigned int i;
+
+ for (i = 0; i < n_sg; i++) {
+ void *p = kmap_local_page(sg_page(sg));
+ unsigned int l = min_t(size_t, sg->length, 16);
+
+ printk("%s[%x] %10lx %04x %04x %*phN\n",
+ prefix, i, page_to_pfn(sg_page(sg)), sg->offset, sg->length,
+ l, p + sg->offset);
+ kunmap_local(p);
+ sg++;
+ }
+}
+
+/*
+ * Create a keyed symmetric cipher for use in content crypto ops.
+ */
+int afs_open_crypto(struct afs_vnode *vnode)
+{
+ struct crypto_skcipher *ci;
+ struct sha256_ctx sha;
+ int ret = 0;
+ u8 key[SHA256_DIGEST_SIZE];
+
+ afs_lock_for_io(vnode);
+ if (vnode->content_ci)
+ goto out;
+
+ ci = crypto_alloc_skcipher("cbc(aes)", 0, 0);
+ if (IS_ERR(ci)) {
+ ret = PTR_ERR(ci);
+ pr_err("Can't allocate cipher: %d\n", ret);
+ goto out;
+ }
+
+ if (crypto_skcipher_ivsize(ci) > 16 &&
+ crypto_skcipher_blocksize(ci) > 16) {
+ pr_err("iv wrong size: %u\n", crypto_skcipher_ivsize(ci));
+ ret = -EINVAL;
+ goto error_ci;
+ }
+
+ sha256_init(&sha);
+ sha256_update(&sha, vnode->volume->cell->name, vnode->volume->cell->name_len);
+ sha256_update(&sha, (u8 *)&vnode->fid, sizeof(vnode->fid));
+ sha256_final(&sha, key);
+
+ crypto_skcipher_set_flags(ci, CRYPTO_TFM_REQ_FORBID_WEAK_KEYS);
+ ret = crypto_skcipher_setkey(ci, key, sizeof(key));
+ if (ret < 0) {
+ pr_err("Setkey failed: %d\n", ret);
+ goto error_ci;
+ }
+
+ vnode->content_ci = ci;
+ ret = 0;
+out:
+ afs_unlock_for_io(vnode);
+ return ret;
+
+error_ci:
+ crypto_free_skcipher(ci);
+ goto out;
+}
+
+/*
+ * Encrypt part of a write for fscrypt.
+ */
+int afs_encrypt_block(struct netfs_io_request *wreq,
+ unsigned long long start,
+ struct scatterlist *src_sg,
+ struct scatterlist *dst_sg,
+ gfp_t gfp)
+{
+ struct skcipher_request *req;
+ struct crypto_skcipher *ci = AFS_FS_I(wreq->inode)->content_ci;
+ size_t len = wreq->crypto_bsize, reqsize, ivsize;
+ int ret;
+ u8 *iv;
+
+ kenter("%llx", start);
+
+ reqsize = round_up(sizeof(struct skcipher_request) +
+ crypto_skcipher_reqsize(ci),
+ CRYPTO_MINALIGN);
+ ivsize = crypto_skcipher_ivsize(ci);
+
+ netfs_dump_sg("SRC", src_sg, 1);
+
+ req = kzalloc(reqsize + ivsize, gfp);
+ if (!req)
+ return -ENOMEM;
+
+ iv = (void *)req + reqsize;
+ *(__be64 *)iv = cpu_to_be64(start);
+
+ skcipher_request_set_tfm(req, ci);
+ skcipher_request_set_crypt(req, src_sg, dst_sg, len, iv);
+ ret = crypto_skcipher_encrypt(req);
+ if (ret < 0)
+ pr_err("R=%x Encrypt %llx failed: %d\n",
+ wreq->debug_id, start, ret);
+
+ netfs_dump_sg("ENC", dst_sg, 1);
+
+ skcipher_request_free(req);
+ return ret;
+}
+
+/*
+ * Decrypt part of a read for fscrypt. The caller reserved an extra
+ * scatterlist element before each of source_sg and dest_sg for our purposes,
+ * should we need them.
+ */
+int afs_decrypt_block(struct netfs_io_request *rreq,
+ unsigned long long start, size_t len,
+ struct scatterlist *src_sg, unsigned int n_src,
+ struct scatterlist *dst_sg, unsigned int n_dst)
+{
+ struct skcipher_request *req;
+ struct crypto_skcipher *ci = AFS_FS_I(rreq->inode)->content_ci;
+ size_t reqsize, ivsize;
+ u8 *iv;
+ int ret = -ENOMEM;
+ DECLARE_CRYPTO_WAIT(wait);
+
+ _enter("%llx,%zx", start, len);
+
+ netfs_dump_sg("DEC", src_sg, n_src);
+
+ reqsize = round_up(sizeof(struct skcipher_request) +
+ crypto_skcipher_reqsize(ci),
+ CRYPTO_MINALIGN);
+ ivsize = crypto_skcipher_ivsize(ci);
+
+ req = kzalloc(reqsize + ivsize, GFP_KERNEL);
+ if (!req)
+ return -ENOMEM;
+
+ iv = (void *)req + reqsize;
+ *(__be64 *)iv = cpu_to_be64(start);
+
+ skcipher_request_set_tfm(req, ci);
+ //skcipher_request_set_callback(
+ // req, CRYPTO_TFM_REQ_MAY_BACKLOG | CRYPTO_TFM_REQ_MAY_SLEEP,
+ // crypto_req_done, &wait);
+ skcipher_request_set_crypt(req, src_sg, dst_sg, len, iv);
+
+ //ret = crypto_wait_req(crypto_skcipher_decrypt(req), &wait);
+ ret = crypto_skcipher_decrypt(req);
+ if (ret < 0)
+ pr_err("Decrypt failed: %d\n", ret);
+
+ netfs_dump_sg("DEC", dst_sg, n_dst);
+
+ skcipher_request_free(req);
+ _leave(" = %d", ret);
+ return ret;
+}
diff --git a/fs/netfs/buffered_read.c b/fs/netfs/buffered_read.c
index 06765c2..2ecf8be 100644
--- a/fs/netfs/buffered_read.c
+++ b/fs/netfs/buffered_read.c
@@ -157,6 +157,7 @@ int netfs_prepare_read_buffer(struct netfs_io_subrequest *subreq,
case NETFS_UNBUFFERED_READ:
case NETFS_DIO_READ:
case NETFS_READ_GAPS:
+ case NETFS_RMW_READ:
return netfs_prepare_unbuffered_read_buffer(subreq, max_segs);
case NETFS_READ_SINGLE:
return netfs_prepare_read_single_buffer(subreq, max_segs);
diff --git a/fs/netfs/bvecq.c b/fs/netfs/bvecq.c
index 74110e7..860bf16 100644
--- a/fs/netfs/bvecq.c
+++ b/fs/netfs/bvecq.c
@@ -645,8 +645,10 @@ ssize_t bvecq_extract(struct bvecq_pos *pos, size_t max_size,
bvecq_pos_set(&tmp_pos, pos);
amount = bvecq_slice(&tmp_pos, max_size, max_slots, &nslots);
bvecq_pos_unset(&tmp_pos);
- if (nslots == 0)
+ if (nslots == 0) {
+ kleave(" = -EIO [nslots]");
return -EIO;
+ }
dst = bvecq_alloc_chain(nslots, GFP_KERNEL);
if (!dst)
diff --git a/fs/netfs/crypto.c b/fs/netfs/crypto.c
index 3ff8ed6..a34add2 100644
--- a/fs/netfs/crypto.c
+++ b/fs/netfs/crypto.c
@@ -4,7 +4,7 @@
* Copyright (C) 2026 Red Hat, Inc. All Rights Reserved.
* Written by David Howells (dhowells@redhat.com)
*/
-
+#define __KDEBUG
#include <linux/fs.h>
#include <linux/mm.h>
#include <linux/pagemap.h>
@@ -114,6 +114,12 @@ int netfs_encrypt(struct netfs_io_request *wreq, unsigned long long to, gfp_t gf
while (start < to) {
struct scatterlist sg;
+ _debug("ENCRYPT %llx-%llx", start, to);
+ _debug("enc %u/%u %x",
+ wreq->encrypt_cursor.slot,
+ wreq->encrypt_cursor.bvecq->nr_slots,
+ wreq->encrypt_cursor.offset);
+
sg_init_table(&sg, 1);
ret = netfs_bvecq_to_sglist(&wreq->encrypt_cursor, bsize, &sg, 1);
diff --git a/fs/netfs/direct_write.c b/fs/netfs/direct_write.c
index 4866304..ce994c6 100644
--- a/fs/netfs/direct_write.c
+++ b/fs/netfs/direct_write.c
@@ -186,6 +186,8 @@ static int netfs_unbuffered_load_bounce(struct netfs_io_subrequest *subreq)
size_t amount = subreq->len;
int ret;
+ kenter("");
+
/* Expand the bounce buffer as needed. */
to = round_up(subreq->start + subreq->len, wreq->crypto_bsize);
end = round_up(wreq->start + wreq->len, wreq->crypto_bsize);
@@ -212,12 +214,21 @@ static int netfs_unbuffered_load_bounce(struct netfs_io_subrequest *subreq)
if (amount > wreq->len - wreq->submitted)
amount = wreq->len - wreq->submitted;
+ kdebug("from:");
+ bvecq_dump(wreq->copy_cursor.bvecq);
+ kdebug("to:");
+ bvecq_dump(wreq->bounce_copy.bvecq);
+
got = bvecq_copy_to_bvecq(&wreq->copy_cursor, &wreq->bounce_copy, amount);
- if (got != amount)
+ if (got != amount) {
+ kleave(" = -EFAULT [got %zx != %zx]", got, amount);
return -EFAULT;
+ }
/* And then encrypt the data in-place. */
- return netfs_encrypt(wreq, to, GFP_KERNEL);
+ ret = netfs_encrypt(wreq, to, GFP_KERNEL);
+ kleave(" = %d", ret);
+ return ret;
}
/*
@@ -248,13 +259,18 @@ int netfs_prepare_unbuffered_write_buffer(struct netfs_io_subrequest *subreq,
if (copy) {
got = bvecq_extract(&stream->dispatch_cursor, len, max_segs,
&subreq->content.bvecq);
- if (got < 0)
+ if (got < 0) {
+ kleave(" = %zd [ex]", len);
return -ENOMEM;
+ }
len = got;
+
+ _debug("extract %zx/%zx", len, subreq->len);
} else {
bvecq_pos_set(&subreq->content, &stream->dispatch_cursor);
len = bvecq_slice(&stream->dispatch_cursor, len, max_segs, &subreq->nr_segs);
+ kdebug("slice %zx/%zx", len, subreq->len);
}
if (len < subreq->len) {
@@ -268,6 +284,7 @@ int netfs_prepare_unbuffered_write_buffer(struct netfs_io_subrequest *subreq,
stream->buffered -= subreq->len;
if (stream->buffered == 0)
netfs_all_subreqs_queued(subreq->rreq);
+ kleave(" = 0");
return 0;
}
@@ -372,6 +389,15 @@ static int netfs_unbuffered_write(struct netfs_io_request *wreq)
if (wreq->origin == NETFS_DIO_WRITE)
inode_dio_begin(wreq->inode);
+ if (wreq->copy_cursor.bvecq)
+ kdebug("copy %u/%u %x",
+ wreq->copy_cursor.slot, wreq->copy_cursor.bvecq->nr_slots,
+ wreq->copy_cursor.offset);
+
+ if (wreq->bounce_copy.bvecq)
+ kdebug("bounce %u/%u %x",
+ wreq->bounce_copy.slot, wreq->bounce_copy.bvecq->nr_slots,
+ wreq->bounce_copy.offset);
for (;;) {
bool retry = false;
@@ -519,6 +545,9 @@ ssize_t netfs_unbuffered_write_iter_locked(struct kiocb *iocb, struct iov_iter *
n, len, wreq->load_cursor.bvecq->nr_slots,
wreq->load_cursor.bvecq->max_slots);
+ kdebug("load %u/%u %x",
+ wreq->load_cursor.slot, wreq->load_cursor.bvecq->nr_slots, wreq->load_cursor.offset);
+
/* Set up the bounce buffer if we need it. Allow for padding the
* request out to the crypo block size and allocate at least one bvecq
* into it.
diff --git a/fs/netfs/write_collect.c b/fs/netfs/write_collect.c
index b3d8eda..1ebaf81 100644
--- a/fs/netfs/write_collect.c
+++ b/fs/netfs/write_collect.c
@@ -540,6 +540,8 @@ void netfs_write_subrequest_terminated(void *_op, ssize_t transferred_or_error)
_enter("%x[%x] %zd", wreq->debug_id, subreq->debug_index, transferred_or_error);
+ WARN_ON_ONCE(transferred_or_error == -ENOMEM);
+
switch (subreq->source) {
case NETFS_UPLOAD_TO_SERVER:
netfs_stat(&netfs_n_wh_upload_done);
diff --git a/fs/netfs/write_issue.c b/fs/netfs/write_issue.c
index 4f268c1..d2049dd 100644
--- a/fs/netfs/write_issue.c
+++ b/fs/netfs/write_issue.c
@@ -251,8 +251,10 @@ static int netfs_prepare_buffered_write_buffer(struct netfs_io_subrequest *subre
got = bvecq_extract(&stream->dispatch_cursor, subreq->len, max_segs,
&subreq->content.bvecq);
- if (got < 0)
+ if (got < 0) {
+ kleave(" = %zd [ex]", len);
return -ENOMEM;
+ }
len = got;
_debug("extract %zx/%zx", len, subreq->len);
@@ -737,6 +739,8 @@ static int netfs_queue_wb_folio(struct netfs_io_request *wreq,
GFP_NOFS);
if (ret < 0)
return ret;
+ kdebug("-- add --");
+ bvecq_dump(wreq->bounce_collect.bvecq);
}
if (unlikely(test_bit(NETFS_RREQ_CONTENT_ENCRYPTION, &wreq->flags))) {
diff --git a/include/linux/netfs.h b/include/linux/netfs.h
index fb2f89b..af81559 100644
--- a/include/linux/netfs.h
+++ b/include/linux/netfs.h
@@ -206,6 +206,7 @@ struct netfs_io_subrequest {
refcount_t ref;
short error; /* 0 or error that occurred */
unsigned short debug_index; /* Index in list (for debugging output) */
+ unsigned short back_excess; /* Amount of excess data from block rounding up */
u8 retry_count; /* The number of retries (0 on initial pass) */
enum netfs_io_source source; /* Where to read from/write to */
unsigned char stream_nr; /* I/O stream this belongs to */