Skip to content

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

Open
Marti2203 wants to merge 2 commits into
php:PHP-8.4from
Marti2203:fix-dns-partial-records
Open

Marti2203 wants to merge 2 commits into
php:PHP-8.4from
Marti2203:fix-dns-partial-records

Conversation

@Marti2203

Copy link
Copy Markdown
Contributor

php_parserr() creates the record array and adds host, class and ttl before it parses the type-specific data. If that parse fails (a CHECKCP() bound check, a failing dn_expand(), or the CAA length check), it returns NULL but leaves the populated array in subarray. The callers in PHP_FUNCTION(dns_get_record) only test Z_TYPE(retval) != IS_UNDEF, so the incomplete array is appended to the result (or to $authns / $addtl) before the NULL ends the loop.

For a response whose last answer is malformed:

  • MX/CNAME/NS/PTR/SRV with a bad compression pointer in the target name: the record has type (and pri for MX) but no target.
  • An A record with rdlength < 4 at the end of the message: the record has only host, class and ttl.
  • SOA, A6, NAPTR and CAA follow the same pattern.

This change destroys the array on every failure path, as default: already does for unknown types (zval_ptr_dtor(subarray); ZVAL_UNDEF(subarray);). subarray is set to UNDEF at the start of php_parserr(), so the early failures before array_init() are unaffected.

There is no memory-safety issue; the impact is low: a resolver or authoritative server sending a malformed response can make dns_get_record() return entries with missing type/target/ip keys.

Tested with an LD_PRELOAD shim that replaces res_nsearch() with canned responses (one good A record followed by an MX whose target is the compression pointer 0xc0ff; an A record with rdlength 2; a CNAME with a bad pointer):

before: [{"type":"A","ip":"192.0.2.1",...}, {"host":"example.test","class":"IN","ttl":3600,"type":"MX","pri":10}]
after:  [{"type":"A","ip":"192.0.2.1",...}]

No .phpt is included: it would need a resolver mock, which the test suite doesn't have (GH-23798 left out a test for the same reason).

Related: GH-22201 (missing CHECKCP in the A6 branch); with this change that check also drops the partial record. GH-17919 is about dns_get_record() error reporting in general.

This was found while following up cases from an LLM-Enhanced Static Analysis Engine and verified manually.

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.
Comment thread ext/standard/dns.c Outdated
#define CHECKCP(n) do { \
if (cp + n > end) { \
return NULL; \
PARSERR_FAIL(); \

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fix itself is correct, I d prefer personally a goto though.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, updated to a single fail: label at the end of php_parserr() (5bd7c58). No functional change; the resolver-shim cases give the same output as before.

As suggested in review, replace the PARSERR_FAIL() macro with a single
fail: label at the end of php_parserr(). No functional change.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants