From mboxrd@z Thu Jan 1 00:00:00 1970 Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.remote-ip=100.103.45.18 ARC-Seal: i=1; d=subspace.kernel.org; s=arc20261015; a=rsa-sha256; cv=none; t=1794591256; b=gf4ZWuIthlTcEc/N39mOTFtttGBFZOd5Q2Vt59h0SHze/fjaBpAaco7T69ZhrPFzFK0Z bkwNsYnlW0nePEAk3K5DZoipi5bYsIljOkd/v9oQHlA3z0dmZDGkaRAMh1T12WuDsJONR AACylfWI0+qZn8nts9d91L89TA12KczCz7HTcu3m4NAVN4FqPMYjJe6rNb2dROosQRZYM HMMsueVTLEfyQ2hF3XR5T+2Qy7f/n5dsz87eQhPoMeFURMWA9YWlPpx+GU1K1jH362nF8 lDLYU3iK+9loRtkyFQQfBVtoqVmVSfx6Vho0ebtIPAa4fFH1h9fzOntiJ0C/LP3d0fw== ARC-Message-Signature: i=1; d=subspace.kernel.org; s=arc20261015; a=rsa-sha256; c=relaxed/relaxed; t=1794591256; h=Received:DKIM-Signature:From:To:Cc:Subject:Date:Message-ID:X-Mailer: In-Reply-To:References:User-Agent:X-stable:X-Patchwork-Hint: MIME-Version:Content-Transfer-Encoding; bh=gponNTkXNsWIb7LoZNSWqxLl4yb14dRWauypp8RADik=; b=5i7U2zDYUeR/hCe6mI1qrhWkyQUHDtApqLo0iFcBzSvxTTeUCKSiNxXtkAODlkEf+zop jWl8bc2oXIgK08Af75Y9E/hoh7J1fLF74YYx9YeFUnnJpW63qnx9QzbGoZ31QNg9F7fOC 1xYmx0RTOfZu9yGGlSfrKcQWOrJrcuHOJqb66qSPVvds/6ezQONpJcgjf75NVOgMVGAYX 9BJ2ybWtam67Ipb9KjypgXm/xEQyje1Ptxig5Nh8FOGUetHIfvyIlTzEbOguac4kTB0L1 nv5bdQ4QQZtJ+lBXzJ68JtEC77guGOT8eMKnmBHuN8Gbi3sTa6+1RlzdNRDPF+Zwdvg== ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass header.d=linuxfoundation.org header.i=@linuxfoundation.org header.a=rsa-sha256 header.s=korg header.b=nIPoOmCD; arc=none smtp.remote-ip=100.103.45.18 Received: from smtp.kernel.org (quasi.space.kernel.org [100.103.45.18]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature ECDSA (prime256v1)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id DE44D6076F82; Fri, 13 Nov 2026 17:34:16 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=linuxfoundation.org header.i=@linuxfoundation.org header.a=rsa-sha256 header.s=korg header.b=nIPoOmCD Received: by smtp.kernel.org (Postfix) with ESMTPSA id 8B69C1F000C4; Fri, 13 Nov 2026 17:34:16 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=linuxfoundation.org; s=korg; t=1794591256; bh=gponNTkXNsWIb7LoZNSWqxLl4yb14dRWauypp8RADik=; h=From:To:Cc:Subject:Date:In-Reply-To:References; b=nIPoOmCDynk5WlOrrJ6B3ZIUig82PHuD06D8Ke+KZQOaJsjw5pWK75Tmehk9k8jK+ fK3Odav0W6PZj2Zkml7w+XSiXn1TH7o+fmWInBHPUXqeKBKkYFSUMqLcpJuuhDmhUK ce0n8X/jVQ88FR9iRwXmnsWs3TLyX9n1mlp9uELw= From: Greg Kroah-Hartman To: stable@vger.kernel.org Cc: Greg Kroah-Hartman , patches@lists.linux.dev, Matthias Goergens , Jan Kara , Sasha Levin Subject: [PATCH 6.12 379/670] udf: check the unallocated space table when it is loaded Date: Fri, 13 Nov 2026 10:49:03 -0500 Message-ID: <20261113153824.623858314@linuxfoundation.org> X-Mailer: git-send-email 2.56.0 In-Reply-To: <20261113153811.177190860@linuxfoundation.org> References: <20261113153811.177190860@linuxfoundation.org> User-Agent: quilt/0.69 X-stable: review X-Patchwork-Hint: ignore X-Mailing-List: patches@lists.linux.dev List-Id: List-Post: List-Help: List-Subscribe: List-Unsubscribe: List-Owner: Precedence: list MIME-Version: 1.0 Content-Transfer-Encoding: 8bit 6.12-stable review patch. If anyone has any objections, please let me know. ------------------ From: Matthias Goergens [ Upstream commit e614c3e35a6699ed691b4d17e1ec4230e34aa3eb ] udf_table_new_block() hands out the first block of the extent closest to its goal and trusts that the extent covers at least one whole block inside the partition. Nothing checks that. A zero-length extent makes it return the block the extent points at, which need not be free, and the subtraction of one block then underflows the length into the type bits, turning the entry into a bogus continuation. An extent running past the end of the partition makes it return blocks behind the partition. Such tables are not only found on crafted images: until the previous commit, udf_table_new_block() itself left zero-length extents behind in tables written by mkudffs, and then converted them into extents running past the partition. Check every extent once when the table is loaded, and refuse read-write access if one is empty, is not a whole number of blocks, or does not lie inside the partition, in the same way as for other allocation information the kernel cannot use. Check first that the table is an Unallocated Space Entry at all: the walk starts at the offset of the allocation descriptors in one, and for a file entry that offset lies before the inode's in-memory copy of the descriptors. Treat a chain of allocation extent descriptors that leads back to itself the same way: the walk would otherwise return the same extents forever, and the mount would never finish. Let a fatal signal interrupt the walk and fail the mount. Refusing read-write access to an empty extent is deliberate. It shows that an old kernel took the last block but left the entry behind. If consumed again, the entry returns an in-use block and turns into a huge bogus extent. Refusing the table before that happens avoids this second allocation; the filesystem can still be mounted read-only. The check cannot detect every overlap that may already exist in a damaged table. This patch should be backported together with the previous one: that patch stops creating new empty extents, and this one rejects existing ones before they are consumed again. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Cc: stable@vger.kernel.org Signed-off-by: Matthias Goergens Link: https://patch.msgid.link/81233decd91f4a48bf4d74e3d15b547c7711e39f.1793349907.git.matthias.goergens@gmail.com Signed-off-by: Jan Kara Signed-off-by: Sasha Levin --- fs/udf/super.c | 80 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 79 insertions(+), 1 deletion(-) diff --git a/fs/udf/super.c b/fs/udf/super.c index 556540f2797ae..8d3d758714da2 100644 --- a/fs/udf/super.c +++ b/fs/udf/super.c @@ -1105,6 +1105,73 @@ static int check_partition_desc(struct super_block *sb, return 0; } +/* + * Check the Unallocated Space Table once when it is loaded: the allocator + * hands out blocks from the start of each extent and trusts that every + * extent covers at least one whole block inside the partition. + */ +static int udf_check_unalloc_table(struct super_block *sb, + struct inode *table, u32 partition_len) +{ + struct extent_position epos = { + .block = UDF_I(table)->i_location, + .offset = sizeof(struct unallocSpaceEntry), + }; + struct kernel_lb_addr eloc; + uint32_t elen, blocks; + struct kernel_lb_addr seen_block = {}; + uint32_t seen_offset = 0; + u64 steps = 0, period = 1; + int8_t etype; + int ret; + + /* The walk below assumes the layout of an Unallocated Space Entry */ + if (!UDF_I(table)->i_use) { + udf_err(sb, "unallocated space table is not an unallocated space entry\n"); + return -EFSCORRUPTED; + } + + while ((ret = udf_next_aext(table, &epos, &eloc, &elen, &etype, 1)) > 0) { + blocks = elen >> sb->s_blocksize_bits; + if (!blocks || (elen & (sb->s_blocksize - 1)) || + eloc.logicalBlockNum >= partition_len || + blocks > partition_len - eloc.logicalBlockNum) { + udf_err(sb, "invalid unallocated space table extent (block %u, length %u)\n", + eloc.logicalBlockNum, elen); + ret = -EFSCORRUPTED; + break; + } + /* + * A chain of allocation extents can lead back to itself, and + * then the walk returns the same extents forever. Remember a + * position at doubling intervals; a walk that comes back to it + * is in a loop. + */ + if (epos.block.logicalBlockNum == seen_block.logicalBlockNum && + epos.block.partitionReferenceNum == + seen_block.partitionReferenceNum && + epos.offset == seen_offset) { + udf_err(sb, "unallocated space table loops back on itself\n"); + ret = -EFSCORRUPTED; + break; + } + if (++steps == period) { + seen_block = epos.block; + seen_offset = epos.offset; + steps = 0; + period <<= 1; + } + if (fatal_signal_pending(current)) { + udf_err(sb, "interrupted while checking the unallocated space table\n"); + ret = -EINTR; + break; + } + cond_resched(); + } + brelse(epos.bh); + return ret; +} + static int udf_fill_partdesc_info(struct super_block *sb, struct partitionDesc *p, int p_index) { @@ -1165,6 +1232,16 @@ static int udf_fill_partdesc_info(struct super_block *sb, p_index); return PTR_ERR(inode); } + err = udf_check_unalloc_table(sb, inode, map->s_partition_len); + if (err) { + iput(inode); + if (err == -EINTR) + return err; + if (!sb_rdonly(sb)) + return -EACCES; + UDF_SET_FLAG(sb, UDF_FLAG_RW_INCOMPAT); + return 0; + } map->s_uspace.s_table = inode; map->s_partition_flags |= UDF_PART_FLAG_UNALLOC_TABLE; udf_debug("unallocSpaceTable (part %d) @ %lu\n", @@ -2228,8 +2305,9 @@ static int udf_fill_super(struct super_block *sb, struct fs_context *fc) /* * EACCES is special - we want to propagate to * upper layers that we cannot handle RW mount. + * EINTR means that a fatal signal is pending. */ - if (ret == -EACCES) + if (ret == -EACCES || ret == -EINTR) break; } else break; -- 2.53.0