From: Viacheslav Dubeyko <Slava.Dubeyko@ibm.com>
To: "glaubitz@physik.fu-berlin.de" <glaubitz@physik.fu-berlin.de>,
"contact@gvernon.com" <contact@gvernon.com>,
"slava@dubeyko.com" <slava@dubeyko.com>,
"frank.li@vivo.com" <frank.li@vivo.com>,
"skhan@linuxfoundation.org" <skhan@linuxfoundation.org>,
"linux-fsdevel@vger.kernel.org" <linux-fsdevel@vger.kernel.org>
Cc: "linux-kernel-mentees@lists.linux.dev"
<linux-kernel-mentees@lists.linux.dev>,
"syzbot+97e301b4b82ae803d21b@syzkaller.appspotmail.com"
<syzbot+97e301b4b82ae803d21b@syzkaller.appspotmail.com>,
"linux-kernel@vger.kernel.org" <linux-kernel@vger.kernel.org>
Subject: Re: [PATCH] hfs: Validate CNIDs in hfs_read_inode
Date: Fri, 3 Oct 2025 22:40:16 +0000 [thread overview]
Message-ID: <405569eb2e0ec4ce2afa9c331eb791941d0cf726.camel@ibm.com> (raw)
In-Reply-To: <20251003024544.477462-1-contact@gvernon.com>
On Fri, 2025-10-03 at 03:45 +0100, George Anthony Vernon wrote:
> hfs_read_inode previously did not validate CNIDs read from disk, thereby
> allowing inodes to be constructed with disallowed CNIDs and placed on
> the dirty list, eventually hitting a bug on writeback.
>
> Validate reserved CNIDs according to Apple technical note TN1150.
>
> This issue was discussed at length on LKML previously, the discussion
> is linked below.
>
> Syzbot tested this patch on mainline and the bug did not replicate.
> This patch was regression tested by issuing various system calls on a
> mounted HFS filesystem and validating that file creation, deletion,
> reads and writes all work.
>
> Link: https://lore.kernel.org/all/427fcb57-8424-4e52-9f21-7041b2c4ae5b@
> I-love.SAKURA.ne.jp/T/
> Reported-by: syzbot+97e301b4b82ae803d21b@syzkaller.appspotmail.com
> Closes: https://syzkaller.appspot.com/bug?extid=97e301b4b82ae803d21b
> Tested-by: syzbot+97e301b4b82ae803d21b@syzkaller.appspotmail.com
Let's pay respect to previous efforts. I am suggesting to add this line:
Signed-off-by: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
Are you OK with it?
> Signed-off-by: George Anthony Vernon <contact@gvernon.com>
> ---
> fs/hfs/inode.c | 34 ++++++++++++++++++++++++++++++++++
> 1 file changed, 34 insertions(+)
>
> diff --git a/fs/hfs/inode.c b/fs/hfs/inode.c
> index 9cd449913dc8..6f893011492a 100644
> --- a/fs/hfs/inode.c
> +++ b/fs/hfs/inode.c
> @@ -321,6 +321,30 @@ static int hfs_test_inode(struct inode *inode, void *data)
> }
> }
>
> +/*
> + * is_valid_cnid
> + *
> + * Validate the CNID of a catalog record read from disk
> + */
> +static bool is_valid_cnid(unsigned long cnid, s8 type)
I think we can declare like this:
static inline
bool is_valid_cnid(unsigned long cnid, s8 type)
Why cnid has unsigned long type? The u32 is pretty enough.
Why type has signed type (s8)? We don't expect negative values here. Let's use
u8 type.
> +{
> + if (likely(cnid >= HFS_FIRSTUSER_CNID))
> + return true;
> +
> + switch (cnid) {
> + case HFS_POR_CNID:
> + case HFS_ROOT_CNID:
> + return type == HFS_CDR_DIR;
> + case HFS_EXT_CNID:
> + case HFS_CAT_CNID:
> + case HFS_BAD_CNID:
> + case HFS_EXCH_CNID:
> + return type == HFS_CDR_FIL;
> + default:
> + return false;
We can simply have default that is doing nothing:
default:
/* continue logic */
break;
> + }
I believe that it will be better to return false by default here (after switch).
> +}
> +
> /*
> * hfs_read_inode
> */
> @@ -359,6 +383,11 @@ static int hfs_read_inode(struct inode *inode, void *data)
> }
>
> inode->i_ino = be32_to_cpu(rec->file.FlNum);
> + if (!is_valid_cnid(inode->i_ino, HFS_CDR_FIL)) {
> + pr_warn("rejected cnid %lu\n", inode->i_ino);
The syzbot reported the issue on specially corrupted volume. So, probably, it
will be better to mentioned that "volume is probably corrupted" and to advice to
run FSCK tool.
> + make_bad_inode(inode);
We already have make_bad_inode(inode) under default case of switch. Let's jump
there without replicating this call multiple times. It makes the code more
complicated.
> + break;
> + }
> inode->i_mode = S_IRUGO | S_IXUGO;
> if (!(rec->file.Flags & HFS_FIL_LOCK))
> inode->i_mode |= S_IWUGO;
> @@ -372,6 +401,11 @@ static int hfs_read_inode(struct inode *inode, void *data)
> break;
> case HFS_CDR_DIR:
> inode->i_ino = be32_to_cpu(rec->dir.DirID);
> + if (!is_valid_cnid(inode->i_ino, HFS_CDR_DIR)) {
> + pr_warn("rejected cnid %lu\n", inode->i_ino);
Ditto.
> + make_bad_inode(inode);
Ditto.
> + break;
> + }
> inode->i_size = be16_to_cpu(rec->dir.Val) + 2;
> HFS_I(inode)->fs_blocks = 0;
> inode->i_mode = S_IFDIR | (S_IRWXUGO & ~hsb->s_dir_umask);
We have practically the same check for the case of hfs_write_inode():
int hfs_write_inode(struct inode *inode, struct writeback_control *wbc)
{
struct inode *main_inode = inode;
struct hfs_find_data fd;
hfs_cat_rec rec;
int res;
hfs_dbg("ino %lu\n", inode->i_ino);
res = hfs_ext_write_extent(inode);
if (res)
return res;
if (inode->i_ino < HFS_FIRSTUSER_CNID) {
switch (inode->i_ino) {
case HFS_ROOT_CNID:
break;
case HFS_EXT_CNID:
hfs_btree_write(HFS_SB(inode->i_sb)->ext_tree);
return 0;
case HFS_CAT_CNID:
hfs_btree_write(HFS_SB(inode->i_sb)->cat_tree);
return 0;
default:
BUG();
return -EIO;
I think we need to select something one here. :) I believe we need to remove
BUG() and return -EIO, finally. What do you think?
}
}
<skipped>
}
What's about to use your check here too?
Mostly, I like your approach but the patch needs some polishing yet. ;)
Thanks,
Slava.
next prev parent reply other threads:[~2025-10-03 22:40 UTC|newest]
Thread overview: 12+ messages / expand[flat|nested] mbox.gz Atom feed top
2025-10-03 2:45 [PATCH] hfs: Validate CNIDs in hfs_read_inode George Anthony Vernon
2025-10-03 22:40 ` Viacheslav Dubeyko [this message]
2025-10-04 1:25 ` George Anthony Vernon
2025-10-07 13:40 ` Viacheslav Dubeyko
2025-10-09 12:57 ` Tetsuo Handa
2025-10-29 3:20 ` George Anthony Vernon
2025-10-29 10:06 ` Tetsuo Handa
2025-11-04 1:47 ` [PATCH v2 0/2] hfs: Validate CNIDs read from filesystem George Anthony Vernon
2025-11-04 1:47 ` [PATCH v2 1/2] hfs: Validate CNIDs in hfs_read_inode George Anthony Vernon
2025-11-04 22:34 ` Viacheslav Dubeyko
2025-11-04 1:47 ` [PATCH v2 2/2] hfs: Update sanity check of the root record George Anthony Vernon
2025-11-04 23:01 ` Viacheslav Dubeyko
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=405569eb2e0ec4ce2afa9c331eb791941d0cf726.camel@ibm.com \
--to=slava.dubeyko@ibm.com \
--cc=contact@gvernon.com \
--cc=frank.li@vivo.com \
--cc=glaubitz@physik.fu-berlin.de \
--cc=linux-fsdevel@vger.kernel.org \
--cc=linux-kernel-mentees@lists.linux.dev \
--cc=linux-kernel@vger.kernel.org \
--cc=skhan@linuxfoundation.org \
--cc=slava@dubeyko.com \
--cc=syzbot+97e301b4b82ae803d21b@syzkaller.appspotmail.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;
as well as URLs for NNTP newsgroup(s).