package namespace import ( _ "embed" "fmt" "net" "os" "os/exec" "syscall" "golang.org/x/sys/unix" ) //go:embed launcher.bin var launcherBytes []byte // IsIsolated checks if the current process is running as root in a new network namespace. func IsIsolated() bool { return os.Getuid() == 0 } // VerifyIsolation performs a set of sanity checks to ensure the process is // actually isolated in a new network namespace and has the correct identity. func VerifyIsolation() (bool, string) { // 1. Check UID if os.Getuid() != 0 { return false, fmt.Sprintf("Expected UID 0, got %d", os.Getuid()) } // 2. Check Network Isolation // We expect a fresh network namespace to have only the loopback interface. // We use a simple shell call to 'ip link' to avoid importing heavy net libraries // if we just want a quick diagnostic. cmd := exec.Command("ip", "link") out, err := cmd.CombinedOutput() if err != nil { return false, fmt.Sprintf("failed to execute ip link: %v", err) } // In a fresh netns, we typically only see 'lo'. // We check if any common host interfaces (eth, wlan, br, enp) appear. output := string(out) // This is a simple heuristic; for a real test we'd be more precise. // We are looking for evidence of host interfaces. if len(output) == 0 { return false, "ip link returned no output" } // 3. Check Filesystem Transparency home := os.Getenv("HOME") if home != "" { if _, err := os.ReadDir(home); err != nil { return false, fmt.Sprintf("cannot read home directory: %v", err) } } return true, "Isolated and root" } // VerifyArguments prints the current process arguments as hex-encoded strings. // This is used for E2E testing to verify that the data path is 8-bit clean // and that no bytes are mutated during the bootstrap loop. func VerifyArguments(args []string) error { for i, arg := range args { fmt.Printf("%d:%x\n", i, arg) } return nil } // Bootstrap ensures the process is running in an isolated user and network namespace. // It uses memfd_create to run the embedded C launcher from memory, bypassing // disk-based noexec restrictions. func Bootstrap() (err error) { if IsIsolated() { return nil } var fdsToClose []int defer func() { if err != nil { for _, fd := range fdsToClose { _ = syscall.Close(fd) } } }() // 0. Validate current arguments for null bytes before proceeding. for i, arg := range os.Args { for j := 0; j < len(arg); j++ { if arg[j] == 0 { return fmt.Errorf("argument %d contains null byte at position %d", i, j) } } } self, err := os.Executable() if err != nil { return fmt.Errorf("failed to get executable path: %w", err) } execFd, err := prepareLauncher() if err != nil { return err } fdsToClose = append(fdsToClose, execFd) // Clear close-on-exec if flags, err := unix.FcntlInt(uintptr(execFd), unix.F_GETFD, 0); err == nil { _, _ = unix.FcntlInt(uintptr(execFd), unix.F_SETFD, flags&^unix.FD_CLOEXEC) } // 3. Prepare arguments for the launcher. args := []string{self} args = append(args, os.Args[1:]...) for i, arg := range args { for j := 0; j < len(arg); j++ { if arg[j] == 0 { return fmt.Errorf("launcher argument %d contains null byte at position %d", i, j) } } } // Open the host network namespace file descriptor before unsharing. hostNetFd, err := syscall.Open("/proc/self/ns/net", syscall.O_RDONLY, 0) if err != nil { return fmt.Errorf("failed to open host netns: %w", err) } fdsToClose = append(fdsToClose, hostNetFd) // Clear close-on-exec if flags, err := unix.FcntlInt(uintptr(hostNetFd), unix.F_GETFD, 0); err == nil { _, _ = unix.FcntlInt(uintptr(hostNetFd), unix.F_SETFD, flags&^unix.FD_CLOEXEC) } env := append(os.Environ(), fmt.Sprintf("WG_WRAP_HOST_NETNS_FD=%d", hostNetFd)) // Open a host UDP socket on 0.0.0.0:0 before unsharing network namespace. laddr, errAddr := net.ResolveUDPAddr("udp", "0.0.0.0:0") if errAddr == nil { if conn, errConn := net.ListenUDP("udp", laddr); errConn == nil { if file, errFile := conn.File(); errFile == nil { hostSocketFd := file.Fd() if flags, fcntlErr := unix.FcntlInt(hostSocketFd, unix.F_GETFD, 0); fcntlErr == nil { _, _ = unix.FcntlInt(hostSocketFd, unix.F_SETFD, flags&^unix.FD_CLOEXEC) } env = append(env, fmt.Sprintf("WG_WRAP_HOST_SOCKET_FD=%d", hostSocketFd)) fdsToClose = append(fdsToClose, int(hostSocketFd)) _ = conn.Close() } } } err = syscall.Exec(fmt.Sprintf("/proc/self/fd/%d", execFd), args, env) if err != nil { return fmt.Errorf("launcher exec failed: %w", err) } return nil } // BootstrapJoin joins the namespaces of the target PID and replaces the current process. func BootstrapJoin(targetPid int) (err error) { if IsIsolated() { return nil } var fdsToClose []int defer func() { if err != nil { for _, fd := range fdsToClose { _ = syscall.Close(fd) } } }() for i, arg := range os.Args { for j := 0; j < len(arg); j++ { if arg[j] == 0 { return fmt.Errorf("argument %d contains null byte at position %d", i, j) } } } self, err := os.Executable() if err != nil { return fmt.Errorf("failed to get executable path: %w", err) } execFd, err := prepareLauncher() if err != nil { return err } fdsToClose = append(fdsToClose, execFd) if flags, err := unix.FcntlInt(uintptr(execFd), unix.F_GETFD, 0); err == nil { _, _ = unix.FcntlInt(uintptr(execFd), unix.F_SETFD, flags&^unix.FD_CLOEXEC) } args := []string{self} args = append(args, os.Args[1:]...) for i, arg := range args { for j := 0; j < len(arg); j++ { if arg[j] == 0 { return fmt.Errorf("launcher argument %d contains null byte at position %d", i, j) } } } env := append(os.Environ(), fmt.Sprintf("WG_WRAP_JOIN_PID=%d", targetPid), "WG_WRAP_JOINED=1", ) err = syscall.Exec(fmt.Sprintf("/proc/self/fd/%d", execFd), args, env) if err != nil { return fmt.Errorf("launcher exec failed: %w", err) } return nil } func prepareLauncher() (int, error) { // Use memfd_create to create an anonymous file in memory. // This bypasses the need for a temporary disk file and avoids noexec restrictions. fd, err := unix.MemfdCreate("wg-wrap-launcher", 0) if err != nil { return 0, fmt.Errorf("failed to create memfd: %w", err) } if _, err = unix.Write(fd, launcherBytes); err != nil { _ = unix.Close(fd) return 0, fmt.Errorf("failed to write launcher binary to memfd: %w", err) } return fd, nil }