Re: [PATCH v2 2/2] hfsplus: validate the wrapper and partition map before following them
From: Viacheslav Dubeyko
Date: Thu Oct 01 2026 - 17:20:17 EST
On Fri, 2026-10-02 at 00:21 +0800, Matthias Goergens wrote:
> hfsplus_read_wrapper() rereads the volume header through a bare "goto
> reread", following an HFS wrapper's embedded-volume descriptor or, if
> the header matches neither signature, the partition map found by
> hfs_part_find(). hfsplus_read_mdb() does not check the offset it
> returns, and hfs_part_find() does not check the start of a new-style
> map
> entry, so a descriptor or entry with a zero offset leaves part_start
> where it was and the mount loops forever.
>
> Check both where they are parsed. TN1150 places the embedded volume
> in
> the wrapper's allocation blocks, and the boot blocks, MDB and volume
> bitmap of an HFS volume are not part of any allocation block. So
> hfsplus_read_mdb() now rejects a wrapper whose allocation blocks
> start
> at or before its MDB (drAlBlSt <= 2), whose embedded volume is empty,
> or
> whose embedded volume ends past the wrapper's last allocation block
> (drNmAlBlks). hfs_part_find() gets the same check as in hfs: a
> partition must be non-empty, start inside the device and, for a
> new-style map, start after the map, which begins at block 1 and whose
> size the first entry's pmMapBlkCnt gives (TN1189).
>
> A wrapper hop now moves part_start forward by at least three sectors,
> and a partition-table hop moves it past the map entries it has read.
> So
> the loop ends, at the latest when a read goes past the end of the
> device, and the maps read on the way do not overlap.
>
> Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
> Cc: stable@xxxxxxxxxxxxxxx
> Signed-off-by: Matthias Goergens <matthias.goergens@xxxxxxxxx>
> ---
> v2: check the wrapper in hfsplus_read_mdb() and the entries in
> hfs_part_find() instead of limiting the hops in
> hfsplus_read_wrapper().
>
> A wrapper whose embedded-volume descriptor points back at itself
> hangs
> the mount without this patch and fails at once with it:
>
> img=hfsplus-wrapper-loop.img
> put() { printf "$2" | dd of=$img bs=1 seek=$1 conv=notrunc
> status=none; }
> truncate --size=64K $img
> put $((1024 + 0x00)) '\x42\x44' # drSigWord 'BD'
> put $((1024 + 0x0a)) '\x82\x00' # drAtrb: SLOCK | SPARED
> put $((1024 + 0x14)) '\x00\x00\x02\x00' # drAlBlkSiz 512
> put $((1024 + 0x7c)) '\x48\x2b' # drEmbedSigWord 'H+'
> put $((1024 + 0x7e)) '\x00\x00\x00\x64' # drEmbedExtent: start 0,
> count 100
> mount -o ro,loop -t hfsplus $img /mnt
>
> The partition map from the hfs patch hangs an hfsplus mount the same
> way, with "-t hfsplus".
>
> fs/hfsplus/part_tbl.c | 24 +++++++++++++++++++++++-
> fs/hfsplus/wrapper.c | 16 +++++++++++++++-
> include/linux/hfs_common.h | 1 +
> 3 files changed, 39 insertions(+), 2 deletions(-)
>
> diff --git a/fs/hfsplus/part_tbl.c b/fs/hfsplus/part_tbl.c
> index 9ec21664eda6b..ceba3139f1f56 100644
> --- a/fs/hfsplus/part_tbl.c
> +++ b/fs/hfsplus/part_tbl.c
> @@ -67,6 +67,22 @@ struct old_pmap {
> } pdEntry[42];
> } __packed;
>
> +/*
> + * Check a partition map entry before following it. The partition
> must
> + * be non-empty, start inside the device and start at or after
> @first:
> + * after the driver descriptor map in block 0 for an old-style map,
> and
> + * after the whole of a new-style map, whose size the first entry's
> + * pmMapBlkCnt gives (TN1189). Every hop then moves forward, past
> the
> + * map entries just read, so hfsplus_read_wrapper() cannot loop and
> + * reads each block of a map at most once.
> + */
> +static bool hfs_part_valid(struct super_block *sb, sector_t base,
> + u64 first, u32 start, u32 size)
> +{
> + return start >= first && size &&
> + base + start < bdev_nr_sectors(sb->s_bdev);
> +}
Please, see my comments for HFS patch. I have pretty the same comments
here.
> +
> static int hfs_parse_old_pmap(struct super_block *sb, struct
> old_pmap *pm,
> sector_t *part_start, sector_t *part_size)
> {
> @@ -76,7 +92,9 @@ static int hfs_parse_old_pmap(struct super_block
> *sb, struct old_pmap *pm,
> for (i = 0; i < 42; i++) {
> struct old_pmap_entry *p = &pm->pdEntry[i];
>
> - if (p->pdStart && p->pdSize &&
> + if (hfs_part_valid(sb, *part_start, HFS_DD_BLK + 1,
> + be32_to_cpu(p->pdStart),
> + be32_to_cpu(p->pdSize)) &&
> p->pdFSID == cpu_to_be32(0x54465331)/*"TFS1"*/
> &&
> (sbi->part < 0 || sbi->part == i)) {
> *part_start += be32_to_cpu(p->pdStart);
> @@ -99,6 +117,10 @@ static int hfs_parse_new_pmap(struct super_block
> *sb, void *buf,
>
> do {
> if (!memcmp(pm->pmPartType, "Apple_HFS", 9) &&
> + hfs_part_valid(sb, *part_start,
> + HFS_PMAP_BLK + (u64)size,
> + be32_to_cpu(pm->pmPyPartStart),
> + be32_to_cpu(pm->pmPartBlkCnt)) &&
> (sbi->part < 0 || sbi->part == i)) {
> *part_start += be32_to_cpu(pm-
> >pmPyPartStart);
> *part_size = be32_to_cpu(pm->pmPartBlkCnt);
> diff --git a/fs/hfsplus/wrapper.c b/fs/hfsplus/wrapper.c
> index 30cf4fe78b3d2..35cd2563dfdfe 100644
> --- a/fs/hfsplus/wrapper.c
> +++ b/fs/hfsplus/wrapper.c
> @@ -66,7 +66,7 @@ int hfsplus_submit_bio(struct super_block *sb,
> sector_t sector,
> static int hfsplus_read_mdb(void *bufptr, struct hfsplus_wd *wd)
> {
> u32 extent;
> - u16 attrib;
> + u16 attrib, nmalblks;
> __be16 sig;
>
> sig = *(__be16 *)(bufptr + HFSP_WRAPOFF_EMBEDSIG);
> @@ -87,11 +87,25 @@ static int hfsplus_read_mdb(void *bufptr, struct
> hfsplus_wd *wd)
> return 0;
> wd->ablk_start =
> be16_to_cpu(*(__be16 *)(bufptr +
> HFSP_WRAPOFF_ABLKSTART));
> + nmalblks = be16_to_cpu(*(__be16 *)(bufptr +
> HFSP_WRAPOFF_NMALBLKS));
>
> extent = get_unaligned_be32(bufptr + HFSP_WRAPOFF_EMBEDEXT);
> wd->embed_start = (extent >> 16) & 0xFFFF;
> wd->embed_count = extent & 0xFFFF;
>
> + /*
> + * The boot blocks, MDB and volume bitmap of an HFS volume
> are not
> + * part of any allocation block, and the embedded volume
> occupies
> + * allocation blocks of the wrapper (TN1150). So the
> embedded
> + * volume starts after the wrapper's MDB and ends inside the
> + * wrapper.
> + */
> + if (wd->ablk_start <= HFS_MDB_BLK)
> + return 0;
> + if (!wd->embed_count ||
> + wd->embed_start + wd->embed_count > nmalblks)
> + return 0;
> +
> return 1;
> }
>
> diff --git a/include/linux/hfs_common.h b/include/linux/hfs_common.h
> index d6a615e74b26e..123475efe5b37 100644
> --- a/include/linux/hfs_common.h
> +++ b/include/linux/hfs_common.h
> @@ -44,6 +44,7 @@
>
> #define HFSP_WRAPOFF_SIG 0x00
> #define HFSP_WRAPOFF_ATTRIB 0x0A
> +#define HFSP_WRAPOFF_NMALBLKS 0x12
Does this value is based on specification? Where does it come from?
Thanks,
Slava.
> #define HFSP_WRAPOFF_ABLKSIZE 0x14
> #define HFSP_WRAPOFF_ABLKSTART 0x1C
> #define HFSP_WRAPOFF_EMBEDSIG 0x7C