//go:build linux && rootrunner package host import ( "bufio" "errors" "fmt" "os" "os/exec" "path/filepath" "strconv" "strings" "syscall" "testing" "github.com/carlosframework/platform/internal/release" "golang.org/x/sys/unix" ) func TestPlan14GenerationDescriptorTransferRootRunner(t *testing.T) { const modeKey = "PLAN14_E44_TRANSFER" path := os.Getenv("PLAN14_E44_TRANSFER_PATH") switch os.Getenv(modeKey) { case "mount": if err := unix.Mount("tmpfs", filepath.Dir(path), "tmpfs", 0, "mode=0755"); err != nil { t.Fatal(err) } defer unix.Unmount(filepath.Dir(path), 0) cmd := exec.Command(os.Args[0], "-test.run=^TestPlan14GenerationDescriptorTransferRootRunner$", "-test.timeout=30s") cmd.Env = append(os.Environ(), modeKey+"=receive") cmd.Dir = "/" cmd.ExtraFiles = []*os.File{os.NewFile(3, "transfer")} cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr cmd.SysProcAttr = &syscall.SysProcAttr{Credential: &syscall.Credential{Uid: 65534, Gid: 65534, Groups: []uint32{65534}}} if err := cmd.Run(); err != nil { t.Fatal(err) } return case "receive": if os.Geteuid() != 65534 { t.Fatal("receiver must be unprivileged") } if _, err := os.ReadFile(path); !errors.Is(err, os.ErrNotExist) { t.Fatal("staged path not masked", err) } fmt.Printf("READY %d\n", os.Getpid()) data, control := make([]byte, 1), make([]byte, unix.CmsgSpace(4)) _, n, _, _, err := unix.Recvmsg(3, data, control, 0) if err != nil { t.Fatal(err) } messages, err := unix.ParseSocketControlMessage(control[:n]) if err != nil || len(messages) != 1 { t.Fatal("rights message", err) } fds, err := unix.ParseUnixRights(&messages[0]) if err != nil || len(fds) != 1 { t.Fatal("rights descriptor", err) } file := os.NewFile(uintptr(fds[0]), "unreserved") defer file.Close() body := make([]byte, 10) if n, err := file.Read(body); err != nil || string(body[:n]) != "unreserved" { t.Fatal("received staged bytes", string(body), err) } fmt.Printf("HELD %d\n", fds[0]) if _, err := unix.Read(3, data); err != nil { t.Fatal(err) } return case "send": if os.Geteuid() != 65534 { t.Fatal("sender must be unprivileged") } file, err := os.Open(path) if err != nil { t.Fatal(err) } defer file.Close() if err := unix.Sendmsg(3, []byte{1}, unix.UnixRights(int(file.Fd())), nil, 0); err != nil { t.Fatal(err) } return } dir := generationRootRunnerDir(t) body, err := os.ReadFile(os.Args[0]) if err != nil { t.Fatal(err) } binary := filepath.Join(dir, "transfer.test") if err := os.WriteFile(binary, body, 0755); err != nil { t.Fatal(err) } path = filepath.Join(dir, "versions", "unreserved") if err := os.Mkdir(filepath.Dir(path), 0755); err != nil { t.Fatal(err) } if err := os.WriteFile(path, []byte("unreserved"), 0644); err != nil { t.Fatal(err) } sockets, err := unix.Socketpair(unix.AF_UNIX, unix.SOCK_STREAM|unix.SOCK_CLOEXEC, 0) if err != nil { t.Fatal(err) } parent, child := os.NewFile(uintptr(sockets[0]), "parent"), os.NewFile(uintptr(sockets[1]), "child") defer parent.Close() defer child.Close() cmd := exec.CommandContext(t.Context(), "unshare", "--mount", binary, "-test.run=^TestPlan14GenerationDescriptorTransferRootRunner$", "-test.timeout=45s") cmd.Dir = dir cmd.Env = append(os.Environ(), modeKey+"=mount", "PLAN14_E44_TRANSFER_PATH="+path) cmd.ExtraFiles = []*os.File{child} cmd.Stderr = os.Stderr output, err := cmd.StdoutPipe() if err != nil { t.Fatal(err) } if err := cmd.Start(); err != nil { t.Fatal(err) } defer func() { cmd.Process.Kill(); cmd.Wait() }() scan := bufio.NewScanner(output) number := func(prefix string) int { t.Helper() for scan.Scan() { if s, ok := strings.CutPrefix(scan.Text(), prefix); ok { n, err := strconv.Atoi(s) if err != nil { t.Fatal(err) } return n } t.Log(scan.Text()) } t.Fatal("missing child observation", prefix, scan.Err()) return 0 } pid := number("READY ") sender := exec.CommandContext(t.Context(), binary, "-test.run=^TestPlan14GenerationDescriptorTransferRootRunner$", "-test.timeout=15s") sender.Dir = dir sender.Env = append(os.Environ(), modeKey+"=send", "PLAN14_E44_TRANSFER_PATH="+path) sender.ExtraFiles = []*os.File{parent} sender.SysProcAttr = &syscall.SysProcAttr{Credential: &syscall.Credential{Uid: 65534, Gid: 65534, Groups: []uint32{65534}}} if out, err := sender.CombinedOutput(); err != nil { t.Fatalf("unprivileged sender: %v %s", err, out) } fd := number("HELD ") if err := generationLinkRef(fmt.Sprintf("/proc/%d/fd/%d", pid, fd), release.GenerationBinding{Path: path, Tree: filepath.Dir(path)}); err == nil { t.Fatal("transferred staged descriptor was missed") } t.Setenv("CARLOS_OPT_DIR", dir) if _, _, err := productionGenerationKernelCensus(t.Context()); err == nil { t.Fatal("uncontained descriptor recipient certified") } if _, err := parent.Write([]byte{1}); err != nil { t.Fatal(err) } for scan.Scan() { t.Log(scan.Text()) } if err := cmd.Wait(); err != nil { t.Fatal(err) } }