Skip to content

Predictable DNS Transaction IDs Enable Cache Poisoning in Built-in Resolver. CVE-2026-28810#10864

Merged
RaimoNiskanen merged 8 commits into
erlang:maintfrom
RaimoNiskanen:raimo/kernel/inet_db-catch/OTP-20037
Apr 7, 2026
Merged

Predictable DNS Transaction IDs Enable Cache Poisoning in Built-in Resolver. CVE-2026-28810#10864
RaimoNiskanen merged 8 commits into
erlang:maintfrom
RaimoNiskanen:raimo/kernel/inet_db-catch/OTP-20037

Conversation

@RaimoNiskanen

@RaimoNiskanen RaimoNiskanen commented Mar 17, 2026

Copy link
Copy Markdown
Contributor

The documentation is updated to clarify that inet_res should only be used in trusted networks and with trusted recursive resolvers.

The implementation is also improved to use strong random DNS transaction IDs and source ports for every DNS transaction. This should give ample protection against brute forcing fake DNS replies, but it still does not protect against, for example, an adversary in the path of the DNS transaction that can observe the random values before faking malicious replies, an attack known as CAPEC-598: DNS Spoofing.

According to established practice, RFC 6056, use a random transaction
ID for DNS queries, and also a random source (bind) port number.
This is established practice to minimize the possibility for
a succesful DNS cache poisoning attack through spoofing UDP
answer packets.

The random number generator that is used is `crypto:rand_uniform/2`,
which is deprecated for outdated reasons.  In really old, now obsolete,
libcrypto versions the function was not cryptographically strong,
but that should no longer be the case.

For the `crypto` random number generator to be available,
the Crypto application has to be loaded, and if not this code
falls back to the legacy non-random behaviour.  This should
not be a problem since any Erlang node that is installed
in an exposed environment should have the Crypto application loaded,
for example to run the SSL application.  This can also be done explicitly
with `application:load(crypto)`.

If the performance penalty for randomization is too much
for an Erlang installation on a network protected from
DNS spoofing, there is a boolean `inet` resolver option `random`
that can be set to `false` to restore the legacy behaviour.

To randomize ports, an internal port number argument of -1,
instead of 0, is used by the internal function `inet:bind/3`
to do 5 attempts to bind on truly random port numbers in the range
1024 .. 65535, then fallback to letting the OS pick an ephemeral
port.  A modern OS should pick random epemeral ports, but the range
is smaller and the randomness not documented.  This approach
is a compromise that should make the port number cryptographically
random in all practical cases with a fallback that should be
very hard to exploit.

Transaction ID:s are simply random 16 bits.  There is no attempt
to avoid duplicates.  The probability for duplicates in both
transaction ID and port number should be less than 1/(2^31),
and if that should happen, the query domain, class and type,
also have to match for the reply to be succesfully decoded.
Since the probability is so low this is not a practically exploitable
vulnerability, and if the bogus reply is not an attack it is a slightly
old valid reply so no harm is done.

This commit also rewrites the `inet_dns` decoding code to check
the received reply header in steps, starting with the transaction ID,
and bail out immediately when a reply does not match the query.
The old code decoded the whole reply packet first before checking
any field.  This code is much more efficient in the precense of
bogus reply packets.
Use two ETS set tables to record used transaction IDs.

If a generated random ID is already in the tables;
reject and generate a new.

One table is the New and the other the Old.

When the Old table is oolder than 60 seconds, or the New
table has more than 8192 IDs, wipe the Old table and
retire the New into being the Old.
This reverts commit ee70b41adfa64ff442b64d87fce9ad5e8a32311c.

Avoiding duplicate IDs is deemed not be worth the effort,
since it adds typically five ETS lookup/member and insert
operations on top on the single NIF call to libcrypto.

The probability for a collision is, as explained in the commit
comment implementing the random DNS transaction IDs, too low
to be a usable exploit, and a collision should also not be harmful
in normal use.
…l/inet_res-id-27/OTP-20037

* raimo/kernel/inet_res-id-26/OTP-20037:
  Revert "Avoid duplicate DNS transaction IDs"
  Avoid duplicate DNS transaction IDs
  Randomize `inet_res` transaction ID and source port number
…l/inet_res-id-28/OTP-20037

* raimo/kernel/inet_res-id-27/OTP-20037:
  Revert "Avoid duplicate DNS transaction IDs"
  Avoid duplicate DNS transaction IDs
  Randomize `inet_res` transaction ID and source port number
* Hide the randomized port number -1 internal feature better, so that
  it does not show in the type spec of `gen_udp:open/2`.
  Suppress the Dialyzer warnings that follows from that.

* Mention RFC 5452 in the RFC list.

* Explain in a code comment why we use a deprecated `crypto` function.

* Fix inet:bind_random to actually do a final attempt with port 0.
  Reduce the number of random attempts to 3 since the "always"
  succeed anyway on a "normal" machine.

* Fix SCTP modules to handle port number like the TCP and UDP modules.
…l/inet_res-id-27/OTP-20037

* raimo/kernel/inet_res-id-26/OTP-20037:
  Update after feedback
…l/inet_res-id-28/OTP-20037

* raimo/kernel/inet_res-id-27/OTP-20037:
  Update after feedback
@RaimoNiskanen RaimoNiskanen added this to the OTP-29.0 milestone Mar 17, 2026
@RaimoNiskanen RaimoNiskanen self-assigned this Mar 17, 2026
@RaimoNiskanen RaimoNiskanen added team:PS Assigned to OTP team PS enhancement waiting waiting for changes/input from author in progress priority:low labels Mar 17, 2026
@github-actions

github-actions Bot commented Mar 17, 2026

Copy link
Copy Markdown
Contributor

CT Test Results

    2 files     74 suites   1h 12m 4s ⏱️
1 795 tests 1 377 ✅ 418 💤 0 ❌
2 048 runs  1 571 ✅ 477 💤 0 ❌

Results for commit e2fd738.

♻️ This comment has been updated with latest results.

To speed up review, make sure that you have read Contributing to Erlang/OTP and that all checks pass.

See the TESTING and DEVELOPMENT HowTo guides for details about how to run test locally.

Artifacts

// Erlang/OTP Github Action Bot

Comment thread lib/kernel/src/inet_db.erl Outdated
@bjorng

bjorng commented Mar 18, 2026

Copy link
Copy Markdown
Contributor

Also remember to remove this line:

-compile(nowarn_deprecated_catch).

@RaimoNiskanen
RaimoNiskanen force-pushed the raimo/kernel/inet_db-catch/OTP-20037 branch from 46a1988 to e2fd738 Compare March 18, 2026 13:47
bjorng
bjorng previously approved these changes Mar 18, 2026
@RaimoNiskanen
RaimoNiskanen force-pushed the raimo/kernel/inet_db-catch/OTP-20037 branch from e2fd738 to 36f23c9 Compare April 7, 2026 06:05
@RaimoNiskanen
RaimoNiskanen changed the base branch from master to maint April 7, 2026 06:06
@RaimoNiskanen RaimoNiskanen changed the title Rewrite the last legacy catch in inet_db Predictable DNS Transaction IDs Enable Cache Poisoning in Built-in Resolver. CVE-2026-28810 Apr 7, 2026
@RaimoNiskanen
RaimoNiskanen merged commit 16b8d80 into erlang:maint Apr 7, 2026
1 check passed
@RaimoNiskanen

Copy link
Copy Markdown
Contributor Author

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement in progress priority:low team:PS Assigned to OTP team PS waiting waiting for changes/input from author

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants