summaryrefslogtreecommitdiff
path: root/internal/wireguard/wireguard.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/wireguard/wireguard.go')
-rw-r--r--internal/wireguard/wireguard.go58
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 {