diff options
Diffstat (limited to 'internal/wireguard/wireguard.go')
| -rw-r--r-- | internal/wireguard/wireguard.go | 58 |
1 files changed, 56 insertions, 2 deletions
diff --git a/internal/wireguard/wireguard.go b/internal/wireguard/wireguard.go index 48bd562..3f17392 100644 --- a/internal/wireguard/wireguard.go +++ b/internal/wireguard/wireguard.go @@ -42,6 +42,11 @@ func StartTunnel(cfg *wgconf.Config, dnsServer string) (*Tunnel, error) { fmt.Printf("warning: failed to make mount namespace private: %v\n", err) } + // Block host services (D-Bus, nscd) to prevent name resolution leak bypasses + if err := BlockHostServices(); err != nil { + fmt.Printf("warning: failed to block host services: %v\n", err) + } + tunDev, err := tun.CreateTUN(tunName, mtu) if err != nil { return nil, fmt.Errorf("failed to create TUN device %s: %w", tunName, err) @@ -254,13 +259,62 @@ func ConfigureResolvConf(dns string) error { // 2. Make the mount private to ensure it doesn't propagate back to the host // and to satisfy kernel requirements for mount transitions in some environments. - if err := unix.Mount("/etc/resolv.conf", "/etc/resolv.conf", "", unix.MS_REMOUNT|unix.MS_BIND|unix.MS_PRIVATE, ""); err != nil { - return fmt.Errorf("failed to make /etc/resolv.conf mount private: %w", err) + // We do this by applying MS_PRIVATE in a separate mount call. + if err := unix.Mount("", "/etc/resolv.conf", "", unix.MS_PRIVATE, ""); err != nil { + // If MS_PRIVATE fails, we can log a warning but proceed since / is already private + fmt.Printf("warning: failed to make /etc/resolv.conf mount private: %v\n", err) } return nil } +// BlockHostServices blocks local D-Bus and name service cache daemon (nscd) sockets +// inside the mount namespace. This prevents glibc from bypassing the network namespace +// isolation via host services (e.g. systemd-resolved via D-Bus). +func BlockHostServices() error { + tmpDir, err := os.MkdirTemp("", "wg-wrap-block-") + if err != nil { + return fmt.Errorf("failed to create temp dir: %w", err) + } + defer func() { _ = os.Remove(tmpDir) }() + + tmpFile, err := os.CreateTemp("", "wg-wrap-block-file-") + if err != nil { + return fmt.Errorf("failed to create temp file: %w", err) + } + tmpFileName := tmpFile.Name() + _ = tmpFile.Close() + defer func() { _ = os.Remove(tmpFileName) }() + + // Specific socket files and directories to block + pathsToBlock := []string{ + "/run/dbus/system_bus_socket", + "/run/systemd/resolve/io.systemd.Resolve", + "/run/systemd/resolve/io.systemd.Resolve.Monitor", + "/run/nscd/socket", + "/var/run/dbus/system_bus_socket", + "/var/run/systemd/resolve/io.systemd.Resolve", + "/var/run/systemd/resolve/io.systemd.Resolve.Monitor", + "/var/run/nscd/socket", + } + + for _, p := range pathsToBlock { + stat, err := os.Stat(p) + if err == nil { + source := tmpFileName + if stat.IsDir() { + source = tmpDir + } + if err := unix.Mount(source, p, "", unix.MS_BIND, ""); err != nil { + fmt.Printf("warning: failed to bind-mount block over %s: %v\n", p, err) + } else { + _ = unix.Mount("", p, "", unix.MS_PRIVATE, "") + } + } + } + return nil +} + // HostBind wraps a standard conn.Bind so that its socket creation (Open) // is forced to execute within a host network namespace. type HostBind struct { |
