public inbox for linux-ext4@vger.kernel.org
 help / color / mirror / Atom feed
From: Kees Cook <kees@kernel.org>
To: "Darrick J. Wong" <djwong@kernel.org>
Cc: "Theodore Ts'o" <tytso@mit.edu>, 李龙兴 <coregee2000@gmail.com>,
	"Andreas Dilger" <adilger.kernel@dilger.ca>,
	"Andy Shevchenko" <andriy.shevchenko@intel.com>,
	linux-ext4@vger.kernel.org, linux-kernel@vger.kernel.org,
	linux-hardening@vger.kernel.org
Subject: Re: [PATCH] ext4: Reject on-disk mount options with missing NUL-terminator
Date: Fri, 6 Feb 2026 13:56:52 -0800	[thread overview]
Message-ID: <202602061347.2046B806C5@keescook> (raw)
In-Reply-To: <20260206214502.GP7686@frogsfrogsfrogs>

On Fri, Feb 06, 2026 at 01:45:02PM -0800, Darrick J. Wong wrote:
> On Fri, Feb 06, 2026 at 01:27:03PM -0800, Kees Cook wrote:
> > When mounting an ext4 filesystem, the on-disk superblock's s_mount_opts
> > field (which stores default mount options set via tune2fs) is read and
> > parsed. Unlike userspace-provided mount options which are validated by
> > the VFS layer before reaching the filesystem, the on-disk s_mount_opts
> > is read directly from the disk buffer without NUL-termination validation.
> > 
> > The two option paths use the same parser but arrive differently:
> > 
> >   Userspace mount options:
> >     VFS -> ext4_parse_param()
> > 
> >   On-disk default options:
> >     parse_apply_sb_mount_options() -> parse_options() -> ext4_parse_param()
> > 
> > When s_mount_opts lacks NUL-termination, strscpy_pad()'s internal
> > fortified strnlen() detects reading beyond the 64-byte field, triggering
> > an Oops:
> > 
> >   strnlen: detected buffer overflow: 65 byte read of buffer size 64
> >   WARNING: CPU: 0 PID: 179 at lib/string_helpers.c:1035 __fortify_report+0x5a/0x100
> >   ...
> >   Call Trace:
> >    strnlen+0x71/0xa0 lib/string.c:155
> >    sized_strscpy+0x48/0x2a0 lib/string.c:298
> >    parse_apply_sb_mount_options+0x94/0x4a0 fs/ext4/super.c:2486
> >    __ext4_fill_super+0x31d6/0x51b0 fs/ext4/super.c:5306
> >    ext4_fill_super+0x3972/0xaf70 fs/ext4/super.c:5736
> >    get_tree_bdev_flags+0x38c/0x620 fs/super.c:1698
> >    vfs_get_tree+0x8e/0x340 fs/super.c:1758
> >    fc_mount fs/namespace.c:1199
> >    do_new_mount fs/namespace.c:3718
> >    path_mount+0x7b9/0x23a0 fs/namespace.c:4028
> >    ...
> > 
> > Reject the mount with an error instead. While there is an existing similar
> > check in the ioctl path (which validates userspace input before writing
> > TO disk), this check validates data read FROM disk before parsing.
> > 
> > The painful history here is:
> > 
> > 8b67f04ab9de ("ext4: Add mount options in superblock")
> >   s_mount_opts is created and treated as __nonstring: kstrndup is used to
> >   make sure all 64 potential characters are available for use (i.e. up
> >   to 65 bytes may be allocated).
> > 
> > 04a91570ac67 ("ext4: implemet new ioctls to set and get superblock parameters")
> >   Created ext4_tune_sb_params::mount_opts as 64 bytes in size but
> >   incorrectly treated it and s_mount_opts as a C strings (it used
> >   strscpy_pad() to copy between them).
> > 
> > 8ecb790ea8c3 ("ext4: avoid potential buffer over-read in parse_apply_sb_mount_options()")
> >   As a prerequisite to the ioctl treating s_mount_opts as a C string, this
> >   attempted to switch to using strscpy_pad() with a 65 byte destination
> >   for the case of an unterminated s_mount_opts. (But strscpy_pad() will
> >   fail due to the over-read of s_mount_opts by strnlen().)
> > 
> > 3db63d2c2d1d ("ext4: check if mount_opts is NUL-terminated in ext4_ioctl_set_tune_sb()")
> >   As a continuation of trying to solve the 64/65 mismatch, this started
> >   enforcing a 63 character limit (i.e. 64 bytes total) to incoming values
> >   from userspace to the ioctl API. (But did not check s_mount_opts coming
> >   from disk.)
> > 
> > ee5a977b4e77 ("ext4: fix string copying in parse_apply_sb_mount_options()")
> >   Notices the loud failures of strscpy_pad() introduced by 8ecb790ea8c3,
> >   and attempted to silence them by making the destination 64 and rejecting
> >   too-long strings from the on-disk copy of s_mount_opts, but didn't
> >   actually solve it at all, since the problem was always the over-read
> >   of the source seen by strnlen(). (Note that the report quoted in this
> >   commit exactly matches the report today.)
> 
> Looking through the history of tune2fs, I see v1.42 has commit
> 9a976ac732a7e9 ("tune2fs: Fix mount_opts handling"), which always set
> byte 63 to a null, which implies that s_mount_opts is supposed to be a
> 64-byte field that contains a null-terminated string.  It also doesn't
> appear that slack space after the null terminator is set to any
> particular value.

Okay, so the userspace side is treating it as a C string. Then ignore my
"PATCH ALTERNATIVE" I just sent. ;)

> In e2fsprogs v1.41.13, commit 9345f02671fd39 ("tune2fs, debugfs,
> libext2fs: Add support for ext4 default mount options") added the
> ability to set s_mount_opts, and it rejects any string whose length
> is greater than or equal to 64 bytes.  This validation is still in
> place.

Great, thanks for tracking that down. That looks to be Dec 2010.

> My guess is that the ext4 code should go back to requiring a null
> terminated string, and the __nonstring annotations dropped.

It does already; no __nonstring annotations currently exist for
s_mount_opts. The trouble was that it was originally treated as
effectively __nonstring between 8b67f04ab9de (v2.6.36, Oct 2010) and
04a91570ac67 (v6.18, Nov 2025).

> I guess the hard part is, were there any filesystems written out with a
> 64-byte unterminated s_mount_opts?  It looks like 04a91570ac67 used
> strscpy_pad to copy the string to the superblock and that function
> always zero-terminates the destination, so perhaps we can stick with
> "the ondisk superblock s_mount_opts is always a null terminated string"?

Yeah, if the userspace tools always wrote images with a NUL-terminated
s_mount_opts, then it should be perfectly sane to reject mounts that
don't follow that. (i.e. this patch.)

-- 
Kees Cook

  reply	other threads:[~2026-02-06 21:56 UTC|newest]

Thread overview: 9+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-02-06 21:27 [PATCH] ext4: Reject on-disk mount options with missing NUL-terminator Kees Cook
2026-02-06 21:45 ` Darrick J. Wong
2026-02-06 21:56   ` Kees Cook [this message]
2026-02-08 13:43     ` Andy Shevchenko
2026-02-09 17:27 ` Fedor Pchelkin
2026-02-09 20:00   ` Kees Cook
2026-02-10  5:01     ` Darrick J. Wong
2026-02-10  9:03       ` Fedor Pchelkin
2026-02-11  1:50         ` Kees Cook

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=202602061347.2046B806C5@keescook \
    --to=kees@kernel.org \
    --cc=adilger.kernel@dilger.ca \
    --cc=andriy.shevchenko@intel.com \
    --cc=coregee2000@gmail.com \
    --cc=djwong@kernel.org \
    --cc=linux-ext4@vger.kernel.org \
    --cc=linux-hardening@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=tytso@mit.edu \
    /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