Linux EXT4 FS development
 help / color / mirror / Atom feed
* [PATCH 14/16] fscrypt: Remove unused functions and workqueue
From: Eric Biggers @ 2026-06-24  5:03 UTC (permalink / raw)
  To: linux-fscrypt
  Cc: linux-fsdevel, linux-ext4, linux-f2fs-devel, linux-block,
	Christoph Hellwig, Theodore Ts'o, Andreas Dilger, Baokun Li,
	Jan Kara, Ojaswin Mujoo, Ritesh Harjani, Zhang Yi, Jaegeuk Kim,
	Chao Yu, Eric Biggers
In-Reply-To: <20260624050334.124606-1-ebiggers@kernel.org>

Remove functions that are no longer used:

- fscrypt_decrypt_bio()
- fscrypt_decrypt_pagecache_blocks()
- fscrypt_inode_uses_fs_layer_crypto()
- fscrypt_inode_uses_inline_crypto()
- fscrypt_enqueue_decrypt_work()

This makes the decryption workqueue unused, so remove it too.

Signed-off-by: Eric Biggers <ebiggers@kernel.org>
---
 fs/crypto/bio.c         | 32 --------------------
 fs/crypto/crypto.c      | 65 -----------------------------------------
 include/linux/fscrypt.h | 47 -----------------------------
 3 files changed, 144 deletions(-)

diff --git a/fs/crypto/bio.c b/fs/crypto/bio.c
index 58b6b13eeedd..db095258cfca 100644
--- a/fs/crypto/bio.c
+++ b/fs/crypto/bio.c
@@ -13,42 +13,10 @@
 #include <linux/namei.h>
 #include <linux/pagemap.h>
 
 #include "fscrypt_private.h"
 
-/**
- * fscrypt_decrypt_bio() - decrypt the contents of a bio
- * @bio: the bio to decrypt
- *
- * Decrypt the contents of a "read" bio following successful completion of the
- * underlying disk read.  The bio must be reading a whole number of blocks of an
- * encrypted file directly into the page cache.  If the bio is reading the
- * ciphertext into bounce pages instead of the page cache (for example, because
- * the file is also compressed, so decompression is required after decryption),
- * then this function isn't applicable.  This function may sleep, so it must be
- * called from a workqueue rather than from the bio's bi_end_io callback.
- *
- * Return: %true on success; %false on failure.  On failure, bio->bi_status is
- *	   also set to an error status.
- */
-bool fscrypt_decrypt_bio(struct bio *bio)
-{
-	struct folio_iter fi;
-
-	bio_for_each_folio_all(fi, bio) {
-		int err = fscrypt_decrypt_pagecache_blocks(fi.folio, fi.length,
-							   fi.offset);
-
-		if (err) {
-			bio->bi_status = errno_to_blk_status(err);
-			return false;
-		}
-	}
-	return true;
-}
-EXPORT_SYMBOL(fscrypt_decrypt_bio);
-
 struct fscrypt_zero_done {
 	atomic_t		pending;
 	blk_status_t		status;
 	struct completion	done;
 };
diff --git a/fs/crypto/crypto.c b/fs/crypto/crypto.c
index 8c4660429418..27663f4d8705 100644
--- a/fs/crypto/crypto.c
+++ b/fs/crypto/crypto.c
@@ -36,21 +36,14 @@ module_param(num_prealloc_crypto_pages, uint, 0444);
 MODULE_PARM_DESC(num_prealloc_crypto_pages,
 		"Number of crypto pages to preallocate");
 
 static mempool_t *fscrypt_bounce_page_pool = NULL;
 
-static struct workqueue_struct *fscrypt_read_workqueue;
 static DEFINE_MUTEX(fscrypt_init_mutex);
 
 struct kmem_cache *fscrypt_inode_info_cachep;
 
-void fscrypt_enqueue_decrypt_work(struct work_struct *work)
-{
-	queue_work(fscrypt_read_workqueue, work);
-}
-EXPORT_SYMBOL(fscrypt_enqueue_decrypt_work);
-
 static struct page *fscrypt_alloc_bounce_page(gfp_t gfp_flags)
 {
 	if (WARN_ON_ONCE(!fscrypt_bounce_page_pool)) {
 		/*
 		 * Oops, the filesystem called a function that uses the bounce
@@ -236,54 +229,10 @@ int fscrypt_encrypt_block_inplace(const struct inode *inode, struct page *page,
 				       FS_ENCRYPT, lblk_num, page, page, len,
 				       offs);
 }
 EXPORT_SYMBOL(fscrypt_encrypt_block_inplace);
 
-/**
- * fscrypt_decrypt_pagecache_blocks() - Decrypt data from a pagecache folio
- * @folio: the pagecache folio containing the data to decrypt
- * @len: size of the data to decrypt, in bytes
- * @offs: offset within @folio of the data to decrypt, in bytes
- *
- * Decrypt data that has just been read from an encrypted file.  The data must
- * be located in a pagecache folio that is still locked and not yet uptodate.
- * The length and offset of the data must be aligned to the file's crypto data
- * unit size.  Alignment to the filesystem block size fulfills this requirement,
- * as the filesystem block size is always a multiple of the data unit size.
- *
- * Return: 0 on success; -errno on failure
- */
-int fscrypt_decrypt_pagecache_blocks(struct folio *folio, size_t len,
-				     size_t offs)
-{
-	const struct inode *inode = folio->mapping->host;
-	const struct fscrypt_inode_info *ci = fscrypt_get_inode_info_raw(inode);
-	const unsigned int du_bits = ci->ci_data_unit_bits;
-	const unsigned int du_size = 1U << du_bits;
-	u64 index = ((u64)folio->index << (PAGE_SHIFT - du_bits)) +
-		    (offs >> du_bits);
-	size_t i;
-	int err;
-
-	if (WARN_ON_ONCE(!folio_test_locked(folio)))
-		return -EINVAL;
-
-	if (WARN_ON_ONCE(len <= 0 || !IS_ALIGNED(len | offs, du_size)))
-		return -EINVAL;
-
-	for (i = offs; i < offs + len; i += du_size, index++) {
-		struct page *page = folio_page(folio, i >> PAGE_SHIFT);
-
-		err = fscrypt_crypt_data_unit(ci, FS_DECRYPT, index, page,
-					      page, du_size, i & ~PAGE_MASK);
-		if (err)
-			return err;
-	}
-	return 0;
-}
-EXPORT_SYMBOL(fscrypt_decrypt_pagecache_blocks);
-
 /**
  * fscrypt_decrypt_block_inplace() - Decrypt a filesystem block in-place
  * @inode:     The inode to which this block belongs
  * @page:      The page containing the block to decrypt
  * @len:       Size of block to decrypt.  This must be a multiple of
@@ -369,24 +318,10 @@ void fscrypt_msg(const struct inode *inode, const char *level,
 	va_end(args);
 }
 
 static int __init fscrypt_init(void)
 {
-	/*
-	 * Use an unbound workqueue to allow bios to be decrypted in parallel
-	 * even when they happen to complete on the same CPU.  This sacrifices
-	 * locality, but it's worthwhile since decryption is CPU-intensive.
-	 *
-	 * Also use a high-priority workqueue to prioritize decryption work,
-	 * which blocks reads from completing, over regular application tasks.
-	 */
-	fscrypt_read_workqueue = alloc_workqueue("fscrypt_read_queue",
-						 WQ_UNBOUND | WQ_HIGHPRI,
-						 num_online_cpus());
-	if (!fscrypt_read_workqueue)
-		panic("failed to allocate fscrypt_read_queue");
-
 	fscrypt_inode_info_cachep = KMEM_CACHE(fscrypt_inode_info,
 					       SLAB_RECLAIM_ACCOUNT |
 					       SLAB_PANIC);
 	fscrypt_init_keyring();
 	return 0;
diff --git a/include/linux/fscrypt.h b/include/linux/fscrypt.h
index 43bafdd67dd7..acf5b28eb9d7 100644
--- a/include/linux/fscrypt.h
+++ b/include/linux/fscrypt.h
@@ -341,20 +341,17 @@ static inline void fscrypt_prepare_dentry(struct dentry *dentry,
 		spin_unlock(&dentry->d_lock);
 	}
 }
 
 /* crypto.c */
-void fscrypt_enqueue_decrypt_work(struct work_struct *);
 
 struct page *fscrypt_encrypt_pagecache_blocks(struct folio *folio,
 		size_t len, size_t offs, gfp_t gfp_flags);
 int fscrypt_encrypt_block_inplace(const struct inode *inode, struct page *page,
 				  unsigned int len, unsigned int offs,
 				  u64 lblk_num);
 
-int fscrypt_decrypt_pagecache_blocks(struct folio *folio, size_t len,
-				     size_t offs);
 int fscrypt_decrypt_block_inplace(const struct inode *inode, struct page *page,
 				  unsigned int len, unsigned int offs,
 				  u64 lblk_num);
 
 static inline bool fscrypt_is_bounce_page(struct page *page)
@@ -448,11 +445,10 @@ int fscrypt_fname_disk_to_usr(const struct inode *inode,
 bool fscrypt_match_name(const struct fscrypt_name *fname,
 			const u8 *de_name, u32 de_name_len);
 u64 fscrypt_fname_siphash(const struct inode *dir, const struct qstr *name);
 
 /* bio.c */
-bool fscrypt_decrypt_bio(struct bio *bio);
 int fscrypt_zeroout_range(const struct inode *inode, loff_t pos,
 			  sector_t sector, u64 len);
 
 /* hooks.c */
 int fscrypt_file_open(struct inode *inode, struct file *filp);
@@ -508,13 +504,10 @@ static inline void fscrypt_prepare_dentry(struct dentry *dentry,
 					  bool is_nokey_name)
 {
 }
 
 /* crypto.c */
-static inline void fscrypt_enqueue_decrypt_work(struct work_struct *work)
-{
-}
 
 static inline struct page *fscrypt_encrypt_pagecache_blocks(struct folio *folio,
 		size_t len, size_t offs, gfp_t gfp_flags)
 {
 	return ERR_PTR(-EOPNOTSUPP);
@@ -526,16 +519,10 @@ static inline int fscrypt_encrypt_block_inplace(const struct inode *inode,
 						unsigned int offs, u64 lblk_num)
 {
 	return -EOPNOTSUPP;
 }
 
-static inline int fscrypt_decrypt_pagecache_blocks(struct folio *folio,
-						   size_t len, size_t offs)
-{
-	return -EOPNOTSUPP;
-}
-
 static inline int fscrypt_decrypt_block_inplace(const struct inode *inode,
 						struct page *page,
 						unsigned int len,
 						unsigned int offs, u64 lblk_num)
 {
@@ -749,14 +736,10 @@ static inline int fscrypt_d_revalidate(struct inode *dir, const struct qstr *nam
 {
 	return 1;
 }
 
 /* bio.c */
-static inline bool fscrypt_decrypt_bio(struct bio *bio)
-{
-	return true;
-}
 
 static inline int fscrypt_zeroout_range(const struct inode *inode, loff_t pos,
 					sector_t sector, u64 len)
 {
 	return -EOPNOTSUPP;
@@ -890,40 +873,10 @@ static inline u64 fscrypt_limit_io_blocks(const struct inode *inode, u64 lblk,
 {
 	return nr_blocks;
 }
 #endif /* !CONFIG_FS_ENCRYPTION_INLINE_CRYPT */
 
-/**
- * fscrypt_inode_uses_inline_crypto() - test whether an inode uses inline
- *					encryption
- * @inode: an inode. If encrypted, its key must be set up.
- *
- * Return: true if the inode requires file contents encryption and if the
- *	   encryption should be done in the block layer via blk-crypto rather
- *	   than in the filesystem layer.
- */
-static inline bool fscrypt_inode_uses_inline_crypto(const struct inode *inode)
-{
-	return fscrypt_needs_contents_encryption(inode) &&
-	       inode->i_sb->s_cop->is_block_based;
-}
-
-/**
- * fscrypt_inode_uses_fs_layer_crypto() - test whether an inode uses fs-layer
- *					  encryption
- * @inode: an inode. If encrypted, its key must be set up.
- *
- * Return: true if the inode requires file contents encryption and if the
- *	   encryption should be done in the filesystem layer rather than in the
- *	   block layer via blk-crypto.
- */
-static inline bool fscrypt_inode_uses_fs_layer_crypto(const struct inode *inode)
-{
-	return fscrypt_needs_contents_encryption(inode) &&
-	       !inode->i_sb->s_cop->is_block_based;
-}
-
 /**
  * fscrypt_has_encryption_key() - check whether an inode has had its key set up
  * @inode: the inode to check
  *
  * Return: %true if the inode has had its encryption key set up, else %false.
-- 
2.54.0


^ permalink raw reply related

* [PATCH 15/16] fscrypt: Merge bio.c and inline_crypt.c into block.c
From: Eric Biggers @ 2026-06-24  5:03 UTC (permalink / raw)
  To: linux-fscrypt
  Cc: linux-fsdevel, linux-ext4, linux-f2fs-devel, linux-block,
	Christoph Hellwig, Theodore Ts'o, Andreas Dilger, Baokun Li,
	Jan Kara, Ojaswin Mujoo, Ritesh Harjani, Zhang Yi, Jaegeuk Kim,
	Chao Yu, Eric Biggers
In-Reply-To: <20260624050334.124606-1-ebiggers@kernel.org>

Now that fscrypt always uses blk-crypto on block-based filesystems,
there's no meaningful difference between bio.c and inline_crypt.c.
Therefore merge the two files into one named block.c.

Note: I didn't carry over bio.c's "Copyright (C) 2015, Motorola
Mobility", as none of the code that applied to remained.

Signed-off-by: Eric Biggers <ebiggers@kernel.org>
---
 fs/crypto/Makefile                    |   3 +-
 fs/crypto/bio.c                       | 100 --------------------------
 fs/crypto/{inline_crypt.c => block.c} |  96 +++++++++++++++++++++++--
 fs/crypto/fscrypt_private.h           |   2 +-
 include/linux/fscrypt.h               |  22 +++---
 5 files changed, 101 insertions(+), 122 deletions(-)
 delete mode 100644 fs/crypto/bio.c
 rename fs/crypto/{inline_crypt.c => block.c} (79%)

diff --git a/fs/crypto/Makefile b/fs/crypto/Makefile
index 652c7180ec6d..b03e02f0f09d 100644
--- a/fs/crypto/Makefile
+++ b/fs/crypto/Makefile
@@ -8,7 +8,6 @@ fscrypto-y := crypto.o \
 	      keyring.o \
 	      keysetup.o \
 	      keysetup_v1.o \
 	      policy.o
 
-fscrypto-$(CONFIG_BLOCK) += bio.o
-fscrypto-$(CONFIG_FS_ENCRYPTION_INLINE_CRYPT) += inline_crypt.o
+fscrypto-$(CONFIG_BLOCK) += block.o
diff --git a/fs/crypto/bio.c b/fs/crypto/bio.c
deleted file mode 100644
index db095258cfca..000000000000
--- a/fs/crypto/bio.c
+++ /dev/null
@@ -1,100 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0
-/*
- * Utility functions for file contents encryption/decryption on
- * block device-based filesystems.
- *
- * Copyright (C) 2015, Google, Inc.
- * Copyright (C) 2015, Motorola Mobility
- */
-
-#include <linux/bio.h>
-#include <linux/export.h>
-#include <linux/module.h>
-#include <linux/namei.h>
-#include <linux/pagemap.h>
-
-#include "fscrypt_private.h"
-
-struct fscrypt_zero_done {
-	atomic_t		pending;
-	blk_status_t		status;
-	struct completion	done;
-};
-
-static void fscrypt_zeroout_range_done(struct fscrypt_zero_done *done)
-{
-	if (atomic_dec_and_test(&done->pending))
-		complete(&done->done);
-}
-
-static void fscrypt_zeroout_range_end_io(struct bio *bio)
-{
-	struct fscrypt_zero_done *done = bio->bi_private;
-
-	if (bio->bi_status)
-		cmpxchg(&done->status, 0, bio->bi_status);
-	fscrypt_zeroout_range_done(done);
-	bio_put(bio);
-}
-
-/**
- * fscrypt_zeroout_range() - zero out a range of blocks in an encrypted file
- * @inode: the file's inode
- * @pos: the first file position (in bytes) to zero out
- * @sector: the first sector to zero out
- * @len: bytes to zero out
- *
- * Zero out filesystem blocks in an encrypted regular file on-disk, i.e. write
- * ciphertext blocks which decrypt to the all-zeroes block.  The blocks must be
- * both logically and physically contiguous.  It's also assumed that the
- * filesystem only uses a single block device, ->s_bdev.  @len must be a
- * multiple of the file system logical block size.
- *
- * Note that since each block uses a different IV, this involves writing a
- * different ciphertext to each block; we can't simply reuse the same one.
- *
- * Return: 0 on success; -errno on failure.
- */
-int fscrypt_zeroout_range(const struct inode *inode, loff_t pos,
-			  sector_t sector, u64 len)
-{
-	struct fscrypt_zero_done done = {
-		.pending	= ATOMIC_INIT(1),
-		.done		= COMPLETION_INITIALIZER_ONSTACK(done.done),
-	};
-
-	if (len == 0)
-		return 0;
-
-	do {
-		struct bio *bio;
-		unsigned int n;
-
-		bio = bio_alloc(inode->i_sb->s_bdev, BIO_MAX_VECS, REQ_OP_WRITE,
-				GFP_NOFS);
-		bio->bi_iter.bi_sector = sector;
-		bio->bi_private = &done;
-		bio->bi_end_io = fscrypt_zeroout_range_end_io;
-		fscrypt_set_bio_crypt_ctx(bio, inode, pos, GFP_NOFS);
-
-		for (n = 0; n < BIO_MAX_VECS; n++) {
-			unsigned int bytes_this_page = min(len, PAGE_SIZE);
-
-			__bio_add_page(bio, ZERO_PAGE(0), bytes_this_page, 0);
-			len -= bytes_this_page;
-			pos += bytes_this_page;
-			sector += (bytes_this_page >> SECTOR_SHIFT);
-			if (!len || !fscrypt_mergeable_bio(bio, inode, pos))
-				break;
-		}
-
-		atomic_inc(&done.pending);
-		blk_crypto_submit_bio(bio);
-	} while (len);
-
-	fscrypt_zeroout_range_done(&done);
-
-	wait_for_completion(&done.done);
-	return blk_status_to_errno(done.status);
-}
-EXPORT_SYMBOL(fscrypt_zeroout_range);
diff --git a/fs/crypto/inline_crypt.c b/fs/crypto/block.c
similarity index 79%
rename from fs/crypto/inline_crypt.c
rename to fs/crypto/block.c
index 3c3a46c5af42..60e687da7760 100644
--- a/fs/crypto/inline_crypt.c
+++ b/fs/crypto/block.c
@@ -1,22 +1,22 @@
 // SPDX-License-Identifier: GPL-2.0
 /*
- * Inline encryption support for fscrypt
+ * File contents en/decryption on block-based filesystems
  *
  * Copyright 2019 Google LLC
  */
 
 /*
- * With "inline encryption", the block layer handles the decryption/encryption
- * as part of the bio, instead of the filesystem doing the crypto itself via
- * crypto API.  See Documentation/block/inline-encryption.rst.  fscrypt still
- * provides the key and IV to use.
+ * This file implements fscrypt's file contents en/decryption using blk-crypto
+ * (Documentation/block/inline-encryption.rst).  fscrypt assigns a bio_crypt_ctx
+ * with a key and IV to each bio, and the block layer does the en/decryption.
+ *
+ * This file's exported functions are called only by block-based filesystems.
  */
 
 #include <linux/blk-crypto.h>
 #include <linux/blkdev.h>
-#include <linux/buffer_head.h>
 #include <linux/export.h>
 #include <linux/sched/mm.h>
 #include <linux/slab.h>
 #include <linux/uio.h>
 
@@ -338,5 +338,89 @@ u64 fscrypt_limit_io_blocks(const struct inode *inode, u64 lblk, u64 nr_blocks)
 	dun = ci->ci_hashed_ino + lblk;
 
 	return min_t(u64, nr_blocks, (u64)U32_MAX + 1 - dun);
 }
 EXPORT_SYMBOL_GPL(fscrypt_limit_io_blocks);
+
+struct fscrypt_zero_done {
+	atomic_t		pending;
+	blk_status_t		status;
+	struct completion	done;
+};
+
+static void fscrypt_zeroout_range_done(struct fscrypt_zero_done *done)
+{
+	if (atomic_dec_and_test(&done->pending))
+		complete(&done->done);
+}
+
+static void fscrypt_zeroout_range_end_io(struct bio *bio)
+{
+	struct fscrypt_zero_done *done = bio->bi_private;
+
+	if (bio->bi_status)
+		cmpxchg(&done->status, 0, bio->bi_status);
+	fscrypt_zeroout_range_done(done);
+	bio_put(bio);
+}
+
+/**
+ * fscrypt_zeroout_range() - zero out a range of blocks in an encrypted file
+ * @inode: the file's inode
+ * @pos: the first file position (in bytes) to zero out
+ * @sector: the first sector to zero out
+ * @len: bytes to zero out
+ *
+ * Zero out filesystem blocks in an encrypted regular file on-disk, i.e. write
+ * ciphertext blocks which decrypt to the all-zeroes block.  The blocks must be
+ * both logically and physically contiguous.  It's also assumed that the
+ * filesystem only uses a single block device, ->s_bdev.  @len must be a
+ * multiple of the file system logical block size.
+ *
+ * Note that since each block uses a different IV, this involves writing a
+ * different ciphertext to each block; we can't simply reuse the same one.
+ *
+ * Return: 0 on success; -errno on failure.
+ */
+int fscrypt_zeroout_range(const struct inode *inode, loff_t pos,
+			  sector_t sector, u64 len)
+{
+	struct fscrypt_zero_done done = {
+		.pending	= ATOMIC_INIT(1),
+		.done		= COMPLETION_INITIALIZER_ONSTACK(done.done),
+	};
+
+	if (len == 0)
+		return 0;
+
+	do {
+		struct bio *bio;
+		unsigned int n;
+
+		bio = bio_alloc(inode->i_sb->s_bdev, BIO_MAX_VECS, REQ_OP_WRITE,
+				GFP_NOFS);
+		bio->bi_iter.bi_sector = sector;
+		bio->bi_private = &done;
+		bio->bi_end_io = fscrypt_zeroout_range_end_io;
+		fscrypt_set_bio_crypt_ctx(bio, inode, pos, GFP_NOFS);
+
+		for (n = 0; n < BIO_MAX_VECS; n++) {
+			unsigned int bytes_this_page = min(len, PAGE_SIZE);
+
+			__bio_add_page(bio, ZERO_PAGE(0), bytes_this_page, 0);
+			len -= bytes_this_page;
+			pos += bytes_this_page;
+			sector += (bytes_this_page >> SECTOR_SHIFT);
+			if (!len || !fscrypt_mergeable_bio(bio, inode, pos))
+				break;
+		}
+
+		atomic_inc(&done.pending);
+		blk_crypto_submit_bio(bio);
+	} while (len);
+
+	fscrypt_zeroout_range_done(&done);
+
+	wait_for_completion(&done.done);
+	return blk_status_to_errno(done.status);
+}
+EXPORT_SYMBOL(fscrypt_zeroout_range);
diff --git a/fs/crypto/fscrypt_private.h b/fs/crypto/fscrypt_private.h
index da9040407d4a..74329e0953d1 100644
--- a/fs/crypto/fscrypt_private.h
+++ b/fs/crypto/fscrypt_private.h
@@ -393,11 +393,11 @@ void fscrypt_init_hkdf(struct hmac_sha512_key *hkdf, const u8 *master_key,
 
 void fscrypt_hkdf_expand(const struct hmac_sha512_key *hkdf, u8 context,
 			 const u8 *info, unsigned int infolen,
 			 u8 *okm, unsigned int okmlen);
 
-/* inline_crypt.c */
+/* block.c */
 #ifdef CONFIG_FS_ENCRYPTION_INLINE_CRYPT
 static inline bool
 fscrypt_using_inline_encryption(const struct fscrypt_inode_info *ci)
 {
 	const struct inode *inode = ci->ci_inode;
diff --git a/include/linux/fscrypt.h b/include/linux/fscrypt.h
index acf5b28eb9d7..52ff014aeae6 100644
--- a/include/linux/fscrypt.h
+++ b/include/linux/fscrypt.h
@@ -444,14 +444,10 @@ int fscrypt_fname_disk_to_usr(const struct inode *inode,
 			      struct fscrypt_str *oname);
 bool fscrypt_match_name(const struct fscrypt_name *fname,
 			const u8 *de_name, u32 de_name_len);
 u64 fscrypt_fname_siphash(const struct inode *dir, const struct qstr *name);
 
-/* bio.c */
-int fscrypt_zeroout_range(const struct inode *inode, loff_t pos,
-			  sector_t sector, u64 len);
-
 /* hooks.c */
 int fscrypt_file_open(struct inode *inode, struct file *filp);
 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,
@@ -735,18 +731,10 @@ static inline int fscrypt_d_revalidate(struct inode *dir, const struct qstr *nam
 				       struct dentry *dentry, unsigned int flags)
 {
 	return 1;
 }
 
-/* bio.c */
-
-static inline int fscrypt_zeroout_range(const struct inode *inode, loff_t pos,
-					sector_t sector, u64 len)
-{
-	return -EOPNOTSUPP;
-}
-
 /* hooks.c */
 
 static inline int fscrypt_file_open(struct inode *inode, struct file *filp)
 {
 	if (IS_ENCRYPTED(inode))
@@ -842,20 +830,22 @@ static inline void fscrypt_set_ops(struct super_block *sb,
 {
 }
 
 #endif	/* !CONFIG_FS_ENCRYPTION */
 
-/* inline_crypt.c */
+/* block.c */
 #ifdef CONFIG_FS_ENCRYPTION_INLINE_CRYPT
 
 void fscrypt_set_bio_crypt_ctx(struct bio *bio, const struct inode *inode,
 			       loff_t pos, gfp_t gfp_mask);
 
 bool fscrypt_mergeable_bio(struct bio *bio, const struct inode *inode,
 			   loff_t pos);
 
 u64 fscrypt_limit_io_blocks(const struct inode *inode, u64 lblk, u64 nr_blocks);
+int fscrypt_zeroout_range(const struct inode *inode, loff_t pos,
+			  sector_t sector, u64 len);
 
 #else /* CONFIG_FS_ENCRYPTION_INLINE_CRYPT */
 
 static inline void fscrypt_set_bio_crypt_ctx(struct bio *bio,
 					     const struct inode *inode,
@@ -871,10 +861,16 @@ static inline bool fscrypt_mergeable_bio(struct bio *bio,
 static inline u64 fscrypt_limit_io_blocks(const struct inode *inode, u64 lblk,
 					  u64 nr_blocks)
 {
 	return nr_blocks;
 }
+
+static inline int fscrypt_zeroout_range(const struct inode *inode, loff_t pos,
+					sector_t sector, u64 len)
+{
+	return -EOPNOTSUPP;
+}
 #endif /* !CONFIG_FS_ENCRYPTION_INLINE_CRYPT */
 
 /**
  * fscrypt_has_encryption_key() - check whether an inode has had its key set up
  * @inode: the inode to check
-- 
2.54.0


^ permalink raw reply related

* [PATCH 16/16] fscrypt: Add safety checks to non-block-based en/decryption
From: Eric Biggers @ 2026-06-24  5:03 UTC (permalink / raw)
  To: linux-fscrypt
  Cc: linux-fsdevel, linux-ext4, linux-f2fs-devel, linux-block,
	Christoph Hellwig, Theodore Ts'o, Andreas Dilger, Baokun Li,
	Jan Kara, Ojaswin Mujoo, Ritesh Harjani, Zhang Yi, Jaegeuk Kim,
	Chao Yu, Eric Biggers
In-Reply-To: <20260624050334.124606-1-ebiggers@kernel.org>

fscrypt_encrypt_pagecache_blocks(), fscrypt_encrypt_block_inplace(),
fscrypt_decrypt_block_inplace() would dereference a NULL
fscrypt_inode_info pointer if they were to be called on a file that
hasn't been opened yet or on a block-based filesystem.  Since they have
the ability to report errors anyway, add WARN_ON_ONCE checks for this.

Signed-off-by: Eric Biggers <ebiggers@kernel.org>
---
 fs/crypto/crypto.c | 61 +++++++++++++++++++++++++++++-----------------
 1 file changed, 39 insertions(+), 22 deletions(-)

diff --git a/fs/crypto/crypto.c b/fs/crypto/crypto.c
index 27663f4d8705..c91eda62f9a4 100644
--- a/fs/crypto/crypto.c
+++ b/fs/crypto/crypto.c
@@ -103,35 +103,44 @@ static int fscrypt_crypt_data_unit(const struct fscrypt_inode_info *ci,
 				   fscrypt_direction_t rw, u64 index,
 				   struct page *src_page,
 				   struct page *dest_page, unsigned int len,
 				   unsigned int offs)
 {
-	struct crypto_sync_skcipher *tfm = ci->ci_enc_key.tfm;
-	SYNC_SKCIPHER_REQUEST_ON_STACK(req, tfm);
+	struct crypto_sync_skcipher *tfm;
 	union fscrypt_iv iv;
 	struct scatterlist dst, src;
 	int err;
 
+	if (WARN_ON_ONCE(ci == NULL)) /* File hasn't been opened yet? */
+		return -ENOKEY;
+	tfm = ci->ci_enc_key.tfm;
+	if (WARN_ON_ONCE(tfm == NULL)) /* Called on block-based filesystem? */
+		return -ENOKEY;
+
 	if (WARN_ON_ONCE(len <= 0))
 		return -EINVAL;
 	if (WARN_ON_ONCE(len % FSCRYPT_CONTENTS_ALIGNMENT != 0))
 		return -EINVAL;
 
 	fscrypt_generate_iv(&iv, index, ci);
 
-	skcipher_request_set_callback(
-		req, CRYPTO_TFM_REQ_MAY_BACKLOG | CRYPTO_TFM_REQ_MAY_SLEEP,
-		NULL, NULL);
-	sg_init_table(&dst, 1);
-	sg_set_page(&dst, dest_page, len, offs);
-	sg_init_table(&src, 1);
-	sg_set_page(&src, src_page, len, offs);
-	skcipher_request_set_crypt(req, &src, &dst, len, &iv);
-	if (rw == FS_DECRYPT)
-		err = crypto_skcipher_decrypt(req);
-	else
-		err = crypto_skcipher_encrypt(req);
+	{
+		SYNC_SKCIPHER_REQUEST_ON_STACK(req, tfm);
+		skcipher_request_set_callback(req,
+					      CRYPTO_TFM_REQ_MAY_BACKLOG |
+						      CRYPTO_TFM_REQ_MAY_SLEEP,
+					      NULL, NULL);
+		sg_init_table(&dst, 1);
+		sg_set_page(&dst, dest_page, len, offs);
+		sg_init_table(&src, 1);
+		sg_set_page(&src, src_page, len, offs);
+		skcipher_request_set_crypt(req, &src, &dst, len, &iv);
+		if (rw == FS_DECRYPT)
+			err = crypto_skcipher_decrypt(req);
+		else
+			err = crypto_skcipher_encrypt(req);
+	}
 	if (err)
 		fscrypt_err(ci->ci_inode,
 			    "%scryption failed for data unit %llu: %d",
 			    (rw == FS_DECRYPT ? "De" : "En"), index, err);
 	return err;
@@ -151,11 +160,11 @@ static int fscrypt_crypt_data_unit(const struct fscrypt_inode_info *ci,
  *
  * In the bounce page, the ciphertext data will be located at the same offset at
  * which the plaintext data was located in the source page.  Any other parts of
  * the bounce page will be left uninitialized.
  *
- * This is for use by the filesystem's ->writepages() method.
+ * This is for use by the ->writepages() method of non-block-based filesystems.
  *
  * The bounce page allocation is mempool-backed, so it will always succeed when
  * @gfp_flags includes __GFP_DIRECT_RECLAIM, e.g. when it's GFP_NOFS.  However,
  * only the first page of each bio can be allocated this way.  To prevent
  * deadlocks, for any additional pages a mask like GFP_NOWAIT must be used.
@@ -165,18 +174,24 @@ static int fscrypt_crypt_data_unit(const struct fscrypt_inode_info *ci,
 struct page *fscrypt_encrypt_pagecache_blocks(struct folio *folio,
 		size_t len, size_t offs, gfp_t gfp_flags)
 {
 	const struct inode *inode = folio->mapping->host;
 	const struct fscrypt_inode_info *ci = fscrypt_get_inode_info_raw(inode);
-	const unsigned int du_bits = ci->ci_data_unit_bits;
-	const unsigned int du_size = 1U << du_bits;
+	unsigned int du_bits;
+	unsigned int du_size;
 	struct page *ciphertext_page;
-	u64 index = ((u64)folio->index << (PAGE_SHIFT - du_bits)) +
-		    (offs >> du_bits);
+	u64 index;
 	unsigned int i;
 	int err;
 
+	if (WARN_ON_ONCE(ci == NULL)) /* File hasn't been opened yet? */
+		return ERR_PTR(-ENOKEY);
+
+	du_bits = ci->ci_data_unit_bits;
+	du_size = 1U << du_bits;
+	index = (folio_pos(folio) + offs) >> du_bits;
+
 	VM_BUG_ON_FOLIO(folio_test_large(folio), folio);
 	if (WARN_ON_ONCE(!folio_test_locked(folio)))
 		return ERR_PTR(-EINVAL);
 
 	if (WARN_ON_ONCE(len <= 0 || !IS_ALIGNED(len | offs, du_size)))
@@ -213,11 +228,12 @@ EXPORT_SYMBOL(fscrypt_encrypt_pagecache_blocks);
  *
  * Encrypt a possibly-compressed filesystem block that is located in an
  * arbitrary page, not necessarily in the original pagecache page.  The @inode
  * and @lblk_num must be specified, as they can't be determined from @page.
  *
- * This is not compatible with fscrypt_operations::supports_subblock_data_units.
+ * This function only supports non-block-based filesystems that don't support
+ * sub-block data units (as indicated by the fscrypt_operations fields).
  *
  * Return: 0 on success; -errno on failure
  */
 int fscrypt_encrypt_block_inplace(const struct inode *inode, struct page *page,
 				  unsigned int len, unsigned int offs,
@@ -243,11 +259,12 @@ EXPORT_SYMBOL(fscrypt_encrypt_block_inplace);
  *
  * Decrypt a possibly-compressed filesystem block that is located in an
  * arbitrary page, not necessarily in the original pagecache page.  The @inode
  * and @lblk_num must be specified, as they can't be determined from @page.
  *
- * This is not compatible with fscrypt_operations::supports_subblock_data_units.
+ * This function only supports non-block-based filesystems that don't support
+ * sub-block data units (as indicated by the fscrypt_operations fields).
  *
  * Return: 0 on success; -errno on failure
  */
 int fscrypt_decrypt_block_inplace(const struct inode *inode, struct page *page,
 				  unsigned int len, unsigned int offs,
@@ -273,11 +290,11 @@ EXPORT_SYMBOL(fscrypt_decrypt_block_inplace);
 int fscrypt_initialize(struct super_block *sb)
 {
 	mempool_t *pool;
 
 	/* pairs with smp_store_release() below */
-	if (likely(smp_load_acquire(&fscrypt_bounce_page_pool)))
+	if (smp_load_acquire(&fscrypt_bounce_page_pool))
 		return 0;
 
 	/* No need to allocate a bounce page pool if this FS won't use it. */
 	if (!sb->s_cop->needs_bounce_pages)
 		return 0;
-- 
2.54.0


^ permalink raw reply related

* [PATCH] ext4: zero non-uptodate buffers before encryption in writeback
From: Yun Zhou @ 2026-06-24  6:59 UTC (permalink / raw)
  To: tytso, adilger.kernel, libaokun, jack, ojaswin, ritesh.list,
	yi.zhang
  Cc: linux-ext4, linux-kernel, yun.zhou

ext4_bio_write_folio() encrypts the folio content from offset 0 up to
round_up(len, blocksize) before submitting IO. When blocksize < PAGE_SIZE,
this range may include blocks that are not being written out (holes,
delay, or unwritten blocks). If the folio was freshly allocated by the
page cache (via write_begin) for a partial-page write, these non-target
blocks may remain uninitialized from the buddy allocator.

The encryption engine (AES-XTS) then reads these uninitialized bytes as
operands, triggering a KMSAN uninit-value report in aes_encrypt().

Fix this by zeroing any non-uptodate buffer that is not being written
out, before calling fscrypt_encrypt_pagecache_blocks(). This ensures the
crypto engine never operates on uninitialized data regardless of which
blocks are actually being submitted for IO.

The common case of blocksize == PAGE_SIZE is unaffected since there can
be no non-overlapping blocks within a single-block folio.

Reported-by: syzbot+7add5c56bc2a14145d20@syzkaller.appspotmail.com
Closes: https://syzkaller.appspot.com/bug?extid=7add5c56bc2a14145d20
Signed-off-by: Yun Zhou <yun.zhou@windriver.com>
---
 fs/ext4/page-io.c | 10 ++++++++++
 1 file changed, 10 insertions(+)

diff --git a/fs/ext4/page-io.c b/fs/ext4/page-io.c
index bc674aa4a656..2d380b5a1501 100644
--- a/fs/ext4/page-io.c
+++ b/fs/ext4/page-io.c
@@ -555,12 +555,22 @@ int ext4_bio_write_folio(struct ext4_io_submit *io, struct folio *folio,
 	 * block which might be needed.  This may cause some unneeded blocks
 	 * (e.g. holes) to be unnecessarily encrypted, but this is rare and
 	 * can't happen in the common case of blocksize == PAGE_SIZE.
+	 *
+	 * Zero out any non-uptodate buffers that are not being written out,
+	 * to prevent uninitialized memory from being fed into the crypto
+	 * engine.
 	 */
 	if (fscrypt_inode_uses_fs_layer_crypto(inode)) {
 		gfp_t gfp_flags = GFP_NOFS;
 		unsigned int enc_bytes = round_up(len, i_blocksize(inode));
 		struct page *bounce_page;
 
+		do {
+			if (!buffer_async_write(bh) && !buffer_uptodate(bh))
+				folio_zero_range(folio, bh_offset(bh),
+						 bh->b_size);
+		} while ((bh = bh->b_this_page) != head);
+
 		/*
 		 * Since bounce page allocation uses a mempool, we can only use
 		 * a waiting mask (i.e. request guaranteed allocation) on the
-- 
2.43.0


^ permalink raw reply related

* Re: [PATCH] ext4: cancel dirty accounting for folios without buffers
From: Zhang Yi @ 2026-06-24  8:20 UTC (permalink / raw)
  To: Zhu Jia, tytso, adilger.kernel
  Cc: libaokun, jack, ojaswin, ritesh.list, linux-ext4, linux-kernel,
	stable
In-Reply-To: <20260623094947.7853-1-zhujia.zj@bytedance.com>

On 6/23/2026 5:49 PM, Zhu Jia wrote:
> Since commit cc5095747edf ("ext4: don't BUG if someone dirty pages
> without asking ext4 first"), mpage_prepare_extent_to_map() handles dirty
> folios without buffer heads by warning, clearing PG_dirty, and skipping
> them. ext4 cannot write these folios because there are no buffer heads to
> map and submit.
> 
> That recovery leaves dirty accounting behind: folio_clear_dirty() clears
> PG_dirty but does not undo the accounting charged when the folio was
> dirtied. We have seen this in production as Dirty/nr_dirty staying high
> while Writeback/nr_writeback and device write IO stayed near zero, with
> many writer tasks blocked in balance_dirty_pages() throttling. Thus the
> warning-and-skip recovery can still become a dirty-throttle DoS.
> 
> Use folio_cancel_dirty() so dropping PG_dirty also cancels the dirty
> accounting.

Hi, Zhu jia!

Thanks for the patch. This overall looks good to me. But should we also
clear PAGECACHE_TAG_DIRTY and PAGECACHE_TAG_TOWRITE here? Since the folio
won't be written back again until it gets dirtied, it seems cleaner to
remove these tags as well. Are there any side-effects I'm missing?

Thanks,
Yi.

> 
> Fixes: cc5095747edf ("ext4: don't BUG if someone dirty pages without asking ext4 first")
> Cc: stable@vger.kernel.org
> Signed-off-by: Zhu Jia <zhujia.zj@bytedance.com>
> ---
>  fs/ext4/inode.c | 8 +++++++-
>  1 file changed, 7 insertions(+), 1 deletion(-)
> 
> diff --git a/fs/ext4/inode.c b/fs/ext4/inode.c
> index c2c2d6ac7f3d1..7ea280e70c06e 100644
> --- a/fs/ext4/inode.c
> +++ b/fs/ext4/inode.c
> @@ -2715,7 +2715,13 @@ static int mpage_prepare_extent_to_map(struct mpage_da_data *mpd)
>  			 */
>  			if (!folio_buffers(folio)) {
>  				ext4_warning_inode(mpd->inode, "page %lu does not have buffers attached", folio->index);
> -				folio_clear_dirty(folio);
> +				/*
> +				 * folio_cancel_dirty() pairs the dropped dirty
> +				 * state with dirty accounting, but leaves stale
> +				 * PAGECACHE_TAG_DIRTY/TOWRITE tags behind. Later
> +				 * writeback may rescan this clean folio.
> +				 */
> +				folio_cancel_dirty(folio);
>  				folio_unlock(folio);
>  				continue;
>  			}


^ permalink raw reply

* Re: [f2fs-dev] [PATCH v14 00/15] Exposing case folding behavior
From: patchwork-bot+f2fs @ 2026-06-24  8:59 UTC (permalink / raw)
  To: Chuck Lever
  Cc: viro, brauner, jack, pc, yuezhang.mo, cem, roland.mainz,
	almaz.alexandrovich, adilger.kernel, linux-cifs, sfrench, slava,
	djwong, linux-ext4, linkinjeon, stfrench, sprasad, frank.li,
	ronniesahlberg, glaubitz, jaegeuk, hirofumi, linux-nfs, tytso,
	linux-api, linux-f2fs-devel, linux-xfs, senozhatsky, chuck.lever,
	hansg, anna, linux-fsdevel, sj1557.seo, trondmy
In-Reply-To: <20260507-case-sensitivity-v14-0-e62cc8200435@oracle.com>

Hello:

This series was applied to jaegeuk/f2fs.git (dev)
by Christian Brauner <brauner@kernel.org>:

On Thu, 07 May 2026 04:52:53 -0400 you wrote:
> Christian, let's lock this one in. I will post subsequent changes
> as delta patches.
> 
> Following on from:
> 
> https://lore.kernel.org/linux-nfs/20251021-zypressen-bazillus-545a44af57fd@brauner/T/#m0ba197d75b7921d994cf284f3cef3a62abb11aaa
> 
> [...]

Here is the summary with links:
  - [f2fs-dev,v14,01/15] fs: Move file_kattr initialization to callers
    https://git.kernel.org/jaegeuk/f2fs/c/14c3197ecf07
  - [f2fs-dev,v14,02/15] fs: Add case sensitivity flags to file_kattr
    https://git.kernel.org/jaegeuk/f2fs/c/3035e4454142
  - [f2fs-dev,v14,03/15] fat: Implement fileattr_get for case sensitivity
    https://git.kernel.org/jaegeuk/f2fs/c/c92db2ca726f
  - [f2fs-dev,v14,04/15] exfat: Implement fileattr_get for case sensitivity
    https://git.kernel.org/jaegeuk/f2fs/c/27e0b573dd4a
  - [f2fs-dev,v14,05/15] ntfs3: Implement fileattr_get for case sensitivity
    https://git.kernel.org/jaegeuk/f2fs/c/eeb7b37b9700
  - [f2fs-dev,v14,06/15] hfs: Implement fileattr_get for case sensitivity
    https://git.kernel.org/jaegeuk/f2fs/c/b6fe046c3023
  - [f2fs-dev,v14,07/15] hfsplus: Report case sensitivity in fileattr_get
    https://git.kernel.org/jaegeuk/f2fs/c/a6469a15eefe
  - [f2fs-dev,v14,08/15] xfs: Report case sensitivity in fileattr_get
    https://git.kernel.org/jaegeuk/f2fs/c/c9da43e4e5c3
  - [f2fs-dev,v14,09/15] cifs: Implement fileattr_get for case sensitivity
    https://git.kernel.org/jaegeuk/f2fs/c/e50bc12f5a36
  - [f2fs-dev,v14,10/15] nfs: Implement fileattr_get for case sensitivity
    https://git.kernel.org/jaegeuk/f2fs/c/92d67628a1a9
  - [f2fs-dev,v14,11/15] vboxsf: Implement fileattr_get for case sensitivity
    https://git.kernel.org/jaegeuk/f2fs/c/ef14aa143f1d
  - [f2fs-dev,v14,12/15] isofs: Implement fileattr_get for case sensitivity
    https://git.kernel.org/jaegeuk/f2fs/c/7bbd51b1d748
  - [f2fs-dev,v14,13/15] nfsd: Report export case-folding via NFSv3 PATHCONF
    https://git.kernel.org/jaegeuk/f2fs/c/211cb2ba4877
  - [f2fs-dev,v14,14/15] nfsd: Implement NFSv4 FATTR4_CASE_INSENSITIVE and FATTR4_CASE_PRESERVING
    https://git.kernel.org/jaegeuk/f2fs/c/01ee7c3d2e23
  - [f2fs-dev,v14,15/15] ksmbd: Report filesystem case sensitivity via FS_ATTRIBUTE_INFORMATION
    https://git.kernel.org/jaegeuk/f2fs/c/0164df1d1de7

You are awesome, thank you!
-- 
Deet-doot-dot, I am a bot.
https://korg.docs.kernel.org/patchwork/pwbot.html



^ permalink raw reply

* Re: [f2fs-dev] [PATCH v10 00/22] fs-verity support for XFS with post EOF merkle tree
From: patchwork-bot+f2fs @ 2026-06-24  8:59 UTC (permalink / raw)
  To: Andrey Albershteyn
  Cc: linux-xfs, fsverity, linux-fsdevel, ebiggers, djwong, david,
	linux-unionfs, linux-f2fs-devel, linux-ext4, hch, linux-btrfs
In-Reply-To: <20260520123722.405752-1-aalbersh@kernel.org>

Hello:

This series was applied to jaegeuk/f2fs.git (dev)
by Christian Brauner <brauner@kernel.org>:

On Wed, 20 May 2026 14:36:58 +0200 you wrote:
> Hi all,
> 
> This patch series adds fs-verity support for XFS. This version stores
> merkle tree beyond end of the file, the same way as ext4 does it. The
> difference is that verity descriptor is stored at the next aligned 64k
> block after the merkle tree last block. This is done due to sparse
> merkle tree which doesn't store hashes of zero data blocks.
> 
> [...]

Here is the summary with links:
  - [f2fs-dev,v10,01/22] fsverity: report validation errors through fserror to fsnotify
    (no matching commit)
  - [f2fs-dev,v10,02/22] fsverity: expose ensure_fsverity_info()
    (no matching commit)
  - [f2fs-dev,v10,03/22] ovl: use core fsverity ensure info interface
    (no matching commit)
  - [f2fs-dev,v10,04/22] fsverity: generate and store zero-block hash
    https://git.kernel.org/jaegeuk/f2fs/c/07d09774e2bf
  - [f2fs-dev,v10,05/22] fsverity: pass digest size and hash of the all-zeroes block to ->write
    (no matching commit)
  - [f2fs-dev,v10,06/22] fsverity: hoist pagecache_read from f2fs/ext4 to fsverity
    (no matching commit)
  - [f2fs-dev,v10,07/22] iomap: introduce IOMAP_F_FSVERITY and teach writeback to handle fsverity
    https://git.kernel.org/jaegeuk/f2fs/c/63e242afa466
  - [f2fs-dev,v10,08/22] iomap: teach iomap to read files with fsverity
    https://git.kernel.org/jaegeuk/f2fs/c/1d140731753a
  - [f2fs-dev,v10,09/22] iomap: introduce iomap_fsverity_write() for writing fsverity metadata
    https://git.kernel.org/jaegeuk/f2fs/c/36a36c4cac91
  - [f2fs-dev,v10,10/22] xfs: introduce fsverity on-disk changes
    (no matching commit)
  - [f2fs-dev,v10,11/22] xfs: initialize fs-verity on file open
    (no matching commit)
  - [f2fs-dev,v10,12/22] xfs: don't allow to enable DAX on fs-verity sealed inode
    (no matching commit)
  - [f2fs-dev,v10,13/22] xfs: disable direct read path for fs-verity files
    (no matching commit)
  - [f2fs-dev,v10,14/22] xfs: handle fsverity I/O in write/read path
    (no matching commit)
  - [f2fs-dev,v10,15/22] xfs: use read ioend for fsverity data verification
    (no matching commit)
  - [f2fs-dev,v10,16/22] xfs: add fs-verity support
    (no matching commit)
  - [f2fs-dev,v10,17/22] xfs: remove unwritten extents after preallocations in fsverity metadata
    (no matching commit)
  - [f2fs-dev,v10,18/22] xfs: add fs-verity ioctls
    (no matching commit)
  - [f2fs-dev,v10,19/22] xfs: advertise fs-verity being available on filesystem
    (no matching commit)
  - [f2fs-dev,v10,20/22] xfs: check and repair the verity inode flag state
    (no matching commit)
  - [f2fs-dev,v10,21/22] xfs: introduce health state for corrupted fsverity metadata
    (no matching commit)
  - [f2fs-dev,v10,22/22] xfs: enable ro-compat fs-verity flag
    (no matching commit)

You are awesome, thank you!
-- 
Deet-doot-dot, I am a bot.
https://korg.docs.kernel.org/patchwork/pwbot.html



^ permalink raw reply

* Re: [f2fs-dev] [PATCH v9 01/22] fsverity: report validation errors through fserror to fsnotify
From: patchwork-bot+f2fs @ 2026-06-24  8:59 UTC (permalink / raw)
  To: Andrey Albershteyn
  Cc: linux-xfs, fsverity, linux-fsdevel, ebiggers, djwong,
	linux-unionfs, linux-f2fs-devel, linux-ext4, hch, linux-btrfs
In-Reply-To: <20260428083332.768693-2-aalbersh@kernel.org>

Hello:

This series was applied to jaegeuk/f2fs.git (dev)
by Christian Brauner <brauner@kernel.org>:

On Tue, 28 Apr 2026 10:33:07 +0200 you wrote:
> Reported verification errors to fsnotify through recently added fserror
> interface.
> 
> Reviewed-by: Darrick J. Wong <djwong@kernel.org>
> Reviewed-by: Christoph Hellwig <hch@lst.de>
> Acked-by: Eric Biggers <ebiggers@kernel.org>
> Signed-off-by: Andrey Albershteyn <aalbersh@kernel.org>
> 
> [...]

Here is the summary with links:
  - [f2fs-dev,v9,01/22] fsverity: report validation errors through fserror to fsnotify
    (no matching commit)
  - [f2fs-dev,v9,02/22] fsverity: expose ensure_fsverity_info()
    (no matching commit)
  - [f2fs-dev,v9,03/22] ovl: use core fsverity ensure info interface
    (no matching commit)
  - [f2fs-dev,v9,04/22] fsverity: generate and store zero-block hash
    https://git.kernel.org/jaegeuk/f2fs/c/07d09774e2bf
  - [f2fs-dev,v9,05/22] fsverity: pass digest size and hash of the all-zeroes block to ->write
    (no matching commit)
  - [f2fs-dev,v9,06/22] fsverity: hoist pagecache_read from f2fs/ext4 to fsverity
    (no matching commit)
  - [f2fs-dev,v9,07/22] iomap: introduce IOMAP_F_FSVERITY and teach writeback to handle fsverity
    (no matching commit)
  - [f2fs-dev,v9,08/22] iomap: teach iomap to read files with fsverity
    (no matching commit)
  - [f2fs-dev,v9,09/22] iomap: introduce iomap_fsverity_write() for writing fsverity metadata
    https://git.kernel.org/jaegeuk/f2fs/c/36a36c4cac91
  - [f2fs-dev,v9,10/22] xfs: introduce fsverity on-disk changes
    (no matching commit)
  - [f2fs-dev,v9,11/22] xfs: initialize fs-verity on file open
    (no matching commit)
  - [f2fs-dev,v9,12/22] xfs: don't allow to enable DAX on fs-verity sealed inode
    (no matching commit)
  - [f2fs-dev,v9,13/22] xfs: disable direct read path for fs-verity files
    (no matching commit)
  - [f2fs-dev,v9,14/22] xfs: handle fsverity I/O in write/read path
    (no matching commit)
  - [f2fs-dev,v9,15/22] xfs: use read ioend for fsverity data verification
    (no matching commit)
  - [f2fs-dev,v9,16/22] xfs: add fs-verity support
    (no matching commit)
  - [f2fs-dev,v9,17/22] xfs: remove unwritten extents after preallocations in fsverity metadata
    (no matching commit)
  - [f2fs-dev,v9,18/22] xfs: add fs-verity ioctls
    (no matching commit)
  - [f2fs-dev,v9,19/22] xfs: advertise fs-verity being available on filesystem
    (no matching commit)
  - [f2fs-dev,v9,20/22] xfs: check and repair the verity inode flag state
    (no matching commit)
  - [f2fs-dev,v9,21/22] xfs: introduce health state for corrupted fsverity metadata
    (no matching commit)
  - [f2fs-dev,v9,22/22] xfs: enable ro-compat fs-verity flag
    (no matching commit)

You are awesome, thank you!
-- 
Deet-doot-dot, I am a bot.
https://korg.docs.kernel.org/patchwork/pwbot.html



^ permalink raw reply

* Re: [f2fs-dev] [PATCH v8 00/17] Subject: Exposing case folding behavior
From: patchwork-bot+f2fs @ 2026-06-24  8:59 UTC (permalink / raw)
  To: Chuck Lever
  Cc: viro, brauner, jack, pc, yuezhang.mo, cem, almaz.alexandrovich,
	adilger.kernel, linux-cifs, sfrench, slava, linux-ext4,
	linkinjeon, sprasad, frank.li, ronniesahlberg, glaubitz, jaegeuk,
	hirofumi, linux-nfs, tytso, linux-api, linux-f2fs-devel,
	linux-xfs, senozhatsky, chuck.lever, hansg, anna, linux-fsdevel,
	sj1557.seo, trondmy
In-Reply-To: <20260217214741.1928576-1-cel@kernel.org>

Hello:

This series was applied to jaegeuk/f2fs.git (dev)
by Christian Brauner <brauner@kernel.org>:

On Tue, 17 Feb 2026 16:47:24 -0500 you wrote:
> From: Chuck Lever <chuck.lever@oracle.com>
> 
> Following on from
> 
> https://lore.kernel.org/linux-nfs/20251021-zypressen-bazillus-545a44af57fd@brauner/T/#m0ba197d75b7921d994cf284f3cef3a62abb11aaa
> 
> I'm attempting to implement enough support in the Linux VFS to
> enable file services like NFSD and ksmbd (and user space
> equivalents) to provide the actual status of case folding support
> in local file systems. The default behavior for local file systems
> not explicitly supported in this series is to reflect the usual
> POSIX behaviors:
> 
> [...]

Here is the summary with links:
  - [f2fs-dev,v8,01/17] fs: Move file_kattr initialization to callers
    (no matching commit)
  - [f2fs-dev,v8,02/17] fs: Add case sensitivity flags to file_kattr
    https://git.kernel.org/jaegeuk/f2fs/c/3035e4454142
  - [f2fs-dev,v8,03/17] fat: Implement fileattr_get for case sensitivity
    (no matching commit)
  - [f2fs-dev,v8,04/17] exfat: Implement fileattr_get for case sensitivity
    (no matching commit)
  - [f2fs-dev,v8,05/17] ntfs3: Implement fileattr_get for case sensitivity
    (no matching commit)
  - [f2fs-dev,v8,06/17] hfs: Implement fileattr_get for case sensitivity
    (no matching commit)
  - [f2fs-dev,v8,07/17] hfsplus: Report case sensitivity in fileattr_get
    (no matching commit)
  - [f2fs-dev,v8,08/17] ext4: Report case sensitivity in fileattr_get
    (no matching commit)
  - [f2fs-dev,v8,09/17] xfs: Report case sensitivity in fileattr_get
    (no matching commit)
  - [f2fs-dev,v8,10/17] cifs: Implement fileattr_get for case sensitivity
    (no matching commit)
  - [f2fs-dev,v8,11/17] nfs: Implement fileattr_get for case sensitivity
    (no matching commit)
  - [f2fs-dev,v8,12/17] f2fs: Add case sensitivity reporting to fileattr_get
    (no matching commit)
  - [f2fs-dev,v8,13/17] vboxsf: Implement fileattr_get for case sensitivity
    (no matching commit)
  - [f2fs-dev,v8,14/17] isofs: Implement fileattr_get for case sensitivity
    (no matching commit)
  - [f2fs-dev,v8,15/17] nfsd: Report export case-folding via NFSv3 PATHCONF
    (no matching commit)
  - [f2fs-dev,v8,16/17] nfsd: Implement NFSv4 FATTR4_CASE_INSENSITIVE and FATTR4_CASE_PRESERVING
    (no matching commit)
  - [f2fs-dev,v8,17/17] ksmbd: Report filesystem case sensitivity via FS_ATTRIBUTE_INFORMATION
    (no matching commit)

You are awesome, thank you!
-- 
Deet-doot-dot, I am a bot.
https://korg.docs.kernel.org/patchwork/pwbot.html



^ permalink raw reply

* Re: [PATCH v6 v6 0/3] ext4: improve mballoc statistics reporting and control
From: liubaolin @ 2026-06-24  9:52 UTC (permalink / raw)
  To: corbet, skhan, tytso, adilger.kernel, libaokun, jack, ojaswin,
	ritesh.list, yi.zhang
  Cc: linux-ext4, linux-kernel, Baolin Liu
In-Reply-To: <1816234d.8a1b.19ef8ff3f2e.Coremail.liubaolin12138@163.com>

Dear Maintainer,
    I was wondering if there are still any concerns or questions 
regarding this patch series.

    The patches have been updated based on the previous review comments 
and have also passed the Sashiko AI review.
    I would appreciate any further feedback and look forward to your reply.

Best regards,
Baolin

在 2026/6/24 17:38, Baolin Liu 写道:
> 
> 
> 
> 
> 
> 
> 
> At 2026-05-24 09:54:18, "Baolin Liu" <liubaolin12138@163.com> wrote:
>>From: Baolin Liu <liubaolin@kylinos.cn>
>>
>>This series improves mballoc statistics reporting and control by adding
>>blocks_allocated to /proc/fs/ext4/<dev>/mb_stats, using
>>READ_ONCE()/WRITE_ONCE() for concurrent accesses to s_mb_stats, and
>>allowing mballoc stats to be controlled through
>>/proc/fs/ext4/<dev>/mb_stats.
>>
>>Changes in v6:
>> - Serialize proc and sysfs updates to s_mb_stats with a mutex so that
>>   clearing mb_stats through the proc control path cannot race with
>>   concurrent administrative writes.
>>
>>Baolin Liu (3):
>>  ext4: add blocks_allocated to mb_stats output
>>  ext4: use READ_ONCE/WRITE_ONCE for s_mb_stats
>>  ext4: allow controlling mballoc stats through proc mb_stats
>>
>> Documentation/ABI/testing/sysfs-fs-ext4 |  3 +-
>> Documentation/admin-guide/ext4.rst      |  9 ++-
>> Documentation/filesystems/proc.rst      | 13 +---
>> fs/ext4/ext4.h                          |  2 +
>> fs/ext4/mballoc.c                       | 58 ++++++++++++++----
>> fs/ext4/sysfs.c                         | 80 ++++++++++++++++++++++++-
>> 6 files changed, 135 insertions(+), 30 deletions(-)
>>
>>-- 
>>2.51.0


^ permalink raw reply

* Re: [PATCH] ext4: cancel dirty accounting for folios without buffers
From: Zhu Jia @ 2026-06-24  9:52 UTC (permalink / raw)
  To: Zhang Yi
  Cc: Zhu Jia, tytso, adilger.kernel, libaokun, jack, ojaswin,
	ritesh.list, linux-ext4, linux-kernel, stable
In-Reply-To: <81ed36cc-b5c8-41cf-8b7d-16611e61e294@huaweicloud.com>

Hi Yi,

Thanks for taking a look.

Yes, clearing PAGECACHE_TAG_DIRTY/TOWRITE would make the page-cache state
cleaner. I had a version that did this by adding a helper around
folio_cancel_dirty() and clearing the xarray tags after confirming the
folio was still the same clean page-cache entry.

It looked like this:

static void ext4_cancel_dirty_folio(struct address_space *mapping,
				    struct folio *folio)
{
	XA_STATE(xas, &mapping->i_pages, folio->index);
	unsigned long flags;

	folio_cancel_dirty(folio);

	xas_lock_irqsave(&xas, flags);
	if (xas_load(&xas) == folio && !folio_test_dirty(folio)) {
		xas_clear_mark(&xas, PAGECACHE_TAG_DIRTY);
		xas_clear_mark(&xas, PAGECACHE_TAG_TOWRITE);
	}
	xas_unlock_irqrestore(&xas, flags);
}

The reason I left the tags unchanged in this version is that I was not sure
whether it is appropriate for ext4 to open-code xarray tag cleanup directly.

If you think this is the right direction, I can add the helper back and
send a v2.

Thanks,
Jia

^ permalink raw reply

* Re: [PATCH v6 v6 0/3] ext4: improve mballoc statistics reporting and control
From: liubaolin @ 2026-06-24  9:59 UTC (permalink / raw)
  To: corbet, skhan, tytso, adilger.kernel, libaokun, jack, ojaswin,
	ritesh.list, yi.zhang, liubaolin12138
  Cc: linux-ext4, linux-kernel, Baolin Liu, liubaolin12138
In-Reply-To: <1816234d.8a1b.19ef8ff3f2e.Coremail.liubaolin12138@163.com>

Add recipient: liubaolin12138@163.com

在 2026/6/24 17:38, Baolin Liu 写道:
> 
> 
> 
> 
> 
> 
> 
> At 2026-05-24 09:54:18, "Baolin Liu" <liubaolin12138@163.com> wrote:
>>From: Baolin Liu <liubaolin@kylinos.cn>
>>
>>This series improves mballoc statistics reporting and control by adding
>>blocks_allocated to /proc/fs/ext4/<dev>/mb_stats, using
>>READ_ONCE()/WRITE_ONCE() for concurrent accesses to s_mb_stats, and
>>allowing mballoc stats to be controlled through
>>/proc/fs/ext4/<dev>/mb_stats.
>>
>>Changes in v6:
>> - Serialize proc and sysfs updates to s_mb_stats with a mutex so that
>>   clearing mb_stats through the proc control path cannot race with
>>   concurrent administrative writes.
>>
>>Baolin Liu (3):
>>  ext4: add blocks_allocated to mb_stats output
>>  ext4: use READ_ONCE/WRITE_ONCE for s_mb_stats
>>  ext4: allow controlling mballoc stats through proc mb_stats
>>
>> Documentation/ABI/testing/sysfs-fs-ext4 |  3 +-
>> Documentation/admin-guide/ext4.rst      |  9 ++-
>> Documentation/filesystems/proc.rst      | 13 +---
>> fs/ext4/ext4.h                          |  2 +
>> fs/ext4/mballoc.c                       | 58 ++++++++++++++----
>> fs/ext4/sysfs.c                         | 80 ++++++++++++++++++++++++-
>> 6 files changed, 135 insertions(+), 30 deletions(-)
>>
>>-- 
>>2.51.0


^ permalink raw reply

* Re: [PATCH v6 v6 0/3] ext4: improve mballoc statistics reporting and control
From: liubaolin @ 2026-06-24 10:02 UTC (permalink / raw)
  To: corbet, skhan, tytso, adilger.kernel, libaokun, jack, ojaswin,
	ritesh.list, yi.zhang, liubaolin12138
  Cc: linux-ext4, linux-kernel, Baolin Liu, liubaolin12138
In-Reply-To: <51efbdd8-76c1-4577-b5ff-a47658c7c0b2@163.com>

Dear Maintainer,
     I was wondering if there are still any concerns or questions
regarding this patch series.

     The patches have been updated based on the previous review comments
and have also passed the Sashiko AI review.
     I would appreciate any further feedback and look forward to your reply.

Best regards,
Baolin

在 2026/6/24 17:59, liubaolin 写道:
> Add recipient: liubaolin12138@163.com
> 
> 在 2026/6/24 17:38, Baolin Liu 写道:
>>
>>
>>
>>
>>
>>
>>
>> At 2026-05-24 09:54:18, "Baolin Liu" <liubaolin12138@163.com> wrote:
>>> From: Baolin Liu <liubaolin@kylinos.cn>
>>>
>>> This series improves mballoc statistics reporting and control by adding
>>> blocks_allocated to /proc/fs/ext4/<dev>/mb_stats, using
>>> READ_ONCE()/WRITE_ONCE() for concurrent accesses to s_mb_stats, and
>>> allowing mballoc stats to be controlled through
>>> /proc/fs/ext4/<dev>/mb_stats.
>>>
>>> Changes in v6:
>>> - Serialize proc and sysfs updates to s_mb_stats with a mutex so that
>>>   clearing mb_stats through the proc control path cannot race with
>>>   concurrent administrative writes.
>>>
>>> Baolin Liu (3):
>>>  ext4: add blocks_allocated to mb_stats output
>>>  ext4: use READ_ONCE/WRITE_ONCE for s_mb_stats
>>>  ext4: allow controlling mballoc stats through proc mb_stats
>>>
>>> Documentation/ABI/testing/sysfs-fs-ext4 |  3 +-
>>> Documentation/admin-guide/ext4.rst      |  9 ++-
>>> Documentation/filesystems/proc.rst      | 13 +---
>>> fs/ext4/ext4.h                          |  2 +
>>> fs/ext4/mballoc.c                       | 58 ++++++++++++++----
>>> fs/ext4/sysfs.c                         | 80 ++++++++++++++++++++++++-
>>> 6 files changed, 135 insertions(+), 30 deletions(-)
>>>
>>> -- 
>>> 2.51.0


^ permalink raw reply

* [RESEND] "ext4: get rid of ppath in get_ext_path()" 6.6.y backport request
From: Yoann Congal @ 2026-06-24 10:24 UTC (permalink / raw)
  To: stable
  Cc: Baokun Li, Jan Kara, Ojaswin Mujoo, Theodore Ts'o,
	Andreas Dilger, linux-ext4

Hello,

(Resent with developers/maintainers of the patch in CC)

I'd like to request the backport of
6b854d552711 ("ext4: get rid of ppath in get_ext_path()")
on the 6.6.y branch.

Rational:
6.6.130 commit fb138df7d886 ("ext4: get rid of ppath in ext4_ext_insert_extent()")
created a regression in ext4_ext_map_blocks() by changing the path value
under error (NULL -> ERR_PTR). But path is only checked for NULL value
in ext4_free_ext_path (not ERR_PTR).

The check is added in 6b854d552711 ("ext4: get rid of ppath in get_ext_path()"),
hence this backport request.

More details:
This regression was triggered during LTP test on a 6.6.129->6.6.142
upgrade for a Yocto Project stable branch:
https://autobuilder.yoctoproject.org/valkyrie/#/builders/98/builds/3837
-> https://valkyrie.yocto.io/pub/non-release/20260622-121/testresults/qemuarm64-ltp/core-image-sato/qemu_boot_log.20260623002740

[ 6952.500858] Unable to handle kernel paging request at virtual address ffffffffffffffec
[ 6952.503768] Mem abort info:
[ 6952.504431]   ESR = 0x0000000096000005
[ 6952.505333]   EC = 0x25: DABT (current EL), IL = 32 bits
[ 6952.506541]   SET = 0, FnV = 0
[ 6952.507354]   EA = 0, S1PTW = 0
[ 6952.508154]   FSC = 0x05: level 1 translation fault
[ 6952.509208] Data abort info:
[ 6952.509849]   ISV = 0, ISS = 0x00000005, ISS2 = 0x00000000
[ 6952.511175]   CM = 0, WnR = 0, TnD = 0, TagAccess = 0
[ 6952.512372]   GCS = 0, Overlay = 0, DirtyBit = 0, Xs = 0
[ 6952.513667] swapper pgtable: 4k pages, 39-bit VAs, pgdp=0000000041250000
[ 6952.514909] [ffffffffffffffec] pgd=0000000000000000, p4d=0000000000000000, pud=0000000000000000
[ 6952.516423] Internal error: Oops: 0000000096000005 [#1] PREEMPT SMP
[ 6952.517503] Modules linked in: x_tables tun loop [last unloaded: ip6_tables]
[ 6952.518691] CPU: 1 PID: 1078 Comm: kworker/u12:1 Tainted: G        W          6.6.142-yocto-standard #1
[ 6952.520269] Hardware name: linux,dummy-virt (DT)
[ 6952.521094] Workqueue: writeback wb_workfn (flush-7:0)
[ 6952.521985] pstate: 60400005 (nZCv daif +PAN -UAO -TCO -DIT -SSBS BTYPE=--)
[ 6952.523184] pc : ext4_ext_map_blocks+0x260/0x1860
[ 6952.524011] lr : ext4_ext_map_blocks+0xdb8/0x1860
[ 6952.524851] sp : ffffffc086a3b620
[ 6952.525421] x29: ffffffc086a3b740 x28: ffffffffffffffe4 x27: 000000000000808c
[ 6952.526624] x26: ffffff8017dd9000 x25: 000000000000808c x24: 0000000000000002
[ 6952.527849] x23: ffffff8035e766c8 x22: ffffff802e589690 x21: 000000000000042f
[ 6952.529087] x20: ffffffc086a3b948 x19: ffffff8035e767f0 x18: 0000000000000000
[ 6952.530310] x17: ffffffc081691310 x16: fffffffe001ab548 x15: 0000005564d4cb48
[ 6952.531519] x14: 00000000ffffffff x13: 0000000000000000 x12: ffffffffffffffc0
[ 6952.532683] x11: 0000000000000040 x10: ffffff8005d81d80 x9 : ffffffc0803cce14
[ 6952.533886] x8 : 00000000bab647bc x7 : 0000000000000000 x6 : 000000000000d847
[ 6952.535065] x5 : 0000000000000000 x4 : 0000000000316019 x3 : 0000000000000000
[ 6952.536264] x2 : 0000000000000000 x1 : 0000000000000000 x0 : ffffff803deec880
[ 6952.537425] Call trace:
[ 6952.537860]  ext4_ext_map_blocks+0x260/0x1860
[ 6952.538589]  ext4_map_blocks+0x19c/0x598
[ 6952.539258]  ext4_do_writepages+0x5a4/0xbe0
[ 6952.539977]  ext4_writepages+0x84/0x110
[ 6952.540624]  do_writepages+0x94/0x1e0
[ 6952.541240]  __writeback_single_inode+0x60/0x4d8
[ 6952.542086]  writeback_sb_inodes+0x208/0x4b0
[ 6952.542812]  __writeback_inodes_wb+0x58/0x118
[ 6952.543578]  wb_writeback+0x274/0x440
[ 6952.544198]  wb_workfn+0x3b0/0x5c8
[ 6952.544788]  process_one_work+0x16c/0x3e0
[ 6952.545434]  worker_thread+0x1b4/0x378
[ 6952.546059]  kthread+0x118/0x128
[ 6952.546599]  ret_from_fork+0x10/0x20
[ 6952.547197] Code: 2a0103f9 b9009fe1 b9000e99 b40055fc (79401398)
[ 6952.548170] ---[ end trace 0000000000000000 ]---
[ 6952.551090] ------------[ cut here ]------------

Reading the resulting code in 6.6.142:
fs/ext4/extents.c:
int ext4_ext_map_blocks(handle_t *handle, struct inode *inode,
			struct ext4_map_blocks *map, int flags)
{
	struct ext4_ext_path *path = NULL;
	// ...

got_allocated_blocks:
	path = ext4_ext_insert_extent(handle, inode, path, &newex, flags);
	if (IS_ERR(path)) {
		err = PTR_ERR(path);
		/*
		 * Gracefully handle out of space conditions. If the filesystem
		 * is inconsistent, we'll just leak allocated blocks to avoid
		 * causing even more damage.
		 */
		// ...
		goto out;
	}

	// ...
out:
	ext4_free_ext_path(path);

	trace_ext4_ext_map_blocks_exit(inode, flags, map,
				       err ? err : allocated);
	return err ? err : allocated;
}

=> Under out of space condition (what LTP does a *LOT*): path is given unmodified to
ext4_free_ext_path() that only does a NULL check (no IS_ERR) before
dereferencing it. And that produces the oops and then, the LTP failure.

Notably, master commit 6b854d552711 ("ext4: get rid of ppath in get_ext_path()")
never got backported to 6.6.y. But does add the IS_ERR_OR_NULL() check
to ext4_free_ext_path:
 void ext4_free_ext_path(struct ext4_ext_path *path)
 {
+       if (IS_ERR_OR_NULL(path))
+               return;

Thanks!
-- 
Yoann Congal
Smile ECS

^ permalink raw reply

* Re: [PATCH] ext4: zero non-uptodate buffers before encryption in writeback
From: Jan Kara @ 2026-06-24 11:32 UTC (permalink / raw)
  To: Yun Zhou
  Cc: tytso, adilger.kernel, libaokun, jack, ojaswin, ritesh.list,
	yi.zhang, linux-ext4, linux-kernel
In-Reply-To: <20260624065948.85415-1-yun.zhou@windriver.com>

On Wed 24-06-26 14:59:48, Yun Zhou wrote:
> ext4_bio_write_folio() encrypts the folio content from offset 0 up to
> round_up(len, blocksize) before submitting IO. When blocksize < PAGE_SIZE,
> this range may include blocks that are not being written out (holes,
> delay, or unwritten blocks). If the folio was freshly allocated by the
> page cache (via write_begin) for a partial-page write, these non-target
> blocks may remain uninitialized from the buddy allocator.
> 
> The encryption engine (AES-XTS) then reads these uninitialized bytes as
> operands, triggering a KMSAN uninit-value report in aes_encrypt().
> 
> Fix this by zeroing any non-uptodate buffer that is not being written
> out, before calling fscrypt_encrypt_pagecache_blocks(). This ensures the
> crypto engine never operates on uninitialized data regardless of which
> blocks are actually being submitted for IO.
> 
> The common case of blocksize == PAGE_SIZE is unaffected since there can
> be no non-overlapping blocks within a single-block folio.
> 
> Reported-by: syzbot+7add5c56bc2a14145d20@syzkaller.appspotmail.com
> Closes: https://syzkaller.appspot.com/bug?extid=7add5c56bc2a14145d20
> Signed-off-by: Yun Zhou <yun.zhou@windriver.com>

Eric has just sent patch set that deletes this code [1]. So I think this
patch isn't needed anymore.

[1] lore.kernel.org/20260624050334.124606-1-ebiggers@kernel.org

								Honza

> ---
>  fs/ext4/page-io.c | 10 ++++++++++
>  1 file changed, 10 insertions(+)
> 
> diff --git a/fs/ext4/page-io.c b/fs/ext4/page-io.c
> index bc674aa4a656..2d380b5a1501 100644
> --- a/fs/ext4/page-io.c
> +++ b/fs/ext4/page-io.c
> @@ -555,12 +555,22 @@ int ext4_bio_write_folio(struct ext4_io_submit *io, struct folio *folio,
>  	 * block which might be needed.  This may cause some unneeded blocks
>  	 * (e.g. holes) to be unnecessarily encrypted, but this is rare and
>  	 * can't happen in the common case of blocksize == PAGE_SIZE.
> +	 *
> +	 * Zero out any non-uptodate buffers that are not being written out,
> +	 * to prevent uninitialized memory from being fed into the crypto
> +	 * engine.
>  	 */
>  	if (fscrypt_inode_uses_fs_layer_crypto(inode)) {
>  		gfp_t gfp_flags = GFP_NOFS;
>  		unsigned int enc_bytes = round_up(len, i_blocksize(inode));
>  		struct page *bounce_page;
>  
> +		do {
> +			if (!buffer_async_write(bh) && !buffer_uptodate(bh))
> +				folio_zero_range(folio, bh_offset(bh),
> +						 bh->b_size);
> +		} while ((bh = bh->b_this_page) != head);
> +
>  		/*
>  		 * Since bounce page allocation uses a mempool, we can only use
>  		 * a waiting mask (i.e. request guaranteed allocation) on the
> -- 
> 2.43.0
> 
-- 
Jan Kara <jack@suse.com>
SUSE Labs, CR

^ permalink raw reply

* Re: [PATCH 10/16] fs/buffer: Remove fs-layer decryption code
From: Jan Kara @ 2026-06-24 11:40 UTC (permalink / raw)
  To: Eric Biggers
  Cc: linux-fscrypt, linux-fsdevel, linux-ext4, linux-f2fs-devel,
	linux-block, Christoph Hellwig, Theodore Ts'o, Andreas Dilger,
	Baokun Li, Jan Kara, Ojaswin Mujoo, Ritesh Harjani, Zhang Yi,
	Jaegeuk Kim, Chao Yu
In-Reply-To: <20260624050334.124606-11-ebiggers@kernel.org>

On Tue 23-06-26 22:03:28, Eric Biggers wrote:
> Now that fscrypt's file contents en/decryption is always implemented
> using blk-crypto when the filesystem is block-based, the fs-layer
> decryption code in fs/buffer.c is unused code.  Remove it.
> 
> Signed-off-by: Eric Biggers <ebiggers@kernel.org>

Fine by me. Feel free to add:

Reviewed-by: Jan Kara <jack@suse.cz>

								Honza

> ---
>  fs/buffer.c | 45 ++++++++-------------------------------------
>  1 file changed, 8 insertions(+), 37 deletions(-)
> 
> diff --git a/fs/buffer.c b/fs/buffer.c
> index 9af5f061a1f8..21dd9596a941 100644
> --- a/fs/buffer.c
> +++ b/fs/buffer.c
> @@ -334,82 +334,53 @@ static void end_buffer_async_read(struct buffer_head *bh, int uptodate)
>  
>  still_busy:
>  	spin_unlock_irqrestore(&first->b_uptodate_lock, flags);
>  }
>  
> -struct postprocess_bh_ctx {
> +struct verify_bh_ctx {
>  	struct work_struct work;
>  	struct buffer_head *bh;
>  	struct fsverity_info *vi;
>  };
>  
>  static void verify_bh(struct work_struct *work)
>  {
> -	struct postprocess_bh_ctx *ctx =
> -		container_of(work, struct postprocess_bh_ctx, work);
> +	struct verify_bh_ctx *ctx =
> +		container_of(work, struct verify_bh_ctx, work);
>  	struct buffer_head *bh = ctx->bh;
>  	bool valid;
>  
>  	valid = fsverity_verify_blocks(ctx->vi, bh->b_folio, bh->b_size,
>  				       bh_offset(bh));
>  	end_buffer_async_read(bh, valid);
>  	kfree(ctx);
>  }
>  
> -static void decrypt_bh(struct work_struct *work)
> -{
> -	struct postprocess_bh_ctx *ctx =
> -		container_of(work, struct postprocess_bh_ctx, work);
> -	struct buffer_head *bh = ctx->bh;
> -	int err;
> -
> -	err = fscrypt_decrypt_pagecache_blocks(bh->b_folio, bh->b_size,
> -					       bh_offset(bh));
> -	if (err == 0 && ctx->vi) {
> -		/*
> -		 * We use different work queues for decryption and for verity
> -		 * because verity may require reading metadata pages that need
> -		 * decryption, and we shouldn't recurse to the same workqueue.
> -		 */
> -		INIT_WORK(&ctx->work, verify_bh);
> -		fsverity_enqueue_verify_work(&ctx->work);
> -		return;
> -	}
> -	end_buffer_async_read(bh, err == 0);
> -	kfree(ctx);
> -}
> -
>  /*
>   * I/O completion handler for block_read_full_folio() - folios
>   * which come unlocked at the end of I/O.
>   */
>  static void bh_end_async_read(struct bio *bio)
>  {
>  	struct buffer_head *bh;
>  	bool uptodate = bio_endio_bh(bio, &bh);
>  	struct inode *inode = bh->b_folio->mapping->host;
> -	bool decrypt = fscrypt_inode_uses_fs_layer_crypto(inode);
>  	struct fsverity_info *vi = NULL;
>  
>  	/* needed by ext4 */
>  	if (bh->b_folio->index < DIV_ROUND_UP(inode->i_size, PAGE_SIZE))
>  		vi = fsverity_get_info(inode);
>  
> -	/* Decrypt (with fscrypt) and/or verify (with fsverity) if needed. */
> -	if (uptodate && (decrypt || vi)) {
> -		struct postprocess_bh_ctx *ctx = kmalloc_obj(*ctx, GFP_ATOMIC);
> +	/* Verify (with fsverity) if needed. */
> +	if (vi && uptodate) {
> +		struct verify_bh_ctx *ctx = kmalloc_obj(*ctx, GFP_ATOMIC);
>  
>  		if (ctx) {
>  			ctx->bh = bh;
>  			ctx->vi = vi;
> -			if (decrypt) {
> -				INIT_WORK(&ctx->work, decrypt_bh);
> -				fscrypt_enqueue_decrypt_work(&ctx->work);
> -			} else {
> -				INIT_WORK(&ctx->work, verify_bh);
> -				fsverity_enqueue_verify_work(&ctx->work);
> -			}
> +			INIT_WORK(&ctx->work, verify_bh);
> +			fsverity_enqueue_verify_work(&ctx->work);
>  			return;
>  		}
>  		uptodate = false;
>  	}
>  	end_buffer_async_read(bh, uptodate);
> -- 
> 2.54.0
> 
-- 
Jan Kara <jack@suse.com>
SUSE Labs, CR

^ permalink raw reply

* Re: [PATCH] ext4: cancel dirty accounting for folios without buffers
From: Jan Kara @ 2026-06-24 12:32 UTC (permalink / raw)
  To: Zhu Jia
  Cc: Zhang Yi, tytso, adilger.kernel, libaokun, jack, ojaswin,
	ritesh.list, linux-ext4, linux-kernel, stable
In-Reply-To: <20260624094535.1-zhujia.zj@bytedance.com>

On Wed 24-06-26 17:52:06, Zhu Jia wrote:
> Hi Yi,
> 
> Thanks for taking a look.
> 
> Yes, clearing PAGECACHE_TAG_DIRTY/TOWRITE would make the page-cache state
> cleaner. I had a version that did this by adding a helper around
> folio_cancel_dirty() and clearing the xarray tags after confirming the
> folio was still the same clean page-cache entry.
> 
> It looked like this:
> 
> static void ext4_cancel_dirty_folio(struct address_space *mapping,
> 				    struct folio *folio)
> {
> 	XA_STATE(xas, &mapping->i_pages, folio->index);
> 	unsigned long flags;
> 
> 	folio_cancel_dirty(folio);
> 
> 	xas_lock_irqsave(&xas, flags);
> 	if (xas_load(&xas) == folio && !folio_test_dirty(folio)) {
> 		xas_clear_mark(&xas, PAGECACHE_TAG_DIRTY);
> 		xas_clear_mark(&xas, PAGECACHE_TAG_TOWRITE);
> 	}
> 	xas_unlock_irqrestore(&xas, flags);
> }
> 
> The reason I left the tags unchanged in this version is that I was not sure
> whether it is appropriate for ext4 to open-code xarray tag cleanup directly.
> 
> If you think this is the right direction, I can add the helper back and
> send a v2.

That was a good judgement! Playing with xarray tags like this in filesystem
code is certainly not a good thing. For now, I'd leave the xarray tags
dangling - they will be eventually synced with reality on next writeback
attempt. If this inconsistency of tags needs to be fixed, the fix belongs
to the generic code (so that it can be used in other places as well).

								Honza
-- 
Jan Kara <jack@suse.com>
SUSE Labs, CR

^ permalink raw reply

* Re: [PATCH v3 01/10] ext4: replace ext4_dir_entry with ext4_dir_entry_2
From: Artem Blagodarenko @ 2026-06-24 12:32 UTC (permalink / raw)
  To: XIAO WU; +Cc: linux-ext4, adilger.kernel, Andreas Dilger
In-Reply-To: <tencent_23A0889489F22224F3214F20C2C091373E0A@qq.com>

Hi Xiao,

Thanks for taking the time to verify the Sashiko AI finding and provide a
concrete reproducer and crash log for the missing count-vs-limit check in
ext4_dx_csum_verify()/ext4_dx_csum_set(). You're right that this is a real
bug, independent of this series (it's unmodified upstream logic that this
patch only renamed a parameter type on).

I've added a fix that validates count against limit before it's used to
compute the checksummed range, returning the same "corrupt, run e2fsck"
error path as the existing limit check. It will be included as the first
patch of the next version of this series, ahead of the patch that touches
this function, so the issue is fixed before it would otherwise be flagged
again.

Thanks again for the thorough analysis.

Artem

On Mon, 22 Jun 2026 at 18:29, XIAO WU <xiaowu.417@qq.com> wrote:
>
> Hi Artem,
>
> I came across the Sashiko AI review [1] of this patch and was able to
> reproduce a kernel crash triggered by a missing bounds check in
> ext4_dx_csum_verify().  Although the review notes this is a pre-existing
> issue (not introduced by your patch), I wanted to share the concrete
> reproduction evidence since this patch touches the same function.
>
> On Fri, Jun 19, 2026 at 03:10:05PM -0400, Artem Blagodarenko wrote:
>  > @@ -456,7 +456,7 @@ static __le32 ext4_dx_csum(struct inode *inode,
> struct ext4_dir_entry *dirent,
>  >  }
>  >
>  >  static int ext4_dx_csum_verify(struct inode *inode,
>  > -                   struct ext4_dir_entry *dirent)
>  > +                   struct ext4_dir_entry_2 *dirent)
>  >  {
>  >      struct dx_countlimit *c;
>  >      struct dx_tail *t;
>
> The problem is in the validation logic inside this function:
>
>    c = get_dx_countlimit(inode, dirent, &count_offset);
>    limit = le16_to_cpu(c->limit);
>    count = le16_to_cpu(c->count);          // ← read from disk, never
> validated
>
>    if (count_offset + (limit * sizeof(struct dx_entry)) >
>        EXT4_BLOCK_SIZE(inode->i_sb) - sizeof(struct dx_tail)) {
>            warn_no_space_for_csum(inode);
>            return 0;
>    }
>
>    t = (struct dx_tail *)(((struct dx_entry *)c) + limit);
>
>    size = count_offset + (count * sizeof(struct dx_entry));  // ← uses
> unvalidated count
>    csum = ext4_chksum(ei->i_csum_seed, (__u8 *)dirent, size);
>          → crc32c() reads `size` bytes from bh->b_data
>
> The `limit` value is bounds-checked against the block size, but `count`
> is never validated.  If a corrupted or maliciously crafted filesystem
> sets `count` to a large value like 65535, the computation:
>
>    size = count_offset + (65535 * 8) = ~524288 bytes
>
> causes crc32c() to read far past the 4096-byte buffer_head allocation.
>
> Why KASAN reports this as use-after-free rather than out-of-bounds:
>
> The buffer_head's b_data is a kmalloc'd slab object.  When crc32c()
> reads hundreds of kilobytes past the end of this 4K slab allocation, it
> crosses into adjacent slab pages.  Those pages contain memory that was
> previously allocated and freed (old dentries, inode structures, etc.).
> KASAN's quarantine poisons freed slab objects, so the access lands on a
> poisoned freed page and triggers the slab-use-after-free detector:
>
>    BUG: KASAN: use-after-free in crc32c+0x32a/0x380
>    Read of size 1 at addr ffff88802ca54000 by task poc/10993
>
> The crash is deterministic with a crafted image and triggers on any
> directory read (getdents64 / ls).
>
> [Crash log — kernel 7.1.0-next-20260618, CONFIG_KASAN=y, SMP]
>
>    Call Trace:
>     <TASK>
>     dump_stack_lvl
>     print_report
>     kasan_report
>     crc32c+0x32a/0x380
>     __ext4_read_dirblock+0x90f/0xbb0
>     dx_probe+0xbb/0x1670
>     ext4_htree_fill_tree+0x50e/0xb30
>     ext4_readdir+0x241b/0x39d0
>     iterate_dir+0x29b/0xaf0
>     __x64_sys_getdents64+0x140/0x2c0
>     do_syscall_64+0x129/0x880
>     entry_SYSCALL_64_after_hwframe+0x77/0x7f
>     </TASK>
>
> The PoC is attached below.  It creates a 64 MB ext4 image with
> metadata_csum and dir_index, fills a directory with 800 files to
> trigger htree indexing, unmounts, then corrupts the dx_countlimit's
> count field via direct block write before remounting and listing the
> directory.
>
>    gcc -o poc poc.c -static
>
> [1]
> https://sashiko.dev/#/patchset/20260619191022.27008-1-ablagodarenko%40thelustrecollective.com
>      (Sashiko AI code review — "Out-of-Bounds Access", Severity: High)
>
> Thanks,
> XIAO
>
> // PoC: OOB read via unvalidated count in ext4_dx_csum_verify()
> #define _GNU_SOURCE
> #include <stdio.h>
> #include <stdlib.h>
> #include <string.h>
> #include <unistd.h>
> #include <sys/mount.h>
> #include <sys/stat.h>
> #include <fcntl.h>
> #include <errno.h>
> #include <sys/syscall.h>
> #include <stdint.h>
>
> #define IMG_PATH "poc_ext4.img"
> #define MNT_PATH "poc_mnt"
> #define IMG_SIZE (64 * 1024 * 1024)
> #define BLCK_SIZE 4096
>
> int main(int argc, char **argv)
> {
>      char cmd[4096];
>      int img_fd, ret;
>      char loop_dev[64] = {0};
>      unsigned char buf[BLCK_SIZE];
>
>      printf("[*] ext4 dx_csum count OOB PoC\n");
>
>      /* Cleanup from previous runs */
>      system("umount poc_mnt 2>/dev/null");
>      system("losetup -d /dev/loop0 2>/dev/null");
>      system("rm -rf poc_mnt poc_ext4.img 2>/dev/null");
>      mkdir(MNT_PATH, 0755);
>
>      /* Create and format image with metadata_csum + dir_index */
>      printf("[*] Creating 64 MB image, formatting ext4...\n");
>      img_fd = open(IMG_PATH, O_CREAT | O_RDWR | O_TRUNC, 0644);
>      if (img_fd < 0) { perror("open"); return 1; }
>      ftruncate(img_fd, IMG_SIZE);
>      close(img_fd);
>      snprintf(cmd, sizeof(cmd),
>           "mkfs.ext4 -F -O metadata_csum,dir_index,^has_journal "
>           "-b %d %s 2>/dev/null", BLCK_SIZE, IMG_PATH);
>      if (system(cmd) != 0) { fprintf(stderr, "mkfs failed\n"); return 1; }
>
>      /* Setup loop device */
>      printf("[*] Setting up loop...\n");
>      snprintf(cmd, sizeof(cmd), "losetup -f %s 2>/dev/null && "
>           "losetup -j %s | head -1 | cut -d: -f1", IMG_PATH, IMG_PATH);
>      FILE *fp = popen(cmd, "r");
>      if (fp) {
>          if (fgets(loop_dev, sizeof(loop_dev), fp)) {
>              char *nl = strchr(loop_dev, '\n');
>              if (nl) *nl = '\0';
>          }
>          pclose(fp);
>      }
>      if (!strlen(loop_dev)) { fprintf(stderr, "loop setup failed\n");
> return 1; }
>      printf("[*] Loop device: %s\n", loop_dev);
>
>      /* Mount and create htree directory with 800 files */
>      printf("[*] Mounting and creating htree directory...\n");
>      if (mount(loop_dev, MNT_PATH, "ext4", 0, NULL) < 0) {
>          perror("mount"); return 1;
>      }
>      mkdir(MNT_PATH "/dir", 0755);
>      for (int i = 0; i < 800; i++) {
>          char name[64];
>          snprintf(name, sizeof(name), MNT_PATH "/dir/file_%04d", i);
>          close(open(name, O_CREAT | O_WRONLY, 0644));
>      }
>      printf("[*] Unmounting to corrupt on-disk data...\n");
>      umount(MNT_PATH);
>
>      /*
>       * Corrupt the dx_countlimit count field.
>       * The htree root block is block 0 of the directory inode.
>       * dx_countlimit is at offset 8 in the INDEX-type dx block:
>       *   struct dx_countlimit { __le16 limit; __le16 count; };
>       * We set count = 0xFFFF (65535) to trigger massive OOB read.
>       */
>      printf("[*] Corrupting dx_countlimit.count in block 0...\n");
>      img_fd = open(IMG_PATH, O_RDWR);
>      if (img_fd < 0) { perror("open image"); return 1; }
>      memset(buf, 0, BLCK_SIZE);
>      /* Read block 0 of the directory inode.  Directory inode is typically
>       * inode #2 on a freshly formatted ext4.  For simplicity we scan for
>       * the INDEX signature (0x0A) at dx_root_info.dx_magic_offset. */
>      /* Seek to block group 0's inode table — inode #2 is at offset
>       * (2-1)*256 = 256 bytes into the inode table.  The inode table starts
>       * at block (superblock.s_first_ino_blocks + 1) or similar.
>       * For standard ext4 with 4K blocks: inode table at block 1.
>       * inode #2 at block 1 offset 256.  i_block[0] gives the dir block. */
>      unsigned char inode_buf[256];
>      lseek(img_fd, BLCK_SIZE + 256, SEEK_SET);  /* inode #2 */
>      read(img_fd, inode_buf, 256);
>      uint32_t dir_block = inode_buf[40] | (inode_buf[41]<<8) |
>                   (inode_buf[42]<<16) | (inode_buf[43]<<24);
>      printf("[*] Directory root block: %u\n", dir_block);
>
>      /* Read the dx root block, corrupt count at offset 10 (limit at 8,
> count at 10) */
>      lseek(img_fd, dir_block * BLCK_SIZE, SEEK_SET);
>      read(img_fd, buf, BLCK_SIZE);
>      uint16_t *count_ptr = (uint16_t *)(buf + 10);
>      printf("[*] Original count: %u, setting to 65535\n", *count_ptr);
>      *count_ptr = 0xFFFF;
>      lseek(img_fd, dir_block * BLCK_SIZE, SEEK_SET);
>      write(img_fd, buf, BLCK_SIZE);
>      fsync(img_fd);
>      close(img_fd);
>
>      /* Remount and trigger the bug by reading the directory */
>      printf("[*] Remounting and triggering via getdents64...\n");
>      if (mount(loop_dev, MNT_PATH, "ext4", 0, NULL) < 0) {
>          perror("remount"); return 1;
>      }
>      /* ls triggers ext4_readdir → ext4_htree_fill_tree → dx_probe →
> csum verify */
>      system("ls " MNT_PATH "/dir > /dev/null 2>&1");
>      printf("[*] Done — check dmesg for KASAN report\n");
>
>      umount(MNT_PATH);
>      snprintf(cmd, sizeof(cmd), "losetup -d %s 2>/dev/null", loop_dev);
>      system(cmd);
>      return 0;
> }
>
>

^ permalink raw reply

* Re: [PATCH] ext4: cancel dirty accounting for folios without buffers
From: Jan Kara @ 2026-06-24 12:32 UTC (permalink / raw)
  To: Zhu Jia
  Cc: tytso, adilger.kernel, libaokun, jack, ojaswin, ritesh.list,
	yi.zhang, linux-ext4, linux-kernel, stable
In-Reply-To: <20260623094947.7853-1-zhujia.zj@bytedance.com>

On Tue 23-06-26 17:49:47, Zhu Jia wrote:
> Since commit cc5095747edf ("ext4: don't BUG if someone dirty pages
> without asking ext4 first"), mpage_prepare_extent_to_map() handles dirty
> folios without buffer heads by warning, clearing PG_dirty, and skipping
> them. ext4 cannot write these folios because there are no buffer heads to
> map and submit.
> 
> That recovery leaves dirty accounting behind: folio_clear_dirty() clears
> PG_dirty but does not undo the accounting charged when the folio was
> dirtied. We have seen this in production as Dirty/nr_dirty staying high
> while Writeback/nr_writeback and device write IO stayed near zero, with
> many writer tasks blocked in balance_dirty_pages() throttling. Thus the
> warning-and-skip recovery can still become a dirty-throttle DoS.
> 
> Use folio_cancel_dirty() so dropping PG_dirty also cancels the dirty
> accounting.
> 
> Fixes: cc5095747edf ("ext4: don't BUG if someone dirty pages without asking ext4 first")
> Cc: stable@vger.kernel.org
> Signed-off-by: Zhu Jia <zhujia.zj@bytedance.com>

Good point. Feel free to add:

Reviewed-by: Jan Kara <jack@suse.cz>

								Honza

> ---
>  fs/ext4/inode.c | 8 +++++++-
>  1 file changed, 7 insertions(+), 1 deletion(-)
> 
> diff --git a/fs/ext4/inode.c b/fs/ext4/inode.c
> index c2c2d6ac7f3d1..7ea280e70c06e 100644
> --- a/fs/ext4/inode.c
> +++ b/fs/ext4/inode.c
> @@ -2715,7 +2715,13 @@ static int mpage_prepare_extent_to_map(struct mpage_da_data *mpd)
>  			 */
>  			if (!folio_buffers(folio)) {
>  				ext4_warning_inode(mpd->inode, "page %lu does not have buffers attached", folio->index);
> -				folio_clear_dirty(folio);
> +				/*
> +				 * folio_cancel_dirty() pairs the dropped dirty
> +				 * state with dirty accounting, but leaves stale
> +				 * PAGECACHE_TAG_DIRTY/TOWRITE tags behind. Later
> +				 * writeback may rescan this clean folio.
> +				 */
> +				folio_cancel_dirty(folio);
>  				folio_unlock(folio);
>  				continue;
>  			}
> -- 
> 2.20.1
-- 
Jan Kara <jack@suse.com>
SUSE Labs, CR

^ permalink raw reply

* Re: [PATCH v3 10/10] ext4: Add EXT4_IOC_SET_LUFID ioctl for setting LUFID on directory entries
From: Artem Blagodarenko @ 2026-06-24 12:33 UTC (permalink / raw)
  To: XIAO WU; +Cc: linux-ext4, adilger.kernel, Andreas Dilger
In-Reply-To: <tencent_2D1051E0281A7550C4CA60798593BF905B09@qq.com>

Hi Xiao,

Thanks for the detailed reproducer and analysis of the race condition on
EXT4_I(inode)->i_dirdata, and for the additional issues you flagged from
the same Sashiko AI review. I've confirmed all of them by reading the
code directly and fixed each one:

- ddh_length was missing the header's own length (only esl_data_len was
used), which silently dropped the last byte of every LUFID payload set
through this ioctl. Fixed to include the header size, and tightened the
esl_data_len upper bound so the result can't wrap the __u8 field.
- '.' and '..' are now rejected -- they must stay the fixed first two
entries of the directory's first block, and can't go through this
ioctl's general delete+re-add path.
- The handler now checks inode_permission(dir, MAY_WRITE) before
proceeding, instead of relying only on mnt_want_write_file().
- ext4_dirdata_set_lufid() now also locks the target inode (nested under
the parent directory, consistently dir-then-inode) for the
i_dirdata set/use/restore window, closing the race you reproduced
between concurrent calls on different hardlinks of the same inode.

All four fixes will be included in the next version of this series.

Thanks again for the thorough reproducers -- they were a big help in
pinning down the root causes.

Artem

On Mon, 22 Jun 2026 at 18:21, XIAO WU <xiaowu.417@qq.com> wrote:
>
> Hi Artem,
>
> I came across the Sashiko AI review [1] of this patch and was able to
> reproduce a kernel crash triggered by a race condition in
> ext4_dirdata_set_lufid().  I wanted to share the evidence in case it's
> helpful.
>
> The core issue is that EXT4_I(inode)->i_dirdata is set to a
> stack-local pointer without locking the target inode:
>
>  > +static int ext4_dirdata_set_lufid(struct inode *dir,
>  > +                                  const char *name, int namelen,
>  > +                                  struct ext4_dentry_param *edp)
>  > +{
>  > ...
>  > +    EXT4_I(inode)->i_dirdata = edp;
>
> The function locks the parent directory (inode_lock(dir)), but does NOT
> lock the target inode.  If two threads operate on different hardlinks
> to the *same* inode (in different directories), they race to overwrite
> inode->i_dirdata with each other's stack-local edp pointers.  The
> winner's stack pointer is later consumed through ext4_add_entry() →
> ext4_lookup() → ext4_dentry_get_fid(), reading from a stale or
> overwritten stack frame.
>
> With KASAN enabled this manifests as a null-ptr-deref because KASAN
> poisons freed stack memory.
>
> [Reproduction]
>
> The PoC creates two subdirectories (/mnt/test/da and /mnt/test/db),
> hardlinks the same file into both, then runs 8 child processes that
> hammer EXT4_IOC_SET_LUFID on the two directories simultaneously via
> ioctl().  Each child is pinned to a specific CPU to maximize the race
> window.  The kernel panics within a few seconds.
>
> [Crash log — kernel 7.1.0-next-20260618, CONFIG_KASAN=y, SMP]
>
>    Oops: general protection fault, probably for non-canonical address
>    0xdffffc0000000000: 0000 [#1] SMP KASAN NOPTI
>    KASAN: null-ptr-deref in range [0x0000000000000000-0x0000000000000007]
>
>    RIP: 0010:ext4_dirdata_set_lufid+0x3e8/0xb00
>    RAX: dffffc0000000000 RBX: 0000000000000000
>    ...
>    Call Trace:
>     <TASK>
>     ext4_ioctl_set_lufid+0x2d9/0x350
>     __ext4_ioctl+0x1d2/0x43c0
>     __x64_sys_ioctl+0x193/0x210
>     do_syscall_64+0x129/0x850
>     entry_SYSCALL_64_after_hwframe+0x77/0x7f
>     </TASK>
>
> The same crash was independently hit by a second thread at [#2],
> confirming the race condition.
>
> Additionally, the Sashiko review [1] noted a few other issues in this
> patch that may be worth checking:
>
> - The ioctl does not reject '.' and '..' — modifying these special
>    entries via EXT4_IOC_SET_LUFID would corrupt the directory layout.
>
> - The ddh_length computation in ext4_find_dest_de() appears to
>    exclude the header size (1 byte), which can cause the header and
>    data insertion to overflow by one byte into the adjacent directory
>    entry when (fname_len + esl_data_len) % 4 == 0.
>
> - The handler calls mnt_want_write_file() but omits an explicit
>    inode_permission(dir, MAY_WRITE) check, which may allow an
>    unprivileged user with read-only access to the directory to
>    modify directory entries.
>
> The PoC is attached below.  It compiles with:
>
>    gcc -o poc poc.c -static
>
> [1]
> https://sashiko.dev/#/patchset/20260619191022.27008-1-ablagodarenko%40thelustrecollective.com
>      (Sashiko AI code review — "Dangling Pointer", Severity: Critical)
>
> Thanks,
> XIAO
>
> // PoC: race on EXT4_I(inode)->i_dirdata via EXT4_IOC_SET_LUFID
> #define _GNU_SOURCE
> #include <stdio.h>
> #include <stdlib.h>
> #include <string.h>
> #include <unistd.h>
> #include <stdint.h>
> #include <sys/types.h>
> #include <sys/stat.h>
> #include <sys/ioctl.h>
> #include <sys/mount.h>
> #include <sys/wait.h>
> #include <fcntl.h>
> #include <errno.h>
> #include <sched.h>
> #include <linux/loop.h>
> #include <linux/fs.h>
>
> struct ext4_set_lufid {
>      uint8_t  esl_name_len;
>      char     esl_name[256];
>      uint8_t  esl_data_len;
>      char     esl_data[255];
> };
> #define EXT4_IOC_SET_LUFID _IOW('f', 47, struct ext4_set_lufid)
> #define MNT "/mnt/test"
>
> int main(void)
> {
>      int fd, ret;
>
>      setvbuf(stdout, NULL, _IONBF, 0);
>      printf("=== EXT4_IOC_SET_LUFID PoC ===\n\n");
>
>      /* Setup loopback ext4 filesystem */
>      system("umount -l /mnt/test 2>/dev/null; losetup -d /dev/loop0
> 2>/dev/null; true");
>      system("rm -rf /mnt/test /tmp/img 2>/dev/null");
>      mkdir(MNT, 0755);
>      system("dd if=/dev/zero of=/tmp/img bs=1M count=64 2>/dev/null");
>      system("losetup /dev/loop0 /tmp/img 2>/dev/null");
>      system("mkfs.ext4 -F -b 1024 -O ^metadata_csum,^64bit,^flex_bg
> /dev/loop0 2>/dev/null");
>      if (mount("/dev/loop0", MNT, "ext4", 0, NULL) != 0) {
>          printf("Mount failed\n"); return 1;
>      }
>
>      /* Create target file with two hardlinks in different directories */
>      mkdir(MNT "/da", 0755);
>      mkdir(MNT "/db", 0755);
>      close(open(MNT "/t", O_CREAT|O_WRONLY, 0644));
>      link(MNT "/t", MNT "/da/t");
>      link(MNT "/t", MNT "/db/t");
>
>      printf("Racing EXT4_IOC_SET_LUFID on hardlinks...\n");
>      for (int r = 0; r < 20; r++) {
>          pid_t kids[8];
>          for (int i = 0; i < 8; i++) {
>              kids[i] = fork();
>              if (kids[i] == 0) {
>                  /* Half target /da, half target /db — same inode */
>                  const char *dir = (i < 4) ? MNT "/da" : MNT "/db";
>                  cpu_set_t cpuset;
>                  CPU_ZERO(&cpuset);
>                  CPU_SET(i % 2, &cpuset);
>                  sched_setaffinity(0, sizeof(cpu_set_t), &cpuset);
>
>                  for (int j = 0; j < 5000; j++) {
>                      struct ext4_set_lufid l = {0};
>                      l.esl_name_len = 2;
>                      memcpy(l.esl_name, "t\0", 2);
>                      l.esl_data_len = 5;
>                      memcpy(l.esl_data, "ABCDE", 5);
>                      int f = open(dir, O_RDONLY);
>                      if (f >= 0) {
>                          ioctl(f, EXT4_IOC_SET_LUFID, &l);
>                          close(f);
>                      }
>                  }
>                  _exit(0);
>              }
>          }
>          for (int i = 0; i < 8; i++) {
>              int ws;
>              waitpid(kids[i], &ws, 0);
>          }
>          printf("."); fflush(stdout);
>      }
>      printf("\n");
>
>      printf("\n=== dmesg ===\n"); fflush(stdout);
>      system("dmesg | grep -iE 'KASAN|BUG:|Call Trace|general protection'
> | tail -40");
>
>      umount(MNT);
>      system("losetup -d /dev/loop0 2>/dev/null");
>      printf("Done.\n");
>      return 0;
> }
>

^ permalink raw reply

* Re: [PATCH] ext4: cancel dirty accounting for folios without buffers
From: Zhu Jia @ 2026-06-24 13:10 UTC (permalink / raw)
  To: Jan Kara
  Cc: Zhu Jia, Zhang Yi, Theodore Ts'o, Andreas Dilger, Baokun Li,
	Ojaswin Mujoo, Ritesh Harjani, linux-ext4, linux-kernel, stable
In-Reply-To: <x3jm3mhgsr7zx4hvfgdvmwoqyz5vxx2fjyxy6gs6him46767f6@dkkirnw54x6x>

On Wed, Jun 24, 2026 at 02:32:27PM +0200, Jan Kara wrote:
> On Wed 24-06-26 17:52:06, Zhu Jia wrote:
> > The reason I left the tags unchanged in this version is that I was not sure
> > whether it is appropriate for ext4 to open-code xarray tag cleanup directly.
> > 
> > If you think this is the right direction, I can add the helper back and
> > send a v2.
> 
> That was a good judgement! Playing with xarray tags like this in filesystem
> code is certainly not a good thing. For now, I'd leave the xarray tags
> dangling - they will be eventually synced with reality on next writeback
> attempt. If this inconsistency of tags needs to be fixed, the fix belongs
> to the generic code (so that it can be used in other places as well).
> 
> 								Honza

Thanks, makes sense. I'll keep the fix as-is and leave the xarray tags
alone.

Thanks,
Jia

^ permalink raw reply

* Re: [PATCH v2] ext4: fix ABBA deadlock in ext4_xattr_inode_cache_find()
From: Jan Kara @ 2026-06-24 13:13 UTC (permalink / raw)
  To: Aditya Srivastava
  Cc: tytso, jack, adilger.kernel, libaokun, ritesh.list, yi.zhang,
	linux-ext4, linux-kernel, Colin Ian King
In-Reply-To: <20260623095911.2372-1-aditya.ansh182@gmail.com>

On Tue 23-06-26 09:59:11, Aditya Srivastava wrote:
> From: Aditya Prakash Srivastava <aditya.ansh182@gmail.com>
> 
> Syzbot/stress-ng reported an ABBA deadlock in ext4 when exercising
> concurrent xattr workloads (using the ea_inode mount/format option).
> 
> The deadlock occurs between the running transaction and the eviction
> thread:
> - Task 1 (stress-ng): Holds a reference to a shared mbcache_entry (ce)
>   and calls ext4_xattr_inode_cache_find() -> ext4_iget() to retrieve
>   the corresponding EA inode. Since the EA inode is currently being
>   evicted, ext4_iget() blocks in __wait_on_freeing_inode() waiting for
>   eviction to complete.
> - Task 2 (eviction thread): Currently evicting the same EA inode in
>   ext4_evict_ea_inode(). It calls mb_cache_entry_wait_unused(oe) which
>   blocks waiting for Task 1 to release the reference to the mbcache_entry.
> 
> To break this deadlock, perform a non-blocking lookup of the EA inode
> using VFS's find_inode_nowait() API. If the EA inode is currently being
> evicted (marked with I_FREEING or I_WILL_FREE), simply skip it (treat
> as a cache miss) rather than waiting for eviction to complete. If the
> returned inode is found to be I_NEW, wait for its initialization to
> clear using wait_on_new_inode().
> 
> This deadlock was made much easier to hit after commit 0a46ef234756
> ("ext4: do not create EA inode under buffer lock") which removed
> synchronization on the buffer lock.
> 
> Reported-by: Colin Ian King <colin.i.king@gmail.com>
> Closes: https://bugzilla.kernel.org/show_bug.cgi?id=219283
> Fixes: 0a46ef234756 ("ext4: do not create EA inode under buffer lock")
> Signed-off-by: Aditya Prakash Srivastava <aditya.ansh182@gmail.com>

I was looking into this for quite some time in the past and then run out of
time when redesigning locking of the xattrs (which is a mess). Your
solution is a bit hacky but as a quick stability fix before we can rework
xattr locking it actually looks as a neat idea!

> diff --git a/fs/ext4/xattr.c b/fs/ext4/xattr.c
> index 982a1f831e22..ef13e7a76153 100644
> --- a/fs/ext4/xattr.c
> +++ b/fs/ext4/xattr.c
> @@ -1523,6 +1523,20 @@ static struct inode *ext4_xattr_inode_create(handle_t *handle,
>  	return ea_inode;
>  }
>  
> +static int ext4_xattr_inode_match(struct inode *inode, u64 ino, void *data)
> +{
> +	if (inode->i_ino != ino)
> +		return 0;
> +	spin_lock(&inode->i_lock);
> +	if (inode_state_read(inode) & (I_FREEING | I_WILL_FREE)) {
> +		spin_unlock(&inode->i_lock);
> +		return 0;
> +	}

I think you should also skip I_CREATING inodes here... I don't think you
can really spot them here but just that we don't have to worry.

> +	__iget(inode);
> +	spin_unlock(&inode->i_lock);
> +	return 1;
> +}
> +
>  static struct inode *
>  ext4_xattr_inode_cache_find(struct inode *inode, const void *value,
>  			    size_t value_len, u32 hash)
> @@ -1549,10 +1563,19 @@ ext4_xattr_inode_cache_find(struct inode *inode, const void *value,
>  	}
>  
>  	while (ce) {
> -		ea_inode = ext4_iget(inode->i_sb, ce->e_value,
> -				     EXT4_IGET_EA_INODE);
> -		if (IS_ERR(ea_inode))
> +		ea_inode = find_inode_nowait(inode->i_sb, ce->e_value,
> +					     ext4_xattr_inode_match, NULL);
> +		if (!ea_inode)
>  			goto next_entry;
> +		if (inode_state_read_once(ea_inode) & I_NEW)
> +			wait_on_new_inode(ea_inode);
> +		if (is_bad_inode(ea_inode) ||
> +		    !(EXT4_I(ea_inode)->i_flags & EXT4_EA_INODE_FL) ||
> +		    ext4_test_inode_state(ea_inode, EXT4_STATE_XATTR) ||
> +		    EXT4_I(ea_inode)->i_file_acl) {
> +			iput(ea_inode);
> +			goto next_entry;
> +		}

So instead of opencoding these checks here, I'd rather implement
EXT4_IGET_NOWAIT flag which will use find_inode_nowait() like above for the
inode lookup and then you don't have to opencode the sanity checks here and
they can stay contained in ext4_iget() code...

								Honza
-- 
Jan Kara <jack@suse.com>
SUSE Labs, CR

^ permalink raw reply

* Re: [PATCH] ext4: cancel dirty accounting for folios without buffers
From: Zhang Yi @ 2026-06-24 13:29 UTC (permalink / raw)
  To: Jan Kara, Zhu Jia
  Cc: Zhang Yi, tytso, adilger.kernel, libaokun, ojaswin, ritesh.list,
	linux-ext4, linux-kernel, stable
In-Reply-To: <x3jm3mhgsr7zx4hvfgdvmwoqyz5vxx2fjyxy6gs6him46767f6@dkkirnw54x6x>

On 6/24/2026 8:32 PM, Jan Kara wrote:
> On Wed 24-06-26 17:52:06, Zhu Jia wrote:
>> Hi Yi,
>>
>> Thanks for taking a look.
>>
>> Yes, clearing PAGECACHE_TAG_DIRTY/TOWRITE would make the page-cache state
>> cleaner. I had a version that did this by adding a helper around
>> folio_cancel_dirty() and clearing the xarray tags after confirming the
>> folio was still the same clean page-cache entry.
>>
>> It looked like this:
>>
>> static void ext4_cancel_dirty_folio(struct address_space *mapping,
>> 				    struct folio *folio)
>> {
>> 	XA_STATE(xas, &mapping->i_pages, folio->index);
>> 	unsigned long flags;
>>
>> 	folio_cancel_dirty(folio);
>>
>> 	xas_lock_irqsave(&xas, flags);
>> 	if (xas_load(&xas) == folio && !folio_test_dirty(folio)) {
>> 		xas_clear_mark(&xas, PAGECACHE_TAG_DIRTY);
>> 		xas_clear_mark(&xas, PAGECACHE_TAG_TOWRITE);
>> 	}
>> 	xas_unlock_irqrestore(&xas, flags);
>> }
>>
>> The reason I left the tags unchanged in this version is that I was not sure
>> whether it is appropriate for ext4 to open-code xarray tag cleanup directly.
>>
>> If you think this is the right direction, I can add the helper back and
>> send a v2.
> 
> That was a good judgement! Playing with xarray tags like this in filesystem
> code is certainly not a good thing. For now, I'd leave the xarray tags
> dangling - they will be eventually synced with reality on next writeback
> attempt. If this inconsistency of tags needs to be fixed, the fix belongs
> to the generic code (so that it can be used in other places as well).
> 
> 								Honza

Yes, I agree. Directly clearing the tag via open code is not a good
approach. However, I took a look at the !nr_to_submit branch in
ext4_bio_write_folio(), and it seems to have a similar simple handling
pattern—it directly calls __folio_start_writeback() and
folio_end_writeback(), which appears to be an elegant way to clear them.
Could we also call these two helpers just after folio_cancel_dirty()
here?

Thanks,
Yi.



^ permalink raw reply

* [PATCH v4 00/11] Data in direntry (dirdata) feature
From: Artem Blagodarenko @ 2026-06-24 13:36 UTC (permalink / raw)
  To: linux-ext4; +Cc: adilger.kernel, Artem Blagodarenko

EXT4 currently stores a hash in the directory entry
(dirent) immediately after the file name to support
simultaneous fscrypt and casefold functionality.

It has been discussed within the EXT4 community that
this hash could instead be stored in dirdata. This
would make it the second (or third, in the case of
64-bit inode counts) user of dirdata.

At the same time, the existing format—where the hash
is placed after the file name—must continue to be
supported. With these patches, EXT4 can handle the
hash in both formats.

The first user of this feature is LUFID -
Locally Unique File ID.

Support for fscrypt and case-insensitive directories
with dirdata enabled has been verified using a
dedicated xfstest submitted to the xfstests list as
a separate patch.

e2fsprogs support is provided in a separate patches
series.

Changes in v4:
- syzbot ci actually ran the v3 series and found real,
  reproducible KASAN slab-out-of-bounds and use-after-free
  reads, all rooted in ext4_dir_entry_len() decoding
  de->rec_len with a hardcoded full-block size even when
  the entry lives in a smaller buffer (inline directory
  data). Gave it an explicit blocksize parameter and fixed
  every caller to pass the real containing-buffer size.
- dx_get_dx_info() and get_dx_countlimit() additionally
  needed dir=NULL (not just the right blocksize) when
  computing past the on-disk '.'/'..' entries, since those
  never carry the casefold+fscrypt hash regardless of the
  directory's feature flags; passing the real dir made
  ext4_dirent_rec_len() add 8 bytes of hash space that was
  never written on disk, corrupting dx_root_info's offset
  for every casefold+encrypt directory.
- ext4_dirdata_get()/ext4_dirdata_set(): fixed bounds checks
  that were off by EXT4_BASE_DIR_LEN (the 8-byte dirent
  header), a LUFID memcpy that used the wrong source/length,
  an out-of-bounds array write for maximum-length filenames,
  and an uninitialized gap byte leaking a stale memory byte
  to disk.
- EXT4_IOC_SET_LUFID: fixed ddh_length under-counting the
  header byte (silently dropping the last byte of every
  LUFID payload), rejected '.'/'..' as targets, added a
  missing inode_permission(dir, MAY_WRITE) check, and closed
  a data race on the shared i_dirdata field by also locking
  the target inode (not just the parent directory) for the
  duration it's used.
- Fixed a missing bounds check in ext4_dx_csum_verify()/
  ext4_dx_csum_set() that let an unvalidated on-disk `count`
  field drive an out-of-bounds checksum read. This bug
  predates this series, but is included as patch 1 (ahead
  of the patch that touches this function) since it was
  found via review of this series.
- Thanks to Sashiko AI review and to Xiao (xiaowu.417@qq.com)
  for reproducing several of the above with concrete crash
  logs and PoCs.

Artem Blagodarenko (11):
  ext4: validate count against limit in ext4_dx_csum_verify/_set
  ext4: replace ext4_dir_entry with ext4_dir_entry_2
  ext4: add ext4_dir_entry_is_tail()
  ext4: refactor dx_root to support variable dirent sizes
  ext4: add dirdata format definitions and access helpers
  ext4: preserve dirdata bits in get_dtype()
  ext4: add ext4_dir_entry_len() and harden dirdata parsing
  ext4: rename ext4_dir_rec_len() and clarify dirdata usage
  ext4: dirdata feature
  ext4: add dirdata set/get helpers
  ext4: Add EXT4_IOC_SET_LUFID ioctl for setting LUFID on directory
    entries

 foofile.txt               |   0
 fs/ext4/dir.c             |   9 +-
 fs/ext4/ext4.h            | 211 +++++++++++-
 fs/ext4/inline.c          |  41 ++-
 fs/ext4/ioctl.c           |  84 +++++
 fs/ext4/namei.c           | 699 +++++++++++++++++++++++++++++---------
 fs/ext4/sysfs.c           |   2 +
 include/uapi/linux/ext4.h |  13 +
 8 files changed, 861 insertions(+), 198 deletions(-)
 create mode 100644 foofile.txt

-- 
2.43.7


^ permalink raw reply

* [PATCH v4 01/11] ext4: validate count against limit in ext4_dx_csum_verify/_set
From: Artem Blagodarenko @ 2026-06-24 13:36 UTC (permalink / raw)
  To: linux-ext4
  Cc: adilger.kernel, Artem Blagodarenko, xiaowu.417,
	Artem Blagodarenko
In-Reply-To: <20260624133642.18438-1-ablagodarenko@thelustrecollective.com>

dx_countlimit's count field was read from disk and used directly to
compute the checksummed range (count_offset + count * sizeof(dx_entry))
without ever being checked against limit -- only limit itself was
bounds-checked against the block size. A corrupted or maliciously
crafted filesystem image that sets count to a large value (e.g. 65535)
makes ext4_chksum() read far past the end of the directory block
buffer, hitting adjacent slab objects.

Reported-by: xiaowu.417@qq.com
Signed-off-by: Artem Blagodarenko <artem.blagodarenko@gmail.com>
---
 fs/ext4/namei.c | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/fs/ext4/namei.c b/fs/ext4/namei.c
index cc49ae04a6f6..a283e285937a 100644
--- a/fs/ext4/namei.c
+++ b/fs/ext4/namei.c
@@ -477,6 +477,10 @@ static int ext4_dx_csum_verify(struct inode *inode,
 		warn_no_space_for_csum(inode);
 		return 0;
 	}
+	if (count > limit) {
+		EXT4_ERROR_INODE(inode, "dir seems corrupt?  Run e2fsck -D.");
+		return 0;
+	}
 	t = (struct dx_tail *)(((struct dx_entry *)c) + limit);
 
 	if (t->dt_checksum != ext4_dx_csum(inode, dirent, count_offset,
@@ -506,6 +510,10 @@ static void ext4_dx_csum_set(struct inode *inode, struct ext4_dir_entry *dirent)
 		warn_no_space_for_csum(inode);
 		return;
 	}
+	if (count > limit) {
+		EXT4_ERROR_INODE(inode, "dir seems corrupt?  Run e2fsck -D.");
+		return;
+	}
 	t = (struct dx_tail *)(((struct dx_entry *)c) + limit);
 
 	t->dt_checksum = ext4_dx_csum(inode, dirent, count_offset, count, t);
-- 
2.43.7


^ permalink raw reply related


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox