Skip to content

Commit

Permalink
syscall: add jail support to ForkExec on FreeBSD
Browse files Browse the repository at this point in the history
Introduce a new SysProcAttr member called Jail on FreeBSD. This allows
supplying an existing jail's ID to which the child process is attached
before calling the exec system call.

Fixes golang#46259

Change-Id: Ie282e5b83429131f9a9e1e27cfcb3bcc995d1d4d
  • Loading branch information
hdbhm committed Dec 19, 2022
1 parent 0b2ad1d commit ec3c16d
Show file tree
Hide file tree
Showing 2 changed files with 126 additions and 0 deletions.
10 changes: 10 additions & 0 deletions src/syscall/exec_freebsd.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down Expand Up @@ -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)
Expand Down
116 changes: 116 additions & 0 deletions src/syscall/exec_freebsd_test.go
Original file line number Diff line number Diff line change
@@ -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 = &paramPath[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 = &paramPersist[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.
}

0 comments on commit ec3c16d

Please sign in to comment.