Skip to content

Commit bf35761

Browse files
committed
fix: include well-known public DNS in AllowList port-53 exemption
The AllowList DNS exemption only covered the host's resolvers from /etc/resolv.conf, but the guest hardcodes 8.8.8.8/8.8.4.4 in its initrd. On any host with different resolvers, AllowList silently broke DNS resolution. Add Google (8.8.8.8, 8.8.4.4) and Cloudflare (1.1.1.1, 1.0.0.1) to the exempted resolver set. Port 53 to arbitrary IPs is still blocked, preserving the anti-exfiltration property. Signed-off-by: danbugs <danilochiarlone@gmail.com>
1 parent ac978df commit bf35761

3 files changed

Lines changed: 53 additions & 26 deletions

File tree

host/Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

host/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "hyperlight-unikraft-host"
3-
version = "0.3.0"
3+
version = "0.4.0"
44
edition = "2021"
55
description = "Embedded Hyperlight host for running Unikraft unikernels"
66
license = "MIT OR Apache-2.0"

host/src/lib.rs

Lines changed: 51 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -304,12 +304,25 @@ impl BlockList {
304304
}
305305
}
306306

307+
/// DNS resolver IPs that the AllowList exempts on port 53.
308+
///
309+
/// Includes the host's configured resolvers (from `/etc/resolv.conf` on
310+
/// Unix, `ipconfig /all` on Windows) **plus** well-known public DNS
311+
/// servers (Google, Cloudflare) that the guest may hardcode in its own
312+
/// `/etc/resolv.conf`.
307313
fn dns_resolvers() -> &'static HashSet<IpAddr> {
308314
static RESOLVERS: std::sync::OnceLock<HashSet<IpAddr>> = std::sync::OnceLock::new();
309315
RESOLVERS.get_or_init(|| {
316+
let mut set = HashSet::new();
317+
// Well-known public DNS that the guest's initrd may hardcode.
318+
for ip in [
319+
"8.8.8.8", "8.8.4.4", // Google
320+
"1.1.1.1", "1.0.0.1", // Cloudflare
321+
] {
322+
set.insert(ip.parse::<IpAddr>().unwrap());
323+
}
310324
#[cfg(unix)]
311325
{
312-
let mut set = HashSet::new();
313326
if let Ok(contents) = std::fs::read_to_string("/etc/resolv.conf") {
314327
for line in contents.lines() {
315328
let line = line.trim();
@@ -322,11 +335,9 @@ fn dns_resolvers() -> &'static HashSet<IpAddr> {
322335
}
323336
}
324337
}
325-
set
326338
}
327339
#[cfg(windows)]
328340
{
329-
let mut set = HashSet::new();
330341
if let Ok(output) = std::process::Command::new("ipconfig").arg("/all").output() {
331342
let text = String::from_utf8_lossy(&output.stdout);
332343
let mut in_dns_block = false;
@@ -347,12 +358,8 @@ fn dns_resolvers() -> &'static HashSet<IpAddr> {
347358
}
348359
}
349360
}
350-
set
351-
}
352-
#[cfg(not(any(unix, windows)))]
353-
{
354-
HashSet::new()
355361
}
362+
set
356363
})
357364
}
358365

@@ -2597,34 +2604,43 @@ mod tests {
25972604
}
25982605

25992606
#[test]
2600-
fn test_port53_arbitrary_ip_blocked() {
2601-
// RFC5737 TEST-NET-2 address — guaranteed not in /etc/resolv.conf.
2607+
fn allowlist_permits_dns_to_well_known_resolvers() {
2608+
// AllowList exempts port 53 to well-known public DNS and host
2609+
// resolvers so the guest's hardcoded nameservers (8.8.8.8 etc.)
2610+
// work even when the host uses different resolvers.
2611+
let al = AllowList::from_hosts(&["198.51.100.1"]).unwrap();
2612+
let policy = NetworkPolicy::AllowList(al);
2613+
for ip in ["8.8.8.8:53", "8.8.4.4:53", "1.1.1.1:53", "1.0.0.1:53"] {
2614+
let addr: std::net::SocketAddr = ip.parse().unwrap();
2615+
assert!(
2616+
policy.check(&addr).is_ok(),
2617+
"AllowList must permit DNS (port 53) to well-known resolver {}",
2618+
ip
2619+
);
2620+
}
2621+
}
2622+
2623+
#[test]
2624+
fn allowlist_denies_port53_to_arbitrary_ip() {
2625+
// Port 53 is NOT a blanket bypass — only known DNS resolvers
2626+
// are exempted. RFC5737 TEST-NET addresses are never resolvers.
26022627
let al = AllowList::from_hosts(&["198.51.100.1"]).unwrap();
26032628
let policy = NetworkPolicy::AllowList(al);
26042629
let addr: std::net::SocketAddr = "198.51.100.99:53".parse().unwrap();
26052630
assert!(
26062631
policy.check(&addr).is_err(),
2607-
"port 53 to a non-resolver IP must be denied"
2632+
"port 53 to an unknown IP must still be denied"
26082633
);
26092634
}
26102635

2611-
#[cfg(unix)]
26122636
#[test]
2613-
fn test_port53_real_resolver_allowed() {
2614-
let resolvers = dns_resolvers();
2615-
if resolvers.is_empty() {
2616-
eprintln!("skipping: no resolvers found in /etc/resolv.conf");
2617-
return;
2618-
}
2619-
let resolver_ip = *resolvers.iter().next().unwrap();
2620-
// Allowlist uses a TEST-NET IP that won't match the resolver.
2637+
fn allowlist_denies_non_dns_to_unlisted_ip() {
26212638
let al = AllowList::from_hosts(&["198.51.100.1"]).unwrap();
26222639
let policy = NetworkPolicy::AllowList(al);
2623-
let addr = std::net::SocketAddr::new(resolver_ip, 53);
2640+
let addr: std::net::SocketAddr = "198.51.100.99:443".parse().unwrap();
26242641
assert!(
2625-
policy.check(&addr).is_ok(),
2626-
"port 53 to a configured resolver ({}) must be allowed",
2627-
resolver_ip
2642+
policy.check(&addr).is_err(),
2643+
"AllowList must deny non-DNS traffic to unlisted IPs"
26282644
);
26292645
}
26302646

@@ -2683,6 +2699,17 @@ mod tests {
26832699
);
26842700
}
26852701

2702+
#[test]
2703+
fn blocklist_permits_dns_to_non_blocked_ip() {
2704+
let bl = BlockList::from_hosts(&["203.0.113.1"]).unwrap();
2705+
let policy = NetworkPolicy::BlockList(bl);
2706+
let addr: std::net::SocketAddr = "8.8.8.8:53".parse().unwrap();
2707+
assert!(
2708+
policy.check(&addr).is_ok(),
2709+
"BlockList must allow DNS to non-blocked IPs"
2710+
);
2711+
}
2712+
26862713
#[test]
26872714
fn blocklist_resolves_hostnames() {
26882715
let bl = BlockList::from_hosts(&["localhost"]).unwrap();

0 commit comments

Comments
 (0)