Commit 40d40a773 for clamav.net
commit 40d40a773445ca907113b3de44c49fb8fc6f3982
Author: Val S. <valsnyde@cisco.com>
Date: Fri Aug 7 13:46:05 2026 -0400
Libclamav: fix PESpin rebuilt section size overflow (#67)
The PESpin unpacker accumulated rebuilt section sizes in a signed int
before allocating the intermediate rebuilt image buffer. A crafted PE with
many sections could wrap the total to a small value and then copy full
section contents into that undersized allocation.
Track the rebuilt size in a 64-bit accumulator and reject additions that
would exceed CLI_MAX_ALLOCATION before allocating. Also avoid related
undefined behavior in the PESpin checksum and resource bitmap paths, and
share bitmap-based cleanup through one helper.
Reported-by: Feng Xue
CLAM-3008
diff --git a/libclamav/spin.c b/libclamav/spin.c
index 5414024eb..2621d8e5e 100644
--- a/libclamav/spin.c
+++ b/libclamav/spin.c
@@ -62,6 +62,64 @@
#include "packlibs.h"
#include "spin.h"
+/**
+ * @brief Add a section size to the rebuilt PESpin image size.
+ *
+ * @return 0 if the size was added, 1 if the rebuilt image would exceed
+ * CLI_MAX_ALLOCATION.
+ */
+static int spin_add_blob_size(uint64_t *blobsz, uint32_t size)
+{
+ if (*blobsz > (uint64_t)CLI_MAX_ALLOCATION ||
+ (uint64_t)size > (uint64_t)CLI_MAX_ALLOCATION - *blobsz) {
+ cli_dbgmsg("spin: rebuilt section data exceeds allocation limit\n");
+ return 1;
+ }
+
+ *blobsz += size;
+ return 0;
+}
+
+/**
+ * @brief Replace an already-counted section size in the rebuilt PESpin image.
+ *
+ * @return 0 if the size was replaced, 1 if the previous size was not counted
+ * or the rebuilt image would exceed CLI_MAX_ALLOCATION.
+ */
+static int spin_replace_blob_size(uint64_t *blobsz, uint32_t old_size, uint32_t new_size)
+{
+ uint64_t adjusted_blobsz;
+
+ if (*blobsz < old_size) {
+ cli_dbgmsg("spin: rebuilt section data accounting underflow\n");
+ return 1;
+ }
+
+ adjusted_blobsz = *blobsz - old_size;
+ if (adjusted_blobsz > (uint64_t)CLI_MAX_ALLOCATION ||
+ (uint64_t)new_size > (uint64_t)CLI_MAX_ALLOCATION - adjusted_blobsz) {
+ cli_dbgmsg("spin: rebuilt section data exceeds allocation limit\n");
+ return 1;
+ }
+
+ *blobsz = adjusted_blobsz + new_size;
+ return 0;
+}
+
+/**
+ * @brief Free PESpin section buffers that are marked as grown in the bitmap.
+ */
+static void spin_free_grown_sections(char **sects, int sectcnt, uint32_t bitmap)
+{
+ int j;
+
+ for (j = 0; j < sectcnt; j++) {
+ if (bitmap & 1)
+ free(sects[j]);
+ bitmap >>= 1;
+ }
+}
+
static char exec86(uint8_t aelle, uint8_t cielle, char *curremu, int *retval)
{
int len = 0;
@@ -143,7 +201,7 @@ static uint32_t summit(char *src, int size)
int i;
while (size) {
- eax ^= *src++ << 8 & 0xff00;
+ eax ^= ((uint32_t)(uint8_t)*src++ << 8) & 0xff00;
eax = eax >> 3 & 0x1fffffff;
for (i = 0; i < 4; i++) {
uint32_t swap;
@@ -164,7 +222,8 @@ int unspin(char *src, int ssize, struct cli_exe_section *sections, int sectcnt,
{
char *curr, *emu, *ep, *spinned;
char **sects;
- int blobsz = 0, j;
+ uint64_t blobsz = 0;
+ int j;
uint32_t key32, bitmap, bitman;
uint32_t len;
uint8_t key8;
@@ -402,12 +461,16 @@ int unspin(char *src, int ssize, struct cli_exe_section *sections, int sectcnt,
len = 0;
for (j = 0; j < sectcnt; j++) {
if (bitmap & 1) {
+ if (spin_add_blob_size(&blobsz, sections[j].vsz)) {
+ len = 1;
+ break;
+ }
+
if ((sects[j] = (char *)cli_max_malloc(sections[j].vsz)) == NULL) {
cli_dbgmsg("spin: malloc(%u) failed\n", sections[j].vsz);
len = 1;
break;
}
- blobsz += sections[j].vsz;
memset(sects[j], 0, sections[j].vsz);
cli_dbgmsg("spin: Growing sect%d: was %x will be %x\n", j, sections[j].rsz, sections[j].vsz);
if (cli_unfsg(src + sections[j].raw, sects[j], sections[j].rsz, sections[j].vsz, NULL, NULL) == -1) {
@@ -415,7 +478,11 @@ int unspin(char *src, int ssize, struct cli_exe_section *sections, int sectcnt,
cli_dbgmsg("spin: Unpack failure\n");
}
} else {
- blobsz += sections[j].rsz;
+ if (spin_add_blob_size(&blobsz, sections[j].rsz)) {
+ len = 1;
+ break;
+ }
+
sects[j] = src + sections[j].raw;
cli_dbgmsg("spin: Not growing sect%d\n", j);
}
@@ -425,12 +492,7 @@ int unspin(char *src, int ssize, struct cli_exe_section *sections, int sectcnt,
cli_dbgmsg("spin: decompression complete\n");
if (len) {
- int t;
- for (t = 0; t < j; t++) {
- if (bitman & 1)
- free(sects[t]);
- bitman = bitman >> 1 & 0x7fffffff;
- }
+ spin_free_grown_sections(sects, j, bitman);
free(sects);
return 1;
}
@@ -444,7 +506,7 @@ int unspin(char *src, int ssize, struct cli_exe_section *sections, int sectcnt,
break;
}
- if (j != sectcnt && ((bitman & (1 << j)) == 0)) { /* FIXME: not really sure either the res sect is lamed or just compressed, but this'll save some major headaches */
+ if (j != sectcnt && j < 32 && ((bitman & ((uint32_t)1 << j)) == 0)) { /* FIXME: not really sure either the res sect is lamed or just compressed, but this'll save some major headaches */
cli_dbgmsg("spin: Resources (sect%d) appear to be compressed\n\tuncompressed offset %x, len %x\n\tcompressed offset %x, len %x\n", j, sections[j].rva, key32 - sections[j].rva, key32, sections[j].vsz - (key32 - sections[j].rva));
if ((curr = (char *)cli_max_malloc(sections[j].vsz)) != NULL) {
@@ -454,26 +516,34 @@ int unspin(char *src, int ssize, struct cli_exe_section *sections, int sectcnt,
free(curr);
cli_dbgmsg("spin: Failed to grow resources, continuing anyway\n");
- blobsz += sections[j].rsz;
} else {
- sects[j] = curr;
- bitman |= 1 << j;
- cli_dbgmsg("spin: Resources grown\n");
- blobsz += sections[j].vsz;
+ if (spin_replace_blob_size(&blobsz, sections[j].rsz, sections[j].vsz)) {
+ free(curr);
+ len = 1;
+ } else {
+ sects[j] = curr;
+ bitman |= (uint32_t)1 << j;
+ cli_dbgmsg("spin: Resources grown\n");
+ }
}
} else {
/* malloc failed but i'm too deep into this crap to quit without leaking more :( */
cli_dbgmsg("spin: memory allocation failed, continuing anyway\n");
- blobsz += sections[j].rsz;
}
} else {
cli_dbgmsg("spin: No res?!\n");
}
}
+ if (len) {
+ spin_free_grown_sections(sects, sectcnt, bitman);
+ free(sects);
+ return 1;
+ }
+
bitmap = bitman; /* save as a free() bitmap */
- if ((ep = (char *)cli_max_malloc(blobsz)) != NULL) {
+ if ((ep = (char *)cli_max_malloc((size_t)blobsz)) != NULL) {
struct cli_exe_section *rebhlp;
if ((rebhlp = (struct cli_exe_section *)cli_max_malloc(sizeof(struct cli_exe_section) * (sectcnt))) != NULL) {
char *to = ep;
@@ -505,11 +575,7 @@ int unspin(char *src, int ssize, struct cli_exe_section *sections, int sectcnt,
}
cli_dbgmsg("spin: free bitmap is %x\n", bitman);
- for (j = 0; j < sectcnt; j++) {
- if (bitmap & 1)
- free(sects[j]);
- bitmap = bitmap >> 1 & 0x7fffffff;
- }
+ spin_free_grown_sections(sects, sectcnt, bitmap);
free(sects);
return 1; /* :( */
}