libfsimage/iso9660: harden Rock Ridge SUSP parsing against malformed lengths

The directory and Rock Ridge / SUSP walk in iso9660_dir() derives several
lengths directly from attacker-controlled on-disk fields without validating
them.  libfsimage is used by pygrub, which parses the filesystem of an
untrusted guest disk image from dom0, so these are reachable across a trust
boundary.

Five related problems are addressed:

  * The directory record loop advances by

        idr = (char *)idr + idr->length.l

    and only stops on length.l == 0.  A record whose length is smaller than
    the fixed part of the on-disk layout cannot hold its own mandatory
    fields, yet the body still reads name_len/extent/size and computes the
    System Use area length from it.  Require length to cover at least the
    fixed record (sizeof(*idr) - sizeof(idr->name)) before entering the body.

    This is CVE-2026-42494.

  * The System Use area length is computed before the inner loop as

        rr_len = idr->length.l - idr->name_len.l
                 - sizeof(struct iso_directory_record) + sizeof(idr->name);

    in unsigned arithmetic.  If length.l is smaller than name_len.l plus the
    fixed record size, rr_len underflows to a huge value and the whole SUSP
    walk runs off the directory buffer.  Guard the subtraction and treat such
    records as having no System Use area.

    This is CVE-2026-42495.

  * Inside the loop, each entry is consumed with

        rr_len -= rr_ptr.rr->len;
        rr_ptr.ptr += rr_ptr.rr->len;

    with no lower or upper bound on the entry's own len byte.  A len of 0
    spins forever; a len greater than the remaining rr_len underflows it and
    walks past the buffer.  Validate 4 <= len <= rr_len at the top of the
    loop and stop on violation: a structurally broken entry stream cannot be
    advanced reliably, so continuing is not meaningful.

    This is CVE-2026-62423.

  * The NM handler subtracted the 5-byte SUSP/NM header from len without a
    lower-bound check, underflowing name_len (the original report).  The
    generic check above only guarantees len >= 4; NM has an extra flags byte,
    so keep an NM-specific len >= 5 check.

    This is CVE-2026-62424.

  * The CE continuation resets rr_ptr/rr_len from ce.offset and ce.size, both
    image-controlled, into the fixed single-sector RRCONT_BUF with no bounds
    check.  Reject a window that does not fit in the buffer.

    This is CVE-2026-62425.

This is XSA-497.

Signed-off-by: Syed Abdul Khaliq <abdul@bugqore.com>
Reviewed-by: Jan Beulich <jbeulich@suse.com>
This commit is contained in:
Syed Abdul Khaliq authored and Andrew Cooper committed 2026-07-28 12:40:10 +01:00
1 parent 40c59b6c29
commit fd11acab3d
1 file changed
+46 -3
+46 -3
View File
@@ -180,7 +180,15 @@ iso9660_dir (fsi_file_t *ffi, char *dirname)
extent++;
idr = (struct iso_directory_record *)DIRREC;
for (; idr->length.l > 0;
/*
* length is taken verbatim from the (untrusted) image. A record
* shorter than the fixed part of the on-disk layout cannot hold its
* own mandatory fields (name_len, extent, size, ...), which the loop
* body reads below; stop the walk rather than dereference past it.
*/
for (; idr->length.l >= sizeof(*idr) - sizeof(idr->name)
&& idr->length.l
>= sizeof(*idr) - sizeof(idr->name) + idr->name_len.l;
idr = (struct iso_directory_record *)((char *)idr + idr->length.l) )
{
const char *name = (const char *)idr->name;
@@ -201,21 +209,39 @@ iso9660_dir (fsi_file_t *ffi, char *dirname)
}
/*
* Parse Rock-Ridge extension
* Parse Rock-Ridge extension.
*
* length and name_len are taken verbatim from the (untrusted)
* image. Reject a record whose name would already overrun the
* fixed on-disk layout, so that the System Use area length does
* not underflow to a huge value below.
*/
if (idr->length.l < idr->name_len.l
+ sizeof(struct iso_directory_record) - sizeof(idr->name))
rr_len = 0;
else
rr_len = (idr->length.l - idr->name_len.l
- sizeof(struct iso_directory_record)
+ sizeof(idr->name));
rr_ptr.ptr = ((char *)idr + idr->name_len.l
+ sizeof(struct iso_directory_record)
- sizeof(idr->name));
if (rr_ptr.i & 1)
if ((rr_ptr.i & 1) && rr_len)
rr_ptr.i++, rr_len--;
ce_ptr = NULL;
rr_flag = RR_FLAG_NM | RR_FLAG_PX /*| RR_FLAG_SL*/;
while (rr_len >= 4)
{
/*
* A SUSP entry is at least 4 bytes (signature, length,
* version) and must fit in the remaining System Use area.
* A shorter or overlong len is unparseable: stop, rather
* than spin forever (len == 0) or underflow rr_len in the
* advance below (len > rr_len).
*/
if (rr_ptr.rr->len < 4 || rr_ptr.rr->len > rr_len)
break;
if (rr_ptr.rr->version != 1)
{
#ifndef STAGE1_5
@@ -236,9 +262,17 @@ iso9660_dir (fsi_file_t *ffi, char *dirname)
rr_flag &= rr_ptr.rr->u.rr.flags.l;
break;
case RRMAGIC('N', 'M'):
/*
* The generic check above only guarantees len >= 4;
* NM additionally has a flags byte, so len must be at
* least 5 for name_len not to underflow.
*/
if (rr_ptr.rr->len >= (4+sizeof(struct NM)))
{
name = (const char *)rr_ptr.rr->u.nm.name;
name_len = rr_ptr.rr->len - (4+sizeof(struct NM));
rr_flag &= ~RR_FLAG_NM;
}
break;
case RRMAGIC('P', 'X'):
if (rr_ptr.rr->len >= (4+sizeof(struct PX)))
@@ -339,6 +373,15 @@ iso9660_dir (fsi_file_t *ffi, char *dirname)
memcpy(NAME_BUF, name, name_len);
name = (const char *)NAME_BUF;
}
/*
* offset and size are image-controlled; the loaded
* continuation lives in a single-sector buffer. Bail
* out if the referenced window does not fit inside it.
*/
if (ce_ptr->u.ce.offset.l >= ISO_SECTOR_SIZE
|| ce_ptr->u.ce.size.l
> ISO_SECTOR_SIZE - ce_ptr->u.ce.offset.l)
break;
rr_ptr.ptr = (char *)RRCONT_BUF + ce_ptr->u.ce.offset.l;
rr_len = ce_ptr->u.ce.size.l;
if (!iso9660_devread(ffi, ce_ptr->u.ce.extent.l, 0, ISO_SECTOR_SIZE, (char *)RRCONT_BUF))
Reference in new issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.