Summary
This report covers mosdns reproduced from the official GitHub release asset
v5.3.4. On the reproduction date (2026-08-03), the downloaded binary
reported version v5.3.4-0-gb732318.
The issue was reproduced on the documented configuration path for:
- a DoQ listener via
quic_server
- forwarding to a plain UDP upstream via the
forward plugin
Scope is limited to the client-triggered DoQ forwarding path to the backend UDP
upstream.
On that path:
- one live
mosdns process reused one backend UDP source port across all 16
sampled forwarded queries;
- backend DNS IDs advanced as a straight sequence (
3..18 in the base run);
- a non-zero client-supplied DoQ DNS Message ID was accepted, and the DoQ
response sent back to the client carried that same non-zero ID.
Four fresh reruns reproduced the same shape every time.
Attached evidence:
Related public confirmations in the same backend-state family:
RFC 9250 baseline
RFC 9250 Section 4.2.1 gives the relevant proxying rules for DoQ message IDs:
- “When sending queries over a QUIC connection, the DNS Message ID MUST be set
to 0.”
- “When forwarding a DNS message from DoQ over another transport, a DNS
Message ID MUST be generated according to the rules of the protocol that is
in use.”
Reference: RFC 9250 §4.2.1
That section matters here for two separate reasons.
First, DoQ itself is not supposed to use the DNS Message ID as a client-chosen
matching field. In the tested path, mosdns accepted a non-zero DoQ query ID
and returned a DoQ response carrying the same non-zero ID.
Second, once a DoQ proxy forwards onto another transport, it must generate the
backend ID for that transport. This report does not claim that RFC 9250
requires random UDP IDs. The narrower point is that the generated backend state
on the tested UDP hop is highly structured: one source port for the whole run,
plus a simple sequential DNS ID.
Observed behavior
The observed behavior is consistent with connection-scoped backend forwarding
state on the UDP leg.
In the base run:
- the backend UDP observer saw one source port reused across all 16 forwarded
queries;
- the backend DNS IDs formed the sequence
3..18;
- the non-zero DoQ query used client ID
16705, and the client-visible DoQ
response carried the same ID back.
Across the shipped four-run repeatability set, that shape stayed stable:
- sequential backend DNS-ID runs:
4/4
- one backend source-port reused within run:
4/4
- non-zero DoQ ID accepted:
4/4
- client-visible non-zero ID reflected:
4/4
This is not the fixed-zero family seen in dnsproxy / AdGuard Home. The
backend DNS ID still changes per query. The problem is the combination of a
stable backend UDP source port and a simple backend ID sequence. On that hop,
the forwarded (source-port, txid) state is much more predictable than a
fresh per-query UDP forwarding tuple.
Reproduce
Prerequisites:
- Python 3
curl, unzip, and openssl
- network access to download the official
mosdns release asset
This report package is self-contained. The attached wrapper downloads the
official mosdns release asset on its own, bootstraps
attachments/runtime/.venv if needed, and then runs the standalone reproducer
shipped in this directory.
To rerun the base evidence:
cd attachments/scripts
bash repro-g10-mosdns-backend-state.sh
- Inspect
../artifacts/g10/<RUN_ID>/summary.txt
- Inspect
udp_observer.jsonl, queries.json, and summary.json
To rerun the four-sample repeatability set shipped with this report:
cd attachments/scripts
bash repro-g10-mosdns-repeatability.sh
- Inspect
../artifacts/g10/repeatability.md
The main entrypoints are:
attachments/scripts/repro-g10-mosdns-backend-state.sh
attachments/scripts/repro-g10-mosdns-repeatability.sh
attachments/scripts/run_g10_mosdns_backend_state.py
The exact base-run config is shipped in
attachments/artifacts/g10/20260803T093921Z-g10/mosdns.yaml. Because it was
captured from a live run, its certificate paths point into that run's temporary
directory. For a readable static equivalent, use
attachments/configs/mosdns-minimal.yaml.
Evidence package
The attachment set is enough for a vendor to verify the claim and rerun it:
- exact binary provenance:
version.txt and release-url.txt
- exact runtime config from the base run:
mosdns.yaml
- readable static config template:
attachments/configs/mosdns-minimal.yaml
- backend packet trace:
udp_observer.jsonl
- per-query client results:
zero.json, nonzero.json, sample-*.json, and
queries.json
- process log:
server.log
- repeatability summary across four fresh reruns:
repeatability.md
The wrapper script is the primary reproducer. The bundled artifacts then let a
reviewer check the concrete base run without having to trust a prose summary.
Impact
This report does not claim a full forged-response exploit on its own. The claim
supported by the data is narrower and cleaner: on the tested DoQ-to-UDP path,
mosdns does not emit a fresh backend forwarding tuple per query. Within a
run, the backend source port stays fixed and the backend DNS ID advances as a
simple counter.
That puts mosdns in the same backend-state reduction family as the already
public RouteDNS and AdGuard Home cases, even though the exact generated ID
pattern is different. In mosdns, the backend ID is not frozen at zero; it is
still query-specific, but it is easy to model. For backend UDP spoofing
analysis, that distinction matters less than the overall shape: one stable
field and one predictable field on the forwarded UDP hop.
Attachments
attachments.zip
sha256
8122b2ad8ecf3684730230d7a2f745fcab662987be9bba296a4acda05dd26990 attachments.zip
Summary
This report covers
mosdnsreproduced from the official GitHub release assetv5.3.4. On the reproduction date (2026-08-03), the downloaded binaryreported version
v5.3.4-0-gb732318.The issue was reproduced on the documented configuration path for:
quic_serverforwardpluginScope is limited to the client-triggered DoQ forwarding path to the backend UDP
upstream.
On that path:
mosdnsprocess reused one backend UDP source port across all 16sampled forwarded queries;
3..18in the base run);response sent back to the client carried that same non-zero ID.
Four fresh reruns reproduced the same shape every time.
Attached evidence:
attachments/artifacts/g10/20260803T093921Z-g10/summary.txt
attachments/artifacts/g10/20260803T093921Z-g10/udp_observer.jsonl
attachments/artifacts/g10/20260803T093921Z-g10/mosdns.yaml
attachments/configs/mosdns-minimal.yaml
attachments/artifacts/g10/repeatability.md
Related public confirmations in the same backend-state family:
RFC 9250 baseline
RFC 9250 Section 4.2.1 gives the relevant proxying rules for DoQ message IDs:
to 0.”
Message ID MUST be generated according to the rules of the protocol that is
in use.”
Reference: RFC 9250 §4.2.1
That section matters here for two separate reasons.
First, DoQ itself is not supposed to use the DNS Message ID as a client-chosen
matching field. In the tested path,
mosdnsaccepted a non-zero DoQ query IDand returned a DoQ response carrying the same non-zero ID.
Second, once a DoQ proxy forwards onto another transport, it must generate the
backend ID for that transport. This report does not claim that RFC 9250
requires random UDP IDs. The narrower point is that the generated backend state
on the tested UDP hop is highly structured: one source port for the whole run,
plus a simple sequential DNS ID.
Observed behavior
The observed behavior is consistent with connection-scoped backend forwarding
state on the UDP leg.
In the base run:
queries;
3..18;16705, and the client-visible DoQresponse carried the same ID back.
Across the shipped four-run repeatability set, that shape stayed stable:
4/44/44/44/4This is not the fixed-zero family seen in
dnsproxy/AdGuard Home. Thebackend DNS ID still changes per query. The problem is the combination of a
stable backend UDP source port and a simple backend ID sequence. On that hop,
the forwarded
(source-port, txid)state is much more predictable than afresh per-query UDP forwarding tuple.
Reproduce
Prerequisites:
curl,unzip, andopensslmosdnsrelease assetThis report package is self-contained. The attached wrapper downloads the
official
mosdnsrelease asset on its own, bootstrapsattachments/runtime/.venvif needed, and then runs the standalone reproducershipped in this directory.
To rerun the base evidence:
cd attachments/scriptsbash repro-g10-mosdns-backend-state.sh../artifacts/g10/<RUN_ID>/summary.txtudp_observer.jsonl,queries.json, andsummary.jsonTo rerun the four-sample repeatability set shipped with this report:
cd attachments/scriptsbash repro-g10-mosdns-repeatability.sh../artifacts/g10/repeatability.mdThe main entrypoints are:
attachments/scripts/repro-g10-mosdns-backend-state.shattachments/scripts/repro-g10-mosdns-repeatability.shattachments/scripts/run_g10_mosdns_backend_state.pyThe exact base-run config is shipped in
attachments/artifacts/g10/20260803T093921Z-g10/mosdns.yaml. Because it wascaptured from a live run, its certificate paths point into that run's temporary
directory. For a readable static equivalent, use
attachments/configs/mosdns-minimal.yaml.Evidence package
The attachment set is enough for a vendor to verify the claim and rerun it:
version.txtandrelease-url.txtmosdns.yamlattachments/configs/mosdns-minimal.yamludp_observer.jsonlzero.json,nonzero.json,sample-*.json, andqueries.jsonserver.logrepeatability.mdThe wrapper script is the primary reproducer. The bundled artifacts then let a
reviewer check the concrete base run without having to trust a prose summary.
Impact
This report does not claim a full forged-response exploit on its own. The claim
supported by the data is narrower and cleaner: on the tested DoQ-to-UDP path,
mosdnsdoes not emit a fresh backend forwarding tuple per query. Within arun, the backend source port stays fixed and the backend DNS ID advances as a
simple counter.
That puts
mosdnsin the same backend-state reduction family as the alreadypublic RouteDNS and AdGuard Home cases, even though the exact generated ID
pattern is different. In
mosdns, the backend ID is not frozen at zero; it isstill query-specific, but it is easy to model. For backend UDP spoofing
analysis, that distinction matters less than the overall shape: one stable
field and one predictable field on the forwarded UDP hop.
Attachments
attachments.zip
sha256