CVE-2014-3597 (C, published 2014-08-23T01:55Z)
Description: value: Multiple buffer overflows in the php_parserr function in ext/standard/dns.c in PHP before 5.4.32 and 5.5.x before 5.5.16 allow remote DNS servers to cause a denial of service (application crash) or possibly execute arbitrary code via a crafted DNS record, related to the dns_get_record function and the dn_expand function.  NOTE: this issue exists because of an incomplete fix for CVE-2014-4049.
Severity: MEDIUM (CVSS v3 base score: )
Weakness: CWE-119 \u2014 Improper Restriction of Operations within the Bounds of a Memory Buffer
The product performs operations on a memory buffer, but it reads from or writes to a memory location outside the buffer's intended boundary. This may result in read or write operations on unexpected memory locations that could be linked to other variables, data structures, or internal program data.
Repository: https://github.com/php/php-src
Files changed:
ext/standard/dns.c
Commit message: Fixed Sec Bug #67717 segfault in dns_get_record CVE-2014-3597

Incomplete fix for CVE-2014-4049

Check possible buffer overflow
- pass real buffer end to dn_expand calls
- check buffer len before each read


Vulnerable code:
static u_char *php_parserr(u_char *cp, querybuf *answer, int type_to_fetch, int store, int raw, zval **subarray)
	n = dn_expand(answer->qb2, answer->qb2+65536, cp, name, sizeof(name) - 2);
			n = dn_expand(answer->qb2, answer->qb2+65536, cp, name, (sizeof name) - 2);
				int ll = 0;
				while (ll < dlen) {
					n = cp[ll];
					if ((ll + n) >= dlen) {
						n = dlen - (ll + 1);
					memcpy(tp + ll , cp + ll + 1, n);
					add_next_index_stringl(entries, cp + ll + 1, n, 1);
					ll = ll + n + 1;
				tp[dlen] = '\0';
				add_assoc_stringl(*subarray, "txt", tp, (dlen>0)?dlen - 1:0, 0);
			n = dn_expand(answer->qb2, answer->qb2+65536, cp, name, (sizeof name) -2);
			n = dn_expand(answer->qb2, answer->qb2+65536, cp, name, (sizeof name) -2);
				n = dn_expand(answer->qb2, answer->qb2+65536, cp, name, (sizeof name) - 2);
			n = dn_expand(answer->qb2, answer->qb2+65536, cp, name, (sizeof name) - 2);
			add_assoc_stringl(*subarray, "flags", (char*)++cp, n, 1);
			add_assoc_stringl(*subarray, "services", (char*)++cp, n, 1);
			add_assoc_stringl(*subarray, "regex", (char*)++cp, n, 1);
			n = dn_expand(answer->qb2, answer->qb2+65536, cp, name, (sizeof name) - 2);
				cp = php_parserr(cp, &answer, type_to_fetch, store_results, raw, &retval);
					cp = php_parserr(cp, &answer, DNS_T_ANY, authns != NULL, raw, &retval);
					cp = php_parserr(cp, &answer, DNS_T_ANY, 1, raw, &retval);

Fixed code:
#define CHECKCP(n) do { \
	if (cp + n > end) { \
		return NULL; \
	} \
} while (0)

static u_char *php_parserr(u_char *cp, u_char *end, querybuf *answer, int type_to_fetch, int store, int raw, zval **subarray)
	n = dn_expand(answer->qb2, end, cp, name, sizeof(name) - 2);
	CHECKCP(10);
	CHECKCP(dlen);
			CHECKCP(4);
			CHECKCP(2);
			n = dn_expand(answer->qb2, end, cp, name, (sizeof name) - 2);
			CHECKCP(1);
			CHECKCP(n);
			CHECKCP(1);
			CHECKCP(n);
				int l1 = 0, l2 = 0;
				while (l1 < dlen) {
					n = cp[l1];
					if ((l1 + n) >= dlen) {
						n = dlen - (l1 + 1);
					}
					if (n) {
						memcpy(tp + l2 , cp + l1 + 1, n);
						add_next_index_stringl(entries, cp + l1 + 1, n, 1);
					l1 = l1 + n + 1;
					l2 = l2 + n;
				tp[l2] = '\0';
				add_assoc_stringl(*subarray, "txt", tp, l2, 0);
			n = dn_expand(answer->qb2, end, cp, name, (sizeof name) -2);
			n = dn_expand(answer->qb2, end, cp, name, (sizeof name) -2);
			CHECKCP(5*4);
			CHECKCP(8*2);
			CHECKCP(1);
				CHECKCP(2);
				n = dn_expand(answer->qb2, end, cp, name, (sizeof name) - 2);
			CHECKCP(3*2);
			n = dn_expand(answer->qb2, end, cp, name, (sizeof name) - 2);
			CHECKCP(2*2);

			CHECKCP(1);
			cp++;
			CHECKCP(n);
			add_assoc_stringl(*subarray, "flags", (char*)cp, n, 1);

			CHECKCP(1);
			cp++;
			CHECKCP(n);
			add_assoc_stringl(*subarray, "services", (char*)cp, n, 1);

			CHECKCP(1);
			cp++;
			CHECKCP(n);
			add_assoc_stringl(*subarray, "regex", (char*)cp, n, 1);

			n = dn_expand(answer->qb2, end, cp, name, (sizeof name) - 2);
				cp = php_parserr(cp, end, &answer, type_to_fetch, store_results, raw, &retval);
					cp = php_parserr(cp, end, &answer, DNS_T_ANY, authns != NULL, raw, &retval);
					cp = php_parserr(cp, end, &answer, DNS_T_ANY, 1, raw, &retval);