From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from mail-oi1-f198.google.com (mail-oi1-f198.google.com [209.85.167.198]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 08CCF384CD1 for ; Thu, 2 Jul 2026 06:28:24 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.167.198 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1782973707; cv=none; b=Uaf58HKc+zZtnl73jaPM8mI4kY6a+MqIw0OhYxfal2ZkEuwWnF6/0w5e59IXC6ukT+cFoVQfE6uqOdKuNOO0TPJUu5NtzLr0UgApUAn6pxR5WcUZct/hR49a0DyVsx8sOnFBz9K4/cmICYv4Uk0f1W0eVYfSfpt9P0eScTx+uC8= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1782973707; c=relaxed/simple; bh=BB6tmiEfOPBLd6oRHuMETPW4dV2wijjTdekChMyUo0c=; h=MIME-Version:Date:In-Reply-To:Message-ID:Subject:From:To: Content-Type; b=p/us7a6rEjgVDBBUM4JlPuKl9+ggCVpqVRGdHsI8ZNxv5xCKQa5PTtI0wyaq/l8JvsOX9wouTOR1iPD4s9SJKPN7sNkj1xkwSi9X+bIObElv/mmGzBHLUmZbb+K9+sVMo796apQuNssi3g//uNsk9Ghm+LQ8Pk2co1X1RhYf3PM= ARC-Authentication-Results:i=1; smtp.subspace.kernel.org; dmarc=fail (p=none dis=none) header.from=syzkaller.appspotmail.com; spf=pass smtp.mailfrom=M3KW2WVRGUFZ5GODRSRYTGD7.apphosting.bounces.google.com; arc=none smtp.client-ip=209.85.167.198 Authentication-Results: smtp.subspace.kernel.org; dmarc=fail (p=none dis=none) header.from=syzkaller.appspotmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=M3KW2WVRGUFZ5GODRSRYTGD7.apphosting.bounces.google.com Received: by mail-oi1-f198.google.com with SMTP id 5614622812f47-4893fc86bebso1960567b6e.0 for ; Wed, 01 Jul 2026 23:28:24 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1782973704; x=1783578504; h=content-type:to:from:subject:message-id:in-reply-to:date :mime-version:x-gm-message-state:from:to:cc:subject:date:message-id :reply-to:content-type; bh=eeBkPGZNBL5qXITKhB8MdbyXGFOW+m9F3s19Oa/ESHQ=; b=i1/yIFvRkyKqNd0iznF90+LJciBqFeH48L/NF46+LvYbolLGKo+ET6z0hSsJBB1NMZ nuKkrRGnFpfjAvvJ3nV5IW7CFhtxQOW8okR8xjY2B91jyx2P7dxB01m1a4KAAQAjSrcI zfEHTGCg+RA9SByMVvUT2/tvr4S+jEhAzl9e937dEzRv5HzBcUhaF/+OyZ94IYjcPxd6 tCuBP4uiS/7dRCvvz96Zvvr8TZxE+DCfTSMIu1iLlKiLB3fTp5e3MdEqhkbj7duq3xP2 gKrpLErrRL/UyC3O8jSWcOttbevQ8CejlGThHPTmN3RCyYTqflPUa6qNssYoBhekrX2A Mx+A== X-Gm-Message-State: AOJu0YyZGEzyJegvBd9OARa5iWUc22yD3dBdHCB14xeNEerbMYtMThL2 7xxACzKeCAn/eWyD8tnbxsBjEuOe/woH1mtFCpbUQxmj80asdS7OaLR+X5x0aaHpr/QS88XWepJ nE3Wehnc5ngj5MkAXGekvUWD3lGll5fLJuW9O7zoQbfrGK3YOvPTCyRsZHiE= Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 X-Received: by 2002:a05:6808:1a11:b0:495:eb85:fc2b with SMTP id 5614622812f47-49624400dc3mr2683641b6e.10.1782973704182; Wed, 01 Jul 2026 23:28:24 -0700 (PDT) Date: Wed, 01 Jul 2026 23:28:24 -0700 In-Reply-To: <00000000000089f55405ee486239@google.com> X-Google-Appengine-App-Id: s~syzkaller X-Google-Appengine-App-Id-Alias: syzkaller Message-ID: <6a460508.3978deff.3304ed.0002.GAE@google.com> Subject: Forwarded: [PATCH] hfs: validate catalog CNIDs before instantiating inodes From: syzbot To: linux-kernel@vger.kernel.org Content-Type: text/plain; charset="UTF-8" For archival purposes, forwarding an incoming command email to linux-kernel@vger.kernel.org. *** Subject: [PATCH] hfs: validate catalog CNIDs before instantiating inodes Author: davemadmaxxx@gmail.com hfs_cat_find_brec() first resolves a catalog thread record by CNID and then looks up the corresponding catalog record by parent/name. On a corrupted filesystem image, the second lookup may find a record whose CNID does not match the CNID that was requested. Validate the catalog record found by the second lookup before returning it to callers. Inspect the already-found record with hfs_bnode_read(), not hfs_brec_read(), and reject records whose CNID is invalid for their record type or does not match the requested CNID. Also validate CNIDs in hfs_read_inode() before the inode is populated, and propagate hfs_read_inode() errors from the resource-fork lookup path. For the root inode path, require the root catalog record to be a directory with DirID == HFS_ROOT_CNID, and drop the root inode reference if it was instantiated as a bad inode. This keeps hfs_write_inode() unchanged and prevents corrupted catalog records from reaching the existing reserved-CNID BUG() path during writeback. Reported-by: syzbot+97e301b4b82ae803d21b@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=97e301b4b82ae803d21b Cc: George Anthony Vernon Cc: Tetsuo Handa Signed-off-by: David Maximiliano Hermitte #syz test: git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git master Please retest this HFS patch on current upstream master. The previous syzbot run ended with "lost connection to test machine". --- fs/hfs/catalog.c | 48 +++++++++++++++++++++++++++++++++++++++++++++++- fs/hfs/hfs_fs.h | 19 +++++++++++++++++++ fs/hfs/inode.c | 9 ++++++++- fs/hfs/super.c | 8 +++++++- 4 files changed, 81 insertions(+), 3 deletions(-) diff --git a/fs/hfs/catalog.c b/fs/hfs/catalog.c index 1bfa36d71e24..fa2a0a5975e3 100644 --- a/fs/hfs/catalog.c +++ b/fs/hfs/catalog.c @@ -182,6 +182,47 @@ int hfs_cat_keycmp(const btree_key *key1, const btree_key *key2) key2->cat.CName.name, key2->cat.CName.len); } +static int hfs_cat_validate_found_cnid(struct hfs_find_data *fd, u32 cnid) +{ + hfs_cat_rec rec; + u32 found_cnid; + unsigned int rec_len; + size_t cnid_off; + + if (fd->entrylength <= 0) + return -EIO; + + if ((unsigned int)fd->entrylength > sizeof(rec)) + rec_len = sizeof(rec); + else + rec_len = fd->entrylength; + + memset(&rec, 0, sizeof(rec)); + hfs_bnode_read(fd->bnode, &rec, fd->entryoffset, rec_len); + + switch (rec.type) { + case HFS_CDR_FIL: + cnid_off = offsetof(struct hfs_cat_file, FlNum); + if ((size_t)rec_len < cnid_off + sizeof(rec.file.FlNum)) + return -EIO; + found_cnid = be32_to_cpu(rec.file.FlNum); + break; + case HFS_CDR_DIR: + cnid_off = offsetof(struct hfs_cat_dir, DirID); + if ((size_t)rec_len < cnid_off + sizeof(rec.dir.DirID)) + return -EIO; + found_cnid = be32_to_cpu(rec.dir.DirID); + break; + default: + return -EIO; + } + + if (!hfs_is_valid_cnid(found_cnid, rec.type) || found_cnid != cnid) + return -EIO; + + return 0; +} + /* Try to get a catalog entry for given catalog id */ // move to read_super??? int hfs_cat_find_brec(struct super_block *sb, u32 cnid, @@ -208,7 +249,12 @@ int hfs_cat_find_brec(struct super_block *sb, u32 cnid, return -EIO; } memcpy(fd->search_key->cat.CName.name, rec.thread.CName.name, len); - return hfs_brec_find(fd); + + res = hfs_brec_find(fd); + if (res) + return res; + + return hfs_cat_validate_found_cnid(fd, cnid); } static inline diff --git a/fs/hfs/hfs_fs.h b/fs/hfs/hfs_fs.h index f3624514fcb0..670638f17438 100644 --- a/fs/hfs/hfs_fs.h +++ b/fs/hfs/hfs_fs.h @@ -155,6 +155,25 @@ extern int hfs_cat_move(u32 cnid, struct inode *src_dir, extern void hfs_cat_build_key(struct super_block *sb, btree_key *key, u32 parent, const struct qstr *name); +/* + * Validate the CNID of a catalog record. + */ +static inline bool hfs_is_valid_cnid(u32 cnid, u8 type) +{ + if (likely(cnid >= HFS_FIRSTUSER_CNID)) + return true; + + switch (cnid) { + case HFS_ROOT_CNID: + return type == HFS_CDR_DIR; + case HFS_EXT_CNID: + case HFS_CAT_CNID: + return type == HFS_CDR_FIL; + default: + return false; + } +} + /* dir.c */ extern const struct file_operations hfs_dir_operations; extern const struct inode_operations hfs_dir_inode_operations; diff --git a/fs/hfs/inode.c b/fs/hfs/inode.c index ac4a9055c5c0..c685f1bb7009 100644 --- a/fs/hfs/inode.c +++ b/fs/hfs/inode.c @@ -367,6 +367,9 @@ static int hfs_read_inode(struct inode *inode, void *data) rec = idata->rec; switch (rec->type) { case HFS_CDR_FIL: + if (!hfs_is_valid_cnid(be32_to_cpu(rec->file.FlNum), rec->type)) + return -EIO; + if (!HFS_IS_RSRC(inode)) { hfs_inode_read_fork(inode, rec->file.ExtRec, rec->file.LgLen, rec->file.PyLen, be16_to_cpu(rec->file.ClpSize)); @@ -390,6 +393,9 @@ static int hfs_read_inode(struct inode *inode, void *data) inode->i_mapping->a_ops = &hfs_aops; break; case HFS_CDR_DIR: + if (!hfs_is_valid_cnid(be32_to_cpu(rec->dir.DirID), rec->type)) + return -EIO; + inode->i_ino = be32_to_cpu(rec->dir.DirID); inode->i_size = be16_to_cpu(rec->dir.Val) + 2; HFS_I(inode)->fs_blocks = 0; @@ -571,7 +577,8 @@ static struct dentry *hfs_file_lookup(struct inode *dir, struct dentry *dentry, res = hfs_brec_read(&fd, &rec, sizeof(rec)); if (!res) { struct hfs_iget_data idata = { NULL, &rec }; - hfs_read_inode(inode, &idata); + + res = hfs_read_inode(inode, &idata); } hfs_find_exit(&fd); if (res) { diff --git a/fs/hfs/super.c b/fs/hfs/super.c index a466c401f6bb..5275936304c7 100644 --- a/fs/hfs/super.c +++ b/fs/hfs/super.c @@ -361,7 +361,8 @@ static int hfs_fill_super(struct super_block *sb, struct fs_context *fc) goto bail_hfs_find; } hfs_bnode_read(fd.bnode, &rec, fd.entryoffset, fd.entrylength); - if (rec.type != HFS_CDR_DIR) + if (rec.type != HFS_CDR_DIR || + be32_to_cpu(rec.dir.DirID) != HFS_ROOT_CNID) res = -EIO; } if (res) @@ -372,6 +373,11 @@ static int hfs_fill_super(struct super_block *sb, struct fs_context *fc) if (!root_inode) goto bail_no_root; + if (is_bad_inode(root_inode)) { + iput(root_inode); + goto bail_no_root; + } + set_default_d_op(sb, &hfs_dentry_operations); res = -ENOMEM; sb->s_root = d_make_root(root_inode); -- 2.43.0