From nobody Fri Sep 25 12:38:43 2026 Received: from mail-pz2-f12.google.com (mail-pz2-f12.google.com [74.125.228.12]) (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 2836647DFB0 for ; Sat, 12 Sep 2026 13:24:16 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=74.125.228.12 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789219457; cv=none; b=tQfc5zDc2UGRg3oqa3gJREFotecXOJ/O7TEdmh0znOSB9OX507mbs5KT1vLuAOm2iLvXxOfBfaaWG/5Tvu968zJsDid+qbmRz/rSZyNPiV/+q0ufluqAqSUpjzQkdTIJFIy12UMlg15tN9f6IIb8LK1RGFEl/2KiupJ9QL0SFOM= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789219457; c=relaxed/simple; bh=cHwswaLHS63EipwJZ5k7IuKeyHsjmD0CMBOGzoXUuo8=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=JcElJ2JZ2gWSAbff/4S/+wBbR/EsONWrV8E4iHAYpZ2tKDda02uIWEfStuSeyVXnOigFbDLPlVIVEN2P+LFd1HuzzzKpioE1augIgPH7nVQJiQ2m5muXcLZhKM8gH0lfaCg2bjjzApUC2ddKA8mfegsM2Bay0jE4adJMkpBkK28= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=rCOvvFZq; arc=none smtp.client-ip=74.125.228.12 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="rCOvvFZq" Received: by mail-pz2-f12.google.com with SMTP id d2e1a72fcca58-8631d0023daso256072b3a.2 for ; Sat, 12 Sep 2026 06:24:16 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1789219455; x=1789824255; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=R/LNreBp2TtdR9tBywqnjgXm+zGPC2XwaiQWB1Gl4ag=; b=rCOvvFZqWHjQtWcbBiMf61NN819ATfjo38Rj4FD3xrkOtcacXymGX5iQM+t90ZAXuF Rf+ZPYd9zhGIjbfMPidG8Y/JI2Mkl9yIP4weNBy+Iymp5OjhG+CtAhUs8wYsRwaaeETv iBD9ZKyZk8Ec8y3BgYi+BxIZ3gdc3DCDsJtBM81Y88ftOcg2x8YglOi9z0/116QyW1bp odjcL8xaAUNwoMRdAQcupoZrf58T2ox+xVG/q7YRxVEMwO4n8kZjHC7QkyU/crKGGGQf lDbmJvKiDAbMtCfjmzzEwf+TWO6dBzdwSFF4Ff8QsreQEF2pwNtM7a7UH7q6L6xKoYu+ ZXdQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1789219455; x=1789824255; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to:content-type; bh=R/LNreBp2TtdR9tBywqnjgXm+zGPC2XwaiQWB1Gl4ag=; b=HKL9seIUeJkehMvaRswOctY1KSGBzeLRajbi/BI6JA3cqJxE3/QiG9yn4GKuLDy5rt TAIrYyNypOxW7eboRJTlEkB+E8/e3EYnEjocgZ7nSTHO0pTznrf53IFiQITl7QnOhVGv i4zvQcJimPt/Utjyee7Sc73eZvO5mTN2a7EIxmg4p2lG5a4Nv8pJ7jDsU9W1Oxb79iY5 RxRZsK8zPcE9P3U77sNlU41796CsXmGhS6EkCehrnoaeM604/EqqGN4TK4C6jHUO5FBP ddbDPXI9lZnKedPYgi1M1dJ7yf1QFs511ecqXzAGsZkHDEaw851HHAAy6F99zcUVEoK0 ln7Q== X-Forwarded-Encrypted: i=1; AKwUvBxVvESru5gFLh4dh7U/lvruobZz5k1gVG6169iWUtx6O4LUJ8tZ8nyFxKYNldLZsHauXljOyBszWWNpM44=@vger.kernel.org X-Gm-Message-State: AFuF++kjTqRK4BnsqMv9S8+g0PVfJceT9/Jok3DnbT/3p7L5L9UYvbLr J5gZYBfh51cgCBA4atCyWVw2TEgsb9XonSruIpuRRyxE4hHt4j2PnCe2 X-Gm-Gg: AYBFou3Psnzq7VZjgRXqxOQutYO1OPb2cQ9FOEujBnsBBW5pubZfd3xmoqFVeQDIcKR NmIbcymLil7cBdn9QJizrXhrQOXOuECEMcIu4qmG5lUGSQWz4GmOrLjOvCKawo3jA4WigQWXI2Y rRjKKf3qhfd4Lv6xepcGGBQXD/sCJzyJSklWqKXEG8Cw9PjoQvnt0rZYpqCNk0rUlbjJqsRDZsn YM3BKShAJil/lRIU6vG9d2tBL2zRzLoivu82PGkB6ajHOI16cZy+LiEgqqCJp5fi+3VXQ6rDNnp p/Vm+Tz2us41DoImBxKCyQFjjWtTYTC3Vmw9XmeOccAVFI5B6SStjTEPCjAjmS6VIVJwLPE0Oql Va5cnzCRjVMYFbU4p87lGYDaREIcxN2lO4ZwM8mdrdmKXCZoemUTAuwzGPcjU9Fklv4AsJZvUWo b43DY8ZSKLoExuaucETWE3CCDBO/3ncWfeTGJDh16WsATlODT1NShMe+lO0rf+3T3wW7YAgqdxR FYTvGevZVtQGT+LBiC2wjTMqFXh X-Received: by 2002:a05:6a00:1906:b0:842:5b66:3c7f with SMTP id d2e1a72fcca58-86cc59a9c7emr4306818b3a.0.1789219455392; Sat, 12 Sep 2026 06:24:15 -0700 (PDT) Received: from thangnn-ASUS.. ([2405:4802:1d38:5c70:dfd9:c41e:7c9b:c69]) by smtp.gmail.com with ESMTPSA id d2e1a72fcca58-86b292c9839sm2410819b3a.33.2026.09.12.06.24.13 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sat, 12 Sep 2026 06:24:15 -0700 (PDT) From: Nguyen Ngoc Thang To: Viacheslav Dubeyko Cc: John Paul Adrian Glaubitz , Yangtao Li , linux-fsdevel@vger.kernel.org, linux-kernel@vger.kernel.org, syzbot+f8ce6c197125ab9d72ce@syzkaller.appspotmail.com Subject: [PATCH v4 1/2] hfsplus: fix recursive tree_lock in hfsplus_file_extend() Date: Sat, 12 Sep 2026 20:24:06 +0700 Message-ID: <20260912132407.16856-2-ngocthang2710.1999@gmail.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260912132407.16856-1-ngocthang2710.1999@gmail.com> References: <20260912132407.16856-1-ngocthang2710.1999@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" hfs_bmap_reserve() calls hfsplus_file_extend() on tree->inode with tree->tree_lock already held. For the extents overflow B-tree's own inode, growing it can call hfsplus_ext_read_extent() -> hfs_find_init() on that same tree, taking tree_lock a second time (lockdep: "possible recursive locking ... &tree->tree_lock/1"). This happens two ways: - the fork already claims more blocks than its eight extents describe (a corrupted on-disk fork), so hfsplus_ext_read_extent() is called immediately to look up the rest; or - the fork's eight extents get exhausted during this call, and inserting a new overflow extent record for the file would need the same lookup. Per the HFS+ format the extents overflow file is fully described by its eight fork extents and can never legitimately have overflow extents of its own, so both cases mean it cannot grow any further. Move the check into hfsplus_ext_read_extent() itself, the one place that actually re-enters hfs_find_init(), rather than duplicating it at each caller, and report -ENOSPC. For the second case, don't allocate blocks on the chance the fork still has room and undo it if not: hfsplus_ext_fork_full() tests the fork first. If it does have a free extent slot, any free space works, same as before. If it's already full, the only way to grow is a contiguous extension of the last extent, so only search for free space starting exactly at the block right after it, and fail with -ENOSPC immediately if that block isn't free -- nothing gets allocated in that case, so there's nothing to undo. The prior allocate-then-free-on-failure code stays at the insert_extent label as a backstop, in case this reasoning has a gap. Reported-by: syzbot+f8ce6c197125ab9d72ce@syzkaller.appspotmail.com Signed-off-by: Nguyen Ngoc Thang Co-Authored-By: Claude Sonnet 5 --- fs/hfsplus/extents.c | 59 +++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 55 insertions(+), 4 deletions(-) diff --git a/fs/hfsplus/extents.c b/fs/hfsplus/extents.c index eb7c11524d18..236f2d9a7a2d 100644 --- a/fs/hfsplus/extents.c +++ b/fs/hfsplus/extents.c @@ -84,6 +84,17 @@ static u32 hfsplus_ext_lastblock(struct hfsplus_extent *= ext) return be32_to_cpu(ext->start_block) + be32_to_cpu(ext->block_count); } =20 +/* True if all eight extents of a fork are in use (no free slot left) */ +static bool hfsplus_ext_fork_full(struct hfsplus_extent *ext) +{ + int i; + + for (i =3D 0; i < 8; ext++, i++) + if (!ext->block_count) + return false; + return true; +} + static int __hfsplus_ext_write_extent(struct inode *inode, struct hfs_find_data *fd) { @@ -217,6 +228,15 @@ static int hfsplus_ext_read_extent(struct inode *inode= , u32 block) block < hip->cached_start + hip->cached_blocks) return 0; =20 + /* + * The extents overflow file is fully described by its own fork + * extents; looking up an overflow extent for it would re-enter + * hfs_find_init() on the extents tree, whose tree_lock may already + * be held by the caller. + */ + if (inode->i_ino =3D=3D HFSPLUS_EXT_CNID) + return -ENOSPC; + res =3D hfs_find_init(HFSPLUS_SB(inode->i_sb)->ext_tree, &fd); if (!res) { res =3D __hfsplus_ext_cache_extent(&fd, inode, block); @@ -465,13 +485,30 @@ int hfsplus_file_extend(struct inode *inode, bool zer= oout) } =20 len =3D hip->clump_blocks; - start =3D hfsplus_block_allocate(sb, sbi->total_blocks, goal, &len); - if (start >=3D sbi->total_blocks) { - start =3D hfsplus_block_allocate(sb, goal, 0, &len); - if (start >=3D goal) { + if (inode->i_ino =3D=3D HFSPLUS_EXT_CNID && + hip->alloc_blocks =3D=3D hip->first_blocks && + hfsplus_ext_fork_full(hip->first_extents)) { + /* + * No free slot is left in the fork, and the extents overflow + * file can't record an overflow extent of its own: the only + * way to grow it is a contiguous extension of the last + * extent, so only accept free space starting exactly at + * goal instead of allocating anywhere and having to undo it. + */ + start =3D hfsplus_block_allocate(sb, goal + 1, goal, &len); + if (start !=3D goal) { res =3D -ENOSPC; goto out; } + } else { + start =3D hfsplus_block_allocate(sb, sbi->total_blocks, goal, &len); + if (start >=3D sbi->total_blocks) { + start =3D hfsplus_block_allocate(sb, goal, 0, &len); + if (start >=3D goal) { + res =3D -ENOSPC; + goto out; + } + } } =20 if (zeroout) { @@ -526,6 +563,20 @@ int hfsplus_file_extend(struct inode *inode, bool zero= out) return res; =20 insert_extent: + /* + * The fork-full precheck above keeps the extents overflow file's + * own inode from ever landing here with blocks already allocated; + * this is a backstop, so still free what was allocated rather + * than leak it. + */ + if (inode->i_ino =3D=3D HFSPLUS_EXT_CNID) { + if (hfsplus_block_free(sb, start, len)) + pr_err("can't free extent: start %u, count %u\n", + start, len); + res =3D -ENOSPC; + goto out; + } + hfs_dbg("insert new extent\n"); res =3D hfsplus_ext_write_extent_locked(inode); if (res) --=20 2.43.0 From nobody Fri Sep 25 12:38:43 2026 Received: from mail-pz2-f43.google.com (mail-pz2-f43.google.com [74.125.228.43]) (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 6D21847DFB9 for ; Sat, 12 Sep 2026 13:24:18 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=74.125.228.43 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789219459; cv=none; b=UycETkYEB8y4qPbZraAEk298DAzcQMCKxfIvJGV1papV2KOMDBPvgUhyB9RuxDcF0yRTIjch5Si6hfME3YRHZIJ3QxPOJKDHThjNXBE0Mga85Faj2rAbocefrcrCSrsi4STtiCyFk3Z5zPcRC8oe9GIJ9O3oCODPiyHqxJyj98g= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789219459; c=relaxed/simple; bh=OGnOLUANxAIKeUE2V8GE9FEBbxM0GNuhltrq3p8+tco=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=bj7sIUBGUhJYl93wDCP2i/UPksYXRBDIEWSFJkEfXNDAy62wOyg6joCZDmWKOsUsmxFqJW7w45bi/lBzVgZok8D45zOXgauqDjq+27NtgCtMoVtn2uCfqHZr8bbkxDojiVk9dV6MeR13pr22fB2zGG0Ghfwfy92UjNBlbI2G2Nw= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=mEaWen8Q; arc=none smtp.client-ip=74.125.228.43 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="mEaWen8Q" Received: by mail-pz2-f43.google.com with SMTP id d2e1a72fcca58-8693af0d7c4so1063022b3a.3 for ; Sat, 12 Sep 2026 06:24:18 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1789219458; x=1789824258; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=C4hkvii+G5WCvJx9xVx3ahKrNV+HTp+VsVBK0jsNMTU=; b=mEaWen8Q8lezvFby2whoKipBEOrNmLY7efLpcddu3yDZeOtyVXhQN5dm9L9n9VKwql 6UJI2HhOCKkUju+QyL4PewFLaMv6B1N8MPTtcH50ELZeLQm3Od4ayNNU+9Qhd9iDUoQH wSnUUMKBV4vUHCooQEhWLIg6eBF2JCJ1WKB1TX0dhT9tLfv9O+8gdwoz6I94H0quDMWV 3XA/q+bDVcLViD4RL1LfS+k9dakGTpdb8ZxVl05n1/Qqo014LRAp/XJjey4YUn2S6uDp l35SPpsfnlDFMpjPyTPuC1pgcjCkB1HXbpPK9aLJAp9QEB8mwiskScnc8m+poEXrf+Kj RhWw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1789219458; x=1789824258; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to:content-type; bh=C4hkvii+G5WCvJx9xVx3ahKrNV+HTp+VsVBK0jsNMTU=; b=i7kvwSaKZUUFOGfh9YedDYs2a2mWfifAC01Ye1/hu7nz+bYxz+JjdDZ23Kq2LkYXcV rBAeZzVjyiYg0CD3HLuQJL7w0l/PdK4xZvCHdedPGiSrMY8KXIIydJlYR9aXAFt7tiiR KKt9njP7/3WyCXTD9gk19zLzNMwRnW9uS25EZQKnZZDJzbfmEH23Zit0wHCdn4++K1Wo fLCsmnwwxUcqSsJriD2I0r5KPLW9hRg02auSfktdI2rSxM+KWlu+zf/G4SZEfQtznwRf WkvB9Z39dBAJ1enJGtcJHPdJOFLHzOYP6JGMvxf3fDRkDEWTuEP53PTmKdKQ3gqrfmx4 DyUw== X-Forwarded-Encrypted: i=1; AKwUvByM1bj5y0h/KIZ2FpjPKkVA9osx08EO9uLrFNYVNZzfeUjtiOIQTNIav0rb+xMnnQtIgOjPmYDaTZ67lgo=@vger.kernel.org X-Gm-Message-State: AFuF++nPP5IjJyzHPcKAUKlKEUeHMHAvWp6EMTucPV2CAEOMIENsDFAB zCpJh1Yz+dtTqQr+ipHc1HvYfb3CIyy7dUebOiUB8uLffYG+272y1LgOntj0xQ== X-Gm-Gg: AYBFou30hKxjJfXXs+Cx/fHqhMj2ZTYoRgOYcA93Ch2bERWqDEBax7ql9mxYJ2C13bn MFYg8bfEBq56xmNWRJ81P9elyjee7VnBZNqnUPgbZP2VbTpbJVuSAQUXhhPFipZ6myyuJqS7PsF mKDGzpB+VJXS9dFuQWui4/07sCahE/tA5WKHa0jwUo56nI4oA3RL19njaDEKiwD+tV6lX9glSKE EgfAvDeJsXTFkS7iz8l62sm3f6MMVaFz7C7aaovChJzfO6K4YPa7atyaNeETngTqL7uN5AOGzqC ZQWTUupE7TWntmHbyOkSNplC/kWAv3fxJDsEytsLmIagOo2jYOHaQxmyihvqenq6noOuM81OdwA 0Se4RDIdHpESkOIi2gPiT7H29DOzcoBdiXokIYZtYd/3/8TQMkBg5SKsKQ4+5IgLL4nLWqYyb1/ mY1qOkPfHV4eZvmO8QPuc4hUBj/TOg7VsjQupmTZpvzRLvJx3v0SIbu2qMubsWLyMw7mAHYA1pU AY04AqaN2aAOHWlzvDtUaDaWz6D X-Received: by 2002:a05:6a00:4c97:b0:869:c1c8:97 with SMTP id d2e1a72fcca58-86b338d0c45mr15147029b3a.17.1789219457776; Sat, 12 Sep 2026 06:24:17 -0700 (PDT) Received: from thangnn-ASUS.. ([2405:4802:1d38:5c70:dfd9:c41e:7c9b:c69]) by smtp.gmail.com with ESMTPSA id d2e1a72fcca58-86b292c9839sm2410819b3a.33.2026.09.12.06.24.15 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sat, 12 Sep 2026 06:24:17 -0700 (PDT) From: Nguyen Ngoc Thang To: Viacheslav Dubeyko Cc: John Paul Adrian Glaubitz , Yangtao Li , linux-fsdevel@vger.kernel.org, linux-kernel@vger.kernel.org, syzbot+f8ce6c197125ab9d72ce@syzkaller.appspotmail.com Subject: [PATCH v4 2/2] hfsplus: validate b-tree fork extents at mount time Date: Sat, 12 Sep 2026 20:24:07 +0700 Message-ID: <20260912132407.16856-3-ngocthang2710.1999@gmail.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260912132407.16856-1-ngocthang2710.1999@gmail.com> References: <20260912132407.16856-1-ngocthang2710.1999@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" A hfsplus_check_fork() pass over a special file's eight fork extents, called from hfs_btree_open() for the extents, catalog and attributes trees: - block_count =3D=3D 0 but start_block !=3D 0: garbage left in a slot that should be blank (this is what the syzbot-reported image has in the extents overflow file's fork, slots 3 and 6); - start_block + block_count > sbi->total_blocks: an extent pointing past the end of the volume; - a non-zero extent following a zero one: a hole in the used range. If the first extent itself fails these checks, the b-tree's location on disk is unknown and there is nothing to recover, so hfs_btree_open() fails as it already does for the other structural checks in that function, and the mount fails. If only a later extent is affected, the tree can still be opened (its first extent, and hence its root node, is fine); mark it corrupt and let the caller decide. hfsplus_fill_super() forces the volume read-only in that case, and hfsplus_reconfigure() checks the same per-tree flag on remount instead of re-deriving it, refusing to go back to read-write. attr_tree may be NULL (volumes without an attributes fork), so both checks guard for that. This also gives the previous patch's hfsplus_file_extend() fix a mount-time backstop: a fuzzed or damaged extents overflow fork like the one in the syzbot report is caught here before any write ever reaches it. Reported-by: syzbot+f8ce6c197125ab9d72ce@syzkaller.appspotmail.com Signed-off-by: Nguyen Ngoc Thang Co-Authored-By: Claude Sonnet 5 --- fs/hfsplus/btree.c | 12 ++++++++++++ fs/hfsplus/extents.c | 37 +++++++++++++++++++++++++++++++++++++ fs/hfsplus/hfsplus_fs.h | 4 ++++ fs/hfsplus/super.c | 9 +++++++++ 4 files changed, 62 insertions(+) diff --git a/fs/hfsplus/btree.c b/fs/hfsplus/btree.c index 2ea8cd5658e1..0a05ade53070 100644 --- a/fs/hfsplus/btree.c +++ b/fs/hfsplus/btree.c @@ -293,6 +293,18 @@ struct hfs_btree *hfs_btree_open(struct super_block *s= b, u32 id) goto free_inode; } =20 + switch (hfsplus_check_fork(sb, HFSPLUS_I(tree->inode)->first_extents)) { + case -EIO: + pr_err("%s (cnid 0x%x) fork's first extent is corrupt\n", + hfs_btree_name(id), id); + goto free_inode; + case 1: + pr_warn("%s (cnid 0x%x) fork has corrupt extents, forcing read-only.\n", + hfs_btree_name(id), id); + tree->corrupt =3D true; + break; + } + mapping =3D tree->inode->i_mapping; page =3D read_mapping_page(mapping, 0, NULL); if (IS_ERR(page)) diff --git a/fs/hfsplus/extents.c b/fs/hfsplus/extents.c index 236f2d9a7a2d..a9303ce5bf8f 100644 --- a/fs/hfsplus/extents.c +++ b/fs/hfsplus/extents.c @@ -95,6 +95,43 @@ static bool hfsplus_ext_fork_full(struct hfsplus_extent = *ext) return true; } =20 +/* + * Check a fork's eight extents for the corruption a fuzzed or damaged + * volume header can contain: garbage in a slot that should be unused, + * an extent that runs past the end of the volume, or a used extent + * following an unused one. + * + * Returns 0 if the fork is fully consistent, 1 if only extents after + * the first are affected (the b-tree can still be located, so it's + * safe to mount read-only), or -EIO if the first extent itself is + * unusable. + */ +int hfsplus_check_fork(struct super_block *sb, struct hfsplus_extent *ext) +{ + struct hfsplus_sb_info *sbi =3D HFSPLUS_SB(sb); + bool seen_hole =3D false; + int i; + + for (i =3D 0; i < 8; i++, ext++) { + u32 start =3D be32_to_cpu(ext->start_block); + u32 count =3D be32_to_cpu(ext->block_count); + bool bad; + + if (!count) { + bad =3D start !=3D 0; + seen_hole =3D true; + } else { + bad =3D seen_hole || start + count < start || + start + count > sbi->total_blocks; + } + + if (bad) + return i ? 1 : -EIO; + } + + return 0; +} + static int __hfsplus_ext_write_extent(struct inode *inode, struct hfs_find_data *fd) { diff --git a/fs/hfsplus/hfsplus_fs.h b/fs/hfsplus/hfsplus_fs.h index 1e5b58e6a13f..8d47219e67d3 100644 --- a/fs/hfsplus/hfsplus_fs.h +++ b/fs/hfsplus/hfsplus_fs.h @@ -56,6 +56,9 @@ struct hfs_btree { unsigned int max_key_len; unsigned int depth; =20 + /* fork extents past the first were found corrupt at open time */ + bool corrupt; + struct mutex tree_lock; =20 unsigned int pages_per_bnode; @@ -440,6 +443,7 @@ int hfsplus_free_fork(struct super_block *sb, u32 cnid, struct hfsplus_fork_raw *fork, int type); int hfsplus_file_extend(struct inode *inode, bool zeroout); void hfsplus_file_truncate(struct inode *inode); +int hfsplus_check_fork(struct super_block *sb, struct hfsplus_extent *ext); =20 /* inode.c */ extern const struct address_space_operations hfsplus_aops; diff --git a/fs/hfsplus/super.c b/fs/hfsplus/super.c index ff7d6b3336a6..b65edb8ee589 100644 --- a/fs/hfsplus/super.c +++ b/fs/hfsplus/super.c @@ -400,6 +400,11 @@ static int hfsplus_reconfigure(struct fs_context *fc) pr_warn("filesystem is marked journaled, leaving read-only.\n"); sb->s_flags |=3D SB_RDONLY; fc->sb_flags |=3D SB_RDONLY; + } else if (sbi->ext_tree->corrupt || sbi->cat_tree->corrupt || + (sbi->attr_tree && sbi->attr_tree->corrupt)) { + pr_warn("a b-tree fork was corrupt at mount time, leaving read-only.\n"= ); + sb->s_flags |=3D SB_RDONLY; + fc->sb_flags |=3D SB_RDONLY; } } return 0; @@ -564,6 +569,10 @@ static int hfsplus_fill_super(struct super_block *sb, = struct fs_context *fc) } sb->s_xattr =3D hfsplus_xattr_handlers; =20 + if (sbi->ext_tree->corrupt || sbi->cat_tree->corrupt || + (sbi->attr_tree && sbi->attr_tree->corrupt)) + sb->s_flags |=3D SB_RDONLY; + inode =3D hfsplus_iget(sb, HFSPLUS_ALLOC_CNID); if (IS_ERR(inode)) { pr_err("failed to load allocation file\n"); --=20 2.43.0