Skip to content

[WIP] feat(net): optional allow_net filtering for UDP#778

Draft
G4614 wants to merge 2 commits into
boxlite-ai:mainfrom
G4614:fix/sec-2-udp-filter
Draft

[WIP] feat(net): optional allow_net filtering for UDP#778
G4614 wants to merge 2 commits into
boxlite-ai:mainfrom
G4614:fix/sec-2-udp-filter

Conversation

@G4614

@G4614 G4614 commented Jun 15, 2026

Copy link
Copy Markdown
Contributor

⚠️ [WIP — decision logic tested; live forwarder NOT integration-verified]
Part of a source-level security audit. The UDP policy (udpAllowed) is unit
tested; the live forwarder is gated behind BOXLITE_UDP_FILTER=true (OFF by
default) because its interaction with gvproxy's embedded DNS resolver and the
datagram relay path could not be validated without a network-stack harness.

Problem

allow_net only filtered TCP. UDP used gvproxy's default NAT forwarder, so a
restricted box could exfiltrate to any host over UDP/QUIC (DNS-over-UDP to an
attacker on :53, HTTP/3 on :443, custom UDP channels). The DNS sinkhole governs
only name resolution, not datagram destinations.

Change

  • udpAllowed(destIP, destPort, filter) — drops UDP to destinations not permitted
    by the AllowNet filter; reuses TCPFilter.MatchesIP, so gateway/internal IPs
    (incl. the DNS resolver) and allowlisted hosts pass. No filter → all UDP allowed.
  • OverrideUDPHandler installs a filtered udp.NewForwarder (mirrors the TCP
    override). Wired in main.go behind BOXLITE_UDP_FILTER=true, OFF by
    default
    so the live path is unchanged.

Test (two-sided) — policy, runs locally

TestUDPAllowed: attacker :53/:443 and unlisted hosts blocked; allowlisted +
gateway DNS pass. Reverting udpAllowed to "always allow" (pre-fix) makes the
blocked cases pass and the test fails. Full bridge suite + go vet green.

What needs a resource to verify (before enabling by default)

  • A gvisor netstack integration harness (the repo has none — forked_network_test
    only reflect-checks the stack field) to drive real UDP/DNS packets and confirm:
    (a) non-allowlisted UDP is dropped end to end, (b) DNS still resolves (i.e.
    overriding the UDP handler does not bypass the embedded resolver endpoint), and
    (c) the datagram relay/idle-timeout works for allowed flows.

Audit finding #2 (high).

🤖 Generated with Claude Code

allow_net only constrained TCP — UDP went through gvproxy's default NAT
forwarder, so a restricted box could exfiltrate to any host over UDP/QUIC
(DNS-over-UDP to an attacker on :53, HTTP/3 on :443, custom channels). The DNS
sinkhole only governs name resolution, not datagram destinations.

Add a filtered UDP forwarder that drops datagrams whose destination IP is not
permitted by the AllowNet filter. The decision (udpAllowed) reuses
TCPFilter.MatchesIP, so the gateway/internal IPs (incl. the embedded DNS
resolver) and allowlisted hosts pass while everything else is dropped. With no
allow_net, all UDP is allowed (unchanged).

[WIP] gated behind BOXLITE_UDP_FILTER=true, OFF by default — the live forwarder
path is NOT integration-verified. In particular, whether overriding the UDP
protocol handler bypasses gvproxy's embedded DNS resolver endpoint (DNS-break
risk) and the datagram relay/idle-timeout behavior must be validated against a
running network stack; the repo has no such harness (forked_network_test only
reflit-checks the stack field). Wiring it on by default needs that verification.

Two-sided test: TestUDPAllowed confirms attacker :53/:443 and unlisted hosts are
blocked while allowlisted + gateway DNS pass; reverting udpAllowed to its pre-fix
"always allow" makes the blocked cases pass through and the test fails.

Audit finding #2 (high).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jun 15, 2026

Copy link
Copy Markdown

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: c7a5a110-7ad5-43b1-9a73-a69aec81f63d

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands and usage tips.

@cla-assistant

cla-assistant Bot commented Jun 15, 2026

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.


boxlite security fixes seems not to be a GitHub user. You need a GitHub account to be able to sign the CLA. If you have already a GitHub account, please add the email address used for this commit to your account.
You have signed the CLA already but the status is still pending? Let us recheck it.

1 similar comment
@cla-assistant

cla-assistant Bot commented Jun 15, 2026

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.


boxlite security fixes seems not to be a GitHub user. You need a GitHub account to be able to sign the CLA. If you have already a GitHub account, please add the email address used for this commit to your account.
You have signed the CLA already but the status is still pending? Let us recheck it.

…tion)

Adds TestUDPFilter_BoundDNSEndpointNotBypassed: builds a gvisor stack, binds a
UDP endpoint on gateway:53 (as gvproxy's DNS resolver does via gonet.DialUDP),
installs a filtering UDP forwarder, and injects real IPv4/UDP datagrams.

It confirms the load-bearing property behind OverrideUDPHandler: the transport
demuxer delivers :53 to the bound resolver endpoint (DNS keeps working) while
non-DNS UDP reaches the filtering forwarder (where allow_net drops it). This
disproves the DNS-break concern flagged in the original WIP commit — overriding
the UDP protocol handler does not bypass the bound DNS endpoint.

Remaining caveat (still BOXLITE_UDP_FILTER-gated, off by default): end-to-end
relay of allow-listed flows under real guest traffic is not yet load-tested.

Audit finding #2 (high).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.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.

1 participant