From a0189b9fe26b56a6b423d6bf9d775ccf46bd3f0f Mon Sep 17 00:00:00 2001 From: "Val S." Date: Tue, 7 Apr 2026 19:31:55 -0400 Subject: [PATCH] HFS+: Validate compressed attribute record bounds MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The HFS+ compressed-file attribute parser validated the attribute name length as a UTF-16 character count, but later used that same field as a byte offset by multiplying it by two. A crafted attribute record could therefore place the inline attribute record header near the end of the node and trigger an out-of-bounds read when ClamAV copied the record header or payload. Fix this by converting the attribute name length to a checked byte count before using it in offset calculations. Validate that the inline attribute record header fits in the node before reading it, and verify that the claimed attribute payload also fits before copying it. Credit: Sebastián Alba Vives CLAM-2969 --- libclamav/hfsplus.c | 32 ++++++++++++++++++++++++++++---- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/libclamav/hfsplus.c b/libclamav/hfsplus.c index 8a0be063ea..57f1473107 100644 --- a/libclamav/hfsplus.c +++ b/libclamav/hfsplus.c @@ -610,14 +610,30 @@ static cl_error_t hfsplus_check_attribute(cli_ctx *ctx, hfsPlusVolumeHeader *vol goto done; } - if (recordStart + sizeof(hfsPlusAttributeKey) + attrKey.nameLength >= topOfOffsets) { + uint32_t nameBytes; + uint32_t attrRecordStart; + uint32_t attrDataStart; + uint32_t bytesRemaining; + + nameBytes = (uint32_t)attrKey.nameLength * 2; + attrRecordStart = recordStart + sizeof(hfsPlusAttributeKey) + nameBytes; + + if (attrRecordStart >= topOfOffsets) { cli_dbgmsg("hfsplus_check_attribute: Attribute name is longer than expected: %u\n", attrKey.nameLength); status = CL_EFORMAT; goto done; } - if (attrKey.cnid == expectedCnid && attrKey.nameLength * 2 == nameLen && memcmp(&nodeBuf[recordStart + 14], name, nameLen) == 0) { - memcpy(&attrRec, &(nodeBuf[recordStart + sizeof(hfsPlusAttributeKey) + attrKey.nameLength * 2]), sizeof(attrRec)); + bytesRemaining = topOfOffsets - attrRecordStart; + if (bytesRemaining < sizeof(attrRec)) { + cli_dbgmsg("hfsplus_check_attribute: Not enough data for an attribute record at location %x for %u!\n", + nextStart, recordNum); + status = CL_EFORMAT; + goto done; + } + + if (attrKey.cnid == expectedCnid && nameBytes == nameLen && memcmp(&nodeBuf[recordStart + 14], name, nameLen) == 0) { + memcpy(&attrRec, &(nodeBuf[attrRecordStart]), sizeof(attrRec)); attrRec.recordType = be32_to_host(attrRec.recordType); attrRec.attributeSize = be32_to_host(attrRec.attributeSize); @@ -631,7 +647,15 @@ static cl_error_t hfsplus_check_attribute(cli_ctx *ctx, hfsPlusVolumeHeader *vol goto done; } - memcpy(record, &(nodeBuf[recordStart + sizeof(hfsPlusAttributeKey) + attrKey.nameLength * 2 + sizeof(attrRec)]), attrRec.attributeSize); + attrDataStart = attrRecordStart + sizeof(attrRec); + if (attrDataStart > topOfOffsets || topOfOffsets - attrDataStart < attrRec.attributeSize) { + cli_dbgmsg("hfsplus_check_attribute: Attribute data overruns node at location %x for %u!\n", + nextStart, recordNum); + status = CL_EFORMAT; + goto done; + } + + memcpy(record, &(nodeBuf[attrDataStart]), attrRec.attributeSize); *recordSize = attrRec.attributeSize; if (found) {