Skip to content

Subnet conflict mitigation #18

Description

@nirs

Problem statement

In macOS 26 (Tahoe), the vmnet framework allows unprivileged users to create virtual networks with custom subnets via the new architecture. However, this flexibility introduces a high risk of subnet collisions. If a user or admin configures a virtual bridge using a subnet that is already in use by a physical interface (like Wi-Fi) or a system-reserved range, it can lead to:

  • Host connectivity loss: Hijacking the physical LAN's gateway IP (e.g., your router's address) causes the Mac to lose internet access.
  • Service instability: Overlapping with Apple's reserved ranges for virtualizationd (192.168.64.0/24) or Internet Sharing (192.168.2.0/24) causes undefined behavior in system services.
  • Zombie routes: Failed cleanup of previous sessions can leave "ghost" routes in the kernel that block new networks from starting.

Suggested mitigation

The broker should implement a validation layer that checks for collisions before attempting to start the interface. Instead of blindly trusting the configuration, the broker will perform a two-stage pre-flight check:

  1. Static blocklist: Prevent the use of known Apple-reserved subnets.
  2. Dynamic collision detection: Query the live system state to ensure the requested subnet does not overlap with any active physical or virtual interface.

Reserved subnets to avoid

Subnet Owner Action
192.168.2.0/24 macOS 26 root Warn
192.168.64.0/24 Apple Container Warn
192.168.105.0/24 Legacy shared range Warn
192.168.0.0/16 Documented range Block

Example implementation

#include <ifaddrs.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <arpa/inet.h>

/**
 * Checks if a requested subnet conflicts with an existing interface.
 * Returns 0 if safe, -1 if a collision is detected.
 */
static int check_subnet_collision(const char *requested_subnet, const char *ctx_name) {
    struct ifaddrs *ifap, *ifa;
    struct in_addr req_addr;
    
    if (inet_pton(AF_INET, requested_subnet, &req_addr) != 1) {
        return 0; 
    }

    // Convert requested address to host byte order
    uint32_t host_req = ntohl(req_addr.s_addr);
    // Mask for /24 (255.255.255.0) in host order
    uint32_t mask = 0xFFFFFF00;

    if (getifaddrs(&ifap) != 0) {
        return 0;
    }

    for (ifa = ifap; ifa != NULL; ifa = ifa->ifa_next) {
        if (ifa->ifa_addr && ifa->ifa_addr->sa_family == AF_INET) {
            struct sockaddr_in *sa = (struct sockaddr_in *)ifa->ifa_addr;
            
            // Convert existing interface address to host byte order
            uint32_t host_ifa = ntohl(sa->sin_addr.s_addr);

            // Compare the network portions
            if ((host_req & mask) == (host_ifa & mask)) {
                WARNF("[%s] SUBNET COLLISION: %s requested, but %s is already using %s",
                      ctx_name, requested_subnet, ifa->ifa_name, inet_ntoa(sa->sin_addr));
                freeifaddrs(ifap);
                return -1;
            }
        }
    }
    
    freeifaddrs(ifap);
    return 0;
}

Integration into existing network_config function

static vmnet_network_configuration_ref network_config(const struct context *ctx) {
    // ... initialization code ...
    
    // Example using user-provided static subnet from patch
    const char *target_subnet = "192.168.2.1"; 

    if (check_subnet_collision(target_subnet, ctx->name) != 0) {
        // Handle collision (e.g., exit or return error status)
        goto error;
    }
    
    // Proceed with vmnet_network_configuration_set_ipv4_subnet...
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions