summaryrefslogtreecommitdiff
path: root/internal/namespace/namespace.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/namespace/namespace.go')
-rw-r--r--internal/namespace/namespace.go95
1 files changed, 95 insertions, 0 deletions
diff --git a/internal/namespace/namespace.go b/internal/namespace/namespace.go
index ab3797d..0f2618b 100644
--- a/internal/namespace/namespace.go
+++ b/internal/namespace/namespace.go
@@ -194,3 +194,98 @@ func Bootstrap() (err error) {
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() {
+ for _, fd := range fdsToClose {
+ _ = syscall.Close(fd)
+ }
+ }()
+
+ // 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)
+ }
+
+ // 1. Create a secure temporary file for the launcher binary.
+ tmpFile, err := os.CreateTemp("", "wg-wrap-launcher-")
+ if err != nil {
+ return fmt.Errorf("failed to create temp launcher file: %w", err)
+ }
+ launcherPath := tmpFile.Name()
+
+ // 2. Write the embedded launcher binary to the temp file.
+ if _, err := tmpFile.Write(launcherBytes); err != nil {
+ _ = tmpFile.Close()
+ _ = os.Remove(launcherPath)
+ return fmt.Errorf("failed to write launcher binary: %w", err)
+ }
+
+ // Ensure the binary is executable (0700)
+ if err := tmpFile.Chmod(0700); err != nil {
+ _ = tmpFile.Close()
+ _ = os.Remove(launcherPath)
+ return fmt.Errorf("failed to set launcher permissions: %w", err)
+ }
+
+ // 2b. Open a read-only fd of the launcher to exec
+ execFd, err := syscall.Open(launcherPath, syscall.O_RDONLY, 0)
+ if err != nil {
+ _ = tmpFile.Close()
+ _ = os.Remove(launcherPath)
+ return fmt.Errorf("failed to open launcher for exec: %w", err)
+ }
+ fdsToClose = append(fdsToClose, execFd)
+
+ // Close the write file descriptor (to avoid ETXTBSY)
+ _ = tmpFile.Close()
+
+ // Unlink the file from disk (makes it invisible and ensures it is deleted on exit)
+ _ = os.Remove(launcherPath)
+
+ // Clear close-on-exec so it remains open across syscall.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)
+ }
+ }
+ }
+
+ // Set environment variables to tell the C launcher to join,
+ // and to tell the second wg-wrap instance that we are in a joined session.
+ 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
+}