@@ -299,20 +299,50 @@ impl BlockList {
299299 }
300300}
301301
302+ /// Parse `/etc/resolv.conf` and return the set of nameserver IPs.
303+ ///
304+ /// On non-Unix platforms this returns an empty set (DNS goes through the
305+ /// system resolver and never needs a raw port-53 exemption).
306+ fn parse_resolv_conf ( ) -> HashSet < IpAddr > {
307+ #[ cfg( unix) ]
308+ {
309+ let mut resolvers = HashSet :: new ( ) ;
310+ if let Ok ( contents) = std:: fs:: read_to_string ( "/etc/resolv.conf" ) {
311+ for line in contents. lines ( ) {
312+ let line = line. trim ( ) ;
313+ if let Some ( rest) = line. strip_prefix ( "nameserver" ) {
314+ if let Some ( ip_str) = rest. split_whitespace ( ) . next ( ) {
315+ if let Ok ( ip) = ip_str. parse :: < IpAddr > ( ) {
316+ resolvers. insert ( ip) ;
317+ }
318+ }
319+ }
320+ }
321+ }
322+ resolvers
323+ }
324+ #[ cfg( not( unix) ) ]
325+ {
326+ HashSet :: new ( )
327+ }
328+ }
329+
302330impl NetworkPolicy {
303331 fn check ( & self , addr : & std:: net:: SocketAddr ) -> Result < ( ) > {
304332 match self {
305333 NetworkPolicy :: AllowAll => Ok ( ( ) ) ,
306334 NetworkPolicy :: AllowList ( al) => {
307- if addr. port ( ) == 53 || al. is_allowed ( & addr. ip ( ) ) {
335+ if ( addr. port ( ) == 53 && parse_resolv_conf ( ) . contains ( & addr. ip ( ) ) )
336+ || al. is_allowed ( & addr. ip ( ) )
337+ {
308338 Ok ( ( ) )
309339 } else {
310340 Err ( anyhow ! ( "network policy denies connection to {}" , addr) )
311341 }
312342 }
313343 NetworkPolicy :: BlockList ( bl) => {
314- // DNS (port 53) is always allowed so the guest can resolve names .
315- if addr. port ( ) == 53 {
344+ // Allow DNS (port 53) only to configured resolver IPs .
345+ if addr. port ( ) == 53 && parse_resolv_conf ( ) . contains ( & addr . ip ( ) ) {
316346 return Ok ( ( ) ) ;
317347 }
318348 if bl. is_blocked ( & addr. ip ( ) ) {
@@ -2619,6 +2649,54 @@ mod tests {
26192649 assert ! ( err. to_string( ) . contains( "network policy denies" ) , "{err}" ) ;
26202650 }
26212651
2652+ #[ test]
2653+ fn test_port53_arbitrary_ip_blocked ( ) {
2654+ // Only 1.1.1.1 is in the allowlist — 8.8.8.8:53 must be denied
2655+ // because 8.8.8.8 is (almost certainly) not in /etc/resolv.conf.
2656+ let al = AllowList :: from_hosts ( & [ "1.1.1.1" ] ) . unwrap ( ) ;
2657+ let policy = NetworkPolicy :: AllowList ( al) ;
2658+ let addr: std:: net:: SocketAddr = "8.8.8.8:53" . parse ( ) . unwrap ( ) ;
2659+ assert ! (
2660+ policy. check( & addr) . is_err( ) ,
2661+ "port 53 to a non-resolver IP must be denied"
2662+ ) ;
2663+ }
2664+
2665+ #[ cfg( unix) ]
2666+ #[ test]
2667+ fn test_port53_real_resolver_allowed ( ) {
2668+ // Grab the first resolver from /etc/resolv.conf and verify port
2669+ // 53 to that IP is allowed even when the IP is not in the
2670+ // AllowList itself.
2671+ let resolvers = parse_resolv_conf ( ) ;
2672+ if resolvers. is_empty ( ) {
2673+ eprintln ! ( "skipping: no resolvers found in /etc/resolv.conf" ) ;
2674+ return ;
2675+ }
2676+ let resolver_ip = * resolvers. iter ( ) . next ( ) . unwrap ( ) ;
2677+ let al = AllowList :: from_hosts ( & [ "1.1.1.1" ] ) . unwrap ( ) ;
2678+ let policy = NetworkPolicy :: AllowList ( al) ;
2679+ let addr = std:: net:: SocketAddr :: new ( resolver_ip, 53 ) ;
2680+ assert ! (
2681+ policy. check( & addr) . is_ok( ) ,
2682+ "port 53 to a configured resolver ({}) must be allowed" ,
2683+ resolver_ip
2684+ ) ;
2685+ }
2686+
2687+ #[ test]
2688+ fn test_port53_blocklist_enforced ( ) {
2689+ // Even on port 53, a blocklisted IP must be denied — unless it
2690+ // happens to be a real resolver. Use a bogus IP to be safe.
2691+ let bl = BlockList :: from_hosts ( & [ "192.0.2.1" ] ) . unwrap ( ) ;
2692+ let policy = NetworkPolicy :: BlockList ( bl) ;
2693+ let addr: std:: net:: SocketAddr = "192.0.2.1:53" . parse ( ) . unwrap ( ) ;
2694+ assert ! (
2695+ policy. check( & addr) . is_err( ) ,
2696+ "blocklisted IP must be denied even on port 53"
2697+ ) ;
2698+ }
2699+
26222700 #[ test]
26232701 fn allowlist_resolves_hostnames ( ) {
26242702 let al = AllowList :: from_hosts ( & [ "localhost" ] ) . unwrap ( ) ;
@@ -2651,11 +2729,15 @@ mod tests {
26512729 }
26522730
26532731 #[ test]
2654- fn network_policy_blocklist_allows_dns ( ) {
2732+ fn network_policy_blocklist_denies_blocked_ip_on_port53 ( ) {
2733+ // A blocked IP must not be exempted just because the port is 53.
26552734 let bl = BlockList :: from_hosts ( & [ "1.2.3.4" ] ) . unwrap ( ) ;
26562735 let policy = NetworkPolicy :: BlockList ( bl) ;
26572736 let addr: std:: net:: SocketAddr = "1.2.3.4:53" . parse ( ) . unwrap ( ) ;
2658- assert ! ( policy. check( & addr) . is_ok( ) ) ;
2737+ assert ! (
2738+ policy. check( & addr) . is_err( ) ,
2739+ "blocked IP 1.2.3.4 must be denied even on port 53"
2740+ ) ;
26592741 }
26602742
26612743 #[ test]
0 commit comments