ares_dns_write: reject messages larger than 64k by ManoharPaturi · Pull Request #1314 · c-ares/c-ares · GitHub
Skip to content

ares_dns_write: reject messages larger than 64k - #1314

Merged
bradh352 merged 1 commit into
c-ares:mainfrom
ManoharPaturi:fix-rdlength-truncation
Oct 19, 2026
Merged

bradh352 merged 1 commit into
c-ares:mainfrom
ManoharPaturi:fix-rdlength-truncation

Conversation

@ManoharPaturi

@ManoharPaturi ManoharPaturi commented Sep 28, 2026 •

Copy link
Copy Markdown
Contributor

Problem

ares_dns_write() silently truncates any RDATA or option value that does not fit its 16-bit length prefix:

  • ares_dns_write_rr() (src/lib/record/ares_dns_write.c:1401): ares_buf_append_be16(buf, (unsigned short)(rdlength & 0xFFFF))
  • the OPT/SVCB/HTTPS option writers (:667, :1053, :1104): ares_buf_append_be16(buf, (unsigned short)(val_len & 0xFFFF))

A record whose RDATA exceeds 65535 bytes is serialized "successfully" with a wrapped rdlength while the full RDATA is still emitted — a structurally corrupt message reported as ARES_SUCCESS.

Reproduced against current main: a TXT RR with 300 × 255-byte character-strings (rdlength 76500) yields a 76,841-byte message with rdlength wrapped to 10964; re-parsing the output fails with ARES_EFORMERR. The same class is reachable through ares_dns_rr_set_opt/SVCB/HTTPS values > 65535 (the length prefix truncates while the value is appended, desynchronizing every following option).

The TCP-framing writer already guards this exact condition (if (msg_len > 65535) { status = ARES_EBADQUERY; } at :1492) — the RR-level writers were missing the same check.

Fix

ares_dns_write_buf() now enforces the real limit, the one the parser and the TCP writer already enforce: after the additional section is written, a message larger than 65535 bytes is rejected with ARES_EBADQUERY. That single check also bounds the header's unchecked (unsigned short) section count casts, since more than 65535 records cannot fit in 65535 bytes, and it replaces the now redundant post-hoc check in ares_dns_write_buf_tcp().

The per-record rdlength check is kept as an early exit for record data over 65535 bytes, and the three option value length checks (OPT, SVCB, HTTPS) are dropped: an option value over 65535 bytes always makes the record's data over 65535 bytes too, so the rdlength check already catches it and the message level check catches that.

Verification

DNSWriteOversizedRdlength (test/ares-test-internal.cc) builds the 300x255 TXT RR through the public record API and requires ARES_EBADQUERY exactly.

  • pristine main: FAILS (write "succeeds" with a corrupt message)
  • this branch: PASSES, surrounding ares_dns_write/parse round-trip tests unaffected

DNSWriteMaximumMessageSize hits the exact byte limit with a raw record (ARES_REC_TYPE_RAW_RR + ares_dns_rr_set_bin()): the framing is measured with a one byte payload, then the largest message that fits (65535 bytes total) must write ARES_SUCCESS and parse back through ares_dns_parse(), and one byte more must fail with ARES_EBADQUERY.

  • previous state of this branch (per-record check only): FAILS (the oversized message still wrote "successfully")
  • this branch: PASSES

Signed-off-by: Manohar Paturi 186662190+ManoharPaturi@users.noreply.github.com

Copilot AI lite review requested due to automatic review settings September 28, 2026 18:36

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@bradh352 bradh352 left a comment

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.

Thanks, this is a real bug: ares_dns_write() wrote a cut-down length for record data over 65535 bytes, which breaks the framing of every record after it. I confirmed the new test fails on the base commit and passes with the fix, including under the address and undefined-behaviour sanitizers.

The main request is in the inline comments: put the limit on the whole message, not on each record or option. The parser and the TCP writer both cap a message at 65535 bytes, so this change still produces output the library itself rejects.

  • Low — src/lib/record/ares_dns_write.c, in ares_dns_write_buf_tcp(): the goto done after ares_dns_write_buf() is tagged /* LCOV_EXCL_LINE: OutOfMemory */. An oversized message sent over TCP can now reach it through ARES_EBADQUERY, so the tag hides a path that can actually run. Drop the tag.
  • Low — docs/ares_dns_record.3, RETURN VALUES: the man page lists only ARES_SUCCESS, ARES_ENOMEM and ARES_EFORMERR for ares_dns_write(). Please add ARES_EBADQUERY, described as "the message is too large to encode".
  • Nit — PRs are squash-merged, so the title and body become the commit. The title is too long for one line, which is why GitHub split it. The commit body is only that leftover fragment, with no explanation and no Signed-off-by: line (see CONTRIBUTING.md). Suggested title: ares_dns_write: reject messages larger than 64k.
  • Overlap: #1257, an open PR from another author, adds the same per-record check in ares_dns_write_rr() and has had the same request to move it to the whole message. One message-level check covers both PRs.

Comment thread src/lib/record/ares_dns_write.c
Comment thread src/lib/record/ares_dns_write.c Outdated
Comment thread src/lib/record/ares_dns_write.c Outdated
Comment thread src/lib/record/ares_dns_write.c Outdated
Comment thread test/ares-test-internal.cc Outdated
Comment thread test/ares-test-internal.cc Outdated
Comment thread test/ares-test-internal.cc Outdated
@ManoharPaturi
ManoharPaturi force-pushed the fix-rdlength-truncation branch from 301a6dc to 0fd35bc Compare October 18, 2026 18:49
@ManoharPaturi ManoharPaturi changed the title ares_dns_write: reject RDATA and option values that do not fit their 16-bit length prefixes ares_dns_write: reject records and messages that exceed DNS size limits Oct 18, 2026

@bradh352 bradh352 left a comment

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.

Thanks, the message-level check is exactly what was needed. I confirmed both new tests fail on the base commit and pass here, including under the address and undefined-behaviour sanitizers, and that the 65535-byte edge case round-trips through ares_dns_parse().

What's left is small: two items from the last round's summary weren't picked up (the coverage-exclusion tag and the man page), and clang-format still needs a pass over the tests.

  • Low — src/lib/record/ares_dns_write.c, ares_dns_write_buf_tcp(): the goto done after ares_dns_write_buf() is still tagged /* LCOV_EXCL_LINE: OutOfMemory */. Now that ares_dns_write_buf() returns ARES_EBADQUERY for any message over 65535 bytes, an oversized query sent over TCP reaches this line on a normal path. The tag hides a path that can actually run. Please drop it.
  • Low — docs/ares_dns_record.3, RETURN VALUES: the man page still lists only ARES_SUCCESS, ARES_ENOMEM and ARES_EFORMERR for ares_dns_write(). It can now also return ARES_EBADQUERY. Please add it, described as "the message is too large to encode".
  • Nit — The reply on the formatting thread says the tests were reformatted, but git clang-format --diff still rewrites both new tests: the declaration alignment in DNSWriteOversizedRdlength and most of the wrapped EXPECT_EQ(...) calls. Please run git clang-format origin/main and commit the result.
  • Nit — PRs are squash-merged, so the title becomes the commit subject. At 71 characters it's still long for one line; ares_dns_write: reject messages larger than 64k says the same thing. The PR description now explains the change well.

Comment thread test/ares-test-internal.cc Outdated
Comment thread test/ares-test-internal.cc Outdated
@ManoharPaturi
ManoharPaturi force-pushed the fix-rdlength-truncation branch from 0fd35bc to afdc2cb Compare October 19, 2026 00:45

@bradh352 bradh352 left a comment

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 two test nits from the last round are fixed: the framing comment now matches the code, and the oversized test reuses one buffer. The writer code hasn't changed since the last round, and both tests still pass here, including under the address and undefined-behaviour sanitizers.

Three items from the previous two rounds are still open. They're all small, but this is the third time they've come up, so please check each one before the next push.

  • Low — src/lib/record/ares_dns_write.c, ares_dns_write_buf_tcp(), line 1497: the goto done after ares_dns_write_buf() is still tagged /* LCOV_EXCL_LINE: OutOfMemory */. That tag tells the coverage tool to ignore the line because only an allocation failure can reach it. That is no longer true: ares_dns_write_buf() now returns ARES_EBADQUERY for any message over 65535 bytes, so an oversized query sent over TCP reaches this line on a normal path. Please drop the tag:
    status = ares_dns_write_buf(dnsrec, buf);
    if (status != ARES_SUCCESS) {
      goto done;
    }
  • Low — docs/ares_dns_record.3, RETURN VALUES (around line 451): the man page still lists only ARES_SUCCESS, ARES_ENOMEM and ARES_EFORMERR. ares_dns_write() can now also return ARES_EBADQUERY. A public function's man page has to change in the same PR as its behaviour. After the ARES_EFORMERR sentence, add for example:
    \fIares_dns_write(3)\fP also returns
    .B ARES_EBADQUERY
    if the message is too large to encode.
    
  • Nit — Formatting: git clang-format --diff origin/main still rewrites both new tests. It changes the declaration alignment at the top of DNSWriteOversizedRdlength (the = nullptr columns are out of line) and nearly every wrapped EXPECT_EQ(...) call in both tests. Running git clang-format origin/main and committing the result fixes all of it at once. Please run the tool itself rather than editing by hand: the last two replies said it was done, but the output hasn't changed.
  • Nit — PR title: PRs are squash-merged, so the title becomes the commit subject. At 71 characters it's still long for one line. Suggested: ares_dns_write: reject messages larger than 64k.

ares_dns_write() wrote a cut-down 16-bit rdlength for record data over 65535 bytes, breaking the framing of every record after it, and a record of that size still produced a message the parser and the TCP writer both reject. ares_dns_write_buf() now refuses any message over 65535 bytes with ARES_EBADQUERY, checked after the additional section is written; the per-record rdlength check stays as an early exit. The matching check in ares_dns_write_buf_tcp() is removed as redundant, and the ares_dns_record(3) man page lists the new return value.

Signed-off-by: Manohar Paturi <186662190+ManoharPaturi@users.noreply.github.com>
@ManoharPaturi
ManoharPaturi force-pushed the fix-rdlength-truncation branch from afdc2cb to d2e869e Compare October 19, 2026 00:59
@ManoharPaturi ManoharPaturi changed the title ares_dns_write: reject records and messages that exceed DNS size limits ares_dns_write: reject messages larger than 64k Oct 19, 2026
@ManoharPaturi

Copy link
Copy Markdown
Contributor Author

@bradh352 bradh352 left a comment

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.

All four items from the last round are in. I re-checked them at d2e869e: the coverage-exclusion tag is gone from the goto done in ares_dns_write_buf_tcp(), the ares_dns_record(3) man page lists ARES_EBADQUERY for ares_dns_write(), git clang-format --diff now leaves both tests unchanged, and the title is the short form. Thanks for explaining the clang-format version mismatch.

It builds with warnings as errors and both new tests pass, including under the address and undefined-behaviour sanitizers. The branch merges cleanly with current main. Nothing further from me.

@bradh352
bradh352 enabled auto-merge (squash) October 19, 2026 01:18
@bradh352
bradh352 merged commit e3d6a28 into c-ares:main Oct 19, 2026
29 checks passed
@bradh352

Copy link
Copy Markdown
Member

/backport v1.34

@c-ares-backport-gha

Copy link
Copy Markdown

c-ares-backport-gha Bot added a commit that referenced this pull request Oct 19, 2026
# Description
Backport of #1314 to `v1.34`.

Signed-off-by: Manohar Paturi (@ManoharPaturi)
Co-authored-by: Manohar Paturi <manoharpaturi777@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants