Commit message: - Fix Integer Underflow in Regional Allocator,
  reported by X41 D-Sec.
CVE: CVE-2019-25033
CWE: CWE-190
Vulnerability description: Unbound before 1.9.5 allows an integer overflow in the regional allocator via the ALIGN_UP macro. NOTE: The vendor disputes that this is a vulnerability. Although the code may be vulnerable, a running Unbound installation cannot be remotely or locally exploited

Project: unbound
File: regional.c

regional_create_custom(size_t size)
{
	struct regional* r = (struct regional*)malloc(size);
	size = ALIGN_UP(size, ALIGNMENT);
	log_assert(sizeof(struct regional) <= size);
	if(!r) return NULL;
	r->first_size = size;
	regional_init(r);
	return r;
}

Project: unbound
File: regional.c

regional_get_mem(struct regional* r)
{
	return r->first_size + (count_chunks(r)-1)*REGIONAL_CHUNK_SIZE 
		+ r->total_large;
}

Project: unbound
File: regional.c

regional_create(void)
{
	return regional_create_custom(REGIONAL_CHUNK_SIZE);
}

Project: unbound
File: regional.c

regional_alloc_zero(struct regional *r, size_t size)
{
	void *s = regional_alloc(r, size);
	if(!s) return NULL;
	memset(s, 0, size);
	return s;
}

Project: unbound
File: regional.c

count_large(struct regional* r)
{
	size_t c = 0;
	char* p = r->large_list;
	while(p) {
		c++;
		p = *(char**)p;
	}
	return c;
}

Project: unbound
File: regional.c

regional_alloc_init(struct regional* r, const void *init, size_t size)
{
	void *s = regional_alloc(r, size);
	if(!s) return NULL;
	memcpy(s, init, size);
	return s;
}

Project: unbound
File: regional.c

regional_strdup(struct regional *r, const char *string)
{
	return (char*)regional_alloc_init(r, string, strlen(string)+1);
}

Project: unbound
File: regional.c

regional_free_all(struct regional *r)
{
	char* p = r->next, *np;
	while(p) {
		np = *(char**)p;
		free(p);
		p = np;
	}
	p = r->large_list;
	while(p) {
		np = *(char**)p;
		free(p);
		p = np;
	}
	regional_init(r);
}

Project: unbound
File: regional.c

regional_init(struct regional* r)
{
	size_t a = ALIGN_UP(sizeof(struct regional), ALIGNMENT);
	r->data = (char*)r + a;
	r->available = r->first_size - a;
	r->next = NULL;
	r->large_list = NULL;
	r->total_large = 0;
}

Project: unbound
File: regional.c

regional_log_stats(struct regional *r)
{
	/* some basic assertions put here (non time critical code) */
	log_assert(ALIGNMENT >= sizeof(char*));
	log_assert(REGIONAL_CHUNK_SIZE > ALIGNMENT);
	log_assert(REGIONAL_CHUNK_SIZE-ALIGNMENT > REGIONAL_LARGE_OBJECT_SIZE);
	log_assert(REGIONAL_CHUNK_SIZE >= sizeof(struct regional));
	/* debug print */
	log_info("regional %u chunks, %u large",
		(unsigned)count_chunks(r), (unsigned)count_large(r));
}

Project: unbound
File: regional.c

regional_alloc(struct regional *r, size_t size)
{
	size_t a;
	void *s;
	if(
#if SIZEOF_SIZE_T == 8
		(unsigned long long)size >= 0xffffffffffffff00ULL
#else
		(unsigned)size >= (unsigned)0xffffff00UL
#endif
		)
		return NULL; /* protect against integer overflow in
			malloc and ALIGN_UP */
	a = ALIGN_UP(size, ALIGNMENT);
	/* large objects */
	if(a > REGIONAL_LARGE_OBJECT_SIZE) {
		s = malloc(ALIGNMENT + size);
		if(!s) return NULL;
		r->total_large += ALIGNMENT+size;
		*(char**)s = r->large_list;
		r->large_list = (char*)s;
		return (char*)s+ALIGNMENT;
	}
	/* create a new chunk */
	if(a > r->available) {
		s = malloc(REGIONAL_CHUNK_SIZE);
		if(!s) return NULL;
		*(char**)s = r->next;
		r->next = (char*)s;
		r->data = (char*)s + ALIGNMENT;
		r->available = REGIONAL_CHUNK_SIZE - ALIGNMENT;
	}
	/* put in this chunk */
	r->available -= a;
	s = r->data;
	r->data += a;
	return s;
}

Project: unbound
File: regional.c

regional_destroy(struct regional *r)
{
	if(!r) return;
	regional_free_all(r);
	free(r);
}

Project: unbound
File: regional.c

count_chunks(struct regional* r)
{
	size_t c = 1;
	char* p = r->next;
	while(p) {
		c++;
		p = *(char**)p;
	}
	return c;
}