mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: Guikun Yang <yangguikun19@qq.com>
To: "Theodore Ts'o" <tytso@mit.edu>
Cc: linux-ext4@vger.kernel.org, linux-kernel@vger.kernel.org,
	Andreas Dilger <adilger.kernel@dilger.ca>,
	Jan Kara <jack@suse.cz>, Zhang Yi <yi.zhang@huawei.com>
Subject: [Bug report] ext4: journal replay overwrites the validated superblock, casefold feature bit set after s_encoding decision -> null-ptr-deref in utf8_casefold
Date: Tue, 03 Nov 2026 12:00:00 +0800	[thread overview]
Message-ID: <tencent_A7CF569440AAFAB7D1C884939CE941149706@qq.com> (raw)

Hi,

Mounting a crafted ext4 image with metadata_csum and a journal checksum
enabled ends in a general protection fault during a later lookup. The
chain is that journal replay overwrites the superblock after
ext4_fill_super() has already made a decision based on the previous
contents, so the two ends of the mount path disagree about the casefold
feature.

  general protection fault, probably for non-canonical address
  KASAN: null-ptr-deref in range [0x0000000000000018-0x000000000000001f]
  RIP: 0010:utf8nlookup+0x38/0x890
  Call Trace:
   utf8byte+0x1c0/0x1320
   utf8_casefold+0x161/0x230
   ext4_fname_setup_ci_filename+0x17f/0x4d0
   ext4_lookup+0x161/0x710
   __lookup_slow+0x156/0x320

RBX is 0 (the unicode_map) and RDI is 0x18 (&um->tables), so
utf8_casefold() was called with a NULL sb->s_encoding.

## The chain

1. ext4_encoding_init() (super.c:5468) runs before
   ext4_load_and_init_journal() (super.c:5576). With no casefold
   feature in the on-disk superblock it returns early and
   sb->s_encoding stays NULL.

2. ext4_check_blockref() has an exemption for the journal inode
   (block_validity.c:356):

       if (journal && inode == journal->j_inode)
               return 0;

   so the journal inode's extent can point at any free blocks without
   failing the block reference walk.

3. A dirty journal is constructed in those free blocks whose single
   data block is a copy of block 0 with the CASEFOLD incompatible
   feature bit set. Every checksum is recomputed: the superblock
   checksum, the journal superblock checksum, the block tag checksum
   and the commit block checksum, plus the inode checksums of the
   journal inode and the target directory. Nothing on disk is invalid.

4. On mount, jbd2 replay writes that block back to block 0 via
   __getblk(journal->j_fs_dev, ...) + memcpy (recovery.c:551). The
   in-memory ext4_super_block now advertises casefold, but
   ext4_encoding_init() has already run and sb->s_encoding is still
   NULL.

5. ext4_iget() rejects a casefold flag without the casefold feature
   (inode.c:6568). The root inode is read before replay and would
   fail, so the flag is placed on a subdirectory instead; its inode is
   only read on first lookup, after replay has set the feature bit in
   memory, and the check passes.

6. Any lookup inside that directory takes ext4_fname_setup_ci_filename
   (namei.c:1370), which checks IS_CASEFOLD(dir) - an inode flag,
   independent of the feature bit - and calls
   utf8_casefold(dir->i_sb->s_encoding, ...) with NULL.

## Observed

Deterministic, 3/3 on a QEMU x86_64 kernel with KASAN. The trigger is
two syscalls after the image is built:

  mount("/dev/vda", "/mnt", "ext4", 0, NULL);
  stat("/mnt/d1/nonexistent", &st);      /* GPF here */

Reproducer notes that matter for reproduction:

  - The superblock must have NEEDS_RECOVERY set, otherwise
    ext4_load_journal() calls jbd2_journal_wipe() before
    jbd2_journal_load() and the crafted journal is discarded.
  - The casefold flag cannot go on the root inode; ext4_iget() rejects
    it during mount because the feature bit is not visible yet.

## The larger issue

The crash is one consequence of a more general property: replay writes
into the page cache of blocks that ext4_fill_super() has already
read, checksummed and acted on. s_checksum verifies the on-disk
contents before replay, not after, and ext4_encoding_init() is one of
several decisions made from the pre-replay superblock. Anything read
from the superblock before ext4_load_and_init_journal() and still used
after it is a candidate for the same disagreement.

The reproducer (image builder with checksum recomputation, forged
journal builder, ready-made image and crash log) is available on
request.

Tested on 7.3.0 with CONFIG_UNICODE; the ordering of
ext4_encoding_init() and ext4_load_and_init_journal() and the NULL
use in namei.c are unchanged in current mainline.

Thanks,
Guikun Yang


                 reply	other threads:[~2026-11-03 14:40 UTC|newest]

Thread overview: [no followups] expand[flat|nested]  mbox.gz  Atom feed

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=tencent_A7CF569440AAFAB7D1C884939CE941149706@qq.com \
    --to=yangguikun19@qq.com \
    --cc=adilger.kernel@dilger.ca \
    --cc=jack@suse.cz \
    --cc=linux-ext4@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=tytso@mit.edu \
    --cc=yi.zhang@huawei.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®