From 7737b1b448738be650ddc51fe64b8aae42d433c1 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Mon, 2 Mar 2026 06:18:06 -0800 Subject: [PATCH 01/15] ext4: initialize the write hint in io_submit_init_bio Make io_submit_init_bio complete by also initializing the write hint. Signed-off-by: Christoph Hellwig Link: https://lore.kernel.org/r/20260302141922.370070-2-hch@lst.de Signed-off-by: Eric Biggers --- fs/ext4/page-io.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/fs/ext4/page-io.c b/fs/ext4/page-io.c index a8c95eee91b7..b8cf9f6f9e0b 100644 --- a/fs/ext4/page-io.c +++ b/fs/ext4/page-io.c @@ -416,6 +416,7 @@ void ext4_io_submit_init(struct ext4_io_submit *io, } static void io_submit_init_bio(struct ext4_io_submit *io, + struct inode *inode, struct buffer_head *bh) { struct bio *bio; @@ -429,6 +430,7 @@ static void io_submit_init_bio(struct ext4_io_submit *io, bio->bi_iter.bi_sector = bh->b_blocknr * (bh->b_size >> 9); bio->bi_end_io = ext4_end_bio; bio->bi_private = ext4_get_io_end(io->io_end); + bio->bi_write_hint = inode->i_write_hint; io->io_bio = bio; io->io_next_block = bh->b_blocknr; wbc_init_bio(io->io_wbc, bio); @@ -445,10 +447,8 @@ static void io_submit_add_bh(struct ext4_io_submit *io, submit_and_retry: ext4_io_submit(io); } - if (io->io_bio == NULL) { - io_submit_init_bio(io, bh); - io->io_bio->bi_write_hint = inode->i_write_hint; - } + if (io->io_bio == NULL) + io_submit_init_bio(io, inode, bh); if (!bio_add_folio(io->io_bio, io_folio, bh->b_size, bh_offset(bh))) goto submit_and_retry; wbc_account_cgroup_owner(io->io_wbc, folio, bh->b_size); From d3fc0edc8825f39c86dcc4a8b8544f6e25680a51 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Mon, 2 Mar 2026 06:18:07 -0800 Subject: [PATCH 02/15] ext4: open code fscrypt_set_bio_crypt_ctx_bh io_submit_init_bio already has or can easily get at most information needed to set the crypto context. Open code fscrypt_set_bio_crypt_ctx_bh based on that. Signed-off-by: Christoph Hellwig Link: https://lore.kernel.org/r/20260302141922.370070-3-hch@lst.de Signed-off-by: Eric Biggers --- fs/ext4/page-io.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/fs/ext4/page-io.c b/fs/ext4/page-io.c index b8cf9f6f9e0b..c5ca99b33c26 100644 --- a/fs/ext4/page-io.c +++ b/fs/ext4/page-io.c @@ -417,6 +417,7 @@ void ext4_io_submit_init(struct ext4_io_submit *io, static void io_submit_init_bio(struct ext4_io_submit *io, struct inode *inode, + struct folio *folio, struct buffer_head *bh) { struct bio *bio; @@ -426,7 +427,9 @@ static void io_submit_init_bio(struct ext4_io_submit *io, * __GFP_DIRECT_RECLAIM is set, see comments for bio_alloc_bioset(). */ bio = bio_alloc(bh->b_bdev, BIO_MAX_VECS, REQ_OP_WRITE, GFP_NOIO); - fscrypt_set_bio_crypt_ctx_bh(bio, bh, GFP_NOIO); + fscrypt_set_bio_crypt_ctx(bio, inode, + (folio_pos(folio) + bh_offset(bh)) >> inode->i_blkbits, + GFP_NOIO); bio->bi_iter.bi_sector = bh->b_blocknr * (bh->b_size >> 9); bio->bi_end_io = ext4_end_bio; bio->bi_private = ext4_get_io_end(io->io_end); @@ -448,7 +451,7 @@ static void io_submit_add_bh(struct ext4_io_submit *io, ext4_io_submit(io); } if (io->io_bio == NULL) - io_submit_init_bio(io, inode, bh); + io_submit_init_bio(io, inode, folio, bh); if (!bio_add_folio(io->io_bio, io_folio, bh->b_size, bh_offset(bh))) goto submit_and_retry; wbc_account_cgroup_owner(io->io_wbc, folio, bh->b_size); From 5f18a9d2f75a3a5c055c2d777a29b3aa322b04da Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Mon, 2 Mar 2026 06:18:08 -0800 Subject: [PATCH 03/15] ext4: factor out a io_submit_need_new_bio helper Factor out a helper to prepare for making this logic a bit more complex in the next commit. Signed-off-by: Christoph Hellwig Link: https://lore.kernel.org/r/20260302141922.370070-4-hch@lst.de Signed-off-by: Eric Biggers --- fs/ext4/page-io.c | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/fs/ext4/page-io.c b/fs/ext4/page-io.c index c5ca99b33c26..58cdbd836fd6 100644 --- a/fs/ext4/page-io.c +++ b/fs/ext4/page-io.c @@ -439,14 +439,23 @@ static void io_submit_init_bio(struct ext4_io_submit *io, wbc_init_bio(io->io_wbc, bio); } +static bool io_submit_need_new_bio(struct ext4_io_submit *io, + struct buffer_head *bh) +{ + if (bh->b_blocknr != io->io_next_block) + return true; + if (!fscrypt_mergeable_bio_bh(io->io_bio, bh)) + return true; + return false; +} + static void io_submit_add_bh(struct ext4_io_submit *io, struct inode *inode, struct folio *folio, struct folio *io_folio, struct buffer_head *bh) { - if (io->io_bio && (bh->b_blocknr != io->io_next_block || - !fscrypt_mergeable_bio_bh(io->io_bio, bh))) { + if (io->io_bio && io_submit_need_new_bio(io, bh)) { submit_and_retry: ext4_io_submit(io); } From 588e7c048d7d2bfcbe7776ee0888ee248adf01d1 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Mon, 2 Mar 2026 06:18:09 -0800 Subject: [PATCH 04/15] ext4, fscrypt: merge fscrypt_mergeable_bio_bh into io_submit_need_new_bio ext4 already has the inode and folio and can't have a NULL folio->mapping in this path. Open code fscrypt_mergeable_bio_bh in io_submit_need_new_bio based on these simplifying assumptions. Signed-off-by: Christoph Hellwig Link: https://lore.kernel.org/r/20260302141922.370070-5-hch@lst.de Signed-off-by: Eric Biggers --- fs/crypto/inline_crypt.c | 23 ----------------------- fs/ext4/page-io.c | 7 +++++-- include/linux/fscrypt.h | 9 --------- 3 files changed, 5 insertions(+), 34 deletions(-) diff --git a/fs/crypto/inline_crypt.c b/fs/crypto/inline_crypt.c index c0852b920dbc..0da53956a9b1 100644 --- a/fs/crypto/inline_crypt.c +++ b/fs/crypto/inline_crypt.c @@ -406,29 +406,6 @@ bool fscrypt_mergeable_bio(struct bio *bio, const struct inode *inode, } EXPORT_SYMBOL_GPL(fscrypt_mergeable_bio); -/** - * fscrypt_mergeable_bio_bh() - test whether data can be added to a bio - * @bio: the bio being built up - * @next_bh: the next buffer_head for which I/O will be submitted - * - * Same as fscrypt_mergeable_bio(), except this takes a buffer_head instead of - * an inode and block number directly. - * - * Return: true iff the I/O is mergeable - */ -bool fscrypt_mergeable_bio_bh(struct bio *bio, - const struct buffer_head *next_bh) -{ - const struct inode *inode; - u64 next_lblk; - - if (!bh_get_inode_and_lblk_num(next_bh, &inode, &next_lblk)) - return !bio->bi_crypt_context; - - return fscrypt_mergeable_bio(bio, inode, next_lblk); -} -EXPORT_SYMBOL_GPL(fscrypt_mergeable_bio_bh); - /** * fscrypt_dio_supported() - check whether DIO (direct I/O) is supported on an * inode, as far as encryption is concerned diff --git a/fs/ext4/page-io.c b/fs/ext4/page-io.c index 58cdbd836fd6..293314d7f236 100644 --- a/fs/ext4/page-io.c +++ b/fs/ext4/page-io.c @@ -440,11 +440,14 @@ static void io_submit_init_bio(struct ext4_io_submit *io, } static bool io_submit_need_new_bio(struct ext4_io_submit *io, + struct inode *inode, + struct folio *folio, struct buffer_head *bh) { if (bh->b_blocknr != io->io_next_block) return true; - if (!fscrypt_mergeable_bio_bh(io->io_bio, bh)) + if (!fscrypt_mergeable_bio(io->io_bio, inode, + (folio_pos(folio) + bh_offset(bh)) >> inode->i_blkbits)) return true; return false; } @@ -455,7 +458,7 @@ static void io_submit_add_bh(struct ext4_io_submit *io, struct folio *io_folio, struct buffer_head *bh) { - if (io->io_bio && io_submit_need_new_bio(io, bh)) { + if (io->io_bio && io_submit_need_new_bio(io, inode, folio, bh)) { submit_and_retry: ext4_io_submit(io); } diff --git a/include/linux/fscrypt.h b/include/linux/fscrypt.h index 516aba5b858b..6af3c1907adc 100644 --- a/include/linux/fscrypt.h +++ b/include/linux/fscrypt.h @@ -876,9 +876,6 @@ void fscrypt_set_bio_crypt_ctx_bh(struct bio *bio, bool fscrypt_mergeable_bio(struct bio *bio, const struct inode *inode, u64 next_lblk); -bool fscrypt_mergeable_bio_bh(struct bio *bio, - const struct buffer_head *next_bh); - bool fscrypt_dio_supported(struct inode *inode); u64 fscrypt_limit_io_blocks(const struct inode *inode, u64 lblk, u64 nr_blocks); @@ -906,12 +903,6 @@ static inline bool fscrypt_mergeable_bio(struct bio *bio, return true; } -static inline bool fscrypt_mergeable_bio_bh(struct bio *bio, - const struct buffer_head *next_bh) -{ - return true; -} - static inline bool fscrypt_dio_supported(struct inode *inode) { return !fscrypt_needs_contents_encryption(inode); From a18b1ab81654b06e7ff402e5d0b85249e9504bcb Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Mon, 2 Mar 2026 06:18:10 -0800 Subject: [PATCH 05/15] fscrypt: move fscrypt_set_bio_crypt_ctx_bh to buffer.c fscrypt_set_bio_crypt_ctx_bh is only used by submit_bh_wbc now. Move it there and merge bh_get_inode_and_lblk_num into it. Note that this does not add ifdefs for fscrypt as the compiler will optimize away the dead code if it is not built in. Signed-off-by: Christoph Hellwig Link: https://lore.kernel.org/r/20260302141922.370070-6-hch@lst.de Signed-off-by: Eric Biggers --- fs/buffer.c | 21 ++++++++++++++++++- fs/crypto/inline_crypt.c | 45 ---------------------------------------- include/linux/fscrypt.h | 9 -------- 3 files changed, 20 insertions(+), 55 deletions(-) diff --git a/fs/buffer.c b/fs/buffer.c index 22b43642ba57..b6504ec7fa4c 100644 --- a/fs/buffer.c +++ b/fs/buffer.c @@ -2774,6 +2774,24 @@ static void end_bio_bh_io_sync(struct bio *bio) bio_put(bio); } +static void buffer_set_crypto_ctx(struct bio *bio, const struct buffer_head *bh, + gfp_t gfp_mask) +{ + const struct address_space *mapping = folio_mapping(bh->b_folio); + const struct inode *inode; + u64 lblk; + + /* + * The ext4 journal (jbd2) can submit a buffer_head it directly created + * for a non-pagecache page. fscrypt doesn't care about these. + */ + if (!mapping) + return; + inode = mapping->host; + lblk = (folio_pos(bh->b_folio) + bh_offset(bh)) >> inode->i_blkbits; + fscrypt_set_bio_crypt_ctx(bio, inode, lblk, gfp_mask); +} + static void submit_bh_wbc(blk_opf_t opf, struct buffer_head *bh, enum rw_hint write_hint, struct writeback_control *wbc) @@ -2800,7 +2818,8 @@ static void submit_bh_wbc(blk_opf_t opf, struct buffer_head *bh, bio = bio_alloc(bh->b_bdev, 1, opf, GFP_NOIO); - fscrypt_set_bio_crypt_ctx_bh(bio, bh, GFP_NOIO); + if (IS_ENABLED(CONFIG_FS_ENCRYPTION)) + buffer_set_crypto_ctx(bio, bh, GFP_NOIO); bio->bi_iter.bi_sector = bh->b_blocknr * (bh->b_size >> 9); bio->bi_write_hint = write_hint; diff --git a/fs/crypto/inline_crypt.c b/fs/crypto/inline_crypt.c index 0da53956a9b1..702d13d138aa 100644 --- a/fs/crypto/inline_crypt.c +++ b/fs/crypto/inline_crypt.c @@ -314,51 +314,6 @@ void fscrypt_set_bio_crypt_ctx(struct bio *bio, const struct inode *inode, } EXPORT_SYMBOL_GPL(fscrypt_set_bio_crypt_ctx); -/* Extract the inode and logical block number from a buffer_head. */ -static bool bh_get_inode_and_lblk_num(const struct buffer_head *bh, - const struct inode **inode_ret, - u64 *lblk_num_ret) -{ - struct folio *folio = bh->b_folio; - const struct address_space *mapping; - const struct inode *inode; - - /* - * The ext4 journal (jbd2) can submit a buffer_head it directly created - * for a non-pagecache page. fscrypt doesn't care about these. - */ - mapping = folio_mapping(folio); - if (!mapping) - return false; - inode = mapping->host; - - *inode_ret = inode; - *lblk_num_ret = (folio_pos(folio) + bh_offset(bh)) >> inode->i_blkbits; - return true; -} - -/** - * fscrypt_set_bio_crypt_ctx_bh() - prepare a file contents bio for inline - * crypto - * @bio: a bio which will eventually be submitted to the file - * @first_bh: the first buffer_head for which I/O will be submitted - * @gfp_mask: memory allocation flags - * - * Same as fscrypt_set_bio_crypt_ctx(), except this takes a buffer_head instead - * of an inode and block number directly. - */ -void fscrypt_set_bio_crypt_ctx_bh(struct bio *bio, - const struct buffer_head *first_bh, - gfp_t gfp_mask) -{ - const struct inode *inode; - u64 first_lblk; - - if (bh_get_inode_and_lblk_num(first_bh, &inode, &first_lblk)) - fscrypt_set_bio_crypt_ctx(bio, inode, first_lblk, gfp_mask); -} -EXPORT_SYMBOL_GPL(fscrypt_set_bio_crypt_ctx_bh); - /** * fscrypt_mergeable_bio() - test whether data can be added to a bio * @bio: the bio being built up diff --git a/include/linux/fscrypt.h b/include/linux/fscrypt.h index 6af3c1907adc..26561b7994e0 100644 --- a/include/linux/fscrypt.h +++ b/include/linux/fscrypt.h @@ -869,10 +869,6 @@ void fscrypt_set_bio_crypt_ctx(struct bio *bio, const struct inode *inode, u64 first_lblk, gfp_t gfp_mask); -void fscrypt_set_bio_crypt_ctx_bh(struct bio *bio, - const struct buffer_head *first_bh, - gfp_t gfp_mask); - bool fscrypt_mergeable_bio(struct bio *bio, const struct inode *inode, u64 next_lblk); @@ -891,11 +887,6 @@ static inline void fscrypt_set_bio_crypt_ctx(struct bio *bio, const struct inode *inode, u64 first_lblk, gfp_t gfp_mask) { } -static inline void fscrypt_set_bio_crypt_ctx_bh( - struct bio *bio, - const struct buffer_head *first_bh, - gfp_t gfp_mask) { } - static inline bool fscrypt_mergeable_bio(struct bio *bio, const struct inode *inode, u64 next_lblk) From 60b4fcb49efe7f07aaa92c8d7933ac37b3be85b2 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Mon, 2 Mar 2026 06:18:11 -0800 Subject: [PATCH 06/15] fscrypt: pass a byte offset to fscrypt_generate_dun Logical offsets into an inode are usually expressed as bytes in the VFS. Switch fscrypt_generate_dun to that convention and remove the ci_data_units_per_block_bits member in struct fscrypt_inode_info that was only used to cache the DUN shift based on the logical block size granularity. Signed-off-by: Christoph Hellwig Link: https://lore.kernel.org/r/20260302141922.370070-7-hch@lst.de Signed-off-by: Eric Biggers --- fs/crypto/fscrypt_private.h | 3 --- fs/crypto/inline_crypt.c | 10 ++++------ fs/crypto/keysetup.c | 2 -- 3 files changed, 4 insertions(+), 11 deletions(-) diff --git a/fs/crypto/fscrypt_private.h b/fs/crypto/fscrypt_private.h index 4e8e82a9ccf9..8d3c278a7591 100644 --- a/fs/crypto/fscrypt_private.h +++ b/fs/crypto/fscrypt_private.h @@ -278,9 +278,6 @@ struct fscrypt_inode_info { */ u8 ci_data_unit_bits; - /* Cached value: log2 of number of data units per FS block */ - u8 ci_data_units_per_block_bits; - /* Hashed inode number. Only set for IV_INO_LBLK_32 */ u32 ci_hashed_ino; diff --git a/fs/crypto/inline_crypt.c b/fs/crypto/inline_crypt.c index 702d13d138aa..5279565e9846 100644 --- a/fs/crypto/inline_crypt.c +++ b/fs/crypto/inline_crypt.c @@ -268,14 +268,12 @@ bool __fscrypt_inode_uses_inline_crypto(const struct inode *inode) EXPORT_SYMBOL_GPL(__fscrypt_inode_uses_inline_crypto); static void fscrypt_generate_dun(const struct fscrypt_inode_info *ci, - u64 lblk_num, - u64 dun[BLK_CRYPTO_DUN_ARRAY_SIZE]) + loff_t pos, u64 dun[BLK_CRYPTO_DUN_ARRAY_SIZE]) { - u64 index = lblk_num << ci->ci_data_units_per_block_bits; union fscrypt_iv iv; int i; - fscrypt_generate_iv(&iv, index, ci); + fscrypt_generate_iv(&iv, pos >> ci->ci_data_unit_bits, ci); BUILD_BUG_ON(FSCRYPT_MAX_IV_SIZE > BLK_CRYPTO_MAX_IV_SIZE); memset(dun, 0, BLK_CRYPTO_MAX_IV_SIZE); @@ -309,7 +307,7 @@ void fscrypt_set_bio_crypt_ctx(struct bio *bio, const struct inode *inode, return; ci = fscrypt_get_inode_info_raw(inode); - fscrypt_generate_dun(ci, first_lblk, dun); + fscrypt_generate_dun(ci, first_lblk << inode->i_blkbits, dun); bio_crypt_set_ctx(bio, ci->ci_enc_key.blk_key, dun, gfp_mask); } EXPORT_SYMBOL_GPL(fscrypt_set_bio_crypt_ctx); @@ -356,7 +354,7 @@ bool fscrypt_mergeable_bio(struct bio *bio, const struct inode *inode, if (bc->bc_key != ci->ci_enc_key.blk_key) return false; - fscrypt_generate_dun(ci, next_lblk, next_dun); + fscrypt_generate_dun(ci, next_lblk << inode->i_blkbits, next_dun); return bio_crypt_dun_is_contiguous(bc, bio->bi_iter.bi_size, next_dun); } EXPORT_SYMBOL_GPL(fscrypt_mergeable_bio); diff --git a/fs/crypto/keysetup.c b/fs/crypto/keysetup.c index 40fa05688d3a..d83257e9945e 100644 --- a/fs/crypto/keysetup.c +++ b/fs/crypto/keysetup.c @@ -609,8 +609,6 @@ fscrypt_setup_encryption_info(struct inode *inode, crypt_info->ci_data_unit_bits = fscrypt_policy_du_bits(&crypt_info->ci_policy, inode); - crypt_info->ci_data_units_per_block_bits = - inode->i_blkbits - crypt_info->ci_data_unit_bits; res = setup_file_encryption_key(crypt_info, need_dirhash_key, &mk); if (res) From 22be86a23c5956254b752e4e98f0ef2799565a41 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Mon, 2 Mar 2026 06:18:12 -0800 Subject: [PATCH 07/15] fscrypt: pass a byte offset to fscrypt_mergeable_bio Logical offsets into an inode are usually expressed as bytes in the VFS. Switch fscrypt_mergeable_bio to that convention. Signed-off-by: Christoph Hellwig Link: https://lore.kernel.org/r/20260302141922.370070-8-hch@lst.de Signed-off-by: Eric Biggers --- fs/crypto/bio.c | 3 ++- fs/crypto/inline_crypt.c | 6 +++--- fs/ext4/page-io.c | 2 +- fs/ext4/readpage.c | 3 ++- fs/f2fs/data.c | 3 ++- include/linux/fscrypt.h | 4 ++-- 6 files changed, 12 insertions(+), 9 deletions(-) diff --git a/fs/crypto/bio.c b/fs/crypto/bio.c index 6da683ea69dc..0a701d4a17ef 100644 --- a/fs/crypto/bio.c +++ b/fs/crypto/bio.c @@ -100,7 +100,8 @@ static int fscrypt_zeroout_range_inline_crypt(const struct inode *inode, len -= blocks_this_page; lblk += blocks_this_page; sector += (bytes_this_page >> SECTOR_SHIFT); - if (!len || !fscrypt_mergeable_bio(bio, inode, lblk)) + if (!len || !fscrypt_mergeable_bio(bio, inode, + (loff_t)lblk << blockbits)) break; } diff --git a/fs/crypto/inline_crypt.c b/fs/crypto/inline_crypt.c index 5279565e9846..b0954d17904b 100644 --- a/fs/crypto/inline_crypt.c +++ b/fs/crypto/inline_crypt.c @@ -316,7 +316,7 @@ EXPORT_SYMBOL_GPL(fscrypt_set_bio_crypt_ctx); * fscrypt_mergeable_bio() - test whether data can be added to a bio * @bio: the bio being built up * @inode: the inode for the next part of the I/O - * @next_lblk: the next file logical block number in the I/O + * @pos: the next file position (in bytes) in the I/O * * When building a bio which may contain data which should undergo inline * encryption (or decryption) via fscrypt, filesystems should call this function @@ -334,7 +334,7 @@ EXPORT_SYMBOL_GPL(fscrypt_set_bio_crypt_ctx); * Return: true iff the I/O is mergeable */ bool fscrypt_mergeable_bio(struct bio *bio, const struct inode *inode, - u64 next_lblk) + loff_t pos) { const struct bio_crypt_ctx *bc = bio->bi_crypt_context; const struct fscrypt_inode_info *ci; @@ -354,7 +354,7 @@ bool fscrypt_mergeable_bio(struct bio *bio, const struct inode *inode, if (bc->bc_key != ci->ci_enc_key.blk_key) return false; - fscrypt_generate_dun(ci, next_lblk << inode->i_blkbits, next_dun); + fscrypt_generate_dun(ci, pos, next_dun); return bio_crypt_dun_is_contiguous(bc, bio->bi_iter.bi_size, next_dun); } EXPORT_SYMBOL_GPL(fscrypt_mergeable_bio); diff --git a/fs/ext4/page-io.c b/fs/ext4/page-io.c index 293314d7f236..50f507bab82c 100644 --- a/fs/ext4/page-io.c +++ b/fs/ext4/page-io.c @@ -447,7 +447,7 @@ static bool io_submit_need_new_bio(struct ext4_io_submit *io, if (bh->b_blocknr != io->io_next_block) return true; if (!fscrypt_mergeable_bio(io->io_bio, inode, - (folio_pos(folio) + bh_offset(bh)) >> inode->i_blkbits)) + folio_pos(folio) + bh_offset(bh))) return true; return false; } diff --git a/fs/ext4/readpage.c b/fs/ext4/readpage.c index 830f3b8a321f..ba7cfddd6038 100644 --- a/fs/ext4/readpage.c +++ b/fs/ext4/readpage.c @@ -342,7 +342,8 @@ static int ext4_mpage_readpages(struct inode *inode, struct fsverity_info *vi, * BIO off first? */ if (bio && (last_block_in_bio != first_block - 1 || - !fscrypt_mergeable_bio(bio, inode, next_block))) { + !fscrypt_mergeable_bio(bio, inode, + (loff_t)next_block << blkbits))) { submit_and_realloc: blk_crypto_submit_bio(bio); bio = NULL; diff --git a/fs/f2fs/data.c b/fs/f2fs/data.c index 338df7a2aea6..dca273fedfde 100644 --- a/fs/f2fs/data.c +++ b/fs/f2fs/data.c @@ -541,7 +541,8 @@ static bool f2fs_crypt_mergeable_bio(struct bio *bio, const struct inode *inode, if (fio && fio->encrypted_page) return !bio_has_crypt_ctx(bio); - return fscrypt_mergeable_bio(bio, inode, next_idx); + return fscrypt_mergeable_bio(bio, inode, + (loff_t)next_idx << inode->i_blkbits); } void f2fs_submit_read_bio(struct f2fs_sb_info *sbi, struct bio *bio, diff --git a/include/linux/fscrypt.h b/include/linux/fscrypt.h index 26561b7994e0..98fb14660d40 100644 --- a/include/linux/fscrypt.h +++ b/include/linux/fscrypt.h @@ -870,7 +870,7 @@ void fscrypt_set_bio_crypt_ctx(struct bio *bio, gfp_t gfp_mask); bool fscrypt_mergeable_bio(struct bio *bio, const struct inode *inode, - u64 next_lblk); + loff_t pos); bool fscrypt_dio_supported(struct inode *inode); @@ -889,7 +889,7 @@ static inline void fscrypt_set_bio_crypt_ctx(struct bio *bio, static inline bool fscrypt_mergeable_bio(struct bio *bio, const struct inode *inode, - u64 next_lblk) + loff_t pos) { return true; } From 3c7eaa775d8e008135646bd4b7aa7db7c5e40a0e Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Mon, 2 Mar 2026 06:18:13 -0800 Subject: [PATCH 08/15] fscrypt: pass a byte offset to fscrypt_set_bio_crypt_ctx Logical offsets into an inode are usually expressed as bytes in the VFS. Switch fscrypt_set_bio_crypt_ctx to that convention. Signed-off-by: Christoph Hellwig Link: https://lore.kernel.org/r/20260302141922.370070-9-hch@lst.de Signed-off-by: Eric Biggers --- fs/buffer.c | 7 ++----- fs/crypto/bio.c | 8 ++++---- fs/crypto/inline_crypt.c | 6 +++--- fs/ext4/page-io.c | 5 ++--- fs/ext4/readpage.c | 4 ++-- fs/f2fs/data.c | 4 +++- fs/iomap/direct-io.c | 6 ++---- include/linux/fscrypt.h | 7 +++---- 8 files changed, 21 insertions(+), 26 deletions(-) diff --git a/fs/buffer.c b/fs/buffer.c index b6504ec7fa4c..1c8ee5a59f88 100644 --- a/fs/buffer.c +++ b/fs/buffer.c @@ -2778,8 +2778,6 @@ static void buffer_set_crypto_ctx(struct bio *bio, const struct buffer_head *bh, gfp_t gfp_mask) { const struct address_space *mapping = folio_mapping(bh->b_folio); - const struct inode *inode; - u64 lblk; /* * The ext4 journal (jbd2) can submit a buffer_head it directly created @@ -2787,9 +2785,8 @@ static void buffer_set_crypto_ctx(struct bio *bio, const struct buffer_head *bh, */ if (!mapping) return; - inode = mapping->host; - lblk = (folio_pos(bh->b_folio) + bh_offset(bh)) >> inode->i_blkbits; - fscrypt_set_bio_crypt_ctx(bio, inode, lblk, gfp_mask); + fscrypt_set_bio_crypt_ctx(bio, mapping->host, + folio_pos(bh->b_folio) + bh_offset(bh), gfp_mask); } static void submit_bh_wbc(blk_opf_t opf, struct buffer_head *bh, diff --git a/fs/crypto/bio.c b/fs/crypto/bio.c index 0a701d4a17ef..e7fb2fdd9728 100644 --- a/fs/crypto/bio.c +++ b/fs/crypto/bio.c @@ -75,6 +75,7 @@ static int fscrypt_zeroout_range_inline_crypt(const struct inode *inode, { const unsigned int blockbits = inode->i_blkbits; const unsigned int blocks_per_page = 1 << (PAGE_SHIFT - blockbits); + loff_t pos = (loff_t)lblk << blockbits; struct fscrypt_zero_done done = { .pending = ATOMIC_INIT(1), .done = COMPLETION_INITIALIZER_ONSTACK(done.done), @@ -89,7 +90,7 @@ static int fscrypt_zeroout_range_inline_crypt(const struct inode *inode, 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, lblk, GFP_NOFS); + fscrypt_set_bio_crypt_ctx(bio, inode, pos, GFP_NOFS); for (n = 0; n < BIO_MAX_VECS; n++) { unsigned int blocks_this_page = @@ -98,10 +99,9 @@ static int fscrypt_zeroout_range_inline_crypt(const struct inode *inode, __bio_add_page(bio, ZERO_PAGE(0), bytes_this_page, 0); len -= blocks_this_page; - lblk += blocks_this_page; + pos += bytes_this_page; sector += (bytes_this_page >> SECTOR_SHIFT); - if (!len || !fscrypt_mergeable_bio(bio, inode, - (loff_t)lblk << blockbits)) + if (!len || !fscrypt_mergeable_bio(bio, inode, pos)) break; } diff --git a/fs/crypto/inline_crypt.c b/fs/crypto/inline_crypt.c index b0954d17904b..37d42d357925 100644 --- a/fs/crypto/inline_crypt.c +++ b/fs/crypto/inline_crypt.c @@ -285,7 +285,7 @@ static void fscrypt_generate_dun(const struct fscrypt_inode_info *ci, * fscrypt_set_bio_crypt_ctx() - prepare a file contents bio for inline crypto * @bio: a bio which will eventually be submitted to the file * @inode: the file's inode - * @first_lblk: the first file logical block number in the I/O + * @pos: the first file position (in bytes) in the I/O * @gfp_mask: memory allocation flags - these must be a waiting mask so that * bio_crypt_set_ctx can't fail. * @@ -298,7 +298,7 @@ static void fscrypt_generate_dun(const struct fscrypt_inode_info *ci, * The encryption context will be freed automatically when the bio is freed. */ void fscrypt_set_bio_crypt_ctx(struct bio *bio, const struct inode *inode, - u64 first_lblk, gfp_t gfp_mask) + loff_t pos, gfp_t gfp_mask) { const struct fscrypt_inode_info *ci; u64 dun[BLK_CRYPTO_DUN_ARRAY_SIZE]; @@ -307,7 +307,7 @@ void fscrypt_set_bio_crypt_ctx(struct bio *bio, const struct inode *inode, return; ci = fscrypt_get_inode_info_raw(inode); - fscrypt_generate_dun(ci, first_lblk << inode->i_blkbits, dun); + fscrypt_generate_dun(ci, pos, dun); bio_crypt_set_ctx(bio, ci->ci_enc_key.blk_key, dun, gfp_mask); } EXPORT_SYMBOL_GPL(fscrypt_set_bio_crypt_ctx); diff --git a/fs/ext4/page-io.c b/fs/ext4/page-io.c index 50f507bab82c..181cda58d387 100644 --- a/fs/ext4/page-io.c +++ b/fs/ext4/page-io.c @@ -427,9 +427,8 @@ static void io_submit_init_bio(struct ext4_io_submit *io, * __GFP_DIRECT_RECLAIM is set, see comments for bio_alloc_bioset(). */ bio = bio_alloc(bh->b_bdev, BIO_MAX_VECS, REQ_OP_WRITE, GFP_NOIO); - fscrypt_set_bio_crypt_ctx(bio, inode, - (folio_pos(folio) + bh_offset(bh)) >> inode->i_blkbits, - GFP_NOIO); + fscrypt_set_bio_crypt_ctx(bio, inode, folio_pos(folio) + bh_offset(bh), + GFP_NOIO); bio->bi_iter.bi_sector = bh->b_blocknr * (bh->b_size >> 9); bio->bi_end_io = ext4_end_bio; bio->bi_private = ext4_get_io_end(io->io_end); diff --git a/fs/ext4/readpage.c b/fs/ext4/readpage.c index ba7cfddd6038..fbfa4d830d9a 100644 --- a/fs/ext4/readpage.c +++ b/fs/ext4/readpage.c @@ -355,8 +355,8 @@ static int ext4_mpage_readpages(struct inode *inode, struct fsverity_info *vi, */ bio = bio_alloc(bdev, bio_max_segs(nr_pages), REQ_OP_READ, GFP_KERNEL); - fscrypt_set_bio_crypt_ctx(bio, inode, next_block, - GFP_KERNEL); + fscrypt_set_bio_crypt_ctx(bio, inode, + (loff_t)next_block << blkbits, GFP_KERNEL); ext4_set_bio_post_read_ctx(bio, inode, vi); bio->bi_iter.bi_sector = first_block << (blkbits - 9); bio->bi_end_io = mpage_end_io; diff --git a/fs/f2fs/data.c b/fs/f2fs/data.c index dca273fedfde..07b4ed6bb0cc 100644 --- a/fs/f2fs/data.c +++ b/fs/f2fs/data.c @@ -527,7 +527,9 @@ static void f2fs_set_bio_crypt_ctx(struct bio *bio, const struct inode *inode, * read/write raw data without encryption. */ if (!fio || !fio->encrypted_page) - fscrypt_set_bio_crypt_ctx(bio, inode, first_idx, gfp_mask); + fscrypt_set_bio_crypt_ctx(bio, inode, + (loff_t)first_idx << inode->i_blkbits, + gfp_mask); } static bool f2fs_crypt_mergeable_bio(struct bio *bio, const struct inode *inode, diff --git a/fs/iomap/direct-io.c b/fs/iomap/direct-io.c index e911daedff65..9da5d862ef9e 100644 --- a/fs/iomap/direct-io.c +++ b/fs/iomap/direct-io.c @@ -311,8 +311,7 @@ static int iomap_dio_zero(const struct iomap_iter *iter, struct iomap_dio *dio, bio = iomap_dio_alloc_bio(iter, dio, nr_vecs, REQ_OP_WRITE | REQ_SYNC | REQ_IDLE); - fscrypt_set_bio_crypt_ctx(bio, inode, pos >> inode->i_blkbits, - GFP_KERNEL); + fscrypt_set_bio_crypt_ctx(bio, inode, pos, GFP_KERNEL); bio->bi_iter.bi_sector = iomap_sector(&iter->iomap, pos); bio->bi_private = dio; bio->bi_end_io = iomap_dio_bio_end_io; @@ -342,8 +341,7 @@ static ssize_t iomap_dio_bio_iter_one(struct iomap_iter *iter, nr_vecs = bio_iov_vecs_to_alloc(dio->submit.iter, BIO_MAX_VECS); bio = iomap_dio_alloc_bio(iter, dio, nr_vecs, op); - fscrypt_set_bio_crypt_ctx(bio, iter->inode, - pos >> iter->inode->i_blkbits, GFP_KERNEL); + fscrypt_set_bio_crypt_ctx(bio, iter->inode, pos, GFP_KERNEL); bio->bi_iter.bi_sector = iomap_sector(&iter->iomap, pos); bio->bi_write_hint = iter->inode->i_write_hint; bio->bi_ioprio = dio->iocb->ki_ioprio; diff --git a/include/linux/fscrypt.h b/include/linux/fscrypt.h index 98fb14660d40..90f75fe0e1c9 100644 --- a/include/linux/fscrypt.h +++ b/include/linux/fscrypt.h @@ -865,9 +865,8 @@ static inline void fscrypt_set_ops(struct super_block *sb, bool __fscrypt_inode_uses_inline_crypto(const struct inode *inode); -void fscrypt_set_bio_crypt_ctx(struct bio *bio, - const struct inode *inode, u64 first_lblk, - gfp_t gfp_mask); +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); @@ -885,7 +884,7 @@ static inline bool __fscrypt_inode_uses_inline_crypto(const struct inode *inode) static inline void fscrypt_set_bio_crypt_ctx(struct bio *bio, const struct inode *inode, - u64 first_lblk, gfp_t gfp_mask) { } + loff_t pos, gfp_t gfp_mask) { } static inline bool fscrypt_mergeable_bio(struct bio *bio, const struct inode *inode, From 090c5c1597491e66097ccd21612e3ab9dcfcb231 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Mon, 2 Mar 2026 06:18:14 -0800 Subject: [PATCH 09/15] fscrypt: pass a byte offset to fscrypt_zeroout_range_inline_crypt Logical offsets into an inode are usually expressed as bytes in the VFS. Switch fscrypt_zeroout_range_inline_crypt to that convention. Signed-off-by: Christoph Hellwig Link: https://lore.kernel.org/r/20260302141922.370070-10-hch@lst.de Signed-off-by: Eric Biggers --- fs/crypto/bio.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/fs/crypto/bio.c b/fs/crypto/bio.c index e7fb2fdd9728..7558b3e69701 100644 --- a/fs/crypto/bio.c +++ b/fs/crypto/bio.c @@ -70,12 +70,11 @@ static void fscrypt_zeroout_range_end_io(struct bio *bio) } static int fscrypt_zeroout_range_inline_crypt(const struct inode *inode, - pgoff_t lblk, sector_t sector, + loff_t pos, sector_t sector, unsigned int len) { const unsigned int blockbits = inode->i_blkbits; const unsigned int blocks_per_page = 1 << (PAGE_SHIFT - blockbits); - loff_t pos = (loff_t)lblk << blockbits; struct fscrypt_zero_done done = { .pending = ATOMIC_INIT(1), .done = COMPLETION_INITIALIZER_ONSTACK(done.done), @@ -142,6 +141,7 @@ int fscrypt_zeroout_range(const struct inode *inode, pgoff_t lblk, const unsigned int du_per_page = 1U << du_per_page_bits; u64 du_index = (u64)lblk << (inode->i_blkbits - du_bits); u64 du_remaining = (u64)len << (inode->i_blkbits - du_bits); + loff_t pos = (loff_t)lblk << inode->i_blkbits; sector_t sector = pblk << (inode->i_blkbits - SECTOR_SHIFT); struct page *pages[16]; /* write up to 16 pages at a time */ unsigned int nr_pages; @@ -154,7 +154,7 @@ int fscrypt_zeroout_range(const struct inode *inode, pgoff_t lblk, return 0; if (fscrypt_inode_uses_inline_crypto(inode)) - return fscrypt_zeroout_range_inline_crypt(inode, lblk, sector, + return fscrypt_zeroout_range_inline_crypt(inode, pos, sector, len); BUILD_BUG_ON(ARRAY_SIZE(pages) > BIO_MAX_VECS); From 90950ee5630b68b2b321c78af29e0b3f36080594 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Mon, 2 Mar 2026 06:18:15 -0800 Subject: [PATCH 10/15] fscrypt: pass a byte length to fscrypt_zeroout_range_inline_crypt Range lengths are usually expressed as bytes in the VFS, switch fscrypt_zeroout_range_inline_crypt to this convention. Signed-off-by: Christoph Hellwig Link: https://lore.kernel.org/r/20260302141922.370070-11-hch@lst.de Signed-off-by: Eric Biggers --- fs/crypto/bio.c | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/fs/crypto/bio.c b/fs/crypto/bio.c index 7558b3e69701..303b5acc66a9 100644 --- a/fs/crypto/bio.c +++ b/fs/crypto/bio.c @@ -71,10 +71,8 @@ static void fscrypt_zeroout_range_end_io(struct bio *bio) static int fscrypt_zeroout_range_inline_crypt(const struct inode *inode, loff_t pos, sector_t sector, - unsigned int len) + u64 len) { - const unsigned int blockbits = inode->i_blkbits; - const unsigned int blocks_per_page = 1 << (PAGE_SHIFT - blockbits); struct fscrypt_zero_done done = { .pending = ATOMIC_INIT(1), .done = COMPLETION_INITIALIZER_ONSTACK(done.done), @@ -92,12 +90,10 @@ static int fscrypt_zeroout_range_inline_crypt(const struct inode *inode, fscrypt_set_bio_crypt_ctx(bio, inode, pos, GFP_NOFS); for (n = 0; n < BIO_MAX_VECS; n++) { - unsigned int blocks_this_page = - min(len, blocks_per_page); - unsigned int bytes_this_page = blocks_this_page << blockbits; + unsigned int bytes_this_page = min(len, PAGE_SIZE); __bio_add_page(bio, ZERO_PAGE(0), bytes_this_page, 0); - len -= blocks_this_page; + len -= bytes_this_page; pos += bytes_this_page; sector += (bytes_this_page >> SECTOR_SHIFT); if (!len || !fscrypt_mergeable_bio(bio, inode, pos)) @@ -155,7 +151,7 @@ int fscrypt_zeroout_range(const struct inode *inode, pgoff_t lblk, if (fscrypt_inode_uses_inline_crypto(inode)) return fscrypt_zeroout_range_inline_crypt(inode, pos, sector, - len); + (u64)len << inode->i_blkbits); BUILD_BUG_ON(ARRAY_SIZE(pages) > BIO_MAX_VECS); nr_pages = min_t(u64, ARRAY_SIZE(pages), From cd7db2e7dfeef99c901156f58ab4a38256b0c3f1 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Mon, 2 Mar 2026 06:18:16 -0800 Subject: [PATCH 11/15] fscrypt: pass a byte offset to fscrypt_zeroout_range Logical offsets into an inode are usually expressed as bytes in the VFS. Switch fscrypt_zeroout_range to that convention. Signed-off-by: Christoph Hellwig Link: https://lore.kernel.org/r/20260302141922.370070-12-hch@lst.de Signed-off-by: Eric Biggers --- fs/crypto/bio.c | 7 +++---- fs/ext4/inode.c | 3 ++- fs/f2fs/file.c | 4 +++- include/linux/fscrypt.h | 4 ++-- 4 files changed, 10 insertions(+), 8 deletions(-) diff --git a/fs/crypto/bio.c b/fs/crypto/bio.c index 303b5acc66a9..a07ac8dcf851 100644 --- a/fs/crypto/bio.c +++ b/fs/crypto/bio.c @@ -113,7 +113,7 @@ static int fscrypt_zeroout_range_inline_crypt(const struct inode *inode, /** * fscrypt_zeroout_range() - zero out a range of blocks in an encrypted file * @inode: the file's inode - * @lblk: the first file logical block to zero out + * @pos: the first file position (in bytes) to zero out * @pblk: the first filesystem physical block to zero out * @len: number of blocks to zero out * @@ -127,7 +127,7 @@ static int fscrypt_zeroout_range_inline_crypt(const struct inode *inode, * * Return: 0 on success; -errno on failure. */ -int fscrypt_zeroout_range(const struct inode *inode, pgoff_t lblk, +int fscrypt_zeroout_range(const struct inode *inode, loff_t pos, sector_t pblk, unsigned int len) { const struct fscrypt_inode_info *ci = fscrypt_get_inode_info_raw(inode); @@ -135,9 +135,8 @@ int fscrypt_zeroout_range(const struct inode *inode, pgoff_t lblk, const unsigned int du_size = 1U << du_bits; const unsigned int du_per_page_bits = PAGE_SHIFT - du_bits; const unsigned int du_per_page = 1U << du_per_page_bits; - u64 du_index = (u64)lblk << (inode->i_blkbits - du_bits); + u64 du_index = pos >> du_bits; u64 du_remaining = (u64)len << (inode->i_blkbits - du_bits); - loff_t pos = (loff_t)lblk << inode->i_blkbits; sector_t sector = pblk << (inode->i_blkbits - SECTOR_SHIFT); struct page *pages[16]; /* write up to 16 pages at a time */ unsigned int nr_pages; diff --git a/fs/ext4/inode.c b/fs/ext4/inode.c index 396dc3a5d16b..945613c95ffa 100644 --- a/fs/ext4/inode.c +++ b/fs/ext4/inode.c @@ -405,7 +405,8 @@ int ext4_issue_zeroout(struct inode *inode, ext4_lblk_t lblk, ext4_fsblk_t pblk, KUNIT_STATIC_STUB_REDIRECT(ext4_issue_zeroout, inode, lblk, pblk, len); if (IS_ENCRYPTED(inode) && S_ISREG(inode->i_mode)) - return fscrypt_zeroout_range(inode, lblk, pblk, len); + return fscrypt_zeroout_range(inode, + (loff_t)lblk << inode->i_blkbits, pblk, len); ret = sb_issue_zeroout(inode->i_sb, pblk, len, GFP_NOFS); if (ret > 0) diff --git a/fs/f2fs/file.c b/fs/f2fs/file.c index c8a2f17a8f11..239c2666ceb5 100644 --- a/fs/f2fs/file.c +++ b/fs/f2fs/file.c @@ -4162,7 +4162,9 @@ static int f2fs_secure_erase(struct block_device *bdev, struct inode *inode, if (!ret && (flags & F2FS_TRIM_FILE_ZEROOUT)) { if (IS_ENCRYPTED(inode)) - ret = fscrypt_zeroout_range(inode, off, block, len); + ret = fscrypt_zeroout_range(inode, + (loff_t)off << inode->i_blkbits, block, + len); else ret = blkdev_issue_zeroout(bdev, sector, nr_sects, GFP_NOFS, 0); diff --git a/include/linux/fscrypt.h b/include/linux/fscrypt.h index 90f75fe0e1c9..9fc15e1fbe57 100644 --- a/include/linux/fscrypt.h +++ b/include/linux/fscrypt.h @@ -450,7 +450,7 @@ 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, pgoff_t lblk, +int fscrypt_zeroout_range(const struct inode *inode, loff_t pos, sector_t pblk, unsigned int len); /* hooks.c */ @@ -755,7 +755,7 @@ static inline bool fscrypt_decrypt_bio(struct bio *bio) return true; } -static inline int fscrypt_zeroout_range(const struct inode *inode, pgoff_t lblk, +static inline int fscrypt_zeroout_range(const struct inode *inode, loff_t pos, sector_t pblk, unsigned int len) { return -EOPNOTSUPP; From fb87ab4ad3d0df2397648e5ce2384de26463c183 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Mon, 2 Mar 2026 06:18:17 -0800 Subject: [PATCH 12/15] fscrypt: pass a byte length to fscrypt_zeroout_range Range lengths are usually expressed as bytes in the VFS, switch fscrypt_zeroout_range to this convention. Signed-off-by: Christoph Hellwig Link: https://lore.kernel.org/r/20260302141922.370070-13-hch@lst.de Signed-off-by: Eric Biggers --- fs/crypto/bio.c | 11 ++++++----- fs/ext4/inode.c | 3 ++- fs/f2fs/file.c | 2 +- include/linux/fscrypt.h | 6 +++--- 4 files changed, 12 insertions(+), 10 deletions(-) diff --git a/fs/crypto/bio.c b/fs/crypto/bio.c index a07ac8dcf851..9872408f4f52 100644 --- a/fs/crypto/bio.c +++ b/fs/crypto/bio.c @@ -115,12 +115,13 @@ static int fscrypt_zeroout_range_inline_crypt(const struct inode *inode, * @inode: the file's inode * @pos: the first file position (in bytes) to zero out * @pblk: the first filesystem physical block to zero out - * @len: number of blocks 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. + * 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. @@ -128,7 +129,7 @@ static int fscrypt_zeroout_range_inline_crypt(const struct inode *inode, * Return: 0 on success; -errno on failure. */ int fscrypt_zeroout_range(const struct inode *inode, loff_t pos, - sector_t pblk, unsigned int len) + sector_t pblk, u64 len) { const struct fscrypt_inode_info *ci = fscrypt_get_inode_info_raw(inode); const unsigned int du_bits = ci->ci_data_unit_bits; @@ -136,7 +137,7 @@ int fscrypt_zeroout_range(const struct inode *inode, loff_t pos, const unsigned int du_per_page_bits = PAGE_SHIFT - du_bits; const unsigned int du_per_page = 1U << du_per_page_bits; u64 du_index = pos >> du_bits; - u64 du_remaining = (u64)len << (inode->i_blkbits - du_bits); + u64 du_remaining = len >> du_bits; sector_t sector = pblk << (inode->i_blkbits - SECTOR_SHIFT); struct page *pages[16]; /* write up to 16 pages at a time */ unsigned int nr_pages; @@ -150,7 +151,7 @@ int fscrypt_zeroout_range(const struct inode *inode, loff_t pos, if (fscrypt_inode_uses_inline_crypto(inode)) return fscrypt_zeroout_range_inline_crypt(inode, pos, sector, - (u64)len << inode->i_blkbits); + len); BUILD_BUG_ON(ARRAY_SIZE(pages) > BIO_MAX_VECS); nr_pages = min_t(u64, ARRAY_SIZE(pages), diff --git a/fs/ext4/inode.c b/fs/ext4/inode.c index 945613c95ffa..8ef61198e14c 100644 --- a/fs/ext4/inode.c +++ b/fs/ext4/inode.c @@ -406,7 +406,8 @@ int ext4_issue_zeroout(struct inode *inode, ext4_lblk_t lblk, ext4_fsblk_t pblk, if (IS_ENCRYPTED(inode) && S_ISREG(inode->i_mode)) return fscrypt_zeroout_range(inode, - (loff_t)lblk << inode->i_blkbits, pblk, len); + (loff_t)lblk << inode->i_blkbits, pblk, + (u64)len << inode->i_blkbits); ret = sb_issue_zeroout(inode->i_sb, pblk, len, GFP_NOFS); if (ret > 0) diff --git a/fs/f2fs/file.c b/fs/f2fs/file.c index 239c2666ceb5..8785f7c13657 100644 --- a/fs/f2fs/file.c +++ b/fs/f2fs/file.c @@ -4164,7 +4164,7 @@ static int f2fs_secure_erase(struct block_device *bdev, struct inode *inode, if (IS_ENCRYPTED(inode)) ret = fscrypt_zeroout_range(inode, (loff_t)off << inode->i_blkbits, block, - len); + (u64)len << inode->i_blkbits); else ret = blkdev_issue_zeroout(bdev, sector, nr_sects, GFP_NOFS, 0); diff --git a/include/linux/fscrypt.h b/include/linux/fscrypt.h index 9fc15e1fbe57..90ac62fda926 100644 --- a/include/linux/fscrypt.h +++ b/include/linux/fscrypt.h @@ -450,8 +450,8 @@ 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 pblk, unsigned int len); +int fscrypt_zeroout_range(const struct inode *inode, loff_t pos, sector_t pblk, + u64 len); /* hooks.c */ int fscrypt_file_open(struct inode *inode, struct file *filp); @@ -756,7 +756,7 @@ static inline bool fscrypt_decrypt_bio(struct bio *bio) } static inline int fscrypt_zeroout_range(const struct inode *inode, loff_t pos, - sector_t pblk, unsigned int len) + sector_t pblk, u64 len) { return -EOPNOTSUPP; } From 5ca1a1f017ea0f0e0bcb6ec52064735f2ac1c393 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Mon, 2 Mar 2026 06:18:18 -0800 Subject: [PATCH 13/15] fscrypt: pass a real sector_t to fscrypt_zeroout_range While the pblk argument to fscrypt_zeroout_range is declared as a sector_t, it actually is interpreted as a logical block size unit, which is highly unusual. Switch to passing the 512 byte units that sector_t is defined for. Signed-off-by: Christoph Hellwig Link: https://lore.kernel.org/r/20260302141922.370070-14-hch@lst.de Signed-off-by: Eric Biggers --- fs/crypto/bio.c | 5 ++--- fs/ext4/inode.c | 3 ++- fs/f2fs/file.c | 2 +- include/linux/fscrypt.h | 6 +++--- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/fs/crypto/bio.c b/fs/crypto/bio.c index 9872408f4f52..d07740680602 100644 --- a/fs/crypto/bio.c +++ b/fs/crypto/bio.c @@ -114,7 +114,7 @@ static int fscrypt_zeroout_range_inline_crypt(const struct inode *inode, * 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 - * @pblk: the first filesystem physical block 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 @@ -129,7 +129,7 @@ static int fscrypt_zeroout_range_inline_crypt(const struct inode *inode, * Return: 0 on success; -errno on failure. */ int fscrypt_zeroout_range(const struct inode *inode, loff_t pos, - sector_t pblk, u64 len) + sector_t sector, u64 len) { const struct fscrypt_inode_info *ci = fscrypt_get_inode_info_raw(inode); const unsigned int du_bits = ci->ci_data_unit_bits; @@ -138,7 +138,6 @@ int fscrypt_zeroout_range(const struct inode *inode, loff_t pos, const unsigned int du_per_page = 1U << du_per_page_bits; u64 du_index = pos >> du_bits; u64 du_remaining = len >> du_bits; - sector_t sector = pblk << (inode->i_blkbits - SECTOR_SHIFT); struct page *pages[16]; /* write up to 16 pages at a time */ unsigned int nr_pages; unsigned int i; diff --git a/fs/ext4/inode.c b/fs/ext4/inode.c index 8ef61198e14c..fe258ffd4840 100644 --- a/fs/ext4/inode.c +++ b/fs/ext4/inode.c @@ -406,7 +406,8 @@ int ext4_issue_zeroout(struct inode *inode, ext4_lblk_t lblk, ext4_fsblk_t pblk, if (IS_ENCRYPTED(inode) && S_ISREG(inode->i_mode)) return fscrypt_zeroout_range(inode, - (loff_t)lblk << inode->i_blkbits, pblk, + (loff_t)lblk << inode->i_blkbits, + pblk << (inode->i_blkbits - SECTOR_SHIFT), (u64)len << inode->i_blkbits); ret = sb_issue_zeroout(inode->i_sb, pblk, len, GFP_NOFS); diff --git a/fs/f2fs/file.c b/fs/f2fs/file.c index 8785f7c13657..a264771cfbc2 100644 --- a/fs/f2fs/file.c +++ b/fs/f2fs/file.c @@ -4163,7 +4163,7 @@ static int f2fs_secure_erase(struct block_device *bdev, struct inode *inode, if (!ret && (flags & F2FS_TRIM_FILE_ZEROOUT)) { if (IS_ENCRYPTED(inode)) ret = fscrypt_zeroout_range(inode, - (loff_t)off << inode->i_blkbits, block, + (loff_t)off << inode->i_blkbits, sector, (u64)len << inode->i_blkbits); else ret = blkdev_issue_zeroout(bdev, sector, nr_sects, diff --git a/include/linux/fscrypt.h b/include/linux/fscrypt.h index 90ac62fda926..54712ec61ffb 100644 --- a/include/linux/fscrypt.h +++ b/include/linux/fscrypt.h @@ -450,8 +450,8 @@ 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 pblk, - u64 len); +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); @@ -756,7 +756,7 @@ static inline bool fscrypt_decrypt_bio(struct bio *bio) } static inline int fscrypt_zeroout_range(const struct inode *inode, loff_t pos, - sector_t pblk, u64 len) + sector_t sector, u64 len) { return -EOPNOTSUPP; } From 4377a22d84f726f0a650927edf75cdc0698baf06 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Mon, 2 Mar 2026 06:18:19 -0800 Subject: [PATCH 14/15] ext4: use a byte granularity cursor in ext4_mpage_readpages Replace the next_block variable that is in units of file system blocks and incorrectly uses the sector_t type with a byte offset, as that is what both users want. Signed-off-by: Christoph Hellwig Link: https://lore.kernel.org/r/20260302141922.370070-15-hch@lst.de Signed-off-by: Eric Biggers --- fs/ext4/readpage.c | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/fs/ext4/readpage.c b/fs/ext4/readpage.c index fbfa4d830d9a..6ec503bde00b 100644 --- a/fs/ext4/readpage.c +++ b/fs/ext4/readpage.c @@ -215,11 +215,11 @@ static int ext4_mpage_readpages(struct inode *inode, struct fsverity_info *vi, sector_t last_block_in_bio = 0; const unsigned blkbits = inode->i_blkbits; const unsigned blocksize = 1 << blkbits; - sector_t next_block; sector_t block_in_file; sector_t last_block; sector_t last_block_in_file; sector_t first_block; + loff_t pos; unsigned page_block; struct block_device *bdev = inode->i_sb->s_bdev; int length; @@ -249,7 +249,8 @@ static int ext4_mpage_readpages(struct inode *inode, struct fsverity_info *vi, blocks_per_folio = folio_size(folio) >> blkbits; first_hole = blocks_per_folio; - block_in_file = next_block = EXT4_PG_TO_LBLK(inode, folio->index); + pos = folio_pos(folio); + block_in_file = pos >> blkbits; last_block = EXT4_PG_TO_LBLK(inode, folio->index + nr_pages); last_block_in_file = (ext4_readpage_limit(inode) + blocksize - 1) >> blkbits; @@ -342,8 +343,7 @@ static int ext4_mpage_readpages(struct inode *inode, struct fsverity_info *vi, * BIO off first? */ if (bio && (last_block_in_bio != first_block - 1 || - !fscrypt_mergeable_bio(bio, inode, - (loff_t)next_block << blkbits))) { + !fscrypt_mergeable_bio(bio, inode, pos))) { submit_and_realloc: blk_crypto_submit_bio(bio); bio = NULL; @@ -355,8 +355,7 @@ static int ext4_mpage_readpages(struct inode *inode, struct fsverity_info *vi, */ bio = bio_alloc(bdev, bio_max_segs(nr_pages), REQ_OP_READ, GFP_KERNEL); - fscrypt_set_bio_crypt_ctx(bio, inode, - (loff_t)next_block << blkbits, GFP_KERNEL); + fscrypt_set_bio_crypt_ctx(bio, inode, pos, GFP_KERNEL); ext4_set_bio_post_read_ctx(bio, inode, vi); bio->bi_iter.bi_sector = first_block << (blkbits - 9); bio->bi_end_io = mpage_end_io; From 1546d3feb5e533fbee6710bd51b2847b2ec23623 Mon Sep 17 00:00:00 2001 From: Eric Biggers Date: Sat, 21 Mar 2026 00:53:38 -0700 Subject: [PATCH 15/15] fscrypt: use AES library for v1 key derivation Convert the implementation of the v1 (original / deprecated) fscrypt per-file key derivation algorithm to use the AES library instead of an "ecb(aes)" crypto_skcipher. This is much simpler. While the AES library doesn't support AES-ECB directly yet, we can still simply call aes_encrypt() in a loop. While that doesn't explicitly parallelize the AES encryptions, it doesn't really matter in this case, where a new key is used each time and only 16 to 64 bytes are encrypted. In fact, a quick benchmark (AMD Ryzen 9 9950X) shows that this commit actually greatly improves performance, from ~7000 cycles per key derived to ~1500. The times don't differ much between 32 bytes and 64 bytes either, so clearly the bottleneck is API stuff and key expansion. Granted, performance of the v1 key derivation is no longer very relevant: most users have moved onto v2 encryption policies. The v2 key derivation uses HKDF-SHA512 (which is ~3500 cycles on the same CPU). Still, it's nice that the simpler solution is much faster as well. Compatibility verified with xfstests generic/548. Link: https://lore.kernel.org/r/20260321075338.99809-1-ebiggers@kernel.org Signed-off-by: Eric Biggers --- fs/crypto/Kconfig | 2 +- fs/crypto/keysetup_v1.c | 87 +++++++++++++---------------------------- 2 files changed, 29 insertions(+), 60 deletions(-) diff --git a/fs/crypto/Kconfig b/fs/crypto/Kconfig index 464b54610fd3..983d8ad1f417 100644 --- a/fs/crypto/Kconfig +++ b/fs/crypto/Kconfig @@ -3,6 +3,7 @@ config FS_ENCRYPTION bool "FS Encryption (Per-file encryption)" select CRYPTO select CRYPTO_SKCIPHER + select CRYPTO_LIB_AES select CRYPTO_LIB_SHA256 select CRYPTO_LIB_SHA512 select KEYS @@ -30,7 +31,6 @@ config FS_ENCRYPTION_ALGS select CRYPTO_AES select CRYPTO_CBC select CRYPTO_CTS - select CRYPTO_ECB select CRYPTO_XTS config FS_ENCRYPTION_INLINE_CRYPT diff --git a/fs/crypto/keysetup_v1.c b/fs/crypto/keysetup_v1.c index 3d673c36b678..e6e527c73f16 100644 --- a/fs/crypto/keysetup_v1.c +++ b/fs/crypto/keysetup_v1.c @@ -20,11 +20,10 @@ * managed alongside the master keys in the filesystem-level keyring) */ -#include +#include #include #include #include -#include #include "fscrypt_private.h" @@ -32,48 +31,6 @@ static DEFINE_HASHTABLE(fscrypt_direct_keys, 6); /* 6 bits = 64 buckets */ static DEFINE_SPINLOCK(fscrypt_direct_keys_lock); -/* - * v1 key derivation function. This generates the derived key by encrypting the - * master key with AES-128-ECB using the nonce as the AES key. This provides a - * unique derived key with sufficient entropy for each inode. However, it's - * nonstandard, non-extensible, doesn't evenly distribute the entropy from the - * master key, and is trivially reversible: an attacker who compromises a - * derived key can "decrypt" it to get back to the master key, then derive any - * other key. For all new code, use HKDF instead. - * - * The master key must be at least as long as the derived key. If the master - * key is longer, then only the first 'derived_keysize' bytes are used. - */ -static int derive_key_aes(const u8 *master_key, - const u8 nonce[FSCRYPT_FILE_NONCE_SIZE], - u8 *derived_key, unsigned int derived_keysize) -{ - struct crypto_sync_skcipher *tfm; - int err; - - tfm = crypto_alloc_sync_skcipher("ecb(aes)", 0, FSCRYPT_CRYPTOAPI_MASK); - if (IS_ERR(tfm)) - return PTR_ERR(tfm); - - err = crypto_sync_skcipher_setkey(tfm, nonce, FSCRYPT_FILE_NONCE_SIZE); - if (err == 0) { - SYNC_SKCIPHER_REQUEST_ON_STACK(req, tfm); - struct scatterlist src_sg, dst_sg; - - skcipher_request_set_callback(req, - CRYPTO_TFM_REQ_MAY_BACKLOG | - CRYPTO_TFM_REQ_MAY_SLEEP, - NULL, NULL); - sg_init_one(&src_sg, master_key, derived_keysize); - sg_init_one(&dst_sg, derived_key, derived_keysize); - skcipher_request_set_crypt(req, &src_sg, &dst_sg, - derived_keysize, NULL); - err = crypto_skcipher_encrypt(req); - } - crypto_free_sync_skcipher(tfm); - return err; -} - /* * Search the current task's subscribed keyrings for a "logon" key with * description prefix:descriptor, and if found acquire a read lock on it and @@ -255,29 +212,41 @@ static int setup_v1_file_key_direct(struct fscrypt_inode_info *ci, return 0; } -/* v1 policy, !DIRECT_KEY: derive the file's encryption key */ +/* + * v1 policy, !DIRECT_KEY: derive the file's encryption key. + * + * The v1 key derivation function generates the derived key by encrypting the + * master key with AES-128-ECB using the file's nonce as the AES key. This + * provides a unique derived key with sufficient entropy for each inode. + * However, it's nonstandard, non-extensible, doesn't evenly distribute the + * entropy from the master key, and is trivially reversible: an attacker who + * compromises a derived key can "decrypt" it to get back to the master key, + * then derive any other key. For all new code, use HKDF instead. + * + * The master key must be at least as long as the derived key. If the master + * key is longer, then only the first ci->ci_mode->keysize bytes are used. + */ static int setup_v1_file_key_derived(struct fscrypt_inode_info *ci, const u8 *raw_master_key) { - u8 *derived_key; + const unsigned int derived_keysize = ci->ci_mode->keysize; + u8 derived_key[FSCRYPT_MAX_RAW_KEY_SIZE]; + struct aes_enckey aes; int err; - /* - * This cannot be a stack buffer because it will be passed to the - * scatterlist crypto API during derive_key_aes(). - */ - derived_key = kmalloc(ci->ci_mode->keysize, GFP_KERNEL); - if (!derived_key) - return -ENOMEM; + if (WARN_ON_ONCE(derived_keysize > FSCRYPT_MAX_RAW_KEY_SIZE || + derived_keysize % AES_BLOCK_SIZE != 0)) + return -EINVAL; - err = derive_key_aes(raw_master_key, ci->ci_nonce, - derived_key, ci->ci_mode->keysize); - if (err) - goto out; + static_assert(FSCRYPT_FILE_NONCE_SIZE == AES_KEYSIZE_128); + aes_prepareenckey(&aes, ci->ci_nonce, FSCRYPT_FILE_NONCE_SIZE); + for (unsigned int i = 0; i < derived_keysize; i += AES_BLOCK_SIZE) + aes_encrypt(&aes, &derived_key[i], &raw_master_key[i]); err = fscrypt_set_per_file_enc_key(ci, derived_key); -out: - kfree_sensitive(derived_key); + + memzero_explicit(derived_key, derived_keysize); + /* No need to zeroize 'aes', as its key is not secret. */ return err; }