diff --git a/src/syscall/exec_freebsd.go b/src/syscall/exec_freebsd.go index af5a4158f04fec..818101ea987804 100644 --- a/src/syscall/exec_freebsd.go +++ b/src/syscall/exec_freebsd.go @@ -32,6 +32,7 @@ type SysProcAttr struct { Foreground bool Pgid int // Child's process group ID if Setpgid. Pdeathsig Signal // Signal that the process will get when its parent dies (Linux and FreeBSD only) + Jail int // Jail to which the child process is attached (FreeBSD only). } const ( @@ -99,6 +100,15 @@ func forkAndExecInChild(argv0 *byte, argv, envv []*byte, chroot, dir *byte, attr // Fork succeeded, now in child. + // Attach to the given jail, if any. The system call also changes the + // process' root and working directories to the jail's path directory. + if sys.Jail > 0 { + _, _, err1 = RawSyscall(SYS_JAIL_ATTACH, uintptr(sys.Jail), 0, 0) + if err1 != 0 { + goto childerror + } + } + // Enable tracing if requested. if sys.Ptrace { _, _, err1 = RawSyscall(SYS_PTRACE, uintptr(PTRACE_TRACEME), 0, 0) diff --git a/src/syscall/exec_freebsd_test.go b/src/syscall/exec_freebsd_test.go new file mode 100644 index 00000000000000..edc71bc45ed450 --- /dev/null +++ b/src/syscall/exec_freebsd_test.go @@ -0,0 +1,116 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build freebsd + +package syscall_test + +import ( + "bytes" + "fmt" + "internal/testenv" + "os" + "os/exec" + "path/filepath" + "syscall" + "testing" + "unsafe" +) + +const ( + flagJailCreate = uintptr(0x1) +) + +func prepareJail(t *testing.T) (int, string, func()) { + t.Helper() + + root, err := os.MkdirTemp("", "jail_attach") + if err != nil { + t.Fatal(err) + } + + paramPath := []byte("path\x00") + conf := make([]syscall.Iovec, 4) + conf[0].Base = ¶mPath[0] + conf[0].SetLen(len(paramPath)) + p, err := syscall.BytePtrFromString(root) + if err != nil { + t.Fatal(err) + } + conf[1].Base = p + conf[1].SetLen(len(root) + 1) + + paramPersist := []byte("persist\x00") + conf[2].Base = ¶mPersist[0] + conf[2].SetLen(8) + conf[3].Base = nil + conf[3].SetLen(0) + + id, _, err1 := syscall.Syscall(syscall.SYS_JAIL_SET, + uintptr(unsafe.Pointer(&conf[0])), uintptr(len(conf)), flagJailCreate) + if err1 != 0 { + t.Fatalf("jail_set: %v", err1) + } + + return int(id), root, func() { + _, _, err1 := syscall.Syscall(syscall.SYS_JAIL_REMOVE, id, 0, 0) + if err1 != 0 { + t.Errorf("failed to cleanup jail: %v", err) + } + err := os.RemoveAll(root) + if err != nil { + t.Errorf("failed to cleanup temporary fail root: %v", err) + } + } +} + +func TestJailAttach(t *testing.T) { + // Make sure we are running as root, so we have permissions to create + // and remove jails. + if os.Getuid() != 0 { + t.Skip("kernel prohibits jail system calls in unprivileged process") + } + + jid, root, cleanup := prepareJail(t) + defer cleanup() + + // Since jail attach does an implicit chroot to the jail's path, + // we need the binary there, and it must be statically linked. + x := filepath.Join(root, "syscall.test") + cmd := exec.Command(testenv.GoToolPath(t), "test", "-c", "-o", x, "syscall") + cmd.Env = append(os.Environ(), "CGO_ENABLED=0") + if o, err := cmd.CombinedOutput(); err != nil { + t.Fatalf("Build of syscall in jail root failed, output %v, err %v", o, err) + } + + cmd = exec.Command("/syscall.test", "-test.run=TestJailAttachHelper", "/") + cmd.Env = append(os.Environ(), "GO_WANT_HELPER_PROCESS=1") + cmd.SysProcAttr = &syscall.SysProcAttr{Jail: jid} + out, err := cmd.CombinedOutput() + if err != nil { + t.Fatalf("Cmd failed with err %v, output: %s", err, out) + } + if !bytes.HasSuffix(bytes.TrimSpace(out), []byte("1")) { + t.Fatalf("got: %q, want: a line that ends with 1", out) + } +} + +// TestJailAttachHelper isn't a real test. It's used as a helper process for +// TestJailAttach +func TestJailAttachHelper(t *testing.T) { + if os.Getenv("GO_WANT_HELPER_PROCESS") != "1" { + return + } + defer os.Exit(0) + + jailed, err := syscall.SysctlUint32("security.jail.jailed") + if err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(2) + } + + fmt.Println(jailed) + // TODO: should we check, whether we are in the correct jail? + // This would have to be done by the parent process. +}