* [PATCH 1/4] fscrypt: Cap data units at the page size
2026-11-03 15:31 [PATCH 0/4] ext4: support encryption with block size larger than page size Baokun Li
@ 2026-11-03 15:31 ` Baokun Li
2026-11-03 22:40 ` Eric Biggers
2026-11-03 15:31 ` [PATCH 2/4] fscrypt: Validate the test_dummy_encryption policy for new inodes Baokun Li
` (2 subsequent siblings)
3 siblings, 1 reply; 6+ messages in thread
From: Baokun Li @ 2026-11-03 15:31 UTC (permalink / raw)
To: linux-fscrypt
Cc: ebiggers, jaegeuk, tytso, linux-ext4, adilger.kernel, jack,
yi.zhang, ojaswin, ritesh.list, linux-kernel
blk-crypto-fallback cannot handle a data unit larger than a page:
it walks the bio one page-sized segment at a time, so a data unit
that spans multiple pages never ends on a segment boundary and
the bio fails with BLK_STS_INVAL after the I/O was already
reported to userspace as successful, losing data.
Fix this in fscrypt_supported_v2_policy() by rejecting any v2 policy
that explicitly specifies a data unit size larger than the page size.
A v1 policy always uses the block size as its data unit size,
so fscrypt_supported_v1_policy() has to reject any v1 policy on
a filesystem with a block size larger than the page size (e.g. ext4).
A v2 policy with no data unit size specified uses the block size too,
but rejection is not necessary there: sub-block data units are
supported, so it is enough to cap the data unit bits at PAGE_SHIFT in
fscrypt_policy_v2_du_bits().
Note that a filesystem whose block size exceeds 4K then cannot be
made compatible across page sizes (e.g. 4K and 64K); this can be
avoided by explicitly specifying a data unit size of 4K or smaller.
Suggested-by: Eric Biggers <ebiggers@kernel.org>
Link: https://lore.kernel.org/r/20261015185325.GG2170687@google.com
Signed-off-by: Baokun Li <libaokun@linux.alibaba.com>
---
Documentation/filesystems/fscrypt.rst | 19 +++++++++++++++----
fs/crypto/fscrypt_private.h | 4 +++-
fs/crypto/policy.c | 11 ++++++++++-
3 files changed, 28 insertions(+), 6 deletions(-)
diff --git a/Documentation/filesystems/fscrypt.rst b/Documentation/filesystems/fscrypt.rst
index 6e2e511a70bb..78251a9706ba 100644
--- a/Documentation/filesystems/fscrypt.rst
+++ b/Documentation/filesystems/fscrypt.rst
@@ -649,8 +649,9 @@ This structure must be initialized as follows:
Not all filesystems support setting ``log2_data_unit_size``. ext4
and f2fs support it since Linux v6.7. On filesystems that support
it, the supported nonzero values are 9 through the log2 of the
- filesystem block size, inclusively. The default value of 0 selects
- the filesystem block size.
+ filesystem block size, inclusively, but no greater than the log2 of
+ the page size. The default value of 0 selects the filesystem block
+ size, or the page size if the block size is larger.
The main use case for ``log2_data_unit_size`` is for selecting a
data unit size smaller than the filesystem block size for
@@ -659,8 +660,18 @@ This structure must be initialized as follows:
useful for checking which data unit sizes are supported by a
particular system's inline encryption hardware.
- Leave this field zeroed unless you are certain you need it. Using
- an unnecessarily small data unit size reduces performance.
+ A v1 policy has no ``log2_data_unit_size`` and always uses the
+ filesystem block size, so it cannot be used on a filesystem whose
+ block size is larger than the page size.
+
+ A v2 policy on such a filesystem falls back to the page size, so
+ the data unit size depends on the machine's page size and the data
+ becomes unreadable elsewhere. An image portable across page sizes
+ should explicitly specify a data unit size of 4096 or smaller.
+
+ Leave this field zeroed unless you are certain one of the reasons
+ above applies. Using an unnecessarily small data unit size reduces
+ performance.
- For v2 encryption policies, ``__reserved`` must be zeroed.
diff --git a/fs/crypto/fscrypt_private.h b/fs/crypto/fscrypt_private.h
index e602c31b3844..0dc158563e71 100644
--- a/fs/crypto/fscrypt_private.h
+++ b/fs/crypto/fscrypt_private.h
@@ -203,7 +203,9 @@ static inline int
fscrypt_policy_v2_du_bits(const struct fscrypt_policy_v2 *policy,
const struct inode *inode)
{
- return policy->log2_data_unit_size ?: inode->i_blkbits;
+ if (policy->log2_data_unit_size)
+ return policy->log2_data_unit_size;
+ return min_t(unsigned int, inode->i_blkbits, PAGE_SHIFT);
}
static inline int
diff --git a/fs/crypto/policy.c b/fs/crypto/policy.c
index 6dd510f93e6d..84c496da248e 100644
--- a/fs/crypto/policy.c
+++ b/fs/crypto/policy.c
@@ -228,6 +228,14 @@ static bool fscrypt_supported_v1_policy(const struct fscrypt_policy_v1 *policy,
return false;
}
+ if (inode->i_sb->s_cop->is_block_based &&
+ inode->i_blkbits > PAGE_SHIFT) {
+ /* v1 has no way to select a sub-block data unit size. */
+ fscrypt_warn(inode,
+ "v1 policies can't be used on fs with block size > page size");
+ return false;
+ }
+
return true;
}
@@ -270,7 +278,8 @@ static bool fscrypt_supported_v2_policy(const struct fscrypt_policy_v2 *policy,
return false;
}
if (policy->log2_data_unit_size > inode->i_blkbits ||
- policy->log2_data_unit_size < SECTOR_SHIFT /* 9 */) {
+ policy->log2_data_unit_size < SECTOR_SHIFT /* 9 */ ||
+ policy->log2_data_unit_size > PAGE_SHIFT) {
fscrypt_warn(inode,
"Unsupported log2_data_unit_size in encryption policy: %d",
policy->log2_data_unit_size);
--
2.43.7
^ permalink raw reply [flat|nested] 6+ messages in thread* Re: [PATCH 1/4] fscrypt: Cap data units at the page size
2026-11-03 15:31 ` [PATCH 1/4] fscrypt: Cap data units at the " Baokun Li
@ 2026-11-03 22:40 ` Eric Biggers
0 siblings, 0 replies; 6+ messages in thread
From: Eric Biggers @ 2026-11-03 22:40 UTC (permalink / raw)
To: Baokun Li
Cc: linux-fscrypt, jaegeuk, tytso, linux-ext4, adilger.kernel, jack,
yi.zhang, ojaswin, ritesh.list, linux-kernel
On Tue, Nov 03, 2026 at 11:31:29PM +0800, Baokun Li wrote:
> diff --git a/fs/crypto/fscrypt_private.h b/fs/crypto/fscrypt_private.h
> index e602c31b3844..0dc158563e71 100644
> --- a/fs/crypto/fscrypt_private.h
> +++ b/fs/crypto/fscrypt_private.h
> @@ -203,7 +203,9 @@ static inline int
> fscrypt_policy_v2_du_bits(const struct fscrypt_policy_v2 *policy,
> const struct inode *inode)
> {
> - return policy->log2_data_unit_size ?: inode->i_blkbits;
> + if (policy->log2_data_unit_size)
> + return policy->log2_data_unit_size;
> + return min_t(unsigned int, inode->i_blkbits, PAGE_SHIFT);
> }
This would make the interpretation of the default value
log2_data_unit_size=0 depend on the page size, which (unlike i_blkbits)
is not part of the filesystem's on-disk format.
I think that for a sub-block data unit size to be used, it should need
to continue to be explicitly specified. Otherwise the default would
become ambiguous.
- Eric
^ permalink raw reply [flat|nested] 6+ messages in thread
* [PATCH 2/4] fscrypt: Validate the test_dummy_encryption policy for new inodes
2026-11-03 15:31 [PATCH 0/4] ext4: support encryption with block size larger than page size Baokun Li
2026-11-03 15:31 ` [PATCH 1/4] fscrypt: Cap data units at the " Baokun Li
@ 2026-11-03 15:31 ` Baokun Li
2026-11-03 15:31 ` [PATCH 3/4] ext4: don't put a DUN wraparound in the middle of a read bio Baokun Li
2026-11-03 15:31 ` [PATCH 4/4] ext4: allow large folios and bs > ps encryption Baokun Li
3 siblings, 0 replies; 6+ messages in thread
From: Baokun Li @ 2026-11-03 15:31 UTC (permalink / raw)
To: linux-fscrypt
Cc: ebiggers, jaegeuk, tytso, linux-ext4, adilger.kernel, jack,
yi.zhang, ojaswin, ritesh.list, linux-kernel
Policies set through FS_IOC_SET_ENCRYPTION_POLICY or inherited from
an encrypted directory are validated, but the test_dummy_encryption
policy is not.
fscrypt_prepare_new_inode() applies it directly to the new inode.
As a result, a v1 dummy policy on a filesystem whose block size
exceeds the page size is used unchecked, and data written under
it is lost at writeback.
Fix this by validating dummy policy in fscrypt_prepare_new_inode()
before applying it. Inodes in an encrypted directory inherit an
already validated policy, so the check only concerns the unencrypted
case where the dummy policy is used.
Signed-off-by: Baokun Li <libaokun@linux.alibaba.com>
---
fs/crypto/keysetup.c | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/fs/crypto/keysetup.c b/fs/crypto/keysetup.c
index a77b889d7578..497ba67ec6fe 100644
--- a/fs/crypto/keysetup.c
+++ b/fs/crypto/keysetup.c
@@ -763,6 +763,14 @@ int fscrypt_prepare_new_inode(struct inode *dir, struct inode *inode,
!S_ISLNK(inode->i_mode))
return 0;
+ /*
+ * A policy inherited from an encrypted directory was validated
+ * when the directory's key was set up. The test dummy policy was
+ * not, so check it here.
+ */
+ if (!IS_ENCRYPTED(dir) && !fscrypt_supported_policy(policy, inode))
+ return -EINVAL;
+
*encrypt_ret = true;
get_random_bytes(nonce, FSCRYPT_FILE_NONCE_SIZE);
--
2.43.7
^ permalink raw reply [flat|nested] 6+ messages in thread
* [PATCH 3/4] ext4: don't put a DUN wraparound in the middle of a read bio
2026-11-03 15:31 [PATCH 0/4] ext4: support encryption with block size larger than page size Baokun Li
2026-11-03 15:31 ` [PATCH 1/4] fscrypt: Cap data units at the " Baokun Li
2026-11-03 15:31 ` [PATCH 2/4] fscrypt: Validate the test_dummy_encryption policy for new inodes Baokun Li
@ 2026-11-03 15:31 ` Baokun Li
2026-11-03 15:31 ` [PATCH 4/4] ext4: allow large folios and bs > ps encryption Baokun Li
3 siblings, 0 replies; 6+ messages in thread
From: Baokun Li @ 2026-11-03 15:31 UTC (permalink / raw)
To: linux-fscrypt
Cc: ebiggers, jaegeuk, tytso, linux-ext4, adilger.kernel, jack,
yi.zhang, ojaswin, ritesh.list, linux-kernel
Under IV_INO_LBLK_32, a file's DUNs wrap at 2^32, and the policy
is only allowed when the block size equals the page size.
ext4_mpage_readpages() adds a whole folio to one bio, but checks
only the DUN of the first block. blk-crypto assumes the DUNs
in a bio are contiguous and never wraps them, so blocks past
the wraparound are read with the wrong DUNs.
Writeback and direct I/O get this right, but with large folios
a single folio can still span the wraparound.
Fix this by routing such a folio through the buffer_head path,
which submits one bio per block and so gives each block its own DUN.
fscrypt_limit_io_blocks() tells whether the wraparound falls after
the first block; a wraparound at the first block itself is already
caught by fscrypt_mergeable_bio().
A file's DUNs wrap at most once, so at most one folio per file takes
the slower path, and the per-block bios can be merged again by the
block layer.
Signed-off-by: Baokun Li <libaokun@linux.alibaba.com>
---
fs/ext4/readpage.c | 10 ++++++++++
1 file changed, 10 insertions(+)
diff --git a/fs/ext4/readpage.c b/fs/ext4/readpage.c
index c7b6cdb2e124..43af6d9d6ddb 100644
--- a/fs/ext4/readpage.c
+++ b/fs/ext4/readpage.c
@@ -264,6 +264,16 @@ static int ext4_mpage_readpages(struct inode *inode, struct fsverity_info *vi,
folio_set_mappedtodisk(folio);
}
+ /*
+ * blk-crypto assumes a bio's DUNs are contiguous, but under
+ * IV_INO_LBLK_32 alone they wrap at 2^32. A folio spanning
+ * the wrap would be decrypted with wrong IVs, so it goes to
+ * the buffer_head-based path, one bio per block.
+ */
+ if (first_hole >
+ fscrypt_limit_io_blocks(inode, pos >> blkbits, first_hole))
+ goto confused;
+
/*
* This folio will go to BIO. Do we need to send this
* BIO off first?
--
2.43.7
^ permalink raw reply [flat|nested] 6+ messages in thread
* [PATCH 4/4] ext4: allow large folios and bs > ps encryption
2026-11-03 15:31 [PATCH 0/4] ext4: support encryption with block size larger than page size Baokun Li
` (2 preceding siblings ...)
2026-11-03 15:31 ` [PATCH 3/4] ext4: don't put a DUN wraparound in the middle of a read bio Baokun Li
@ 2026-11-03 15:31 ` Baokun Li
3 siblings, 0 replies; 6+ messages in thread
From: Baokun Li @ 2026-11-03 15:31 UTC (permalink / raw)
To: linux-fscrypt
Cc: ebiggers, jaegeuk, tytso, linux-ext4, adilger.kernel, jack,
yi.zhang, ojaswin, ritesh.list, linux-kernel
ext4 refuses encryption when the block size exceeds the page size
in three places: at mount time (ext4_check_large_folio()),
in ext4_set_context(), and in __ext4_iget().
Sub-block data units are now enabled by default, so encryption
works on a filesystem whose block size exceeds the page size.
Remove the three checks; stable trees should keep them and
not backport this feature.
ext4_set_max_mapping_order() now runs on every mount, and
the preceding patch routes a folio that crosses a DUN wraparound
to the buffer_head path. Encrypted files therefore get the same
large folio orders as unencrypted files, including when block size
is larger than page size.
Signed-off-by: Baokun Li <libaokun@linux.alibaba.com>
---
fs/ext4/crypto.c | 9 ---------
fs/ext4/inode.c | 6 ------
fs/ext4/super.c | 22 +---------------------
3 files changed, 1 insertion(+), 36 deletions(-)
diff --git a/fs/ext4/crypto.c b/fs/ext4/crypto.c
index 318b2eaea741..1a0fccb084ef 100644
--- a/fs/ext4/crypto.c
+++ b/fs/ext4/crypto.c
@@ -156,15 +156,6 @@ static int ext4_set_context(struct inode *inode, const void *ctx, size_t len,
if (ext4_test_inode_flag(inode, EXT4_INODE_DAX))
return -EOPNOTSUPP;
- /*
- * Encryption is not supported when the block size is larger
- * than the page size. This is rejected at mount time by
- * ext4_check_large_folio(), and this check here is for the case
- * when the 'encrypt' feature is enabled on a mounted filesystem.
- */
- if (inode->i_sb->s_blocksize > PAGE_SIZE)
- return -EOPNOTSUPP;
-
res = ext4_convert_inline_data(inode);
if (res)
return res;
diff --git a/fs/ext4/inode.c b/fs/ext4/inode.c
index 10cd85d4c721..b4d581aa5ee1 100644
--- a/fs/ext4/inode.c
+++ b/fs/ext4/inode.c
@@ -6571,12 +6571,6 @@ struct inode *__ext4_iget(struct super_block *sb, unsigned long ino,
ret = -EFSCORRUPTED;
goto bad_inode;
}
- if (IS_ENCRYPTED(inode) && sb->s_blocksize > PAGE_SIZE) {
- ext4_error_inode(inode, function, line, 0,
- "encrypted inode with block size larger than page size");
- ret = -EFSCORRUPTED;
- goto bad_inode;
- }
ext4_set_inode_mapping_order(inode);
diff --git a/fs/ext4/super.c b/fs/ext4/super.c
index 9cae52471dee..ba2d2e184d3a 100644
--- a/fs/ext4/super.c
+++ b/fs/ext4/super.c
@@ -5165,24 +5165,6 @@ static void ext4_set_max_mapping_order(struct super_block *sb)
sbi->s_max_folio_order = EXT4_MAX_PAGECACHE_ORDER(sb);
}
-static int ext4_check_large_folio(struct super_block *sb)
-{
- const char *err_str = NULL;
-
- if (ext4_has_feature_encrypt(sb))
- err_str = "encrypt";
-
- if (!err_str) {
- ext4_set_max_mapping_order(sb);
- } else if (sb->s_blocksize > PAGE_SIZE) {
- ext4_msg(sb, KERN_ERR, "bs(%lu) > ps(%lu) unsupported for %s",
- sb->s_blocksize, PAGE_SIZE, err_str);
- return -EINVAL;
- }
-
- return 0;
-}
-
static int ext4_load_super(struct super_block *sb, ext4_fsblk_t *lsb,
int silent)
{
@@ -5461,9 +5443,7 @@ static int __ext4_fill_super(struct fs_context *fc, struct super_block *sb)
ext4_apply_options(fc, sb);
- err = ext4_check_large_folio(sb);
- if (err < 0)
- goto failed_mount;
+ ext4_set_max_mapping_order(sb);
err = ext4_encoding_init(sb, es);
if (err)
--
2.43.7
^ permalink raw reply [flat|nested] 6+ messages in thread