Skip to content

feat(ebpf): filter IPv6 egress (close the v6 bypass)#60

Merged
CharmingGroot merged 1 commit into
release/v0.1from
feat/ipv6-egress
Jul 22, 2026
Merged

feat(ebpf): filter IPv6 egress (close the v6 bypass)#60
CharmingGroot merged 1 commit into
release/v0.1from
feat/ipv6-egress

Conversation

@CharmingGroot

Copy link
Copy Markdown
Owner

Gap

The kernel guard only enforced IPv4 — non-IPv4 packets passed, so a tool could exfiltrate over IPv6 and defeat the "unbypassable egress" claim. (Found in a hardening audit; was documented as a known gap.) Now both families are default-deny.

eBPF

  • New ALLOW6 map (u128 key = u128::from(Ipv6Addr)).
  • Program branches on IP version; for v6, loads the 16-byte destination as two u64 halves (64-bit ops only → verifier-friendly), builds the u128 key.
  • Loopback ::1 and infra prefixes (link-local fe80::/10, multicast ff00::/8) always pass — mirrors the v4 loopback exception so basic v6 networking (NDP) keeps working.
  • Unknown IP version → drop (fail-closed).

Control plane

  • allow / allow-domain / admin socket / policy lowering all accept v4 and v6. Domain resolution injects both families; allow_insert/remove/list are IpAddr-based; EgressAllowlist gains ips6, GuardConfig gains allow6.

Tests

  • pasu-rules: v6 literal lowering → ips6.
  • admin: v6 parse (allow 2606:4700:4700::1111, deny ::1).
  • IPv6 kernel E2E (egress_e2e): allowlisted v6 connects / non-allowlisted v6 dropped. Self-skips without v6 connectivity — GitHub runners have no IPv6, so it skips there; it exercises the v6 path on a v6-capable host. The existing v4 E2E already proves the updated eBPF program (ALLOW6 map + v6 branch) loads and verifies on the CI kernel.

Docs (README ko/en · deployment.md · CHANGELOG) updated: v4+v6 default-deny, infra prefixes pass.

The kernel guard only enforced IPv4 — non-IPv4 packets passed, so a tool could
exfiltrate over IPv6 and defeat the "unbypassable" egress claim. Now both
families are default-deny.

eBPF: new ALLOW6 map (u128 key = u128::from(Ipv6Addr)); the program branches on
IP version and, for v6, loads the 16-byte destination as two u64 halves (64-bit
ops keep the verifier happy). Loopback (::1) and infra prefixes (link-local
fe80::/10, multicast ff00::/8) always pass, mirroring the v4 loopback exception.

Control plane: `allow` / `allow-domain` / the admin socket / policy lowering all
accept v4 and v6. Domain resolution injects both families into ALLOW / ALLOW6;
allow_insert/remove/list are IpAddr-based.

Tests: pasu-rules v6 lowering; admin v6 parse; an IPv6 kernel E2E (self-skips
without v6 connectivity — GitHub runners have none — but exercises the v6 path
on a v6-capable host; the v4 E2E already proves the updated program verifies).

Signed-off-by: Ho Geun Choi <ohyes9711@gmail.com>
@CharmingGroot
CharmingGroot merged commit 62d5817 into release/v0.1 Jul 22, 2026
4 checks passed
@CharmingGroot
CharmingGroot deleted the feat/ipv6-egress branch July 22, 2026 02:33
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