Commit 70d342954f6 for php

commit 70d342954f6721ed433175f23cf00e32d1200f47
Author: Martin Mirchev <mmirchev@comp.nus.edu.sg>
Date:   Tue Nov 10 21:10:17 2026 +0000

    ext/standard/dns.c: drop partially built records in php_parserr()

    php_parserr() creates the record array and adds host, class and ttl
    before it parses the type-specific data. When that parse fails (a
    CHECKCP() bound check, a failing dn_expand(), or the CAA length check),
    it returned NULL but left the populated array in subarray, and
    dns_get_record() appended it to the result, since the callers only test
    Z_TYPE(retval) != IS_UNDEF. A malformed record therefore showed up as
    an entry with missing type/target/ip keys.

    Destroy the array on every failure path, as the default: case already
    does for unknown types.

    Close GH-25282

diff --git a/NEWS b/NEWS
index 0896086c19e..2478151740c 100644
--- a/NEWS
+++ b/NEWS
@@ -71,6 +71,10 @@ PHP                                                                        NEWS
   . Fix trampoline leak when object construction fails for lazy object.
     (ndossche)

+- Standard:
+  . Fixed dns_get_record() returning partially built records for malformed
+    DNS responses. (Marti2203)
+
 - Streams:
   . Fixed bug GH-22063 (Stream filter chain UAF on self-removal during
     callback). (Ilia Alshanetsky)
diff --git a/ext/standard/dns.c b/ext/standard/dns.c
index 64301ed0416..9028accb3fd 100644
--- a/ext/standard/dns.c
+++ b/ext/standard/dns.c
@@ -441,7 +441,7 @@ PHP_FUNCTION(dns_check_record)

 #define CHECKCP(n) do { \
 	if (cp + n > end) { \
-		return NULL; \
+		goto fail; \
 	} \
 } while (0)

@@ -528,7 +528,7 @@ static uint8_t *php_parserr(uint8_t *cp, uint8_t *end, querybuf *answer, int typ
 			}
 			n = dn_expand(answer->qb2, end, cp, name, (sizeof name) - 2);
 			if (n < 0) {
-				return NULL;
+				goto fail;
 			}
 			cp += n;
 			add_assoc_string(subarray, "target", name);
@@ -565,7 +565,7 @@ static uint8_t *php_parserr(uint8_t *cp, uint8_t *end, querybuf *answer, int typ
 			add_assoc_stringl(subarray, "tag", (char*)cp, n);
 			cp += n;
 			if ( (size_t) dlen < ((size_t)n) + 2 ) {
-				return NULL;
+				goto fail;
 			}
 			n = dlen - n - 2;
 			CHECKCP(n);
@@ -608,13 +608,13 @@ static uint8_t *php_parserr(uint8_t *cp, uint8_t *end, querybuf *answer, int typ
 			add_assoc_string(subarray, "type", "SOA");
 			n = dn_expand(answer->qb2, end, cp, name, (sizeof name) -2);
 			if (n < 0) {
-				return NULL;
+				goto fail;
 			}
 			cp += n;
 			add_assoc_string(subarray, "mname", name);
 			n = dn_expand(answer->qb2, end, cp, name, (sizeof name) -2);
 			if (n < 0) {
-				return NULL;
+				goto fail;
 			}
 			cp += n;
 			add_assoc_string(subarray, "rname", name);
@@ -735,7 +735,7 @@ static uint8_t *php_parserr(uint8_t *cp, uint8_t *end, querybuf *answer, int typ
 			if (cp < p + dlen) {
 				n = dn_expand(answer->qb2, end, cp, name, (sizeof name) - 2);
 				if (n < 0) {
-					return NULL;
+					goto fail;
 				}
 				cp += n;
 				add_assoc_string(subarray, "chain", name);
@@ -752,7 +752,7 @@ static uint8_t *php_parserr(uint8_t *cp, uint8_t *end, querybuf *answer, int typ
 			add_assoc_long(subarray, "port", n);
 			n = dn_expand(answer->qb2, end, cp, name, (sizeof name) - 2);
 			if (n < 0) {
-				return NULL;
+				goto fail;
 			}
 			cp += n;
 			add_assoc_string(subarray, "target", name);
@@ -788,7 +788,7 @@ static uint8_t *php_parserr(uint8_t *cp, uint8_t *end, querybuf *answer, int typ

 			n = dn_expand(answer->qb2, end, cp, name, (sizeof name) - 2);
 			if (n < 0) {
-				return NULL;
+				goto fail;
 			}
 			cp += n;
 			add_assoc_string(subarray, "replacement", name);
@@ -801,6 +801,12 @@ static uint8_t *php_parserr(uint8_t *cp, uint8_t *end, querybuf *answer, int typ
 	}

 	return cp;
+
+fail:
+	/* Drop the partially built record, so that the caller does not publish it. */
+	zval_ptr_dtor(subarray);
+	ZVAL_UNDEF(subarray);
+	return NULL;
 }
 /* }}} */