[File] Excessive memory allocation in CDF chain parsing

Christos Zoulas christos at zoulas.com
Fri Aug 28 11:45:48 EDT 2026


Committed, thanks!

christos

> On Aug 20, 2026, at 4:41 PM, Acts1631 <acts1631kjv at proton.me> wrote:
> 
> 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
> -- 
> File mailing list
> File at astron.com
> https://mailman.astron.com/mailman/listinfo/file



More information about the File mailing list