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;
}
/* }}} */