mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH] adfs: validate zone_spare in adfs_checkdiscrecord()
@ 2026-08-03  3:26 Yuejie Shi
  0 siblings, 0 replies; only message in thread
From: Yuejie Shi @ 2026-08-03  3:26 UTC (permalink / raw)
  To: Russell King, Christian Brauner, Alexander Viro
  Cc: Bae Yeonju, Jan Kara, linux-fsdevel, linux-kernel, stable

The size of a free space map zone is derived from two on-disk fields:

	zone_size = (8 << dr->log2secsize) - le16_to_cpu(dr->zone_spare);

log2secsize is validated by adfs_checkdiscrecord() to be 8, 9 or 10, so
the left-hand term is at most 8192.  zone_spare is a plain 16-bit
on-disk field that nothing checks at all, so any value above the number
of bits in a zone makes this unsigned subtraction wrap to nearly 4G.

zone_size is then used as the scan limit of every map zone:

	dm[zone].dm_endbit = 32 + zone_size;

and both bitmap walkers use dm_endbit as the bound of a buffer_head that
is only (1 << log2secsize) bytes long:

	fragend = find_next_bit_le(map, endbit, start + idlen);

so lookup_zone() and scan_free_map() scan hundreds of megabytes past the
end of the map buffer.  The fragment end they come back with feeds
adfs_map_lookup()'s returned block number, so this is not only a crash:
it is an out-of-bounds read whose result steers which disc block is read
next.

The same expression also divides:

	asb->s_ids_per_zone = zone_size / (asb->s_idlen + 1);

and adfs_map_lookup() does "zone = frag_id / asb->s_ids_per_zone", so a
zone_spare that leaves fewer than idlen + 1 bits is a divide-by-zero as
well.

Reject both in adfs_checkdiscrecord(), which is the single gate both
adfs_validate_bblk() and adfs_validate_dr0() run before adfs_read_map()
is reached, by requiring zone_spare to leave room for at least one
fragment id.

Mounting a crafted image requires CAP_SYS_ADMIN in the initial user
namespace -- ADFS is FS_REQUIRES_DEV and not FS_USERNS_MOUNT -- so a
plain unprivileged local user cannot reach this.  The realistic threat
model is untrusted media and images: automounted removable media,
container/VM disk images opened by a privileged mounting agent, and
forensic or CI tooling.  Once mounted read-only, an ordinary
getdents64() is enough to fire it.

  # 4 GiB sparse image, log2secsize=10, nzones=2, zone_spare=9000
  mount -t adfs -o ro /dev/vda /mnt
  ls -la /mnt

  BUG: KASAN: use-after-free in _find_next_bit+0x68/0xd0
  Read of size 8 at addr ffff0000ccc6d000 by task ls/136
   __asan_load8+0xcc/0xd0
   _find_next_bit+0x68/0xd0
   adfs_map_lookup+0x1b8/0x378
   adfs_dir_read_buffers+0x178/0x2b0
   adfs_fplus_read+0x50/0x2c8
   adfs_dir_read_inode+0xa8/0x120
   adfs_iterate+0xf0/0x2c0
   iterate_dir+0x12c/0x400
   __arm64_sys_getdents64+0xf0/0x230

(KASAN calls it use-after-free only because the scan leaves the
buffer_head's page and lands in freed pages; there is no lifetime bug in
ADFS.)  Hundreds of further reports follow, including reads from inside
adfs_map_lookup() itself.

Real ADFS images use a small zone_spare -- a handful of bits of padding
per zone -- so this rejects nothing that mounts today.

Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
Cc: stable@vger.kernel.org
Signed-off-by: Yuejie Shi <syjcnss@gmail.com>
---
 fs/adfs/super.c |  9 +++++++++
 1 file changed, 9 insertions(+)

diff --git a/fs/adfs/super.c b/fs/adfs/super.c
index a4cd0a5159dd..1ab810f96edb 100644
--- a/fs/adfs/super.c
+++ b/fs/adfs/super.c
@@ -82,6 +82,15 @@ static int adfs_checkdiscrecord(struct adfs_discrecord
 	if (dr->idlen > max_idlen)
 		return 1;
 
+	/*
+	 * zone_spare is subtracted from the number of bits in a map zone to
+	 * give the zone size, which is computed as an unsigned quantity and
+	 * is also the divisor for the number of ids per zone.  Require it to
+	 * leave room for at least one fragment id.
+	 */
+	if (le16_to_cpu(dr->zone_spare) > (8 << dr->log2secsize) - dr->idlen - 1)
+		return 1;
+
 	/* reserved bytes should be zero */
 	for (i = 0; i < sizeof(dr->unused52); i++)
 		if (dr->unused52[i] != 0)
--
2.51.0

^ permalink raw reply	[flat|nested] only message in thread

only message in thread, other threads:[~2026-08-03  3:26 UTC | newest]

Thread overview: (only message) (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-08-03  3:26 [PATCH] adfs: validate zone_spare in adfs_checkdiscrecord() Yuejie Shi

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®