[File] Excessive memory allocation in CDF chain parsing
Acts1631
acts1631kjv at proton.me
Thu Aug 20 16:41:39 EDT 2026
Hello,
A crafted Composite Document File can make libmagic request
multi-gigabyte allocations while parsing attacker-controlled input.
The issue is in the CDF parser's sector-chain handling. The header
accepts sector sizes up to 1 MiB, while cdf_count_chain() limits only
the number of sectors to 10,000. cdf_read_dir() then multiplies that
count by the number of directory entries per sector and passes the
result to CDF_CALLOC() without a memory bound. The same unbounded
chain length also reaches SSAT and stream allocations.
A 2 MiB crafted input with a 1 MiB sector size and a 10,000-sector
directory chain caused the unpatched libFuzzer harness to request
malloc(11141120000) in cdf_read_dir(). An attacker who can make a
process call magic_buffer() or magic_descriptor() on the file can
therefore cause excessive memory consumption or process termination,
depending on allocator and resource limits.
The patch adds a shared 16 MiB CDF memory limit, checks the
sat_len * size calculation before deriving the sector bound, limits
all cdf_count_chain() consumers, and checks the directory allocation
before calling CDF_CALLOC().
diff --git a/src/cdf.c b/src/cdf.c
index 2da240c..1deba80 100644
--- a/src/cdf.c
+++ b/src/cdf.c
@@ -490,8 +490,7 @@ cdf_read_sat(const cdf_info_t *info, cdf_header_t *h, cdf_sat_t *sat)
}
sat->sat_len = h->h_num_sectors_in_master_sat * nsatpersec + i;
-#define CDF_SAT_LIMIT (16 * 1024 * 1024)
- if (ss != 0 && sat->sat_len > CDF_SAT_LIMIT / ss) {
+ if (ss != 0 && sat->sat_len > CDF_MEMORY_LIMIT / ss) {
errno = EFTYPE;
return -1;
}
@@ -563,8 +562,12 @@ size_t
cdf_count_chain(const cdf_sat_t *sat, cdf_secid_t sid, size_t size)
{
size_t i, j;
- cdf_secid_t maxsector = CAST(cdf_secid_t, (sat->sat_len * size)
- / sizeof(maxsector));
+ cdf_secid_t maxsector;
+
+ if (size == 0 || sat->sat_len > SIZE_T_MAX / size)
+ goto out;
+ maxsector = CAST(cdf_secid_t, (sat->sat_len * size) /
+ sizeof(maxsector));
DPRINTF(("Chain:"));
if (sid == CDF_SECID_END_OF_CHAIN) {
@@ -579,6 +582,10 @@ cdf_count_chain(const cdf_sat_t *sat, cdf_secid_t sid, size_t size)
DPRINTF(("Counting chain loop limit"));
goto out;
}
+ if (i >= CDF_MEMORY_LIMIT / size) {
+ DPRINTF(("Counting chain size limit"));
+ goto out;
+ }
if (sid >= maxsector) {
DPRINTF(("Sector %d >= %d\n", sid, maxsector));
goto out;
@@ -715,6 +722,12 @@ cdf_read_dir(const cdf_info_t *info, const cdf_header_t *h,
return -1;
nd = ss / CDF_DIRECTORY_SIZE;
+ if (nd != 0 && ns > CDF_MEMORY_LIMIT / nd /
+ sizeof(dir->dir_tab[0])) {
+ DPRINTF(("Directory size limit"));
+ errno = EFTYPE;
+ return -1;
+ }
dir->dir_len = ns * nd;
dir->dir_tab = CAST(cdf_directory_t *,
diff --git a/src/cdf.h b/src/cdf.h
index 6dddb3d..7f47555 100644
--- a/src/cdf.h
+++ b/src/cdf.h
@@ -49,6 +49,7 @@ typedef int32_t cdf_secid_t;
#define CDF_LOOP_LIMIT 10000
#define CDF_ELEMENT_LIMIT 100000
+#define CDF_MEMORY_LIMIT (16 * 1024 * 1024)
#define CDF_SECID_NULL 0
#define CDF_SECID_FREE -1
More information about the File
mailing list