@@ -304,20 +304,50 @@ impl BlockList {
304304 }
305305}
306306
307+ /// Parse `/etc/resolv.conf` and return the set of nameserver IPs.
308+ ///
309+ /// On non-Unix platforms this returns an empty set (DNS goes through the
310+ /// system resolver and never needs a raw port-53 exemption).
311+ fn parse_resolv_conf ( ) -> HashSet < IpAddr > {
312+ #[ cfg( unix) ]
313+ {
314+ let mut resolvers = HashSet :: new ( ) ;
315+ if let Ok ( contents) = std:: fs:: read_to_string ( "/etc/resolv.conf" ) {
316+ for line in contents. lines ( ) {
317+ let line = line. trim ( ) ;
318+ if let Some ( rest) = line. strip_prefix ( "nameserver" ) {
319+ if let Some ( ip_str) = rest. split_whitespace ( ) . next ( ) {
320+ if let Ok ( ip) = ip_str. parse :: < IpAddr > ( ) {
321+ resolvers. insert ( ip) ;
322+ }
323+ }
324+ }
325+ }
326+ }
327+ resolvers
328+ }
329+ #[ cfg( not( unix) ) ]
330+ {
331+ HashSet :: new ( )
332+ }
333+ }
334+
307335impl NetworkPolicy {
308336 fn check ( & self , addr : & std:: net:: SocketAddr ) -> Result < ( ) > {
309337 match self {
310338 NetworkPolicy :: AllowAll => Ok ( ( ) ) ,
311339 NetworkPolicy :: AllowList ( al) => {
312- if addr. port ( ) == 53 || al. is_allowed ( & addr. ip ( ) ) {
340+ if ( addr. port ( ) == 53 && parse_resolv_conf ( ) . contains ( & addr. ip ( ) ) )
341+ || al. is_allowed ( & addr. ip ( ) )
342+ {
313343 Ok ( ( ) )
314344 } else {
315345 Err ( anyhow ! ( "network policy denies connection to {}" , addr) )
316346 }
317347 }
318348 NetworkPolicy :: BlockList ( bl) => {
319- // DNS (port 53) is always allowed so the guest can resolve names .
320- if addr. port ( ) == 53 {
349+ // Allow DNS (port 53) only to configured resolver IPs .
350+ if addr. port ( ) == 53 && parse_resolv_conf ( ) . contains ( & addr . ip ( ) ) {
321351 return Ok ( ( ) ) ;
322352 }
323353 if bl. is_blocked ( & addr. ip ( ) ) {
@@ -2624,6 +2654,54 @@ mod tests {
26242654 assert ! ( err. to_string( ) . contains( "network policy denies" ) , "{err}" ) ;
26252655 }
26262656
2657+ #[ test]
2658+ fn test_port53_arbitrary_ip_blocked ( ) {
2659+ // Only 1.1.1.1 is in the allowlist — 8.8.8.8:53 must be denied
2660+ // because 8.8.8.8 is (almost certainly) not in /etc/resolv.conf.
2661+ let al = AllowList :: from_hosts ( & [ "1.1.1.1" ] ) . unwrap ( ) ;
2662+ let policy = NetworkPolicy :: AllowList ( al) ;
2663+ let addr: std:: net:: SocketAddr = "8.8.8.8:53" . parse ( ) . unwrap ( ) ;
2664+ assert ! (
2665+ policy. check( & addr) . is_err( ) ,
2666+ "port 53 to a non-resolver IP must be denied"
2667+ ) ;
2668+ }
2669+
2670+ #[ cfg( unix) ]
2671+ #[ test]
2672+ fn test_port53_real_resolver_allowed ( ) {
2673+ // Grab the first resolver from /etc/resolv.conf and verify port
2674+ // 53 to that IP is allowed even when the IP is not in the
2675+ // AllowList itself.
2676+ let resolvers = parse_resolv_conf ( ) ;
2677+ if resolvers. is_empty ( ) {
2678+ eprintln ! ( "skipping: no resolvers found in /etc/resolv.conf" ) ;
2679+ return ;
2680+ }
2681+ let resolver_ip = * resolvers. iter ( ) . next ( ) . unwrap ( ) ;
2682+ let al = AllowList :: from_hosts ( & [ "1.1.1.1" ] ) . unwrap ( ) ;
2683+ let policy = NetworkPolicy :: AllowList ( al) ;
2684+ let addr = std:: net:: SocketAddr :: new ( resolver_ip, 53 ) ;
2685+ assert ! (
2686+ policy. check( & addr) . is_ok( ) ,
2687+ "port 53 to a configured resolver ({}) must be allowed" ,
2688+ resolver_ip
2689+ ) ;
2690+ }
2691+
2692+ #[ test]
2693+ fn test_port53_blocklist_enforced ( ) {
2694+ // Even on port 53, a blocklisted IP must be denied — unless it
2695+ // happens to be a real resolver. Use a bogus IP to be safe.
2696+ let bl = BlockList :: from_hosts ( & [ "192.0.2.1" ] ) . unwrap ( ) ;
2697+ let policy = NetworkPolicy :: BlockList ( bl) ;
2698+ let addr: std:: net:: SocketAddr = "192.0.2.1:53" . parse ( ) . unwrap ( ) ;
2699+ assert ! (
2700+ policy. check( & addr) . is_err( ) ,
2701+ "blocklisted IP must be denied even on port 53"
2702+ ) ;
2703+ }
2704+
26272705 #[ test]
26282706 fn allowlist_resolves_hostnames ( ) {
26292707 let al = AllowList :: from_hosts ( & [ "localhost" ] ) . unwrap ( ) ;
@@ -2656,11 +2734,15 @@ mod tests {
26562734 }
26572735
26582736 #[ test]
2659- fn network_policy_blocklist_allows_dns ( ) {
2737+ fn network_policy_blocklist_denies_blocked_ip_on_port53 ( ) {
2738+ // A blocked IP must not be exempted just because the port is 53.
26602739 let bl = BlockList :: from_hosts ( & [ "1.2.3.4" ] ) . unwrap ( ) ;
26612740 let policy = NetworkPolicy :: BlockList ( bl) ;
26622741 let addr: std:: net:: SocketAddr = "1.2.3.4:53" . parse ( ) . unwrap ( ) ;
2663- assert ! ( policy. check( & addr) . is_ok( ) ) ;
2742+ assert ! (
2743+ policy. check( & addr) . is_err( ) ,
2744+ "blocked IP 1.2.3.4 must be denied even on port 53"
2745+ ) ;
26642746 }
26652747
26662748 #[ test]
0 commit comments