* [PATCH v7 11/43] btrfs: add inode encryption contexts
From: Daniel Vacek @ 2026-05-13 8:52 UTC (permalink / raw)
To: Chris Mason, Josef Bacik, Eric Biggers, Theodore Y. Ts'o,
Jaegeuk Kim, Jens Axboe, David Sterba
Cc: linux-block, Daniel Vacek, linux-fscrypt, linux-btrfs,
linux-kernel, Omar Sandoval, Sweet Tea Dorminy
In-Reply-To: <20260513085340.3673127-1-neelx@suse.com>
From: Omar Sandoval <osandov@osandov.com>
fscrypt stores a context item with encrypted inodes that contains the
related encryption information. fscrypt provides an arbitrary blob for
the filesystem to store, and it does not clearly fit into an existing
structure, so this goes in a new item type.
Signed-off-by: Omar Sandoval <osandov@osandov.com>
Signed-off-by: Sweet Tea Dorminy <sweettea-kernel@dorminy.me>
Signed-off-by: Josef Bacik <josef@toxicpanda.com>
Signed-off-by: Daniel Vacek <neelx@suse.com>
---
v7 changes:
* Fix a path leak as found by Chri's AI review.
v6 changes:
* Shorten the inode context key macro name to BTRFS_FSCRYPT_INODE_CTX_KEY.
v5: https://lore.kernel.org/linux-btrfs/5a88efb484b0874a7430b83bc6e5f6b9aa5858d5.1706116485.git.josef@toxicpanda.com/
---
fs/btrfs/fscrypt.c | 116 ++++++++++++++++++++++++++++++++
fs/btrfs/fscrypt.h | 2 +
fs/btrfs/inode.c | 19 ++++++
fs/btrfs/ioctl.c | 8 ++-
include/uapi/linux/btrfs_tree.h | 10 +++
5 files changed, 153 insertions(+), 2 deletions(-)
diff --git a/fs/btrfs/fscrypt.c b/fs/btrfs/fscrypt.c
index 6cfba7d94e72..c503f817cbe7 100644
--- a/fs/btrfs/fscrypt.c
+++ b/fs/btrfs/fscrypt.c
@@ -1,10 +1,126 @@
// SPDX-License-Identifier: GPL-2.0
+#include <linux/iversion.h>
#include "ctree.h"
+#include "accessors.h"
#include "btrfs_inode.h"
+#include "disk-io.h"
+#include "fs.h"
#include "fscrypt.h"
+#include "ioctl.h"
+#include "messages.h"
+#include "transaction.h"
+#include "xattr.h"
+
+static int btrfs_fscrypt_get_context(struct inode *inode, void *ctx, size_t len)
+{
+ struct btrfs_key key = {
+ .objectid = btrfs_ino(BTRFS_I(inode)),
+ .type = BTRFS_FSCRYPT_INODE_CTX_KEY,
+ .offset = 0,
+ };
+ struct btrfs_path *path;
+ struct extent_buffer *leaf;
+ unsigned long ptr;
+ int ret;
+
+
+ path = btrfs_alloc_path();
+ if (!path)
+ return -ENOMEM;
+
+ ret = btrfs_search_slot(NULL, BTRFS_I(inode)->root, &key, path, 0, 0);
+ if (ret) {
+ len = -ENOENT;
+ goto out;
+ }
+
+ leaf = path->nodes[0];
+ ptr = btrfs_item_ptr_offset(leaf, path->slots[0]);
+ /* fscrypt provides max context length, but it could be less */
+ len = min_t(size_t, len, btrfs_item_size(leaf, path->slots[0]));
+ read_extent_buffer(leaf, ctx, ptr, len);
+
+out:
+ btrfs_free_path(path);
+ return len;
+}
+
+static int btrfs_fscrypt_set_context(struct inode *inode, const void *ctx,
+ size_t len, void *fs_data)
+{
+ struct btrfs_trans_handle *trans = fs_data;
+ struct btrfs_key key = {
+ .objectid = btrfs_ino(BTRFS_I(inode)),
+ .type = BTRFS_FSCRYPT_INODE_CTX_KEY,
+ .offset = 0,
+ };
+ struct btrfs_path *path = NULL;
+ struct extent_buffer *leaf;
+ unsigned long ptr;
+ int ret;
+
+ if (!trans)
+ trans = btrfs_start_transaction(BTRFS_I(inode)->root, 2);
+ if (IS_ERR(trans))
+ return PTR_ERR(trans);
+
+ path = btrfs_alloc_path();
+ if (!path) {
+ ret = -ENOMEM;
+ goto out_err;
+ }
+
+ ret = btrfs_search_slot(trans, BTRFS_I(inode)->root, &key, path, 0, 1);
+ if (ret < 0)
+ goto out_err;
+
+ if (ret > 0) {
+ btrfs_release_path(path);
+ ret = btrfs_insert_empty_item(trans, BTRFS_I(inode)->root, path, &key, len);
+ if (ret)
+ goto out_err;
+ }
+
+ leaf = path->nodes[0];
+ ptr = btrfs_item_ptr_offset(leaf, path->slots[0]);
+
+ len = min_t(size_t, len, btrfs_item_size(leaf, path->slots[0]));
+ write_extent_buffer(leaf, ctx, ptr, len);
+ btrfs_mark_buffer_dirty(trans, leaf);
+ btrfs_release_path(path);
+
+ if (fs_data)
+ goto out_err;
+
+ BTRFS_I(inode)->flags |= BTRFS_INODE_ENCRYPT;
+ btrfs_sync_inode_flags_to_i_flags(BTRFS_I(inode));
+ inode_inc_iversion(inode);
+ inode_set_ctime_current(inode);
+ ret = btrfs_update_inode(trans, BTRFS_I(inode));
+ if (ret)
+ goto out_abort;
+ btrfs_free_path(path);
+ btrfs_end_transaction(trans);
+ return 0;
+out_abort:
+ btrfs_abort_transaction(trans, ret);
+out_err:
+ if (!fs_data)
+ btrfs_end_transaction(trans);
+ btrfs_free_path(path);
+ return ret;
+}
+
+static bool btrfs_fscrypt_empty_dir(struct inode *inode)
+{
+ return inode->i_size == BTRFS_EMPTY_DIR_SIZE;
+}
const struct fscrypt_operations btrfs_fscrypt_ops = {
.inode_info_offs = (int)offsetof(struct btrfs_inode, i_crypt_info) -
(int)offsetof(struct btrfs_inode, vfs_inode),
+ .get_context = btrfs_fscrypt_get_context,
+ .set_context = btrfs_fscrypt_set_context,
+ .empty_dir = btrfs_fscrypt_empty_dir,
};
diff --git a/fs/btrfs/fscrypt.h b/fs/btrfs/fscrypt.h
index 7f4e6888bd43..80adb7e56826 100644
--- a/fs/btrfs/fscrypt.h
+++ b/fs/btrfs/fscrypt.h
@@ -5,6 +5,8 @@
#include <linux/fscrypt.h>
+#include "fs.h"
+
extern const struct fscrypt_operations btrfs_fscrypt_ops;
#endif /* BTRFS_FSCRYPT_H */
diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c
index 8f89e44b2d53..14bad4cdb900 100644
--- a/fs/btrfs/inode.c
+++ b/fs/btrfs/inode.c
@@ -60,6 +60,7 @@
#include "defrag.h"
#include "dir-item.h"
#include "file-item.h"
+#include "fscrypt.h"
#include "uuid-tree.h"
#include "ioctl.h"
#include "file.h"
@@ -6529,6 +6530,9 @@ int btrfs_new_inode_prepare(struct btrfs_new_inode_args *args,
struct inode *inode = args->inode;
int ret;
+ if (fscrypt_is_nokey_name(args->dentry))
+ return -ENOKEY;
+
if (!args->orphan) {
ret = fscrypt_setup_filename(dir, &args->dentry->d_name, 0,
&args->fname);
@@ -6564,6 +6568,9 @@ int btrfs_new_inode_prepare(struct btrfs_new_inode_args *args,
if (dir->i_security)
(*trans_num_items)++;
#endif
+ /* 1 to add fscrypt item */
+ if (args->encrypt)
+ (*trans_num_items)++;
if (args->orphan) {
/* 1 to add orphan item */
(*trans_num_items)++;
@@ -6776,6 +6783,11 @@ int btrfs_create_new_inode(struct btrfs_trans_handle *trans,
BTRFS_I(inode)->i_otime_sec = ts.tv_sec;
BTRFS_I(inode)->i_otime_nsec = ts.tv_nsec;
+ if (args->encrypt) {
+ BTRFS_I(inode)->flags |= BTRFS_INODE_ENCRYPT;
+ btrfs_sync_inode_flags_to_i_flags(BTRFS_I(inode));
+ }
+
/*
* We're going to fill the inode item now, so at this point the inode
* must be fully initialized.
@@ -6849,6 +6861,13 @@ int btrfs_create_new_inode(struct btrfs_trans_handle *trans,
goto discard;
}
}
+ if (args->encrypt) {
+ ret = fscrypt_set_context(inode, trans);
+ if (ret) {
+ btrfs_abort_transaction(trans, ret);
+ goto discard;
+ }
+ }
ret = btrfs_add_inode_to_root(BTRFS_I(inode), false);
if (WARN_ON(ret)) {
diff --git a/fs/btrfs/ioctl.c b/fs/btrfs/ioctl.c
index a39460bf68a7..6a37dd3cc5ee 100644
--- a/fs/btrfs/ioctl.c
+++ b/fs/btrfs/ioctl.c
@@ -153,6 +153,8 @@ static unsigned int btrfs_inode_flags_to_fsflags(const struct btrfs_inode *inode
iflags |= FS_DIRSYNC_FL;
if (flags & BTRFS_INODE_NODATACOW)
iflags |= FS_NOCOW_FL;
+ if (flags & BTRFS_INODE_ENCRYPT)
+ iflags |= FS_ENCRYPT_FL;
if (ro_flags & BTRFS_INODE_RO_VERITY)
iflags |= FS_VERITY_FL;
@@ -181,12 +183,14 @@ void btrfs_sync_inode_flags_to_i_flags(struct btrfs_inode *inode)
new_fl |= S_NOATIME;
if (inode->flags & BTRFS_INODE_DIRSYNC)
new_fl |= S_DIRSYNC;
+ if (inode->flags & BTRFS_INODE_ENCRYPT)
+ new_fl |= S_ENCRYPTED;
if (inode->ro_flags & BTRFS_INODE_RO_VERITY)
new_fl |= S_VERITY;
set_mask_bits(&inode->vfs_inode.i_flags,
S_SYNC | S_APPEND | S_IMMUTABLE | S_NOATIME | S_DIRSYNC |
- S_VERITY, new_fl);
+ S_VERITY | S_ENCRYPTED, new_fl);
}
/*
@@ -199,7 +203,7 @@ static int check_fsflags(unsigned int old_flags, unsigned int flags)
FS_NOATIME_FL | FS_NODUMP_FL | \
FS_SYNC_FL | FS_DIRSYNC_FL | \
FS_NOCOMP_FL | FS_COMPR_FL |
- FS_NOCOW_FL))
+ FS_NOCOW_FL | FS_ENCRYPT_FL))
return -EOPNOTSUPP;
/* COMPR and NOCOMP on new/old are valid */
diff --git a/include/uapi/linux/btrfs_tree.h b/include/uapi/linux/btrfs_tree.h
index cc3b9f7dccaf..3cec245268e7 100644
--- a/include/uapi/linux/btrfs_tree.h
+++ b/include/uapi/linux/btrfs_tree.h
@@ -167,6 +167,8 @@
#define BTRFS_VERITY_DESC_ITEM_KEY 36
#define BTRFS_VERITY_MERKLE_ITEM_KEY 37
+#define BTRFS_FSCRYPT_INODE_CTX_KEY 41
+
#define BTRFS_ORPHAN_ITEM_KEY 48
/* reserve 2-15 close to the inode for later flexibility */
@@ -431,6 +433,7 @@ static inline __u8 btrfs_dir_flags_to_ftype(__u8 flags)
#define BTRFS_INODE_NOATIME (1U << 9)
#define BTRFS_INODE_DIRSYNC (1U << 10)
#define BTRFS_INODE_COMPRESS (1U << 11)
+#define BTRFS_INODE_ENCRYPT (1U << 12)
#define BTRFS_INODE_ROOT_ITEM_INIT (1U << 31)
@@ -447,6 +450,7 @@ static inline __u8 btrfs_dir_flags_to_ftype(__u8 flags)
BTRFS_INODE_NOATIME | \
BTRFS_INODE_DIRSYNC | \
BTRFS_INODE_COMPRESS | \
+ BTRFS_INODE_ENCRYPT | \
BTRFS_INODE_ROOT_ITEM_INIT)
#define BTRFS_INODE_RO_VERITY (1U << 0)
@@ -1075,6 +1079,12 @@ enum {
BTRFS_NR_FILE_EXTENT_TYPES = 3,
};
+enum btrfs_encryption_type {
+ BTRFS_ENCRYPTION_NONE,
+ BTRFS_ENCRYPTION_FSCRYPT,
+ BTRFS_NR_ENCRYPTION_TYPES,
+};
+
struct btrfs_file_extent_item {
/*
* transaction id that created this extent
--
2.53.0
^ permalink raw reply related
* [PATCH v7 08/43] fscrypt: add documentation about extent encryption
From: Daniel Vacek @ 2026-05-13 8:52 UTC (permalink / raw)
To: Chris Mason, Josef Bacik, Eric Biggers, Theodore Y. Ts'o,
Jaegeuk Kim, Jens Axboe, David Sterba, Jonathan Corbet,
Shuah Khan
Cc: linux-block, Daniel Vacek, linux-fscrypt, linux-btrfs,
linux-kernel, linux-doc
In-Reply-To: <20260513085340.3673127-1-neelx@suse.com>
From: Josef Bacik <josef@toxicpanda.com>
Add a couple of sections to the fscrypt documentation about per-extent
encryption.
Signed-off-by: Josef Bacik <josef@toxicpanda.com>
Signed-off-by: Daniel Vacek <neelx@suse.com>
---
v7 changes:
* Fix spelling and typos.
No changes in v6.
v5: https://lore.kernel.org/linux-btrfs/7b2cc4dd423c3930e51b1ef5dd209164ff11c05a.1706116485.git.josef@toxicpanda.com/
---
Documentation/filesystems/fscrypt.rst | 41 +++++++++++++++++++++++++++
1 file changed, 41 insertions(+)
diff --git a/Documentation/filesystems/fscrypt.rst b/Documentation/filesystems/fscrypt.rst
index c0dd35f1af12..a1b0b50da869 100644
--- a/Documentation/filesystems/fscrypt.rst
+++ b/Documentation/filesystems/fscrypt.rst
@@ -283,6 +283,21 @@ alternative master keys or to support rotating master keys. Instead,
the master keys may be wrapped in userspace, e.g. as is done by the
`fscrypt <https://github.com/google/fscrypt>`_ tool.
+Per-extent encryption keys
+--------------------------
+
+For certain file systems, such as btrfs, it's desired to derive a
+per-extent encryption key. This is to enable features such as snapshots
+and reflink, where you could have different inodes pointing at the same
+extent. When a new extent is created fscrypt randomly generates a
+16-byte nonce and the file system stores it alongside the extent.
+Then, it uses a KDF (as described in `Key derivation function`_) to
+derive the extent's key from the master key and nonce.
+
+Currently the inode's master key and encryption policy must match the
+extent, so you cannot share extents between inodes that were encrypted
+differently.
+
DIRECT_KEY policies
-------------------
@@ -1483,6 +1498,27 @@ by the kernel and is used as KDF input or as a tweak to cause
different files to be encrypted differently; see `Per-file encryption
keys`_ and `DIRECT_KEY policies`_.
+Extent encryption context
+-------------------------
+
+The extent encryption context mirrors the important parts of the above
+`Encryption context`_, with a few omissions. The struct is defined as
+follows::
+
+ struct fscrypt_extent_context {
+ u8 version;
+ u8 encryption_mode;
+ u8 master_key_identifier[FSCRYPT_KEY_IDENTIFIER_SIZE];
+ u8 nonce[FSCRYPT_FILE_NONCE_SIZE];
+ };
+
+Currently all fields much match the containing inode's encryption
+context, with the exception of the nonce.
+
+Additionally extent encryption is only supported with
+FSCRYPT_EXTENT_CONTEXT_V2 using the standard policy; all other policies
+are disallowed.
+
Data path changes
-----------------
@@ -1506,6 +1542,11 @@ buffer. Some filesystems, such as UBIFS, already use temporary
buffers regardless of encryption. Other filesystems, such as ext4 and
F2FS, have to allocate bounce pages specially for encryption.
+Inline encryption is not optional for extent encryption based file
+systems; the amount of objects required to be kept around is too much.
+Inline encryption handles the object lifetime details which results in a
+cleaner implementation.
+
Filename hashing and encoding
-----------------------------
--
2.53.0
^ permalink raw reply related
* [PATCH v7 07/43] fscrypt: expose fscrypt_nokey_name
From: Daniel Vacek @ 2026-05-13 8:52 UTC (permalink / raw)
To: Chris Mason, Josef Bacik, Eric Biggers, Theodore Y. Ts'o,
Jaegeuk Kim, Jens Axboe, David Sterba
Cc: linux-block, Daniel Vacek, linux-fscrypt, linux-btrfs,
linux-kernel, Omar Sandoval, Sweet Tea Dorminy
In-Reply-To: <20260513085340.3673127-1-neelx@suse.com>
From: Omar Sandoval <osandov@osandov.com>
btrfs stores its data structures, including filenames in directories, in
its own buffer implementation, struct extent_buffer, composed of
several non-contiguous pages. We could copy filenames into a
temporary buffer and use fscrypt_match_name() against that buffer, such
extensive memcpying would be expensive. Instead, exposing
fscrypt_nokey_name as in this change allows btrfs to recapitulate
fscrypt_match_name() using methods on struct extent_buffer instead of
dealing with a raw byte array.
Signed-off-by: Omar Sandoval <osandov@osandov.com>
Signed-off-by: Sweet Tea Dorminy <sweettea-kernel@dorminy.me>
Signed-off-by: Josef Bacik <josef@toxicpanda.com>
Signed-off-by: Daniel Vacek <neelx@suse.com>
---
v5: https://lore.kernel.org/linux-btrfs/132b64edf1e6b705995fb1a6dc2f194527f6be75.1706116485.git.josef@toxicpanda.com/
* No changes since.
---
fs/crypto/fname.c | 36 ------------------------------------
include/linux/fscrypt.h | 36 ++++++++++++++++++++++++++++++++++++
2 files changed, 36 insertions(+), 36 deletions(-)
diff --git a/fs/crypto/fname.c b/fs/crypto/fname.c
index 629eb0d72e86..ca73efcf5658 100644
--- a/fs/crypto/fname.c
+++ b/fs/crypto/fname.c
@@ -27,42 +27,6 @@
*/
#define FSCRYPT_FNAME_MIN_MSG_LEN 16
-/*
- * struct fscrypt_nokey_name - identifier for directory entry when key is absent
- *
- * When userspace lists an encrypted directory without access to the key, the
- * filesystem must present a unique "no-key name" for each filename that allows
- * it to find the directory entry again if requested. Naively, that would just
- * mean using the ciphertext filenames. However, since the ciphertext filenames
- * can contain illegal characters ('\0' and '/'), they must be encoded in some
- * way. We use base64url. But that can cause names to exceed NAME_MAX (255
- * bytes), so we also need to use a strong hash to abbreviate long names.
- *
- * The filesystem may also need another kind of hash, the "dirhash", to quickly
- * find the directory entry. Since filesystems normally compute the dirhash
- * over the on-disk filename (i.e. the ciphertext), it's not computable from
- * no-key names that abbreviate the ciphertext using the strong hash to fit in
- * NAME_MAX. It's also not computable if it's a keyed hash taken over the
- * plaintext (but it may still be available in the on-disk directory entry);
- * casefolded directories use this type of dirhash. At least in these cases,
- * each no-key name must include the name's dirhash too.
- *
- * To meet all these requirements, we base64url-encode the following
- * variable-length structure. It contains the dirhash, or 0's if the filesystem
- * didn't provide one; up to 149 bytes of the ciphertext name; and for
- * ciphertexts longer than 149 bytes, also the SHA-256 of the remaining bytes.
- *
- * This ensures that each no-key name contains everything needed to find the
- * directory entry again, contains only legal characters, doesn't exceed
- * NAME_MAX, is unambiguous unless there's a SHA-256 collision, and that we only
- * take the performance hit of SHA-256 on very long filenames (which are rare).
- */
-struct fscrypt_nokey_name {
- u32 dirhash[2];
- u8 bytes[149];
- u8 sha256[SHA256_DIGEST_SIZE];
-}; /* 189 bytes => 252 bytes base64url-encoded, which is <= NAME_MAX (255) */
-
/*
* Decoded size of max-size no-key name, i.e. a name that was abbreviated using
* the strong hash and thus includes the 'sha256' field. This isn't simply
diff --git a/include/linux/fscrypt.h b/include/linux/fscrypt.h
index a1cbd1be154c..9eef488340ea 100644
--- a/include/linux/fscrypt.h
+++ b/include/linux/fscrypt.h
@@ -56,6 +56,42 @@ struct fscrypt_name {
#define fname_name(p) ((p)->disk_name.name)
#define fname_len(p) ((p)->disk_name.len)
+/*
+ * struct fscrypt_nokey_name - identifier for directory entry when key is absent
+ *
+ * When userspace lists an encrypted directory without access to the key, the
+ * filesystem must present a unique "no-key name" for each filename that allows
+ * it to find the directory entry again if requested. Naively, that would just
+ * mean using the ciphertext filenames. However, since the ciphertext filenames
+ * can contain illegal characters ('\0' and '/'), they must be encoded in some
+ * way. We use base64url. But that can cause names to exceed NAME_MAX (255
+ * bytes), so we also need to use a strong hash to abbreviate long names.
+ *
+ * The filesystem may also need another kind of hash, the "dirhash", to quickly
+ * find the directory entry. Since filesystems normally compute the dirhash
+ * over the on-disk filename (i.e. the ciphertext), it's not computable from
+ * no-key names that abbreviate the ciphertext using the strong hash to fit in
+ * NAME_MAX. It's also not computable if it's a keyed hash taken over the
+ * plaintext (but it may still be available in the on-disk directory entry);
+ * casefolded directories use this type of dirhash. At least in these cases,
+ * each no-key name must include the name's dirhash too.
+ *
+ * To meet all these requirements, we base64url-encode the following
+ * variable-length structure. It contains the dirhash, or 0's if the filesystem
+ * didn't provide one; up to 149 bytes of the ciphertext name; and for
+ * ciphertexts longer than 149 bytes, also the SHA-256 of the remaining bytes.
+ *
+ * This ensures that each no-key name contains everything needed to find the
+ * directory entry again, contains only legal characters, doesn't exceed
+ * NAME_MAX, is unambiguous unless there's a SHA-256 collision, and that we only
+ * take the performance hit of SHA-256 on very long filenames (which are rare).
+ */
+struct fscrypt_nokey_name {
+ u32 dirhash[2];
+ u8 bytes[149];
+ u8 sha256[32];
+}; /* 189 bytes => 252 bytes base64url-encoded, which is <= NAME_MAX (255) */
+
/* Maximum value for the third parameter of fscrypt_operations.set_context(). */
#define FSCRYPT_SET_CONTEXT_MAX_SIZE 40
--
2.53.0
^ permalink raw reply related
* [PATCH v7 06/43] fscrypt: add a process_bio hook to fscrypt_operations
From: Daniel Vacek @ 2026-05-13 8:52 UTC (permalink / raw)
To: Chris Mason, Josef Bacik, Eric Biggers, Theodore Y. Ts'o,
Jaegeuk Kim, Jens Axboe, David Sterba
Cc: linux-block, Daniel Vacek, linux-fscrypt, linux-btrfs,
linux-kernel
In-Reply-To: <20260513085340.3673127-1-neelx@suse.com>
From: Josef Bacik <josef@toxicpanda.com>
This will allow file systems to set a process_bio hook for inline
encryption. This will be utilized by btrfs in order to make sure the
checksumming work is done on the encrypted bio's.
Signed-off-by: Josef Bacik <josef@toxicpanda.com>
Signed-off-by: Daniel Vacek <neelx@suse.com>
---
v5: https://lore.kernel.org/linux-btrfs/2c638e5fa1b7868dbf79d932b15364c3c30ca9de.1706116485.git.josef@toxicpanda.com/
* No changes since.
---
fs/crypto/inline_crypt.c | 2 +-
include/linux/fscrypt.h | 14 ++++++++++++++
2 files changed, 15 insertions(+), 1 deletion(-)
diff --git a/fs/crypto/inline_crypt.c b/fs/crypto/inline_crypt.c
index c51c6eb2259f..fde844aaac1a 100644
--- a/fs/crypto/inline_crypt.c
+++ b/fs/crypto/inline_crypt.c
@@ -179,7 +179,7 @@ int fscrypt_prepare_inline_crypt_key(struct fscrypt_prepared_key *prep_key,
err = blk_crypto_init_key(blk_key, key_bytes, key_size, key_type,
crypto_mode, fscrypt_get_dun_bytes(ci),
1U << ci->ci_data_unit_bits,
- NULL);
+ sb->s_cop->process_bio);
if (err) {
fscrypt_err(inode, "error %d initializing blk-crypto key", err);
goto fail;
diff --git a/include/linux/fscrypt.h b/include/linux/fscrypt.h
index dda5b1d32f78..a1cbd1be154c 100644
--- a/include/linux/fscrypt.h
+++ b/include/linux/fscrypt.h
@@ -16,6 +16,7 @@
#include <linux/fs.h>
#include <linux/mm.h>
#include <linux/slab.h>
+#include <linux/blk-crypto.h>
#include <uapi/linux/fscrypt.h>
/*
@@ -205,6 +206,19 @@ struct fscrypt_operations {
*/
struct block_device **(*get_devices)(struct super_block *sb,
unsigned int *num_devs);
+
+ /*
+ * A callback if the file system requires the ability to process the
+ * encrypted bio, used only with inline encryption.
+ *
+ * @orig_bio: the original bio submitted.
+ * @enc_bio: the encrypted bio.
+ *
+ * For writes the enc_bio will be different from the orig_bio, for reads
+ * they will be the same. For reads we get the bio before it is
+ * decrypted, for writes we get the bio before it is submitted.
+ */
+ blk_crypto_process_bio_t process_bio;
};
int fscrypt_d_revalidate(struct inode *dir, const struct qstr *name,
--
2.53.0
^ permalink raw reply related
* [PATCH v7 04/43] fscrypt: conditionally don't wipe mk secret until the last active user is done
From: Daniel Vacek @ 2026-05-13 8:52 UTC (permalink / raw)
To: Chris Mason, Josef Bacik, Eric Biggers, Theodore Y. Ts'o,
Jaegeuk Kim, Jens Axboe, David Sterba
Cc: linux-block, Daniel Vacek, linux-fscrypt, linux-btrfs,
linux-kernel
In-Reply-To: <20260513085340.3673127-1-neelx@suse.com>
From: Josef Bacik <josef@toxicpanda.com>
Previously we were wiping the master key secret when we do
FS_IOC_REMOVE_ENCRYPTION_KEY, and then using the fact that it was
cleared as the mechanism from keeping new users from being setup. This
works with inode based encryption, as the per-inode key is derived at
setup time, so the secret disappearing doesn't affect any currently open
files from being able to continue working.
However for extent based encryption we do our key derivation at page
writeout and readpage time, which means we need the master key secret to
be available while we still have our file open.
Since the master key lifetime is controlled by a flag, move the clearing
of the secret to the mk_active_users cleanup stage if we have extent
based encryption enabled on this super block. This counter represents
the actively open files that still exist on the file system, and thus
should still be able to operate normally. Once the last user is closed
we can clear the secret. Until then no new users are allowed, and this
allows currently open files to continue to operate until they're closed.
Signed-off-by: Josef Bacik <josef@toxicpanda.com>
Signed-off-by: Daniel Vacek <neelx@suse.com>
---
v7 changes:
* Updated the comment about key status in fscrypt_master_key_secret structure
as suggested by Eric.
No changes in v6.
v5: https://lore.kernel.org/linux-btrfs/5f28e46ce99d918a16f5bf4d8190870d0fffefc4.1706116485.git.josef@toxicpanda.com/
---
fs/crypto/fscrypt_private.h | 9 ++++++---
fs/crypto/keyring.c | 18 +++++++++++++++++-
2 files changed, 23 insertions(+), 4 deletions(-)
diff --git a/fs/crypto/fscrypt_private.h b/fs/crypto/fscrypt_private.h
index e5ab9893dfed..2f5f4e7f8f65 100644
--- a/fs/crypto/fscrypt_private.h
+++ b/fs/crypto/fscrypt_private.h
@@ -592,9 +592,12 @@ struct fscrypt_master_key_secret {
*
* FSCRYPT_KEY_STATUS_INCOMPLETELY_REMOVED
* Removal of this key has been initiated, but some inodes that were
- * unlocked with it are still in-use. Like ABSENT, ->mk_secret is wiped,
- * and the key can no longer be used to unlock inodes. Unlike ABSENT, the
- * key is still in the keyring; ->mk_decrypted_inodes is nonempty; and
+ * unlocked with it are still in-use.
+ * For filesystems using per-extent encryption ->mk_secret is still
+ * being kept as the per-extent keys are derived at writeout time.
+ * Otherwise, like ABSENT, ->mk_secret is wiped, and the key can
+ * no longer be used to unlock inodes. Unlike ABSENT, the key is
+ * still in the keyring; ->mk_decrypted_inodes is nonempty; and
* ->mk_active_refs > 0, being equal to the size of ->mk_decrypted_inodes.
*
* This state transitions to ABSENT if ->mk_decrypted_inodes becomes empty,
diff --git a/fs/crypto/keyring.c b/fs/crypto/keyring.c
index be8e6e8011f2..796e02a0db25 100644
--- a/fs/crypto/keyring.c
+++ b/fs/crypto/keyring.c
@@ -110,6 +110,14 @@ void fscrypt_put_master_key_activeref(struct super_block *sb,
WARN_ON_ONCE(mk->mk_present);
WARN_ON_ONCE(!list_empty(&mk->mk_decrypted_inodes));
+ /* We can't wipe the master key secret until the last activeref is
+ * dropped on the master key with per-extent encryption since the key
+ * derivation continues to happen as long as there are active refs.
+ * Wipe it here now that we're done using it.
+ */
+ if (sb->s_cop->has_per_extent_encryption)
+ wipe_master_key_secret(&mk->mk_secret);
+
for (i = 0; i <= FSCRYPT_MODE_MAX; i++) {
fscrypt_destroy_prepared_key(
sb, &mk->mk_direct_keys[i]);
@@ -134,7 +142,15 @@ static void fscrypt_initiate_key_removal(struct super_block *sb,
struct fscrypt_master_key *mk)
{
WRITE_ONCE(mk->mk_present, false);
- wipe_master_key_secret(&mk->mk_secret);
+
+ /*
+ * Per-extent encryption requires the master key to stick around until
+ * writeout has completed as we derive the per-extent keys at writeout
+ * time. Once the activeref drops to 0 we'll wipe the master secret
+ * key.
+ */
+ if (!sb->s_cop->has_per_extent_encryption)
+ wipe_master_key_secret(&mk->mk_secret);
fscrypt_put_master_key_activeref(sb, mk);
}
--
2.53.0
^ permalink raw reply related
* [PATCH v7 05/43] blk-crypto: add a process bio callback
From: Daniel Vacek @ 2026-05-13 8:52 UTC (permalink / raw)
To: Chris Mason, Josef Bacik, Eric Biggers, Theodore Y. Ts'o,
Jaegeuk Kim, Jens Axboe, David Sterba
Cc: linux-block, Daniel Vacek, linux-fscrypt, linux-btrfs,
linux-kernel
In-Reply-To: <20260513085340.3673127-1-neelx@suse.com>
From: Josef Bacik <josef@toxicpanda.com>
Btrfs does checksumming, and the checksums need to match the bytes on
disk. In order to facilitate this add a process bio callback for the
blk-crypto layer. This allows the file system to specify a callback and
then can process the encrypted bio as necessary.
For btrfs, writes will have the checksums calculated and saved into our
relevant data structures for storage once the write completes. For
reads we will validate the checksums match what is on disk and error out
if there is a mismatch.
This is incompatible with native encryption obviously, so make sure we
don't use native encryption if this callback is set.
Signed-off-by: Josef Bacik <josef@toxicpanda.com>
Signed-off-by: Daniel Vacek <neelx@suse.com>
---
v7 changes:
* Adapt to fscrypt changes
- b37fbce460ad ("blk-crypto: optimize bio splitting in blk_crypto_fallback_encrypt_bio")
No changes in v6.
v5: https://lore.kernel.org/linux-btrfs/66c781f3b2afdf2c558efdf33a7bba8bcfe47ce7.1706116485.git.josef@toxicpanda.com/
---
block/blk-crypto-fallback.c | 41 +++++++++++++++++++++++++++++++++++++
block/blk-crypto-internal.h | 8 ++++++++
block/blk-crypto-profile.c | 2 ++
block/blk-crypto.c | 6 +++++-
fs/crypto/inline_crypt.c | 3 ++-
include/linux/blk-crypto.h | 15 ++++++++++++--
6 files changed, 71 insertions(+), 4 deletions(-)
diff --git a/block/blk-crypto-fallback.c b/block/blk-crypto-fallback.c
index 61f595410832..5d35f0687bf8 100644
--- a/block/blk-crypto-fallback.c
+++ b/block/blk-crypto-fallback.c
@@ -330,6 +330,17 @@ static void __blk_crypto_fallback_encrypt_bio(struct bio *src_bio,
}
}
+ /* Process the encrypted bio before we submit it. */
+ if (bc->bc_key->crypto_cfg.process_bio) {
+ blk_status_t status;
+
+ status = bc->bc_key->crypto_cfg.process_bio(src_bio, enc_bio);
+ if (status != BLK_STS_OK) {
+ enc_bio->bi_status = status;
+ goto out_free_enc_bio;
+ }
+ }
+
submit_bio(enc_bio);
return;
@@ -358,6 +369,16 @@ static void blk_crypto_fallback_encrypt_bio(struct bio *src_bio)
struct blk_crypto_keyslot *slot;
blk_status_t status;
+ /*
+ * We cannot split bio's that have process_bio, as they require the original bio.
+ * The upper layer must make sure to limit the submitted bio's appropriately.
+ */
+ if (bio_segments(src_bio) > BIO_MAX_VECS && bc->bc_key->crypto_cfg.process_bio) {
+ src_bio->bi_status = BLK_STS_RESOURCE;
+ bio_endio(src_bio);
+ return;
+ }
+
status = blk_crypto_get_keyslot(blk_crypto_fallback_profile,
bc->bc_key, &slot);
if (status != BLK_STS_OK) {
@@ -427,6 +448,13 @@ static void blk_crypto_fallback_decrypt_bio(struct work_struct *work)
struct blk_crypto_keyslot *slot;
blk_status_t status;
+ /* Process the bio first before trying to decrypt. */
+ if (bc->bc_key->crypto_cfg.process_bio) {
+ status = bc->bc_key->crypto_cfg.process_bio(bio, bio);
+ if (status != BLK_STS_OK)
+ goto out;
+ }
+
status = blk_crypto_get_keyslot(blk_crypto_fallback_profile,
bc->bc_key, &slot);
if (status == BLK_STS_OK) {
@@ -435,12 +463,25 @@ static void blk_crypto_fallback_decrypt_bio(struct work_struct *work)
blk_crypto_fallback_tfm(slot));
blk_crypto_put_keyslot(slot);
}
+out:
mempool_free(f_ctx, bio_fallback_crypt_ctx_pool);
bio->bi_status = status;
bio_endio(bio);
}
+/**
+ * blk_crypto_profile_is_fallback - check if this profile is the fallback
+ * profile
+ * @profile: the profile we're checking
+ *
+ * This is just a quick check to make sure @profile is the fallback profile.
+ */
+bool blk_crypto_profile_is_fallback(struct blk_crypto_profile *profile)
+{
+ return profile == blk_crypto_fallback_profile;
+}
+
/**
* blk_crypto_fallback_decrypt_endio - queue bio for fallback decryption
*
diff --git a/block/blk-crypto-internal.h b/block/blk-crypto-internal.h
index 742694213529..c857ebeb9637 100644
--- a/block/blk-crypto-internal.h
+++ b/block/blk-crypto-internal.h
@@ -226,6 +226,8 @@ int blk_crypto_fallback_start_using_mode(enum blk_crypto_mode_num mode_num);
int blk_crypto_fallback_evict_key(const struct blk_crypto_key *key);
+bool blk_crypto_profile_is_fallback(struct blk_crypto_profile *profile);
+
#else /* CONFIG_BLK_INLINE_ENCRYPTION_FALLBACK */
static inline int
@@ -241,6 +243,12 @@ blk_crypto_fallback_evict_key(const struct blk_crypto_key *key)
return 0;
}
+static inline bool
+blk_crypto_profile_is_fallback(struct blk_crypto_profile *profile)
+{
+ return false;
+}
+
#endif /* CONFIG_BLK_INLINE_ENCRYPTION_FALLBACK */
#endif /* __LINUX_BLK_CRYPTO_INTERNAL_H */
diff --git a/block/blk-crypto-profile.c b/block/blk-crypto-profile.c
index 4ac74443687a..ced35ee186f0 100644
--- a/block/blk-crypto-profile.c
+++ b/block/blk-crypto-profile.c
@@ -352,6 +352,8 @@ bool __blk_crypto_cfg_supported(struct blk_crypto_profile *profile,
return false;
if (!(profile->key_types_supported & cfg->key_type))
return false;
+ if (cfg->process_bio && !blk_crypto_profile_is_fallback(profile))
+ return false;
return true;
}
diff --git a/block/blk-crypto.c b/block/blk-crypto.c
index 856d3c5b1fa0..d93c593ae1eb 100644
--- a/block/blk-crypto.c
+++ b/block/blk-crypto.c
@@ -300,6 +300,8 @@ int __blk_crypto_rq_bio_prep(struct request *rq, struct bio *bio,
* @dun_bytes: number of bytes that will be used to specify the DUN when this
* key is used
* @data_unit_size: the data unit size to use for en/decryption
+ * @process_bio: the call back if the upper layer needs to process the encrypted
+ * bio
*
* Return: 0 on success, -errno on failure. The caller is responsible for
* zeroizing both blk_key and key_bytes when done with them.
@@ -309,7 +311,8 @@ int blk_crypto_init_key(struct blk_crypto_key *blk_key,
enum blk_crypto_key_type key_type,
enum blk_crypto_mode_num crypto_mode,
unsigned int dun_bytes,
- unsigned int data_unit_size)
+ unsigned int data_unit_size,
+ blk_crypto_process_bio_t process_bio)
{
const struct blk_crypto_mode *mode;
@@ -343,6 +346,7 @@ int blk_crypto_init_key(struct blk_crypto_key *blk_key,
blk_key->crypto_cfg.dun_bytes = dun_bytes;
blk_key->crypto_cfg.data_unit_size = data_unit_size;
blk_key->crypto_cfg.key_type = key_type;
+ blk_key->crypto_cfg.process_bio = process_bio;
blk_key->data_unit_size_bits = ilog2(data_unit_size);
blk_key->size = key_size;
memcpy(blk_key->bytes, key_bytes, key_size);
diff --git a/fs/crypto/inline_crypt.c b/fs/crypto/inline_crypt.c
index 31791fb98a9e..c51c6eb2259f 100644
--- a/fs/crypto/inline_crypt.c
+++ b/fs/crypto/inline_crypt.c
@@ -178,7 +178,8 @@ int fscrypt_prepare_inline_crypt_key(struct fscrypt_prepared_key *prep_key,
err = blk_crypto_init_key(blk_key, key_bytes, key_size, key_type,
crypto_mode, fscrypt_get_dun_bytes(ci),
- 1U << ci->ci_data_unit_bits);
+ 1U << ci->ci_data_unit_bits,
+ NULL);
if (err) {
fscrypt_err(inode, "error %d initializing blk-crypto key", err);
goto fail;
diff --git a/include/linux/blk-crypto.h b/include/linux/blk-crypto.h
index f7c3cb4a342f..34512f6f5086 100644
--- a/include/linux/blk-crypto.h
+++ b/include/linux/blk-crypto.h
@@ -7,7 +7,7 @@
#define __LINUX_BLK_CRYPTO_H
#include <linux/minmax.h>
-#include <linux/types.h>
+#include <linux/blk_types.h>
#include <uapi/linux/blk-crypto.h>
enum blk_crypto_mode_num {
@@ -19,6 +19,14 @@ enum blk_crypto_mode_num {
BLK_ENCRYPTION_MODE_MAX,
};
+/*
+ * orig_bio must be the bio that was submitted from the upper layer as the upper
+ * layer could have used a specific bioset and expect the orig_bio to be from
+ * its bioset.
+ */
+typedef blk_status_t (*blk_crypto_process_bio_t)(struct bio *orig_bio,
+ struct bio *enc_bio);
+
/*
* Supported types of keys. Must be bitflags due to their use in
* blk_crypto_profile::key_types_supported.
@@ -77,12 +85,14 @@ enum blk_crypto_key_type {
* filesystem block size or the disk sector size.
* @dun_bytes: the maximum number of bytes of DUN used when using this key
* @key_type: the type of this key -- either raw or hardware-wrapped
+ * @proces_bio: optional callback to process encrypted bios.
*/
struct blk_crypto_config {
enum blk_crypto_mode_num crypto_mode;
unsigned int data_unit_size;
unsigned int dun_bytes;
enum blk_crypto_key_type key_type;
+ blk_crypto_process_bio_t process_bio;
};
/**
@@ -150,7 +160,8 @@ int blk_crypto_init_key(struct blk_crypto_key *blk_key,
enum blk_crypto_key_type key_type,
enum blk_crypto_mode_num crypto_mode,
unsigned int dun_bytes,
- unsigned int data_unit_size);
+ unsigned int data_unit_size,
+ blk_crypto_process_bio_t process_bio);
int blk_crypto_start_using_key(struct block_device *bdev,
const struct blk_crypto_key *key);
--
2.53.0
^ permalink raw reply related
* [PATCH v7 03/43] fscrypt: add a __fscrypt_file_open helper
From: Daniel Vacek @ 2026-05-13 8:52 UTC (permalink / raw)
To: Chris Mason, Josef Bacik, Eric Biggers, Theodore Y. Ts'o,
Jaegeuk Kim, Jens Axboe, David Sterba
Cc: linux-block, Daniel Vacek, linux-fscrypt, linux-btrfs,
linux-kernel
In-Reply-To: <20260513085340.3673127-1-neelx@suse.com>
From: Josef Bacik <josef@toxicpanda.com>
We have fscrypt_file_open() which is meant to be called on files being
opened so that their key is loaded when we start reading data from them.
However for btrfs send we are opening the inode directly without a filp,
so we need a different helper to make sure we can load the fscrypt
context for the inode before reading its contents.
Signed-off-by: Josef Bacik <josef@toxicpanda.com>
Signed-off-by: Daniel Vacek <neelx@suse.com>
---
No changes in v7.
v6 changes:
* Adapted to fscrypt changes since the last two years.
v5: https://lore.kernel.org/linux-btrfs/4a372419c3fe6ad425e1b124c342a054e9d6db23.1706116485.git.josef@toxicpanda.com/
---
fs/crypto/hooks.c | 38 ++++++++++++++++++++++++++++++++------
include/linux/fscrypt.h | 8 ++++++++
2 files changed, 40 insertions(+), 6 deletions(-)
diff --git a/fs/crypto/hooks.c b/fs/crypto/hooks.c
index a7a8a3f581a0..3142cf106bde 100644
--- a/fs/crypto/hooks.c
+++ b/fs/crypto/hooks.c
@@ -9,6 +9,37 @@
#include "fscrypt_private.h"
+/**
+ * __fscrypt_file_open() - prepare for filesystem-internal access to a
+ * possibly-encrypted regular file
+ * @dir: the inode for the directory via which the file is being accessed
+ * @inode: the inode being "opened"
+ *
+ * This is like fscrypt_file_open(), but instead of taking the 'struct file'
+ * being opened it takes the parent directory explicitly. This is intended for
+ * use cases such as "send/receive" which involve the filesystem accessing file
+ * contents without setting up a 'struct file'.
+ *
+ * Return: 0 on success, -ENOKEY if the key is missing, or another -errno code
+ */
+int __fscrypt_file_open(struct inode *dir, struct inode *inode)
+{
+ int err;
+
+ err = fscrypt_require_key(inode);
+ if (err)
+ return err;
+
+ if (!fscrypt_has_permitted_context(dir, inode)) {
+ fscrypt_warn(inode,
+ "Inconsistent encryption context (parent directory: %llu)",
+ dir->i_ino);
+ return -EPERM;
+ }
+ return 0;
+}
+EXPORT_SYMBOL_GPL(__fscrypt_file_open);
+
/**
* fscrypt_file_open() - prepare to open a possibly-encrypted regular file
* @inode: the inode being opened
@@ -60,12 +91,7 @@ int fscrypt_file_open(struct inode *inode, struct file *filp)
rcu_read_unlock();
dentry_parent = dget_parent(dentry);
- if (!fscrypt_has_permitted_context(d_inode(dentry_parent), inode)) {
- fscrypt_warn(inode,
- "Inconsistent encryption context (parent directory: %llu)",
- d_inode(dentry_parent)->i_ino);
- err = -EPERM;
- }
+ err = __fscrypt_file_open(d_inode(dentry_parent), inode);
dput(dentry_parent);
return err;
}
diff --git a/include/linux/fscrypt.h b/include/linux/fscrypt.h
index f216f1a78069..dda5b1d32f78 100644
--- a/include/linux/fscrypt.h
+++ b/include/linux/fscrypt.h
@@ -471,6 +471,7 @@ int fscrypt_zeroout_range(const struct inode *inode, loff_t pos,
/* hooks.c */
int fscrypt_file_open(struct inode *inode, struct file *filp);
+int __fscrypt_file_open(struct inode *dir, struct inode *inode);
int __fscrypt_prepare_link(struct inode *inode, struct inode *dir,
struct dentry *dentry);
int __fscrypt_prepare_rename(struct inode *old_dir, struct dentry *old_dentry,
@@ -818,6 +819,13 @@ static inline int fscrypt_file_open(struct inode *inode, struct file *filp)
return 0;
}
+static inline int __fscrypt_file_open(struct inode *dir, struct inode *inode)
+{
+ if (IS_ENCRYPTED(inode))
+ return -EOPNOTSUPP;
+ return 0;
+}
+
static inline int __fscrypt_prepare_link(struct inode *inode, struct inode *dir,
struct dentry *dentry)
{
--
2.53.0
^ permalink raw reply related
* [PATCH v7 01/43] fscrypt: add per-extent encryption support
From: Daniel Vacek @ 2026-05-13 8:52 UTC (permalink / raw)
To: Chris Mason, Josef Bacik, Eric Biggers, Theodore Y. Ts'o,
Jaegeuk Kim, Jens Axboe, David Sterba
Cc: linux-block, Daniel Vacek, linux-fscrypt, linux-btrfs,
linux-kernel
In-Reply-To: <20260513085340.3673127-1-neelx@suse.com>
From: Josef Bacik <josef@toxicpanda.com>
This adds the code necessary for per-extent encryption. We will store a
nonce for every extent we create, and then use the inode's policy and
the extents nonce to derive a per-extent key.
This is meant to be flexible, if we choose to expand the on-disk extent
information in the future we have a version number we can use to change
what exists on disk.
The file system indicates it wants to use per-extent encryption by
setting s_cop->has_per_extent_encryption. This also requires the use of
inline block encryption.
The support is relatively straightforward, the only "extra" bit is we're
deriving a per-extent key to use for the encryption, the inode still
controls the policy and access to the master key.
Since extent based encryption uses a lot of keys, we're requiring the
use of inline block crypto if you use extent-based encryption. This
enables us to take advantage of the built in pooling and reclamation of
the crypto structures that underpin all of the encryption.
The different encryption related options for fscrypt are too numerous to
support for extent based encryption. Support for a few of these options
could possibly be added, but since they're niche options simply reject
them for file systems using extent based encryption.
Signed-off-by: Josef Bacik <josef@toxicpanda.com>
Signed-off-by: Daniel Vacek <neelx@suse.com>
---
v7 changes:
* Code cleanup in setup_extent_info(), no functional changes.
* API cleanup passing a byte offset as an argument rather then logical
block number in new functions. This follows upstream v7.1 changes:
https://lore.kernel.org/linux-fscrypt/20260218061531.3318130-1-hch@lst.de/
* Make fscrypt_set_bio_crypt_ctx_from_extent() and fscrypt_mergeable_extent_bio()
handle unencrypted extents gracefully (ei == NULL). This way they can
be called unconditionally.
* Constify `ctx` argument of fscrypt_load_extent_info().
* Comment cleanups as suggested by Eric.
v6 changes:
* Fixed a merge collision with HW wrapped keydefinition.
* Adapt to fscrypt changes.
- Key derivation is void now instead of returning err.
- Crypt info structure was split from VFS inode
into FS specific inode structure.
v5: https://lore.kernel.org/linux-btrfs/37c2237bf485e44b0c813716f9506413026ce6dc.1706116485.git.josef@toxicpanda.com/
---
fs/crypto/crypto.c | 10 ++-
fs/crypto/fscrypt_private.h | 42 +++++++++
fs/crypto/inline_crypt.c | 81 ++++++++++++++++++
fs/crypto/keysetup.c | 164 ++++++++++++++++++++++++++++++++++++
fs/crypto/policy.c | 47 +++++++++++
include/linux/fscrypt.h | 69 +++++++++++++++
6 files changed, 412 insertions(+), 1 deletion(-)
diff --git a/fs/crypto/crypto.c b/fs/crypto/crypto.c
index 570a2231c945..8ac9d3626b1d 100644
--- a/fs/crypto/crypto.c
+++ b/fs/crypto/crypto.c
@@ -42,6 +42,7 @@ static struct workqueue_struct *fscrypt_read_workqueue;
static DEFINE_MUTEX(fscrypt_init_mutex);
struct kmem_cache *fscrypt_inode_info_cachep;
+struct kmem_cache *fscrypt_extent_info_cachep;
void fscrypt_enqueue_decrypt_work(struct work_struct *work)
{
@@ -402,12 +403,19 @@ static int __init fscrypt_init(void)
if (!fscrypt_inode_info_cachep)
goto fail_free_queue;
+ fscrypt_extent_info_cachep = KMEM_CACHE(fscrypt_extent_info,
+ SLAB_RECLAIM_ACCOUNT);
+ if (!fscrypt_extent_info_cachep)
+ goto fail_free_inode_info;
+
err = fscrypt_init_keyring();
if (err)
- goto fail_free_inode_info;
+ goto fail_free_extent_info;
return 0;
+fail_free_extent_info:
+ kmem_cache_destroy(fscrypt_extent_info_cachep);
fail_free_inode_info:
kmem_cache_destroy(fscrypt_inode_info_cachep);
fail_free_queue:
diff --git a/fs/crypto/fscrypt_private.h b/fs/crypto/fscrypt_private.h
index 8d3c278a7591..e5ab9893dfed 100644
--- a/fs/crypto/fscrypt_private.h
+++ b/fs/crypto/fscrypt_private.h
@@ -66,6 +66,8 @@
#define FSCRYPT_CONTEXT_V1 1
#define FSCRYPT_CONTEXT_V2 2
+#define FSCRYPT_EXTENT_CONTEXT_V1 1
+
/* Keep this in sync with include/uapi/linux/fscrypt.h */
#define FSCRYPT_MODE_MAX FSCRYPT_MODE_AES_256_HCTR2
@@ -89,6 +91,25 @@ struct fscrypt_context_v2 {
u8 nonce[FSCRYPT_FILE_NONCE_SIZE];
};
+/*
+ * fscrypt_extent_context - the encryption context of an extent
+ *
+ * This is the on-disk information stored for an extent. The nonce is used as a
+ * KDF input in conjuction with the inode context to derive a per-extent key for
+ * encryption. This is used only when the filesystem uses per-extent encryption.
+ *
+ * With the current implementation, master_key_identifier and encryption mode
+ * must match the inode context. These are here for future expansion where we
+ * may want the option of mixing different keys and encryption modes for the
+ * same file.
+ */
+struct fscrypt_extent_context {
+ u8 version; /* FSCRYPT_EXTENT_CONTEXT_V1 */
+ u8 encryption_mode;
+ u8 master_key_identifier[FSCRYPT_KEY_IDENTIFIER_SIZE];
+ u8 nonce[FSCRYPT_FILE_NONCE_SIZE];
+};
+
/*
* fscrypt_context - the encryption context of an inode
*
@@ -323,6 +344,25 @@ struct fscrypt_inode_info {
u8 ci_nonce[FSCRYPT_FILE_NONCE_SIZE];
};
+/*
+ * fscrypt_extent_info - the "encryption key" for an extent.
+ *
+ * This contains the derived key for the given extent and the nonce for the
+ * extent.
+ */
+struct fscrypt_extent_info {
+ refcount_t refs;
+
+ /* The derived key for this extent. */
+ struct fscrypt_prepared_key prep_key;
+
+ /* The super block that this extent belongs to. */
+ struct super_block *sb;
+
+ /* This is the extent's nonce, loaded from the fscrypt_extent_context */
+ u8 nonce[FSCRYPT_FILE_NONCE_SIZE];
+};
+
typedef enum {
FS_DECRYPT = 0,
FS_ENCRYPT,
@@ -330,6 +370,7 @@ typedef enum {
/* crypto.c */
extern struct kmem_cache *fscrypt_inode_info_cachep;
+extern struct kmem_cache *fscrypt_extent_info_cachep;
int fscrypt_initialize(struct super_block *sb);
int fscrypt_crypt_data_unit(const struct fscrypt_inode_info *ci,
fscrypt_direction_t rw, u64 index,
@@ -397,6 +438,7 @@ void fscrypt_init_hkdf(struct hmac_sha512_key *hkdf, const u8 *master_key,
#define HKDF_CONTEXT_INODE_HASH_KEY 7 /* info=<empty> */
#define HKDF_CONTEXT_KEY_IDENTIFIER_FOR_HW_WRAPPED_KEY \
8 /* info=<empty> */
+#define HKDF_CONTEXT_PER_EXTENT_ENC_KEY 9 /* info=extent_nonce */
void fscrypt_hkdf_expand(const struct hmac_sha512_key *hkdf, u8 context,
const u8 *info, unsigned int infolen,
diff --git a/fs/crypto/inline_crypt.c b/fs/crypto/inline_crypt.c
index 37d42d357925..1bf8621093ed 100644
--- a/fs/crypto/inline_crypt.c
+++ b/fs/crypto/inline_crypt.c
@@ -312,6 +312,40 @@ void fscrypt_set_bio_crypt_ctx(struct bio *bio, const struct inode *inode,
}
EXPORT_SYMBOL_GPL(fscrypt_set_bio_crypt_ctx);
+/**
+ * fscrypt_set_bio_crypt_ctx_from_extent() - prepare a file contents bio for
+ * inline crypto with extent
+ * encryption
+ * @bio: a bio which will eventually be submitted to the file
+ * @ei: the extent's crypto info
+ * @pos: the first extent logical offset (in bytes) in the I/O
+ * @gfp_mask: memory allocation flags - these must be a waiting mask so that
+ * bio_crypt_set_ctx can't fail.
+ *
+ * If the contents of the file should be encrypted (or decrypted) with inline
+ * encryption, then assign the appropriate encryption context to the bio.
+ *
+ * Normally the bio should be newly allocated (i.e. no pages added yet), as
+ * otherwise fscrypt_mergeable_extent_bio() won't work as intended.
+ *
+ * The encryption context will be freed automatically when the bio is freed.
+ */
+void fscrypt_set_bio_crypt_ctx_from_extent(struct bio *bio,
+ const struct fscrypt_extent_info *ei,
+ loff_t pos, gfp_t gfp_mask)
+{
+ const struct blk_crypto_key *key;
+ u64 dun[BLK_CRYPTO_DUN_ARRAY_SIZE] = {};
+
+ if (!ei)
+ return;
+ key = ei->prep_key.blk_key;
+
+ dun[0] = pos >> key->data_unit_size_bits;
+ bio_crypt_set_ctx(bio, key, dun, gfp_mask);
+}
+EXPORT_SYMBOL_GPL(fscrypt_set_bio_crypt_ctx_from_extent);
+
/**
* fscrypt_mergeable_bio() - test whether data can be added to a bio
* @bio: the bio being built up
@@ -359,6 +393,53 @@ bool fscrypt_mergeable_bio(struct bio *bio, const struct inode *inode,
}
EXPORT_SYMBOL_GPL(fscrypt_mergeable_bio);
+/**
+ * fscrypt_mergeable_extent_bio() - test whether data can be added to a bio
+ * @bio: the bio being built up
+ * @ei: the fscrypt_extent_info for this extent
+ * @pos: the next extent logical offset (in bytes) in the I/O
+ *
+ * When building a bio which may contain data which should undergo inline
+ * encryption (or decryption) via fscrypt, filesystems should call this function
+ * to ensure that the resulting bio contains only contiguous data unit numbers.
+ * This will return false if the next part of the I/O cannot be merged with the
+ * bio because either the encryption key would be different or the encryption
+ * data unit numbers would be discontiguous.
+ *
+ * fscrypt_set_bio_crypt_ctx_from_extent() must have already been called on the
+ * bio.
+ *
+ * This function isn't required in cases where crypto-mergeability is ensured in
+ * another way, such as I/O targeting only a single extent (thus a single key)
+ * combined with fscrypt_limit_io_blocks() to ensure DUN contiguity.
+ *
+ * Return: true iff the I/O is mergeable
+ */
+bool fscrypt_mergeable_extent_bio(struct bio *bio,
+ const struct fscrypt_extent_info *ei,
+ loff_t pos)
+{
+ const struct bio_crypt_ctx *bc = bio_crypt_ctx(bio);
+ u64 next_dun[BLK_CRYPTO_DUN_ARRAY_SIZE] = {};
+
+ if (!ei != !bc)
+ return false;
+ if (!bc)
+ return true;
+
+ /*
+ * Comparing the key pointers is good enough, as all I/O for each key
+ * uses the same pointer. I.e., there's currently no need to support
+ * merging requests where the keys are the same but the pointers differ.
+ */
+ if (bc->bc_key != ei->prep_key.blk_key)
+ return false;
+
+ next_dun[0] = pos >> bc->bc_key->data_unit_size_bits;
+ return bio_crypt_dun_is_contiguous(bc, bio->bi_iter.bi_size, next_dun);
+}
+EXPORT_SYMBOL_GPL(fscrypt_mergeable_extent_bio);
+
/**
* fscrypt_dio_supported() - check whether DIO (direct I/O) is supported on an
* inode, as far as encryption is concerned
diff --git a/fs/crypto/keysetup.c b/fs/crypto/keysetup.c
index ce327bfdada4..9fde198ae5e3 100644
--- a/fs/crypto/keysetup.c
+++ b/fs/crypto/keysetup.c
@@ -847,3 +847,167 @@ int fscrypt_drop_inode(struct inode *inode)
return !READ_ONCE(ci->ci_master_key->mk_present);
}
EXPORT_SYMBOL_GPL(fscrypt_drop_inode);
+
+static struct fscrypt_extent_info *
+setup_extent_info(struct inode *inode, const u8 nonce[FSCRYPT_FILE_NONCE_SIZE])
+{
+ struct fscrypt_extent_info *ei;
+ struct fscrypt_inode_info *ci;
+ struct fscrypt_master_key *mk;
+ u8 derived_key[FSCRYPT_MAX_RAW_KEY_SIZE];
+ int keysize;
+ int err;
+
+ ci = *fscrypt_inode_info_addr(inode);
+ mk = ci->ci_master_key;
+ if (WARN_ON_ONCE(!mk))
+ return ERR_PTR(-ENOKEY);
+
+ ei = kmem_cache_zalloc(fscrypt_extent_info_cachep, GFP_KERNEL);
+ if (!ei)
+ return ERR_PTR(-ENOMEM);
+
+ refcount_set(&ei->refs, 1);
+ memcpy(ei->nonce, nonce, FSCRYPT_FILE_NONCE_SIZE);
+ ei->sb = inode->i_sb;
+
+ keysize = ci->ci_mode->keysize;
+ down_read(&mk->mk_sem);
+ /*
+ * We specifically don't check ->mk_present here because if the inode is
+ * open and has a reference on the master key then it should be
+ * available for us to use no matter if mk_present is true or false.
+ */
+ fscrypt_hkdf_expand(&mk->mk_secret.hkdf, HKDF_CONTEXT_PER_EXTENT_ENC_KEY,
+ ei->nonce, FSCRYPT_FILE_NONCE_SIZE,
+ derived_key, keysize);
+ err = fscrypt_prepare_inline_crypt_key(&ei->prep_key,
+ derived_key, keysize, false, ci);
+ memzero_explicit(derived_key, keysize);
+ up_read(&mk->mk_sem);
+ if (err) {
+ memzero_explicit(ei, sizeof(*ei));
+ kmem_cache_free(fscrypt_extent_info_cachep, ei);
+ return ERR_PTR(err);
+ }
+ return ei;
+}
+
+/**
+ * fscrypt_prepare_new_extent() - prepare to create a new extent for a file
+ * @inode: the encrypted inode
+ *
+ * If the inode is encrypted, setup the fscrypt_extent_info for a new extent.
+ * This will include the nonce and the derived key necessary for the extent to
+ * be encrypted. This is only meant to be used with inline crypto and on inodes
+ * that need their contents encrypted.
+ *
+ * This doesn't persist the new extents encryption context, this is done later
+ * by calling fscrypt_set_extent_context().
+ *
+ * Return: The newly allocated fscrypt_extent_info on success, -EOPNOTSUPP if
+ * we're not encrypted, or another -errno code
+ */
+struct fscrypt_extent_info *fscrypt_prepare_new_extent(struct inode *inode)
+{
+ u8 nonce[FSCRYPT_FILE_NONCE_SIZE];
+
+ if (WARN_ON_ONCE(!*fscrypt_inode_info_addr(inode)))
+ return ERR_PTR(-EOPNOTSUPP);
+ if (WARN_ON_ONCE(!fscrypt_inode_uses_inline_crypto(inode)))
+ return ERR_PTR(-EOPNOTSUPP);
+
+ get_random_bytes(nonce, FSCRYPT_FILE_NONCE_SIZE);
+ return setup_extent_info(inode, nonce);
+}
+EXPORT_SYMBOL_GPL(fscrypt_prepare_new_extent);
+
+/**
+ * fscrypt_load_extent_info() - create an fscrypt_extent_info from the context
+ * @inode: the inode
+ * @ctx: the context buffer
+ * @ctx_size: the size of the context buffer
+ *
+ * Create the fscrypt_extent_info and derive the key based on the
+ * fscrypt_extent_context buffer that is provided.
+ *
+ * Return: The newly allocated fscrypt_extent_info on success, -EOPNOTSUPP if
+ * we're not encrypted, or another -errno code
+ */
+struct fscrypt_extent_info *fscrypt_load_extent_info(struct inode *inode,
+ const u8 *ctx,
+ size_t ctx_size)
+{
+ struct fscrypt_extent_context extent_ctx;
+ const struct fscrypt_inode_info *ci = *fscrypt_inode_info_addr(inode);
+ const struct fscrypt_policy_v2 *policy = &ci->ci_policy.v2;
+
+ if (WARN_ON_ONCE(!ci))
+ return ERR_PTR(-EOPNOTSUPP);
+ if (WARN_ON_ONCE(!fscrypt_inode_uses_inline_crypto(inode)))
+ return ERR_PTR(-EOPNOTSUPP);
+ if (ctx_size < sizeof(extent_ctx))
+ return ERR_PTR(-EINVAL);
+
+ memcpy(&extent_ctx, ctx, sizeof(extent_ctx));
+
+ if (extent_ctx.version != FSCRYPT_EXTENT_CONTEXT_V1) {
+ fscrypt_warn(inode, "Invalid extent encryption context version");
+ return ERR_PTR(-EINVAL);
+ }
+
+ /*
+ * For now we need to validate that the master key and the encryption
+ * mode matches what is in the inode.
+ */
+ if (memcmp(extent_ctx.master_key_identifier,
+ policy->master_key_identifier,
+ sizeof(extent_ctx.master_key_identifier))) {
+ fscrypt_warn(inode, "Mismatching master key identifier");
+ return ERR_PTR(-EINVAL);
+ }
+
+ if (extent_ctx.encryption_mode != policy->contents_encryption_mode) {
+ fscrypt_warn(inode, "Mismatching encryption mode");
+ return ERR_PTR(-EINVAL);
+ }
+
+ return setup_extent_info(inode, extent_ctx.nonce);
+}
+EXPORT_SYMBOL_GPL(fscrypt_load_extent_info);
+
+/**
+ * fscrypt_put_extent_info() - put a reference to a fscrypt_extent_info
+ * @ei: the fscrypt_extent_info being put or NULL.
+ *
+ * Drop a reference and possibly free the fscrypt_extent_info.
+ *
+ * Might sleep, since this may call blk_crypto_evict_key() which can sleep.
+ */
+void fscrypt_put_extent_info(struct fscrypt_extent_info *ei)
+{
+ if (ei && refcount_dec_and_test(&ei->refs)) {
+ fscrypt_destroy_prepared_key(ei->sb, &ei->prep_key);
+ memzero_explicit(ei, sizeof(*ei));
+ kmem_cache_free(fscrypt_extent_info_cachep, ei);
+ }
+}
+EXPORT_SYMBOL_GPL(fscrypt_put_extent_info);
+
+/**
+ * fscrypt_get_extent_info() - get a reference to a fscrypt_extent_info
+ * @ei: the extent_info to get.
+ *
+ * Get a reference on the fscrypt_extent_info. This is useful for file systems
+ * that need to pass the fscrypt_extent_info through various other structures to
+ * make lifetime tracking simpler.
+ *
+ * Return: the ei with an extra ref, NULL if ei was NULL.
+ */
+struct fscrypt_extent_info *fscrypt_get_extent_info(struct fscrypt_extent_info *ei)
+{
+ if (ei)
+ refcount_inc(&ei->refs);
+ return ei;
+}
+EXPORT_SYMBOL_GPL(fscrypt_get_extent_info);
diff --git a/fs/crypto/policy.c b/fs/crypto/policy.c
index 9915e39362db..94dac05c2710 100644
--- a/fs/crypto/policy.c
+++ b/fs/crypto/policy.c
@@ -211,6 +211,12 @@ static bool fscrypt_supported_v1_policy(const struct fscrypt_policy_v1 *policy,
return false;
}
+ if (inode->i_sb->s_cop->has_per_extent_encryption) {
+ fscrypt_warn(inode,
+ "v1 policies aren't supported on file systems that use extent encryption");
+ return false;
+ }
+
return true;
}
@@ -240,6 +246,11 @@ static bool fscrypt_supported_v2_policy(const struct fscrypt_policy_v2 *policy,
count += !!(policy->flags & FSCRYPT_POLICY_FLAG_DIRECT_KEY);
count += !!(policy->flags & FSCRYPT_POLICY_FLAG_IV_INO_LBLK_64);
count += !!(policy->flags & FSCRYPT_POLICY_FLAG_IV_INO_LBLK_32);
+ if (count > 0 && inode->i_sb->s_cop->has_per_extent_encryption) {
+ fscrypt_warn(inode,
+ "DIRECT_KEY and IV_INO_LBLK_* encryption flags aren't supported on file systems that use extent encryption");
+ return false;
+ }
if (count > 1) {
fscrypt_warn(inode, "Mutually exclusive encryption flags (0x%02x)",
policy->flags);
@@ -792,6 +803,42 @@ int fscrypt_set_context(struct inode *inode, void *fs_data)
}
EXPORT_SYMBOL_GPL(fscrypt_set_context);
+/**
+ * fscrypt_set_extent_context() - Set the fscrypt extent context of a new extent
+ * @inode: the inode this extent belongs to
+ * @ei: the fscrypt_extent_info for the given extent
+ * @buf: the buffer to copy the fscrypt extent context into
+ *
+ * This should be called after fscrypt_prepare_new_extent(), using the
+ * fscrypt_extent_info that was created at that point.
+ *
+ * buf should be able to fit up to FSCRYPT_SET_CONTEXT_MAX_SIZE bytes.
+ *
+ * Return: the size of the fscrypt_extent_context, errno if the inode has the
+ * wrong policy version.
+ */
+ssize_t fscrypt_context_for_new_extent(struct inode *inode,
+ struct fscrypt_extent_info *ei, u8 *buf)
+{
+ struct fscrypt_extent_context *ctx = (struct fscrypt_extent_context *)buf;
+ const struct fscrypt_inode_info *ci = *fscrypt_inode_info_addr(inode);
+
+ BUILD_BUG_ON(sizeof(struct fscrypt_extent_context) >
+ FSCRYPT_SET_CONTEXT_MAX_SIZE);
+
+ if (WARN_ON_ONCE(ci->ci_policy.version != 2))
+ return -EINVAL;
+
+ ctx->version = FSCRYPT_EXTENT_CONTEXT_V1;
+ ctx->encryption_mode = ci->ci_policy.v2.contents_encryption_mode;
+ memcpy(ctx->master_key_identifier,
+ ci->ci_policy.v2.master_key_identifier,
+ sizeof(ctx->master_key_identifier));
+ memcpy(ctx->nonce, ei->nonce, FSCRYPT_FILE_NONCE_SIZE);
+ return sizeof(struct fscrypt_extent_context);
+}
+EXPORT_SYMBOL_GPL(fscrypt_context_for_new_extent);
+
/**
* fscrypt_parse_test_dummy_encryption() - parse the test_dummy_encryption mount option
* @param: the mount option
diff --git a/include/linux/fscrypt.h b/include/linux/fscrypt.h
index 54712ec61ffb..f216f1a78069 100644
--- a/include/linux/fscrypt.h
+++ b/include/linux/fscrypt.h
@@ -32,6 +32,7 @@
union fscrypt_policy;
struct fscrypt_inode_info;
+struct fscrypt_extent_info;
struct fs_parameter;
struct seq_file;
@@ -103,6 +104,14 @@ struct fscrypt_operations {
*/
unsigned int supports_subblock_data_units : 1;
+ /*
+ * If set then extent based encryption will be used for this file
+ * system, and fs/crypto/ will enforce limits on the policies that are
+ * allowed to be chosen. Currently this means only v2 policies and a
+ * limited flags are supported.
+ */
+ unsigned int has_per_extent_encryption : 1;
+
/*
* This field exists only for backwards compatibility reasons and should
* only be set by the filesystems that are setting it already. It
@@ -387,6 +396,8 @@ int fscrypt_ioctl_get_nonce(struct file *filp, void __user *arg);
int fscrypt_has_permitted_context(struct inode *parent, struct inode *child);
int fscrypt_context_for_new_inode(void *ctx, struct inode *inode);
int fscrypt_set_context(struct inode *inode, void *fs_data);
+ssize_t fscrypt_context_for_new_extent(struct inode *inode,
+ struct fscrypt_extent_info *ei, u8 *buf);
struct fscrypt_dummy_policy {
const union fscrypt_policy *policy;
@@ -423,6 +434,11 @@ int fscrypt_prepare_new_inode(struct inode *dir, struct inode *inode,
void fscrypt_put_encryption_info(struct inode *inode);
void fscrypt_free_inode(struct inode *inode);
int fscrypt_drop_inode(struct inode *inode);
+struct fscrypt_extent_info *fscrypt_prepare_new_extent(struct inode *inode);
+void fscrypt_put_extent_info(struct fscrypt_extent_info *ei);
+struct fscrypt_extent_info *fscrypt_get_extent_info(struct fscrypt_extent_info *ei);
+struct fscrypt_extent_info *fscrypt_load_extent_info(struct inode *inode,
+ const u8 *ctx, size_t ctx_size);
/* fname.c */
int fscrypt_fname_encrypt(const struct inode *inode, const struct qstr *iname,
@@ -636,6 +652,24 @@ fscrypt_free_dummy_policy(struct fscrypt_dummy_policy *dummy_policy)
{
}
+static inline ssize_t
+fscrypt_context_for_new_extent(struct inode *inode, struct fscrypt_extent_info *ei,
+ u8 *buf)
+{
+ return -EOPNOTSUPP;
+}
+
+static inline struct fscrypt_extent_info *
+fscrypt_load_extent_info(struct inode *inode, const u8 *ctx, size_t ctx_size)
+{
+ return ERR_PTR(-EOPNOTSUPP);
+}
+
+static inline size_t fscrypt_extent_context_size(struct inode *inode)
+{
+ return 0;
+}
+
/* keyring.c */
static inline void fscrypt_destroy_keyring(struct super_block *sb)
{
@@ -688,6 +722,20 @@ static inline int fscrypt_drop_inode(struct inode *inode)
return 0;
}
+static inline struct fscrypt_extent_info *
+fscrypt_prepare_new_extent(struct inode *inode)
+{
+ return ERR_PTR(-EOPNOTSUPP);
+}
+
+static inline void fscrypt_put_extent_info(struct fscrypt_extent_info *ei) { }
+
+static inline struct fscrypt_extent_info *
+fscrypt_get_extent_info(struct fscrypt_extent_info *ei)
+{
+ return ei;
+}
+
/* fname.c */
static inline int fscrypt_setup_filename(struct inode *dir,
const struct qstr *iname,
@@ -868,9 +916,17 @@ bool __fscrypt_inode_uses_inline_crypto(const struct inode *inode);
void fscrypt_set_bio_crypt_ctx(struct bio *bio, const struct inode *inode,
loff_t pos, gfp_t gfp_mask);
+void fscrypt_set_bio_crypt_ctx_from_extent(struct bio *bio,
+ const struct fscrypt_extent_info *ei,
+ loff_t pos, gfp_t gfp_mask);
+
bool fscrypt_mergeable_bio(struct bio *bio, const struct inode *inode,
loff_t pos);
+bool fscrypt_mergeable_extent_bio(struct bio *bio,
+ const struct fscrypt_extent_info *ei,
+ loff_t pos);
+
bool fscrypt_dio_supported(struct inode *inode);
u64 fscrypt_limit_io_blocks(const struct inode *inode, u64 lblk, u64 nr_blocks);
@@ -886,6 +942,11 @@ static inline void fscrypt_set_bio_crypt_ctx(struct bio *bio,
const struct inode *inode,
loff_t pos, gfp_t gfp_mask) { }
+static inline void fscrypt_set_bio_crypt_ctx_from_extent(
+ struct bio *bio,
+ const struct fscrypt_extent_info *ei,
+ loff_t pos, gfp_t gfp_mask) { }
+
static inline bool fscrypt_mergeable_bio(struct bio *bio,
const struct inode *inode,
loff_t pos)
@@ -893,6 +954,14 @@ static inline bool fscrypt_mergeable_bio(struct bio *bio,
return true;
}
+static inline bool fscrypt_mergeable_extent_bio(
+ struct bio *bio,
+ const struct fscrypt_extent_info *ei,
+ loff_t pos)
+{
+ return true;
+}
+
static inline bool fscrypt_dio_supported(struct inode *inode)
{
return !fscrypt_needs_contents_encryption(inode);
--
2.53.0
^ permalink raw reply related
* [PATCH v7 02/43] fscrypt: allow inline encryption for extent based encryption
From: Daniel Vacek @ 2026-05-13 8:52 UTC (permalink / raw)
To: Chris Mason, Josef Bacik, Eric Biggers, Theodore Y. Ts'o,
Jaegeuk Kim, Jens Axboe, David Sterba
Cc: linux-block, Daniel Vacek, linux-fscrypt, linux-btrfs,
linux-kernel
In-Reply-To: <20260513085340.3673127-1-neelx@suse.com>
From: Josef Bacik <josef@toxicpanda.com>
Instead of requiring -o inlinecrypt to enable inline encryption, allow
having s_cop->has_per_extent_encryption to indicate that this file
system supports inline encryption.
Signed-off-by: Josef Bacik <josef@toxicpanda.com>
Signed-off-by: Daniel Vacek <neelx@suse.com>
---
v5: https://lore.kernel.org/linux-btrfs/ba0289bf103653d5d98ef576756c9a2a66192865.1706116485.git.josef@toxicpanda.com/
* No changes since.
---
fs/crypto/inline_crypt.c | 7 +++++--
1 file changed, 5 insertions(+), 2 deletions(-)
diff --git a/fs/crypto/inline_crypt.c b/fs/crypto/inline_crypt.c
index 1bf8621093ed..31791fb98a9e 100644
--- a/fs/crypto/inline_crypt.c
+++ b/fs/crypto/inline_crypt.c
@@ -108,8 +108,11 @@ int fscrypt_select_encryption_impl(struct fscrypt_inode_info *ci,
if (ci->ci_mode->blk_crypto_mode == BLK_ENCRYPTION_MODE_INVALID)
return 0;
- /* The filesystem must be mounted with -o inlinecrypt */
- if (!(sb->s_flags & SB_INLINECRYPT))
+ /*
+ * The filesystem must be mounted with -o inlinecrypt or have
+ * has_per_extent_encryption enabled.
+ */
+ if (!(sb->s_flags & SB_INLINECRYPT) && !sb->s_cop->has_per_extent_encryption)
return 0;
/*
--
2.53.0
^ permalink raw reply related
* [PATCH v7 00/43] btrfs: add fscrypt support
From: Daniel Vacek @ 2026-05-13 8:52 UTC (permalink / raw)
To: Chris Mason, Josef Bacik, Eric Biggers, Theodore Y. Ts'o,
Jaegeuk Kim, Jens Axboe, David Sterba
Cc: linux-block, Daniel Vacek, linux-fscrypt, linux-btrfs,
linux-kernel
Hello,
These are the remaining parts from former series [1] from Omar, Sweet Tea
and Josef. Some bits of it were split into the separate set [2] before.
Notably, at this stage encryption is not supported with RAID5/6 setup
and send is also disabled for now.
For straight git access you can find this series as the `fscrypt-v7` tag
e85358ef9fba here:
[0] https://github.com/dvacek/linux-btrfs/tree/fscrypt-v7
Changes since v6 [3]
* Rebased onto linux v7.1-rc3.
* Adapted to the v7.0 fscrypt API changes, mostly following commit
bb8e2019ad613 ("blk-crypto: handle the fallback above the block layer")
* Addressed all the review feedback, thanks to Eric Biggers, Chris Mason
(and his LLM review prompts) and Neal Gompa.
* Adapted to the v7.1 fscrypt API cleanups, using byte offsets as function
arguments instead of logical block numbers for newly introduced functions.
This should match https://lore.kernel.org/linux-fscrypt/20260218061531.3318130-1-hch@lst.de/
As a result btrfs_set_bio_crypt_ctx_from_extent() and btrfs_mergeable_encrypted_bio()
helpers were no longer needed and they got removed.
There are a few changes since v5 [1]:
* Rebased to btrfs/for-next branch. Couple things changed in the last
years. A few patches were dropped as the code cleaned up or refactored.
More details in the patches themselves.
* As suggested by Qu and Dave, the on-disk format of storing the extent
encryption context was re-designed. Now, a new tree item with dedicated
key is inserted instead of extending the file extent item. As a result
a special care needs to be taken when removing the encrypted extents
to also remove the related encryption context item.
* Fixed bugs found during testing.
Have a nice day,
Daniel
[1] v5 https://lore.kernel.org/linux-btrfs/cover.1706116485.git.josef@toxicpanda.com/
[2] https://lore.kernel.org/linux-btrfs/20251112193611.2536093-1-neelx@suse.com/
[3] v6 https://lore.kernel.org/linux-btrfs/20260206182336.1397715-1-neelx@suse.com/
Josef Bacik (33):
fscrypt: add per-extent encryption support
fscrypt: allow inline encryption for extent based encryption
fscrypt: add a __fscrypt_file_open helper
fscrypt: conditionally don't wipe mk secret until the last active user
is done
blk-crypto: add a process bio callback
fscrypt: add a process_bio hook to fscrypt_operations
fscrypt: add documentation about extent encryption
btrfs: add infrastructure for safe em freeing
btrfs: select encryption dependencies if FS_ENCRYPTION
btrfs: add fscrypt_info and encryption_type to ordered_extent
btrfs: plumb through setting the fscrypt_info for ordered extents
btrfs: populate the ordered_extent with the fscrypt context
btrfs: keep track of fscrypt info and orig_start for dio reads
btrfs: add extent encryption context tree item type
btrfs: pass through fscrypt_extent_info to the file extent helpers
btrfs: implement the fscrypt extent encryption hooks
btrfs: setup fscrypt_extent_info for new extents
btrfs: populate ordered_extent with the orig offset
btrfs: set the bio fscrypt context when applicable
btrfs: add a bio argument to btrfs_csum_one_bio
btrfs: limit encrypted writes to 256 segments
btrfs: implement process_bio cb for fscrypt
btrfs: implement read repair for encryption
btrfs: add test_dummy_encryption support
btrfs: make btrfs_ref_to_path handle encrypted filenames
btrfs: deal with encrypted symlinks in send
btrfs: decrypt file names for send
btrfs: load the inode context before sending writes
btrfs: set the appropriate free space settings in reconfigure
btrfs: support encryption with log replay
btrfs: disable auto defrag on encrypted files
btrfs: disable encryption on RAID5/6
btrfs: disable send if we have encryption enabled
Omar Sandoval (6):
fscrypt: expose fscrypt_nokey_name
btrfs: start using fscrypt hooks
btrfs: add inode encryption contexts
btrfs: add new FEATURE_INCOMPAT_ENCRYPT flag
btrfs: adapt readdir for encrypted and nokey names
btrfs: implement fscrypt ioctls
Sweet Tea Dorminy (4):
btrfs: handle nokey names
btrfs: add get_devices hook for fscrypt
btrfs: set file extent encryption excplicitly
btrfs: add fscrypt_info and encryption_type to extent_map
Documentation/filesystems/fscrypt.rst | 41 +++
block/blk-crypto-fallback.c | 41 +++
block/blk-crypto-internal.h | 8 +
block/blk-crypto-profile.c | 2 +
block/blk-crypto.c | 6 +-
fs/btrfs/Kconfig | 4 +
fs/btrfs/Makefile | 1 +
fs/btrfs/accessors.h | 2 +
fs/btrfs/backref.c | 43 ++-
fs/btrfs/bio.c | 155 +++++++++-
fs/btrfs/bio.h | 14 +-
fs/btrfs/btrfs_inode.h | 7 +-
fs/btrfs/compression.c | 6 +
fs/btrfs/ctree.h | 3 +
fs/btrfs/defrag.c | 14 +
fs/btrfs/delayed-inode.c | 25 +-
fs/btrfs/delayed-inode.h | 5 +-
fs/btrfs/dir-item.c | 110 ++++++-
fs/btrfs/dir-item.h | 10 +-
fs/btrfs/direct-io.c | 28 +-
fs/btrfs/disk-io.c | 3 +-
fs/btrfs/extent_io.c | 115 ++++++-
fs/btrfs/extent_io.h | 3 +
fs/btrfs/extent_map.c | 102 ++++++-
fs/btrfs/extent_map.h | 26 ++
fs/btrfs/file-item.c | 28 +-
fs/btrfs/file-item.h | 2 +-
fs/btrfs/file.c | 79 +++++
fs/btrfs/fs.h | 6 +-
fs/btrfs/fscrypt.c | 413 ++++++++++++++++++++++++++
fs/btrfs/fscrypt.h | 86 ++++++
fs/btrfs/inode.c | 404 +++++++++++++++++++------
fs/btrfs/ioctl.c | 41 ++-
fs/btrfs/ordered-data.c | 35 ++-
fs/btrfs/ordered-data.h | 14 +
fs/btrfs/reflink.c | 43 ++-
fs/btrfs/root-tree.c | 9 +-
fs/btrfs/root-tree.h | 2 +-
fs/btrfs/send.c | 134 ++++++++-
fs/btrfs/super.c | 99 +++++-
fs/btrfs/super.h | 3 +-
fs/btrfs/sysfs.c | 6 +
fs/btrfs/tree-checker.c | 64 +++-
fs/btrfs/tree-log.c | 34 ++-
fs/btrfs/volumes.c | 5 +
fs/crypto/crypto.c | 10 +-
fs/crypto/fname.c | 36 ---
fs/crypto/fscrypt_private.h | 51 +++-
fs/crypto/hooks.c | 38 ++-
fs/crypto/inline_crypt.c | 91 +++++-
fs/crypto/keyring.c | 18 +-
fs/crypto/keysetup.c | 164 ++++++++++
fs/crypto/policy.c | 47 +++
include/linux/blk-crypto.h | 15 +-
include/linux/fscrypt.h | 127 ++++++++
include/uapi/linux/btrfs.h | 1 +
include/uapi/linux/btrfs_tree.h | 26 +-
57 files changed, 2665 insertions(+), 240 deletions(-)
create mode 100644 fs/btrfs/fscrypt.c
create mode 100644 fs/btrfs/fscrypt.h
--
2.53.0
^ permalink raw reply
* Re: [PATCH v2] block/blk-iolatency: Add the processing flow of the chained bio in the QoS and define the related types to solve the problem of incorrect inflight processing in the QoS. The usage of the done_split_bio abstract function in the blk-iolatency project.
From: kernel test robot @ 2026-05-13 8:45 UTC (permalink / raw)
To: Li kunyu, axboe, tj, josef
Cc: llvm, oe-kbuild-all, linux-block, linux-kernel, Li kunyu
In-Reply-To: <20260506024244.2741-1-likunyu10@163.com>
Hi Li,
kernel test robot noticed the following build errors:
[auto build test ERROR on axboe/for-next]
[also build test ERROR on linus/master v7.1-rc3 next-20260508]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]
url: https://github.com/intel-lab-lkp/linux/commits/Li-kunyu/block-blk-iolatency-Add-the-processing-flow-of-the-chained-bio-in-the-QoS-and-define-the-related-types-to-solve-the-prob/20260513-135008
base: https://git.kernel.org/pub/scm/linux/kernel/git/axboe/linux.git for-next
patch link: https://lore.kernel.org/r/20260506024244.2741-1-likunyu10%40163.com
patch subject: [PATCH v2] block/blk-iolatency: Add the processing flow of the chained bio in the QoS and define the related types to solve the problem of incorrect inflight processing in the QoS. The usage of the done_split_bio abstract function in the blk-iolatency project.
config: s390-allnoconfig (https://download.01.org/0day-ci/archive/20260513/202605131650.wyKmtlTK-lkp@intel.com/config)
compiler: clang version 23.0.0git (https://github.com/llvm/llvm-project 5bac06718f502014fade905512f1d26d578a18f3)
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20260513/202605131650.wyKmtlTK-lkp@intel.com/reproduce)
If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202605131650.wyKmtlTK-lkp@intel.com/
All errors (new ones prefixed by >>):
>> ld.lld: error: undefined symbol: __rq_qos_done_split_bio
>>> referenced by bio.c
>>> block/bio.o:(bio_endio) in archive vmlinux.a
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply
* Re: [PATCH v3 04/10] block: introduce dma map backed bio type
From: Christoph Hellwig @ 2026-05-13 8:39 UTC (permalink / raw)
To: Pavel Begunkov
Cc: Jens Axboe, Keith Busch, Christoph Hellwig, Sagi Grimberg,
Alexander Viro, Christian Brauner, Andrew Morton, Sumit Semwal,
Christian König, linux-block, linux-kernel, linux-nvme,
linux-fsdevel, io-uring, linux-media, dri-devel, linaro-mm-sig,
Nitesh Shetty, Kanchan Joshi, Anuj Gupta, Tushar Gohad,
William Power, Phil Cayton, Jason Gunthorpe
In-Reply-To: <646ecd6fde8d9e146cb051efb514deb27ce3883e.1777475843.git.asml.silence@gmail.com>
> + union {
> + struct bio_vec *bi_io_vec;
> + /* Driver specific dma map, present only with BIO_DMABUF_MAP */
> + struct io_dmabuf_map *dmabuf_map;
> + };
... and please add the bi_ prefix we're using for all (well except for
one oddity) fields in struct bio.
^ permalink raw reply
* Re: [PATCH v3 07/10] nvme-pci: implement dma_token backed requests
From: Christoph Hellwig @ 2026-05-13 8:38 UTC (permalink / raw)
To: Pavel Begunkov
Cc: Jens Axboe, Keith Busch, Christoph Hellwig, Sagi Grimberg,
Alexander Viro, Christian Brauner, Andrew Morton, Sumit Semwal,
Christian König, linux-block, linux-kernel, linux-nvme,
linux-fsdevel, io-uring, linux-media, dri-devel, linaro-mm-sig,
Nitesh Shetty, Kanchan Joshi, Anuj Gupta, Tushar Gohad,
William Power, Phil Cayton, Jason Gunthorpe
In-Reply-To: <5cecb1157ab784f9f303a91449fdf11b03aa6002.1777475843.git.asml.silence@gmail.com>
FYI, I really want SGL support before this get merged, but ignoring that
for now:
> +struct nvme_dmabuf_map {
> + struct io_dmabuf_map base;
> + dma_addr_t *dma_list;
> + struct sg_table *sgt;
> + unsigned nr_entries;
I'd make dma_list a variable-sized array at the end of the struture to avoid
an extra allocation and pointer derefernece.
>
> +static void nvme_dmabuf_map_sync(struct nvme_dev *nvme_dev, struct request *req,
> + bool for_cpu)
> +{
> + int length = blk_rq_payload_bytes(req);
> + struct device *dev = nvme_dev->dev;
> + enum dma_data_direction dma_dir;
> + struct bio *bio = req->bio;
> + struct nvme_dmabuf_map *map;
> + dma_addr_t *dma_list;
> + int offset, map_idx;
> +
> + dma_dir = rq_data_dir(req) == READ ? DMA_FROM_DEVICE : DMA_TO_DEVICE;
> + map = container_of(bio->dmabuf_map, struct nvme_dmabuf_map, base);
> + dma_list = map->dma_list;
> +
> + offset = bio->bi_iter.bi_bvec_done;
> + map_idx = offset / NVME_CTRL_PAGE_SIZE;
> + length += offset & (NVME_CTRL_PAGE_SIZE - 1);
Please initialize the variable at declaration time and use or add proper
helpers to simplify this:
static inline struct nvme_dmabuf_map *
to_nvme_dmabuf_map(struct io_dmabuf_map *map)
{
return container_of(map, struct nvme_dmabuf_map, base);
}
....
enum dma_data_direction dma_dir = rq_dma_dir(req);
struct device *dev = nvme_dev->dev;
struct bio *bio = req->bio;
struct nvme_dmabuf_map *map = to_nvme_dmabuf_map(bio->bi_dmabuf_map);
dma_addr_t *dma_list = map->dma_list;
int offset = bio->bi_iter.bi_bvec_done;
int mmap_idx = offset / NVME_CTRL_PAGE_SIZE;
int length = blk_rq_payload_bytes(req) +
offset & (NVME_CTRL_PAGE_SIZE - 1);
Also a lot of these ints sound like they should be unsigned.
> +
> + while (length > 0) {
> + u64 dma_addr = dma_list[map_idx++];
> +
> + if (for_cpu)
> + __dma_sync_single_for_cpu(dev, dma_addr,
> + NVME_CTRL_PAGE_SIZE, dma_dir);
> + else
> + __dma_sync_single_for_device(dev, dma_addr,
> + NVME_CTRL_PAGE_SIZE,
> + dma_dir);
> + length -= NVME_CTRL_PAGE_SIZE;
> + }
> +}
Nothing should be using these __dma_sync helpers that are internal
details. Using them means you call into sync code that should be skipped
on most common server class systems.
Also the for_cpu argument is a bit ugly. I'd rather have separate
routines as in the core dma-mapping code, even if that means a little bit
of code duplication.
> +static blk_status_t nvme_rq_setup_dmabuf_map(struct request *req,
> + struct nvme_queue *nvmeq)
> +{
> + struct nvme_iod *iod = blk_mq_rq_to_pdu(req);
> + int length = blk_rq_payload_bytes(req);
> + u64 dma_addr, prp1_dma, prp2_dma;
> + struct bio *bio = req->bio;
> + struct nvme_dmabuf_map *map;
> + dma_addr_t *dma_list;
> + dma_addr_t prp_dma;
> + __le64 *prp_list;
> + int i, map_idx;
> + int offset;
> +
> + nvme_dmabuf_map_sync(nvmeq->dev, req, false);
> +
> + map = container_of(bio->dmabuf_map, struct nvme_dmabuf_map, base);
> + dma_list = map->dma_list;
> +
> + offset = bio->bi_iter.bi_bvec_done;
> + map_idx = offset / NVME_CTRL_PAGE_SIZE;
> + offset &= (NVME_CTRL_PAGE_SIZE - 1);
> + prp1_dma = dma_list[map_idx++] + offset;
Same comments as for the sync helper above.
> + length -= (NVME_CTRL_PAGE_SIZE - offset);
> + if (length <= 0) {
> + prp2_dma = 0;
> + goto done;
> + }
> +
> + if (length <= NVME_CTRL_PAGE_SIZE) {
> + prp2_dma = dma_list[map_idx];
> + goto done;
> + }
> +
> + if (DIV_ROUND_UP(length, NVME_CTRL_PAGE_SIZE) <=
> + NVME_SMALL_POOL_SIZE / sizeof(__le64))
> + iod->flags |= IOD_SMALL_DESCRIPTOR;
> +
> + prp_list = dma_pool_alloc(nvme_dma_pool(nvmeq, iod), GFP_ATOMIC,
> + &prp_dma);
> + if (!prp_list)
> + return BLK_STS_RESOURCE;
> +
> + iod->descriptors[iod->nr_descriptors++] = prp_list;
> + prp2_dma = prp_dma;
And I really hate how this duplicates all the nasty PRP building logic,
although right now I don't have a good answer to that.
> +static inline bool nvme_rq_is_dmabuf_attached(struct request *req)
> +{
> + if (!IS_ENABLED(CONFIG_DMABUF_TOKEN))
> + return false;
> + return req->bio && bio_flagged(req->bio, BIO_DMABUF_MAP);
> +}
This is something that should go into the block layer.
^ permalink raw reply
* Re: [PATCH v3 06/10] block: forward create_dmabuf_token to drivers
From: Christoph Hellwig @ 2026-05-13 8:25 UTC (permalink / raw)
To: Pavel Begunkov
Cc: Jens Axboe, Keith Busch, Christoph Hellwig, Sagi Grimberg,
Alexander Viro, Christian Brauner, Andrew Morton, Sumit Semwal,
Christian König, linux-block, linux-kernel, linux-nvme,
linux-fsdevel, io-uring, linux-media, dri-devel, linaro-mm-sig,
Nitesh Shetty, Kanchan Joshi, Anuj Gupta, Tushar Gohad,
William Power, Phil Cayton, Jason Gunthorpe
In-Reply-To: <559756c5e22dcfa183080a979de039910d1b896d.1777475843.git.asml.silence@gmail.com>
On Wed, Apr 29, 2026 at 04:25:52PM +0100, Pavel Begunkov wrote:
> Add a trivial implementation of the create_dmabuf_token call for
> block devices that forwards the call to a new blk-mq callback if it's
> available.
This should go into block_device_operations as there is nothing blk-mq
specific about this. I.e. even if this patchset doesn't handle stacking
drivers yet, it should be easy enough to add them in the future.
^ permalink raw reply
* Re: [PATCH v3 05/10] lib: add dmabuf token infrastructure
From: Christoph Hellwig @ 2026-05-13 8:24 UTC (permalink / raw)
To: Pavel Begunkov
Cc: Jens Axboe, Keith Busch, Christoph Hellwig, Sagi Grimberg,
Alexander Viro, Christian Brauner, Andrew Morton, Sumit Semwal,
Christian König, linux-block, linux-kernel, linux-nvme,
linux-fsdevel, io-uring, linux-media, dri-devel, linaro-mm-sig,
Nitesh Shetty, Kanchan Joshi, Anuj Gupta, Tushar Gohad,
William Power, Phil Cayton, Jason Gunthorpe
In-Reply-To: <c61e6d928f86f4cb253ae350272e6039faefd3a6.1777475843.git.asml.silence@gmail.com>
Naming and placement:
This is about dma-buf based I/O. So I'd expect it to be named dma-buf-io
and no io-dmabuf, and live in drivers/dma-buf and not the unrelated lib/.
But I'd like to hear from the dma-buf maintainers about that.
Config option: as this unconditionally when DMA_SHARED_BUFFER is enabled,
why does it need a separate config option?
Interface: io_dmabuf_token_create / ->create_dmabuf_token filling
in a structure allocated by the caller feels odd. My gut feeling
would be to move most of io_dmabuf_token_create into a helper called
by ->create_dmabuf_token so that the token is allocated in the
driver data structure and returned from create_dmabuf_token.
^ permalink raw reply
* Re: [PATCH v3 04/10] block: introduce dma map backed bio type
From: Christoph Hellwig @ 2026-05-13 8:19 UTC (permalink / raw)
To: Pavel Begunkov
Cc: Jens Axboe, Keith Busch, Christoph Hellwig, Sagi Grimberg,
Alexander Viro, Christian Brauner, Andrew Morton, Sumit Semwal,
Christian König, linux-block, linux-kernel, linux-nvme,
linux-fsdevel, io-uring, linux-media, dri-devel, linaro-mm-sig,
Nitesh Shetty, Kanchan Joshi, Anuj Gupta, Tushar Gohad,
William Power, Phil Cayton, Jason Gunthorpe
In-Reply-To: <646ecd6fde8d9e146cb051efb514deb27ce3883e.1777475843.git.asml.silence@gmail.com>
> + if (!bio_flagged(bio_src, BIO_DMABUF_MAP)) {
> + bio->bi_io_vec = bio_src->bi_io_vec;
> + } else {
> + bio->dmabuf_map = bio_src->dmabuf_map;
> + bio_set_flag(bio, BIO_DMABUF_MAP);
> + }
This is backwards, please avoid pointless negations:
if (bio_flagged(bio_src, BIO_DMABUF_MAP)) {
bio->dmabuf_map = bio_src->dmabuf_map;
bio_set_flag(bio, BIO_DMABUF_MAP);
} else {
bio->bi_io_vec = bio_src->bi_io_vec;
}
> + if (bio_flagged(bio, BIO_DMABUF_MAP)) {
> + nsegs = 1;
> +
> + if ((bio->bi_iter.bi_bvec_done & lim->dma_alignment) ||
> + (bio->bi_iter.bi_size & len_align_mask))
> + return -EINVAL;
> + if (bio->bi_iter.bi_size > max_bytes) {
> + bytes = max_bytes;
> + goto split;
> + }
Please add a comment explaining why nsegs is always 1 here.
> @@ -424,7 +424,8 @@ static inline struct bio *__bio_split_to_limits(struct bio *bio,
> switch (bio_op(bio)) {
> case REQ_OP_READ:
> case REQ_OP_WRITE:
> - if (bio_may_need_split(bio, lim))
> + if (bio_may_need_split(bio, lim) ||
> + bio_flagged(bio, BIO_DMABUF_MAP))
> return bio_split_rw(bio, lim, nr_segs);
The BIO_DMABUF_MAP check should go into bio_may_need_split.
> +static inline void bio_advance_iter_dmabuf_map(struct bvec_iter *iter,
> + unsigned int bytes)
> +{
> + iter->bi_bvec_done += bytes;
> + iter->bi_size -= bytes;
> +}
> +
> static inline void bio_advance_iter(const struct bio *bio,
> struct bvec_iter *iter, unsigned int bytes)
> {
> iter->bi_sector += bytes >> 9;
>
> - if (bio_no_advance_iter(bio))
> + if (bio_no_advance_iter(bio)) {
> iter->bi_size -= bytes;
> - else
> + } else if (bio_flagged(bio, BIO_DMABUF_MAP)) {
> + bio_advance_iter_dmabuf_map(iter, bytes);
This is a bit of a mess. You're using bi_bvec_done for something that
is not bvec_done, which makes the naming very confusing. That is even
more confusing than the existing usage, which isn't great. Also we
add yet another conditional to heavily inlined code. I'd suggest
the following:
- add a prep patch to rename bi_bvec_done to bi_offset, as even for
the existing usage it is the offset into the current bio_vec as
much as it is the count of byes done, as those must be the same
and it is used both ways
- add a prep patch to also increase bi_offset for bio_no_advance_iter.
It is not actually use there, but incrementing it is harmless and
this will avoid a new special case
- please also documet this new usage in the commet in struct bvec_iter.
- then just add the dma buf mapping to the bio_no_advance_iter condition
- figure out what to do about dm_bio_rewind_iter, which pokes into these
things that really should be block layer internal
> }
> @@ -391,7 +403,7 @@ static inline void bio_wouldblock_error(struct bio *bio)
> */
> static inline int bio_iov_vecs_to_alloc(struct iov_iter *iter, int max_segs)
> {
> - if (iov_iter_is_bvec(iter))
> + if (iov_iter_is_bvec(iter) || iov_iter_is_dmabuf_map(iter))
> return 0;
> return iov_iter_npages(iter, max_segs);
> }
Please update the comment for this helper.
> @@ -322,6 +327,7 @@ enum {
> BIO_REMAPPED,
> BIO_ZONE_WRITE_PLUGGING, /* bio handled through zone write plugging */
> BIO_EMULATES_ZONE_APPEND, /* bio emulates a zone append operation */
> + BIO_DMABUF_MAP, /* Using premmaped dma buffers */
Shouldn't this be a REQ_ flag as we should never mix and match bios with
and without this flag in a single request?
^ permalink raw reply
* Re: [PATCH v3 03/10] block: move bvec init into __bio_clone
From: Christoph Hellwig @ 2026-05-13 8:12 UTC (permalink / raw)
To: Pavel Begunkov
Cc: Jens Axboe, Keith Busch, Christoph Hellwig, Sagi Grimberg,
Alexander Viro, Christian Brauner, Andrew Morton, Sumit Semwal,
Christian König, linux-block, linux-kernel, linux-nvme,
linux-fsdevel, io-uring, linux-media, dri-devel, linaro-mm-sig,
Nitesh Shetty, Kanchan Joshi, Anuj Gupta, Tushar Gohad,
William Power, Phil Cayton, Jason Gunthorpe
In-Reply-To: <43a91f54d61d3329316e40c69ace781b4d35fe0b.1777475843.git.asml.silence@gmail.com>
On Wed, Apr 29, 2026 at 04:25:49PM +0100, Pavel Begunkov wrote:
> To quote Cristoph: "Historically __bio_clone itself does not clone the
It's Christoph.
> payload, just the bio. But we got rid of the callers that want to clone
> a bio but not the payload long time ago". So let's move ->bi_io_vec
> assignment into __bio_clone(), so we have a single point where it's set.
but this he-said, she-said is totally irrelevant.
Please state here why this is useful/important, which matters for anyone
trying to understand the code in the future.
^ permalink raw reply
* Re: [PATCH v3 02/10] iov_iter: add iterator type for dmabuf maps
From: Christoph Hellwig @ 2026-05-13 8:11 UTC (permalink / raw)
To: Pavel Begunkov
Cc: Jens Axboe, Keith Busch, Christoph Hellwig, Sagi Grimberg,
Alexander Viro, Christian Brauner, Andrew Morton, Sumit Semwal,
Christian König, linux-block, linux-kernel, linux-nvme,
linux-fsdevel, io-uring, linux-media, dri-devel, linaro-mm-sig,
Nitesh Shetty, Kanchan Joshi, Anuj Gupta, Tushar Gohad,
William Power, Phil Cayton, Jason Gunthorpe
In-Reply-To: <20a233d2f35274817aa643cc0fe113707eb47e72.1777475843.git.asml.silence@gmail.com>
On Wed, Apr 29, 2026 at 04:25:48PM +0100, Pavel Begunkov wrote:
> Introduce a new iterator type for dmabuf maps. The map in an opaque
> object with internals and format specific to the subsystem / driver, and
> only it can use that subsystem / driver for issuing IO. The task of the
> middle layers is to pass the map / iterator further down, maybe doing
> basic splitting and length checking. The iterator can only be used by
> operations of the file the associated map was created for.
Nice, this ended up way simpler than what I would have imagined.
Reviewed-by: Christoph Hellwig <hch@lst.de>
^ permalink raw reply
* Re: [PATCH v3 01/10] file: add callback for creating long-term dmabuf maps
From: Christoph Hellwig @ 2026-05-13 8:11 UTC (permalink / raw)
To: Pavel Begunkov
Cc: Christian König, Jens Axboe, Keith Busch, Christoph Hellwig,
Sagi Grimberg, Alexander Viro, Christian Brauner, Andrew Morton,
Sumit Semwal, linux-block, linux-kernel, linux-nvme,
linux-fsdevel, io-uring, linux-media, dri-devel, linaro-mm-sig,
Nitesh Shetty, Kanchan Joshi, Anuj Gupta, Tushar Gohad,
William Power, Phil Cayton, Jason Gunthorpe
In-Reply-To: <6cce2f4d-7400-4618-82ce-cbd5004c92a4@gmail.com>
On Thu, Apr 30, 2026 at 07:33:39PM +0100, Pavel Begunkov wrote:
>> Then the patch should probably define the full interface and not just add the callback here and then the structure in a follow up patch.
>
> I strongly prefer splitting patches so that they touch one tree at
> a time whenever possible. tbh, I don't see much of a problem it being
> not defined as it's just forwarded in first patches, but I can shuffle
> it around in the series so that definitions come first.
file operations without users are pointless. This really should go
with "lib: add dmabuf token infrastructure" as it is the only way for
the reviewer to make any sense of it. I'll move my discussion of the
interface there for that reason.
^ permalink raw reply
* [PATCH 5.15.y] blk-cgroup: Fix NULL deref caused by blkg_policy_data being installed before init
From: Robert Garcia @ 2026-05-13 8:10 UTC (permalink / raw)
To: stable, Tejun Heo
Cc: Jens Axboe, Breno Leitao, Josef Bacik, Robert Garcia, cgroups,
linux-block, linux-kernel
From: Tejun Heo <tj@kernel.org>
[ Upstream commit ec14a87ee1999b19d8b7ed0fa95fea80644624ae ]
blk-iocost sometimes causes the following crash:
BUG: kernel NULL pointer dereference, address: 00000000000000e0
...
RIP: 0010:_raw_spin_lock+0x17/0x30
Code: be 01 02 00 00 e8 79 38 39 ff 31 d2 89 d0 5d c3 0f 1f 00 0f 1f 44 00 00 55 48 89 e5 65 ff 05 48 d0 34 7e b9 01 00 00 00 31 c0 <f0> 0f b1 0f 75 02 5d c3 89 c6 e8 ea 04 00 00 5d c3 0f 1f 84 00 00
RSP: 0018:ffffc900023b3d40 EFLAGS: 00010046
RAX: 0000000000000000 RBX: 00000000000000e0 RCX: 0000000000000001
RDX: ffffc900023b3d20 RSI: ffffc900023b3cf0 RDI: 00000000000000e0
RBP: ffffc900023b3d40 R08: ffffc900023b3c10 R09: 0000000000000003
R10: 0000000000000064 R11: 000000000000000a R12: ffff888102337000
R13: fffffffffffffff2 R14: ffff88810af408c8 R15: ffff8881070c3600
FS: 00007faaaf364fc0(0000) GS:ffff88842fdc0000(0000) knlGS:0000000000000000
CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: 00000000000000e0 CR3: 00000001097b1000 CR4: 0000000000350ea0
Call Trace:
<TASK>
ioc_weight_write+0x13d/0x410
cgroup_file_write+0x7a/0x130
kernfs_fop_write_iter+0xf5/0x170
vfs_write+0x298/0x370
ksys_write+0x5f/0xb0
__x64_sys_write+0x1b/0x20
do_syscall_64+0x3d/0x80
entry_SYSCALL_64_after_hwframe+0x46/0xb0
This happens because iocg->ioc is NULL. The field is initialized by
ioc_pd_init() and never cleared. The NULL deref is caused by
blkcg_activate_policy() installing blkg_policy_data before initializing it.
blkcg_activate_policy() was doing the following:
1. Allocate pd's for all existing blkg's and install them in blkg->pd[].
2. Initialize all pd's.
3. Online all pd's.
blkcg_activate_policy() only grabs the queue_lock and may release and
re-acquire the lock as allocation may need to sleep. ioc_weight_write()
grabs blkcg->lock and iterates all its blkg's. The two can race and if
ioc_weight_write() runs during #1 or between #1 and #2, it can encounter a
pd which is not initialized yet, leading to crash.
The crash can be reproduced with the following script:
#!/bin/bash
echo +io > /sys/fs/cgroup/cgroup.subtree_control
systemd-run --unit touch-sda --scope dd if=/dev/sda of=/dev/null bs=1M count=1 iflag=direct
echo 100 > /sys/fs/cgroup/system.slice/io.weight
bash -c "echo '8:0 enable=1' > /sys/fs/cgroup/io.cost.qos" &
sleep .2
echo 100 > /sys/fs/cgroup/system.slice/io.weight
with the following patch applied:
> diff --git a/block/blk-cgroup.c b/block/blk-cgroup.c
> index fc49be622e05..38d671d5e10c 100644
> --- a/block/blk-cgroup.c
> +++ b/block/blk-cgroup.c
> @@ -1553,6 +1553,12 @@ int blkcg_activate_policy(struct gendisk *disk, const struct blkcg_policy *pol)
> pd->online = false;
> }
>
> + if (system_state == SYSTEM_RUNNING) {
> + spin_unlock_irq(&q->queue_lock);
> + ssleep(1);
> + spin_lock_irq(&q->queue_lock);
> + }
> +
> /* all allocated, init in the same order */
> if (pol->pd_init_fn)
> list_for_each_entry_reverse(blkg, &q->blkg_list, q_node)
I don't see a reason why all pd's should be allocated, initialized and
onlined together. The only ordering requirement is that parent blkgs to be
initialized and onlined before children, which is guaranteed from the
walking order. Let's fix the bug by allocating, initializing and onlining pd
for each blkg and holding blkcg->lock over initialization and onlining. This
ensures that an installed blkg is always fully initialized and onlined
removing the the race window.
Signed-off-by: Tejun Heo <tj@kernel.org>
Reported-by: Breno Leitao <leitao@debian.org>
Fixes: 9d179b865449 ("blkcg: Fix multiple bugs in blkcg_activate_policy()")
Link: https://lore.kernel.org/r/ZN0p5_W-Q9mAHBVY@slm.duckdns.org
Signed-off-by: Jens Axboe <axboe@kernel.dk>
Signed-off-by: Robert Garcia <rob_garcia@163.com>
---
block/blk-cgroup.c | 32 ++++++++++++++++++--------------
1 file changed, 18 insertions(+), 14 deletions(-)
diff --git a/block/blk-cgroup.c b/block/blk-cgroup.c
index 61fdff5406b5..1179c4bee705 100644
--- a/block/blk-cgroup.c
+++ b/block/blk-cgroup.c
@@ -1337,7 +1337,7 @@ int blkcg_activate_policy(struct request_queue *q,
retry:
spin_lock_irq(&q->queue_lock);
- /* blkg_list is pushed at the head, reverse walk to allocate parents first */
+ /* blkg_list is pushed at the head, reverse walk to initialize parents first */
list_for_each_entry_reverse(blkg, &q->blkg_list, q_node) {
struct blkg_policy_data *pd;
@@ -1375,21 +1375,20 @@ int blkcg_activate_policy(struct request_queue *q,
goto enomem;
}
- blkg->pd[pol->plid] = pd;
+ spin_lock(&blkg->blkcg->lock);
+
pd->blkg = blkg;
pd->plid = pol->plid;
- pd->online = false;
- }
+ blkg->pd[pol->plid] = pd;
- /* all allocated, init in the same order */
- if (pol->pd_init_fn)
- list_for_each_entry_reverse(blkg, &q->blkg_list, q_node)
- pol->pd_init_fn(blkg->pd[pol->plid]);
+ if (pol->pd_init_fn)
+ pol->pd_init_fn(pd);
- list_for_each_entry_reverse(blkg, &q->blkg_list, q_node) {
if (pol->pd_online_fn)
- pol->pd_online_fn(blkg->pd[pol->plid]);
- blkg->pd[pol->plid]->online = true;
+ pol->pd_online_fn(pd);
+ pd->online = true;
+
+ spin_unlock(&blkg->blkcg->lock);
}
__set_bit(pol->plid, q->blkcg_pols);
@@ -1406,14 +1405,19 @@ int blkcg_activate_policy(struct request_queue *q,
return ret;
enomem:
- /* alloc failed, nothing's initialized yet, free everything */
+ /* alloc failed, take down everything */
spin_lock_irq(&q->queue_lock);
list_for_each_entry(blkg, &q->blkg_list, q_node) {
struct blkcg *blkcg = blkg->blkcg;
+ struct blkg_policy_data *pd;
spin_lock(&blkcg->lock);
- if (blkg->pd[pol->plid]) {
- pol->pd_free_fn(blkg->pd[pol->plid]);
+ pd = blkg->pd[pol->plid];
+ if (pd) {
+ if (pd->online && pol->pd_offline_fn)
+ pol->pd_offline_fn(pd);
+ pd->online = false;
+ pol->pd_free_fn(pd);
blkg->pd[pol->plid] = NULL;
}
spin_unlock(&blkcg->lock);
--
2.34.1
^ permalink raw reply related
* Re: [PATCH] sched: flush plug in schedule_preempt_disabled() to prevent deadlock
From: Ming Lei @ 2026-05-13 8:08 UTC (permalink / raw)
To: Peter Zijlstra
Cc: Tejun Heo, Jens Axboe, linux-block, linux-kernel, Ingo Molnar,
Juri Lelli, Vincent Guittot, Michael Wu, Xiaosen He,
Thomas Gleixner
In-Reply-To: <20260513073039.GG1889694@noisy.programming.kicks-ass.net>
On Wed, May 13, 2026 at 09:30:39AM +0200, Peter Zijlstra wrote:
> On Wed, May 13, 2026 at 10:07:03AM +0800, Ming Lei wrote:
> > On Tue, May 12, 2026 at 07:16:36AM -1000, Tejun Heo wrote:
> > > Hello, Ming.
> > >
> > > On Tue, May 12, 2026 at 11:45:14PM +0800, Ming Lei wrote:
> > > > On Tue, May 12, 2026 at 02:40:21PM +0200, Peter Zijlstra wrote:
> > > > > On Tue, May 12, 2026 at 02:04:32PM +0200, Peter Zijlstra wrote:
> > > > > > On Tue, May 12, 2026 at 04:59:39PM +0800, Ming Lei wrote:
> > > > > > > On preemptible kernels, a deadlock can occur when a task with plugged IO
> > > > > > > calls schedule_preempt_disabled():
> > > > > > >
> > > > > > > schedule_preempt_disabled()
> > > > > > > sched_preempt_enable_no_resched() // preemption now enabled
> > > > > > > schedule() // <-- preemption can happen here
> > > > > > > sched_submit_work()
> > > > > > > blk_flush_plug()
> > > > > > >
> > > > > > > After sched_preempt_enable_no_resched() re-enables preemption, the task
> > > > > > > can be preempted (e.g., by a higher-priority RT task) before reaching
> > > > > > > blk_flush_plug() in sched_submit_work(). Since the task's state is
> > > > > > > already TASK_UNINTERRUPTIBLE (set by the mutex/rwsem slowpath caller),
> > > > > > > requests in current->plug remain unflushed for an unbounded time.
> > > > > > >
> > > > > > > If another task depends on those plugged requests to make progress (e.g.,
> > > > > > > to release a lock the sleeping task needs), a deadlock results:
> > > > > > >
> > > > > > > - Task A (writeback worker): holds plugged IO, preempted before
> > > > > > > flushing, stuck on run queue behind higher-priority work
> > > > > > > - Task B: waiting for IO completion from Task A's plug, holds a lock
> > > > > > > that Task A needs to be woken up
> > >
> > > My memory is hazy around io_schedule but the above reads really weird to me.
> > > A task, regardless of its current state stays on the runqueue when
> > > preempted, so the condition is temporary. As soon as the preempted task can
> > > get CPU, it should unwind the situation. That's not a deadlock. Is the
> > > problem that there can be preemption-induced delay in flushing the plugs?
> >
> > IMO, preempting a `!TASK_RUNNING` task can be thought as effective sleep,
>
> No it cannot be. Preemption ignores task state.
Yeah, I get similar conclusion too with AI's assistance.
But both two reports show that the preempted task aren't switched back for
long enough time, can you share any idea for Michael & Xiaosen to investigate
further from scheduler side?
https://lore.kernel.org/linux-block/20260417082744.30124-1-michael@allwinnertech.com/
https://lore.kernel.org/linux-block/5660795d-87de-46f5-add4-7729a02225ef@oss.qualcomm.com/
Thanks,
Ming
^ permalink raw reply
* Re: [PATCH 09/12] swap: push down setting sis->bdev into ->swap_activate
From: Damien Le Moal @ 2026-05-13 7:58 UTC (permalink / raw)
To: Christoph Hellwig
Cc: Darrick J. Wong, Andrew Morton, Chris Li, Kairui Song,
Christian Brauner, Jens Axboe, David Sterba, Theodore Ts'o,
Jaegeuk Kim, Chao Yu, Trond Myklebust, Anna Schumaker,
Namjae Jeon, Hyunchul Lee, Steve French, Paulo Alcantara,
Carlos Maiolino, Naohiro Aota, linux-xfs, linux-fsdevel,
linux-doc, linux-mm, linux-block, linux-btrfs, linux-ext4,
linux-f2fs-devel, linux-nfs, linux-cifs
In-Reply-To: <20260513074608.GA3693@lst.de>
On 5/13/26 16:46, Christoph Hellwig wrote:
> On Wed, May 13, 2026 at 04:44:53PM +0900, Damien Le Moal wrote:
>> Hmmm... With zonefs, swap files can be created on top of conventional zone
>> files. So enforcing "no swap on zoned device" here would break that.
>
> We can check that none of the extents fall onto sequential zones instead
> of just devices.
>
> I still wonder why you bother with swap to zonefs at all, though.
Yeah. I do not think anyone actually use that... But since it is there from the
start, kind of stuck with it now.
--
Damien Le Moal
Western Digital Research
^ permalink raw reply
* [PATCH 6.1.y] blk-cgroup: Fix NULL deref caused by blkg_policy_data being installed before init
From: Robert Garcia @ 2026-05-13 7:56 UTC (permalink / raw)
To: stable, Tejun Heo
Cc: Jens Axboe, Breno Leitao, Josef Bacik, Robert Garcia, cgroups,
linux-block, linux-kernel
From: Tejun Heo <tj@kernel.org>
[ Upstream commit ec14a87ee1999b19d8b7ed0fa95fea80644624ae ]
blk-iocost sometimes causes the following crash:
BUG: kernel NULL pointer dereference, address: 00000000000000e0
...
RIP: 0010:_raw_spin_lock+0x17/0x30
Code: be 01 02 00 00 e8 79 38 39 ff 31 d2 89 d0 5d c3 0f 1f 00 0f 1f 44 00 00 55 48 89 e5 65 ff 05 48 d0 34 7e b9 01 00 00 00 31 c0 <f0> 0f b1 0f 75 02 5d c3 89 c6 e8 ea 04 00 00 5d c3 0f 1f 84 00 00
RSP: 0018:ffffc900023b3d40 EFLAGS: 00010046
RAX: 0000000000000000 RBX: 00000000000000e0 RCX: 0000000000000001
RDX: ffffc900023b3d20 RSI: ffffc900023b3cf0 RDI: 00000000000000e0
RBP: ffffc900023b3d40 R08: ffffc900023b3c10 R09: 0000000000000003
R10: 0000000000000064 R11: 000000000000000a R12: ffff888102337000
R13: fffffffffffffff2 R14: ffff88810af408c8 R15: ffff8881070c3600
FS: 00007faaaf364fc0(0000) GS:ffff88842fdc0000(0000) knlGS:0000000000000000
CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: 00000000000000e0 CR3: 00000001097b1000 CR4: 0000000000350ea0
Call Trace:
<TASK>
ioc_weight_write+0x13d/0x410
cgroup_file_write+0x7a/0x130
kernfs_fop_write_iter+0xf5/0x170
vfs_write+0x298/0x370
ksys_write+0x5f/0xb0
__x64_sys_write+0x1b/0x20
do_syscall_64+0x3d/0x80
entry_SYSCALL_64_after_hwframe+0x46/0xb0
This happens because iocg->ioc is NULL. The field is initialized by
ioc_pd_init() and never cleared. The NULL deref is caused by
blkcg_activate_policy() installing blkg_policy_data before initializing it.
blkcg_activate_policy() was doing the following:
1. Allocate pd's for all existing blkg's and install them in blkg->pd[].
2. Initialize all pd's.
3. Online all pd's.
blkcg_activate_policy() only grabs the queue_lock and may release and
re-acquire the lock as allocation may need to sleep. ioc_weight_write()
grabs blkcg->lock and iterates all its blkg's. The two can race and if
ioc_weight_write() runs during #1 or between #1 and #2, it can encounter a
pd which is not initialized yet, leading to crash.
The crash can be reproduced with the following script:
#!/bin/bash
echo +io > /sys/fs/cgroup/cgroup.subtree_control
systemd-run --unit touch-sda --scope dd if=/dev/sda of=/dev/null bs=1M count=1 iflag=direct
echo 100 > /sys/fs/cgroup/system.slice/io.weight
bash -c "echo '8:0 enable=1' > /sys/fs/cgroup/io.cost.qos" &
sleep .2
echo 100 > /sys/fs/cgroup/system.slice/io.weight
with the following patch applied:
> diff --git a/block/blk-cgroup.c b/block/blk-cgroup.c
> index fc49be622e05..38d671d5e10c 100644
> --- a/block/blk-cgroup.c
> +++ b/block/blk-cgroup.c
> @@ -1553,6 +1553,12 @@ int blkcg_activate_policy(struct gendisk *disk, const struct blkcg_policy *pol)
> pd->online = false;
> }
>
> + if (system_state == SYSTEM_RUNNING) {
> + spin_unlock_irq(&q->queue_lock);
> + ssleep(1);
> + spin_lock_irq(&q->queue_lock);
> + }
> +
> /* all allocated, init in the same order */
> if (pol->pd_init_fn)
> list_for_each_entry_reverse(blkg, &q->blkg_list, q_node)
I don't see a reason why all pd's should be allocated, initialized and
onlined together. The only ordering requirement is that parent blkgs to be
initialized and onlined before children, which is guaranteed from the
walking order. Let's fix the bug by allocating, initializing and onlining pd
for each blkg and holding blkcg->lock over initialization and onlining. This
ensures that an installed blkg is always fully initialized and onlined
removing the the race window.
Signed-off-by: Tejun Heo <tj@kernel.org>
Reported-by: Breno Leitao <leitao@debian.org>
Fixes: 9d179b865449 ("blkcg: Fix multiple bugs in blkcg_activate_policy()")
Link: https://lore.kernel.org/r/ZN0p5_W-Q9mAHBVY@slm.duckdns.org
Signed-off-by: Jens Axboe <axboe@kernel.dk>
Signed-off-by: Robert Garcia <rob_garcia@163.com>
---
block/blk-cgroup.c | 32 ++++++++++++++++++--------------
1 file changed, 18 insertions(+), 14 deletions(-)
diff --git a/block/blk-cgroup.c b/block/blk-cgroup.c
index f314192b6de8..ce074d9fb709 100644
--- a/block/blk-cgroup.c
+++ b/block/blk-cgroup.c
@@ -1392,7 +1392,7 @@ int blkcg_activate_policy(struct request_queue *q,
retry:
spin_lock_irq(&q->queue_lock);
- /* blkg_list is pushed at the head, reverse walk to allocate parents first */
+ /* blkg_list is pushed at the head, reverse walk to initialize parents first */
list_for_each_entry_reverse(blkg, &q->blkg_list, q_node) {
struct blkg_policy_data *pd;
@@ -1430,21 +1430,20 @@ int blkcg_activate_policy(struct request_queue *q,
goto enomem;
}
- blkg->pd[pol->plid] = pd;
+ spin_lock(&blkg->blkcg->lock);
+
pd->blkg = blkg;
pd->plid = pol->plid;
- pd->online = false;
- }
+ blkg->pd[pol->plid] = pd;
- /* all allocated, init in the same order */
- if (pol->pd_init_fn)
- list_for_each_entry_reverse(blkg, &q->blkg_list, q_node)
- pol->pd_init_fn(blkg->pd[pol->plid]);
+ if (pol->pd_init_fn)
+ pol->pd_init_fn(pd);
- list_for_each_entry_reverse(blkg, &q->blkg_list, q_node) {
if (pol->pd_online_fn)
- pol->pd_online_fn(blkg->pd[pol->plid]);
- blkg->pd[pol->plid]->online = true;
+ pol->pd_online_fn(pd);
+ pd->online = true;
+
+ spin_unlock(&blkg->blkcg->lock);
}
__set_bit(pol->plid, q->blkcg_pols);
@@ -1461,14 +1460,19 @@ int blkcg_activate_policy(struct request_queue *q,
return ret;
enomem:
- /* alloc failed, nothing's initialized yet, free everything */
+ /* alloc failed, take down everything */
spin_lock_irq(&q->queue_lock);
list_for_each_entry(blkg, &q->blkg_list, q_node) {
struct blkcg *blkcg = blkg->blkcg;
+ struct blkg_policy_data *pd;
spin_lock(&blkcg->lock);
- if (blkg->pd[pol->plid]) {
- pol->pd_free_fn(blkg->pd[pol->plid]);
+ pd = blkg->pd[pol->plid];
+ if (pd) {
+ if (pd->online && pol->pd_offline_fn)
+ pol->pd_offline_fn(pd);
+ pd->online = false;
+ pol->pd_free_fn(pd);
blkg->pd[pol->plid] = NULL;
}
spin_unlock(&blkcg->lock);
--
2.34.1
^ permalink raw reply related
* Re: [PATCH 09/12] swap: push down setting sis->bdev into ->swap_activate
From: Christoph Hellwig @ 2026-05-13 7:46 UTC (permalink / raw)
To: Damien Le Moal
Cc: Christoph Hellwig, Darrick J. Wong, Andrew Morton, Chris Li,
Kairui Song, Christian Brauner, Jens Axboe, David Sterba,
Theodore Ts'o, Jaegeuk Kim, Chao Yu, Trond Myklebust,
Anna Schumaker, Namjae Jeon, Hyunchul Lee, Steve French,
Paulo Alcantara, Carlos Maiolino, Naohiro Aota, linux-xfs,
linux-fsdevel, linux-doc, linux-mm, linux-block, linux-btrfs,
linux-ext4, linux-f2fs-devel, linux-nfs, linux-cifs
In-Reply-To: <acd6428b-a352-4f7b-a349-b2c9e341fd87@kernel.org>
On Wed, May 13, 2026 at 04:44:53PM +0900, Damien Le Moal wrote:
> Hmmm... With zonefs, swap files can be created on top of conventional zone
> files. So enforcing "no swap on zoned device" here would break that.
We can check that none of the extents fall onto sequential zones instead
of just devices.
I still wonder why you bother with swap to zonefs at all, though.
^ permalink raw reply
* Re: [PATCH 09/12] swap: push down setting sis->bdev into ->swap_activate
From: Damien Le Moal @ 2026-05-13 7:44 UTC (permalink / raw)
To: Christoph Hellwig, Darrick J. Wong
Cc: Andrew Morton, Chris Li, Kairui Song, Christian Brauner,
Jens Axboe, David Sterba, Theodore Ts'o, Jaegeuk Kim, Chao Yu,
Trond Myklebust, Anna Schumaker, Namjae Jeon, Hyunchul Lee,
Steve French, Paulo Alcantara, Carlos Maiolino, Naohiro Aota,
linux-xfs, linux-fsdevel, linux-doc, linux-mm, linux-block,
linux-btrfs, linux-ext4, linux-f2fs-devel, linux-nfs, linux-cifs
In-Reply-To: <20260513055806.GC1236@lst.de>
On 5/13/26 14:58, Christoph Hellwig wrote:
> On Tue, May 12, 2026 at 10:08:46AM -0700, Darrick J. Wong wrote:
>>> + /* Only one bdev per swap file for now. */
>>> + if (!sis->bdev)
>>> + sis->bdev = bdev;
>>> + else if (bdev != sis->bdev)
>>> + return -EINVAL;
>>
>> Should this return error if the bdev is zoned? AFAICT XFS and zonefs
>> already guard against this, but other fses might be more naïve.
>
> Yes, now that the bdev is passed down to add_swap_extent we could
> consolidate the check here.
Hmmm... With zonefs, swap files can be created on top of conventional zone
files. So enforcing "no swap on zoned device" here would break that.
--
Damien Le Moal
Western Digital Research
^ permalink raw reply
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox