Commit a136d006b for clamav.net
commit a136d006b2ed388bcf1bff367690099a64d014da
Author: Val S. <valsnyde@cisco.com>
Date: Mon Apr 27 16:38:04 2026 -0400
HFS+: Validate compressed attribute record bounds (#1708)
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
diff --git a/libclamav/hfsplus.c b/libclamav/hfsplus.c
index 8a0be063e..57f147310 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) {