kernel BUG in hfs_write_inode

6 views
Skip to first unread message

杜义恒

unread,
Mar 6, 2026, 7:46:47 AMMar 6
to sl...@dubeyko.com, glau...@physik.fu-berlin.de, fran...@vivo.com, syzk...@googlegroups.com

Dear Linux maintainers and reviewers:


We are reporting a Linux kernel bug titled **kernel BUG in hfs_write_inode**, discovered using a modified version of Syzkaller.

Linux version: ccd1cdca5cd433c8a5dff78b69a79b31d9b77ee1

The bisection log shows the first introduced commit is ffcd06b6d13b72823aba0d7c871f7e4876e7916b.

Commit log for cause commit ffcd06b6d13b72823aba0d7c871f7e4876e7916b:

commit ffcd06b6d13b72823aba0d7c871f7e4876e7916b

Author: Eric Sandeen <san...@redhat.com>

Date:   Mon Sep 16 13:26:21 2024 -0400


    hfs: convert hfs to use the new mount api

    

    Convert the hfs filesystem to use the new mount API.

    Tested by comparing random mount & remount options before and after

    the change.

    

    Signed-off-by: Eric Sandeen <san...@redhat.com>

    Link: https://lore.kernel.org/r/20240916172735....@redhat.com

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

    Signed-off-by: Christian Brauner <bra...@kernel.org>


diff --git a/fs/hfs/super.c b/fs/hfs/super.c

index eeac99765f0d..3bee9b5dba5e 100644

--- a/fs/hfs/super.c

+++ b/fs/hfs/super.c

@@ -15,10 +15,11 @@

 #include <linux/module.h>

 #include <linux/blkdev.h>

 #include <linux/backing-dev.h>

+#include <linux/fs_context.h>

+#include <linux/fs_parser.h>

 #include <linux/mount.h>

 #include <linux/init.h>

 #include <linux/nls.h>

-#include <linux/parser.h>

 #include <linux/seq_file.h>

 #include <linux/slab.h>

 #include <linux/vfs.h>

@@ -111,21 +112,24 @@ static int hfs_statfs(struct dentry *dentry, struct kstatfs *buf)

  return 0;

 }

 

-static int hfs_remount(struct super_block *sb, int *flags, char *data)

+static int hfs_reconfigure(struct fs_context *fc)

 {

+ struct super_block *sb = fc->root->d_sb;

+

  sync_filesystem(sb);

- *flags |= SB_NODIRATIME;

- if ((bool)(*flags & SB_RDONLY) == sb_rdonly(sb))

+ fc->sb_flags |= SB_NODIRATIME;

+ if ((bool)(fc->sb_flags & SB_RDONLY) == sb_rdonly(sb))

  return 0;

- if (!(*flags & SB_RDONLY)) {

+

+ if (!(fc->sb_flags & SB_RDONLY)) {

  if (!(HFS_SB(sb)->mdb->drAtrb & cpu_to_be16(HFS_SB_ATTRIB_UNMNT))) {

  pr_warn("filesystem was not cleanly unmounted, running fsck.hfs is recommended.  leaving read-only.\n");

  sb->s_flags |= SB_RDONLY;

- *flags |= SB_RDONLY;

+ fc->sb_flags |= SB_RDONLY;

  } else if (HFS_SB(sb)->mdb->drAtrb & cpu_to_be16(HFS_SB_ATTRIB_SLOCK)) {

  pr_warn("filesystem is marked locked, leaving read-only.\n");

  sb->s_flags |= SB_RDONLY;

- *flags |= SB_RDONLY;

+ fc->sb_flags |= SB_RDONLY;

  }

  }

  return 0;

@@ -180,7 +184,6 @@ static const struct super_operations hfs_super_operations = {

  .put_super = hfs_put_super,

  .sync_fs = hfs_sync_fs,

  .statfs = hfs_statfs,

- .remount_fs     = hfs_remount,

  .show_options = hfs_show_options,

 };

 

@@ -188,181 +191,112 @@ enum {

  opt_uid, opt_gid, opt_umask, opt_file_umask, opt_dir_umask,

  opt_part, opt_session, opt_type, opt_creator, opt_quiet,

  opt_codepage, opt_iocharset,

- opt_err

 };

 

-static const match_table_t tokens = {

- { opt_uid, "uid=%u" },

- { opt_gid, "gid=%u" },

- { opt_umask, "umask=%o" },

- { opt_file_umask, "file_umask=%o" },

- { opt_dir_umask, "dir_umask=%o" },

- { opt_part, "part=%u" },

- { opt_session, "session=%u" },

- { opt_type, "type=%s" },

- { opt_creator, "creator=%s" },

- { opt_quiet, "quiet" },

- { opt_codepage, "codepage=%s" },

- { opt_iocharset, "iocharset=%s" },

- { opt_err, NULL }

+static const struct fs_parameter_spec hfs_param_spec[] = {

+ fsparam_u32 ("uid", opt_uid),

+ fsparam_u32 ("gid", opt_gid),

+ fsparam_u32oct ("umask", opt_umask),

+ fsparam_u32oct ("file_umask", opt_file_umask),

+ fsparam_u32oct ("dir_umask", opt_dir_umask),

+ fsparam_u32 ("part", opt_part),

+ fsparam_u32 ("session", opt_session),

+ fsparam_string ("type", opt_type),

+ fsparam_string ("creator", opt_creator),

+ fsparam_flag ("quiet", opt_quiet),

+ fsparam_string ("codepage", opt_codepage),

+ fsparam_string ("iocharset", opt_iocharset),

+ {}

 };

 

-static inline int match_fourchar(substring_t *arg, u32 *result)

-{

- if (arg->to - arg->from != 4)

- return -EINVAL;

- memcpy(result, arg->from, 4);

- return 0;

-}

-

 /*

- * parse_options()

+ * hfs_parse_param()

  *

- * adapted from linux/fs/msdos/inode.c written 1992,93 by Werner Almesberger

- * This function is called by hfs_read_super() to parse the mount options.

+ * This function is called by the vfs to parse the mount options.

  */

-static int parse_options(char *options, struct hfs_sb_info *hsb)

+static int hfs_parse_param(struct fs_context *fc, struct fs_parameter *param)

 {

- char *p;

- substring_t args[MAX_OPT_ARGS];

- int tmp, token;

-

- /* initialize the sb with defaults */

- hsb->s_uid = current_uid();

- hsb->s_gid = current_gid();

- hsb->s_file_umask = 0133;

- hsb->s_dir_umask = 0022;

- hsb->s_type = hsb->s_creator = cpu_to_be32(0x3f3f3f3f); /* == '????' */

- hsb->s_quiet = 0;

- hsb->part = -1;

- hsb->session = -1;

-

- if (!options)

- return 1;

-

- while ((p = strsep(&options, ",")) != NULL) {

- if (!*p)

- continue;

-

- token = match_token(p, tokens, args);

- switch (token) {

- case opt_uid:

- if (match_int(&args[0], &tmp)) {

- pr_err("uid requires an argument\n");

- return 0;

- }

- hsb->s_uid = make_kuid(current_user_ns(), (uid_t)tmp);

- if (!uid_valid(hsb->s_uid)) {

- pr_err("invalid uid %d\n", tmp);

- return 0;

- }

- break;

- case opt_gid:

- if (match_int(&args[0], &tmp)) {

- pr_err("gid requires an argument\n");

- return 0;

- }

- hsb->s_gid = make_kgid(current_user_ns(), (gid_t)tmp);

- if (!gid_valid(hsb->s_gid)) {

- pr_err("invalid gid %d\n", tmp);

- return 0;

- }

- break;

- case opt_umask:

- if (match_octal(&args[0], &tmp)) {

- pr_err("umask requires a value\n");

- return 0;

- }

- hsb->s_file_umask = (umode_t)tmp;

- hsb->s_dir_umask = (umode_t)tmp;

- break;

- case opt_file_umask:

- if (match_octal(&args[0], &tmp)) {

- pr_err("file_umask requires a value\n");

- return 0;

- }

- hsb->s_file_umask = (umode_t)tmp;

- break;

- case opt_dir_umask:

- if (match_octal(&args[0], &tmp)) {

- pr_err("dir_umask requires a value\n");

- return 0;

- }

- hsb->s_dir_umask = (umode_t)tmp;

- break;

- case opt_part:

- if (match_int(&args[0], &hsb->part)) {

- pr_err("part requires an argument\n");

- return 0;

- }

- break;

- case opt_session:

- if (match_int(&args[0], &hsb->session)) {

- pr_err("session requires an argument\n");

- return 0;

- }

- break;

- case opt_type:

- if (match_fourchar(&args[0], &hsb->s_type)) {

- pr_err("type requires a 4 character value\n");

- return 0;

- }

- break;

- case opt_creator:

- if (match_fourchar(&args[0], &hsb->s_creator)) {

- pr_err("creator requires a 4 character value\n");

- return 0;

- }

- break;

- case opt_quiet:

- hsb->s_quiet = 1;

- break;

- case opt_codepage:

- if (hsb->nls_disk) {

- pr_err("unable to change codepage\n");

- return 0;

- }

- p = match_strdup(&args[0]);

- if (p)

- hsb->nls_disk = load_nls(p);

- if (!hsb->nls_disk) {

- pr_err("unable to load codepage \"%s\"\n", p);

- kfree(p);

- return 0;

- }

- kfree(p);

- break;

- case opt_iocharset:

- if (hsb->nls_io) {

- pr_err("unable to change iocharset\n");

- return 0;

- }

- p = match_strdup(&args[0]);

- if (p)

- hsb->nls_io = load_nls(p);

- if (!hsb->nls_io) {

- pr_err("unable to load iocharset \"%s\"\n", p);

- kfree(p);

- return 0;

- }

- kfree(p);

- break;

- default:

- return 0;

- }

- }

+ struct hfs_sb_info *hsb = fc->s_fs_info;

+ struct fs_parse_result result;

+ int opt;

+

+ /* hfs does not honor any fs-specific options on remount */

+ if (fc->purpose == FS_CONTEXT_FOR_RECONFIGURE)

+ return 0;

 

- if (hsb->nls_disk && !hsb->nls_io) {

- hsb->nls_io = load_nls_default();

+ opt = fs_parse(fc, hfs_param_spec, param, &result);

+ if (opt < 0)

+ return opt;

+

+ switch (opt) {

+ case opt_uid:

+ hsb->s_uid = result.uid;

+ break;

+ case opt_gid:

+ hsb->s_gid = result.gid;

+ break;

+ case opt_umask:

+ hsb->s_file_umask = (umode_t)result.uint_32;

+ hsb->s_dir_umask = (umode_t)result.uint_32;

+ break;

+ case opt_file_umask:

+ hsb->s_file_umask = (umode_t)result.uint_32;

+ break;

+ case opt_dir_umask:

+ hsb->s_dir_umask = (umode_t)result.uint_32;

+ break;

+ case opt_part:

+ hsb->part = result.uint_32;

+ break;

+ case opt_session:

+ hsb->session = result.uint_32;

+ break;

+ case opt_type:

+ if (strlen(param->string) != 4) {

+ pr_err("type requires a 4 character value\n");

+ return -EINVAL;

+ }

+ memcpy(&hsb->s_type, param->string, 4);

+ break;

+ case opt_creator:

+ if (strlen(param->string) != 4) {

+ pr_err("creator requires a 4 character value\n");

+ return -EINVAL;

+ }

+ memcpy(&hsb->s_creator, param->string, 4);

+ break;

+ case opt_quiet:

+ hsb->s_quiet = 1;

+ break;

+ case opt_codepage:

+ if (hsb->nls_disk) {

+ pr_err("unable to change codepage\n");

+ return -EINVAL;

+ }

+ hsb->nls_disk = load_nls(param->string);

+ if (!hsb->nls_disk) {

+ pr_err("unable to load codepage \"%s\"\n",

+ param->string);

+ return -EINVAL;

+ }

+ break;

+ case opt_iocharset:

+ if (hsb->nls_io) {

+ pr_err("unable to change iocharset\n");

+ return -EINVAL;

+ }

+ hsb->nls_io = load_nls(param->string);

  if (!hsb->nls_io) {

- pr_err("unable to load default iocharset\n");

- return 0;

+ pr_err("unable to load iocharset \"%s\"\n",

+ param->string);

+ return -EINVAL;

  }

+ break;

+ default:

+ return -EINVAL;

  }

- hsb->s_dir_umask &= 0777;

- hsb->s_file_umask &= 0577;

 

- return 1;

+ return 0;

 }

 

 /*

@@ -376,29 +310,25 @@ static int parse_options(char *options, struct hfs_sb_info *hsb)

  * hfs_btree_init() to get the necessary data about the extents and

  * catalog B-trees and, finally, reading the root inode into memory.

  */

-static int hfs_fill_super(struct super_block *sb, void *data, int silent)

+static int hfs_fill_super(struct super_block *sb, struct fs_context *fc)

 {

- struct hfs_sb_info *sbi;

+ struct hfs_sb_info *sbi = HFS_SB(sb);

  struct hfs_find_data fd;

  hfs_cat_rec rec;

  struct inode *root_inode;

+ int silent = fc->sb_flags & SB_SILENT;

  int res;

 

- sbi = kzalloc(sizeof(struct hfs_sb_info), GFP_KERNEL);

- if (!sbi)

- return -ENOMEM;

+ /* load_nls_default does not fail */

+ if (sbi->nls_disk && !sbi->nls_io)

+ sbi->nls_io = load_nls_default();

+ sbi->s_dir_umask &= 0777;

+ sbi->s_file_umask &= 0577;

 

- sbi->sb = sb;

- sb->s_fs_info = sbi;

  spin_lock_init(&sbi->work_lock);

  INIT_DELAYED_WORK(&sbi->mdb_work, flush_mdb);

 

- res = -EINVAL;

- if (!parse_options((char *)data, sbi)) {

- pr_err("unable to parse mount options\n");

- goto bail;

- }

-

+ sbi->sb = sb;

  sb->s_op = &hfs_super_operations;

  sb->s_xattr = hfs_xattr_handlers;

  sb->s_flags |= SB_NODIRATIME;

@@ -451,18 +381,56 @@ static int hfs_fill_super(struct super_block *sb, void *data, int silent)

  return res;

 }

 

-static struct dentry *hfs_mount(struct file_system_type *fs_type,

-       int flags, const char *dev_name, void *data)

+static int hfs_get_tree(struct fs_context *fc)

+{

+ return get_tree_bdev(fc, hfs_fill_super);

+}

+

+static void hfs_free_fc(struct fs_context *fc)

+{

+ kfree(fc->s_fs_info);

+}

+

+static const struct fs_context_operations hfs_context_ops = {

+ .parse_param = hfs_parse_param,

+ .get_tree = hfs_get_tree,

+ .reconfigure = hfs_reconfigure,

+ .free = hfs_free_fc,

+};

+

+static int hfs_init_fs_context(struct fs_context *fc)

 {

- return mount_bdev(fs_type, flags, dev_name, data, hfs_fill_super);

+ struct hfs_sb_info *hsb;

+

+ hsb = kzalloc(sizeof(struct hfs_sb_info), GFP_KERNEL);

+ if (!hsb)

+ return -ENOMEM;

+

+ fc->s_fs_info = hsb;

+ fc->ops = &hfs_context_ops;

+

+ if (fc->purpose != FS_CONTEXT_FOR_RECONFIGURE) {

+ /* initialize options with defaults */

+ hsb->s_uid = current_uid();

+ hsb->s_gid = current_gid();

+ hsb->s_file_umask = 0133;

+ hsb->s_dir_umask = 0022;

+ hsb->s_type = cpu_to_be32(0x3f3f3f3f); /* == '????' */

+ hsb->s_creator = cpu_to_be32(0x3f3f3f3f); /* == '????' */

+ hsb->s_quiet = 0;

+ hsb->part = -1;

+ hsb->session = -1;

+ }

+

+ return 0;

 }

 

 static struct file_system_type hfs_fs_type = {

  .owner = THIS_MODULE,

  .name = "hfs",

- .mount = hfs_mount,

  .kill_sb = kill_block_super,

  .fs_flags = FS_REQUIRES_DEV,

+ .init_fs_context = hfs_init_fs_context,

 };

 MODULE_ALIAS_FS("hfs");

 



The test case, kernel config and full bisection log are attached.

The report is (The full report is attached):

BTRFS warning (device loop5): checksum verify failed on logical 5251072 mirror 1 wanted 0xecf688179d1be1e8 found 0x36c9388970283605 level 0

BTRFS error (device loop5): failed to load root extent

BTRFS error (device loop5): open_ctree failed

loop5: detected capacity change from 0 to 64

------------[ cut here ]------------

kernel BUG at fs/hfs/inode.c:444!

Oops: invalid opcode: 0000 [#1] PREEMPT SMP KASAN NOPTI

CPU: 0 UID: 0 PID: 10432 Comm: syz.5.590 Not tainted 6.12.0-rc6 #1

Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.13.0-1ubuntu1.1 04/01/2014

RIP: 0010:hfs_write_inode+0x32e/0x9e0 fs/hfs/inode.c:444

Code: 3d 86 4c 89 ef e8 d2 8c 45 ff 49 83 fd 03 0f 84 d9 02 00 00 49 83 fd 04 74 12 49 83 fd 02 0f 84 b8 fd ff ff e8 23 89 45 ff 90 <0f> 0b e8 1b 89 45 ff 48 8d 7d 28 48 b8 00 00 00 00 00 fc ff df 48

RSP: 0018:ff1100010d81f660 EFLAGS: 00010293

RAX: 0000000000000000 RBX: 1fe2200021b03ecd RCX: ffffffff820c793e

RDX: ff1100018cfe4480 RSI: ffffffff820c795d RDI: 0000000000000007

RBP: ff1100013d56aed8 R08: 0000000000000000 R09: fffffbfff102ee39

R10: 0000000000000005 R11: 0000000000000000 R12: 0000000000000000

R13: 0000000000000005 R14: ff1100010d81f8b4 R15: ff1100013d56af00

FS:  00007f3a14009700(0000) GS:ff110004ca800000(0000) knlGS:0000000000000000

CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033

CR2: 00007ff6767f2008 CR3: 0000000169208006 CR4: 0000000000771ef0

DR0: 0000000000000000 DR1: 0000000000000000 DR2: 0000000000000000

DR3: 0000000000000000 DR6: 00000000fffe0ff0 DR7: 0000000000000400

PKRU: 80000000

Call Trace:

 <TASK>

 write_inode fs/fs-writeback.c:1503 [inline]

 __writeback_single_inode+0x9d6/0xd80 fs/fs-writeback.c:1723

 writeback_single_inode+0x2b7/0x550 fs/fs-writeback.c:1779

 write_inode_now+0x16a/0x1e0 fs/fs-writeback.c:2829

 hfs_file_fsync+0xab/0x1a0 fs/hfs/inode.c:676

 vfs_fsync_range+0x13a/0x230 fs/sync.c:188

 generic_write_sync include/linux/fs.h:2871 [inline]

 generic_file_write_iter+0x29c/0x340 mm/filemap.c:4185

 do_iter_readv_writev+0x51c/0x7e0 fs/read_write.c:834

 vfs_writev+0x30e/0xd50 fs/read_write.c:1064

 do_pwritev+0x1b7/0x270 fs/read_write.c:1165

 __do_sys_pwritev2 fs/read_write.c:1224 [inline]

 __se_sys_pwritev2 fs/read_write.c:1215 [inline]

 __x64_sys_pwritev2+0xef/0x160 fs/read_write.c:1215

 do_syscall_x64 arch/x86/entry/common.c:52 [inline]

 do_syscall_64+0xc1/0x1d0 arch/x86/entry/common.c:83

 entry_SYSCALL_64_after_hwframe+0x77/0x7f

RIP: 0033:0x7f3a153d639d

Code: 02 b8 ff ff ff ff c3 66 0f 1f 44 00 00 f3 0f 1e fa 48 89 f8 48 89 f7 48 89 d6 48 89 ca 4d 89 c2 4d 89 c8 4c 8b 4c 24 08 0f 05 <48> 3d 01 f0 ff ff 73 01 c3 48 c7 c1 b0 ff ff ff f7 d8 64 89 01 48

RSP: 002b:00007f3a14008b78 EFLAGS: 00000246 ORIG_RAX: 0000000000000148

RAX: ffffffffffffffda RBX: 00007f3a1558f058 RCX: 00007f3a153d639d

RDX: 0000000000000001 RSI: 000000002000ed40 RDI: 0000000000000006

RBP: 00007f3a1544b584 R08: 00000000000000b3 R09: 0000000000000002

R10: 0000000000000909 R11: 0000000000000246 R12: 0000000000000000

R13: 0000000000000001 R14: 00007f3a1558f058 R15: 00007f3a14008d40

 </TASK>

Modules linked in:

---[ end trace 0000000000000000 ]---

RIP: 0010:hfs_write_inode+0x32e/0x9e0 fs/hfs/inode.c:444

Code: 3d 86 4c 89 ef e8 d2 8c 45 ff 49 83 fd 03 0f 84 d9 02 00 00 49 83 fd 04 74 12 49 83 fd 02 0f 84 b8 fd ff ff e8 23 89 45 ff 90 <0f> 0b e8 1b 89 45 ff 48 8d 7d 28 48 b8 00 00 00 00 00 fc ff df 48

RSP: 0018:ff1100010d81f660 EFLAGS: 00010293

RAX: 0000000000000000 RBX: 1fe2200021b03ecd RCX: ffffffff820c793e

RDX: ff1100018cfe4480 RSI: ffffffff820c795d RDI: 0000000000000007

RBP: ff1100013d56aed8 R08: 0000000000000000 R09: fffffbfff102ee39

R10: 0000000000000005 R11: 0000000000000000 R12: 0000000000000000

R13: 0000000000000005 R14: ff1100010d81f8b4 R15: ff1100013d56af00

FS:  00007f3a14009700(0000) GS:ff110004ca800000(0000) knlGS:0000000000000000

CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033

CR2: 00007ff6767f2008 CR3: 0000000169208006 CR4: 0000000000771ef0

DR0: 0000000000000000 DR1: 0000000000000000 DR2: 0000000000000000

DR3: 0000000000000000 DR6: 00000000fffe0ff0 DR7: 0000000000000400

PKRU: 80000000

loop3: detected capacity change from 0 to 262144

BTRFS: device fsid e5902142-921c-4738-ad41-4f5c3cade159 devid 1 transid 8 /dev/loop3 (7:3) scanned by syz.3.585 (10371)

BTRFS info (device loop3): first mount of filesystem e5902142-921c-4738-ad41-4f5c3cade159

BTRFS info (device loop3): using crc32c (crc32c-generic) checksum algorithm

BTRFS info (device loop3): using free-space-tree

BTRFS info (device loop3): last unmount of filesystem e5902142-921c-4738-ad41-4f5c3cade159


bisect.log
kconfig
report0
repro.cprog

Viacheslav Dubeyko

unread,
Mar 6, 2026, 12:37:52 PMMar 6
to 杜义恒, glau...@physik.fu-berlin.de, fran...@vivo.com, syzk...@googlegroups.com
As far as I can see, you have 6.12.0-rc6 Linux kernel under testing.
Are you capable to reproduce the issue for 6.19 or 7.0?

Thanks,
Slava.
Reply all
Reply to author
Forward
0 new messages