blob: 9bf23117af624a85b4ffffc0a04e489fe5e3c7f7 [file] [log] [blame]
// Copyright 2021 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package testrunner
import (
"errors"
"fmt"
)
// MountPt describes the source, destination, and permissions for a
// mount point. This currently only supports bind mounts.
type MountPt struct {
// Src is the path on the root filesystem to mount.
Src string
// Dst is the path inside the NsJail. If empty, we mount at Src.
Dst string
// Writable sets the mount points to rw (default is readonly).
Writable bool
}
// NsJailCmdBuilder facilitates the construction of an NsJail command line
// invocation. See https://github.com/google/nsjail for NsJail docs.
type NsJailCmdBuilder struct {
// Bin is the path to the NsJail binary. This is a required parameter.
Bin string
// Chroot is the path to a directory to set as the chroot.
Chroot string
// Cwd is the path to a directory to set as the working directory.
// Note that this does not add any required mount points, the caller
// is responsible for ensuring that the directory exists in the jail.
Cwd string
// Env is a set of additional environment variables to pass into
// the jail. Takes the form of a list of key=value strings.
// This set of environment variables is appended to the calling process'
// environment, and overrides any duplicate values
Env []string
// IsolateNetwork indicates whether we should use a network namespace.
IsolateNetwork bool
// MountPoints is a list of locations on the current filesystem that
// should be mounted into the NsJail.
MountPoints []*MountPt
}
// AddDefaultMounts adds a set of mounts used by existing host tests.
// This is effectively an allowlist of mounts used by existing tests.
// Adding to this should be avoided if possible.
func (n *NsJailCmdBuilder) AddDefaultMounts() {
n.MountPoints = append(n.MountPoints, []*MountPt{
// Many host tests run emulators, which requires KVM.
{Src: "/dev/kvm"},
// /bin/bash, in turn, is dynamically linked and requires that we mount the
// system linker.
{Src: "/lib"},
{Src: "/lib64"},
// A variety of tests use linux utilities from /usr/bin and /bin.
// e.g. https://cs.opensource.google/fuchsia/fuchsia/+/main:sdk/testing/sl4f/client/test/sl4f_client_test.dart;l=69.
{Src: "/usr/bin"},
{Src: "/bin"},
// Additional mounts for convenience.
{Src: "/dev/urandom"},
{Src: "/dev/zero"},
{Src: "/dev/null", Writable: true},
// Some host tests utilize ssh-keygen to generate key pairs, which reads
// /etc/passwd to figure out the current username.
{Src: "/etc/passwd"},
}...)
}
// Build takes the given subcmd and wraps it in the appropriate NsJail invocation.
func (n *NsJailCmdBuilder) Build(subcmd []string) ([]string, error) {
// Validate the command.
if n.Bin == "" {
return nil, errors.New("NsJailCmdBuilder: Bin cannot be empty")
} else if len(subcmd) == 0 {
return nil, errors.New("NsJailCmdBuilder: subcmd cannot be empty")
}
// Nsjail has a chroot flag but unfortunately it mounts the root
// readonly, so we get around this by mounting it manually as
// writable. Additionally, this must be mounted first to allow
// for sub-mounts.
if n.Chroot != "" {
n.MountPoints = append(
[]*MountPt{
{
Src: n.Chroot,
Dst: "/",
Writable: true,
},
}, n.MountPoints...,
)
}
// Build the actual command invocation.
cmd := []string{n.Bin, "--keep_env"}
if !n.IsolateNetwork {
cmd = append(cmd, "--disable_clone_newnet")
}
if n.Cwd != "" {
cmd = append(cmd, "--cwd", n.Cwd)
}
for _, mountPt := range n.MountPoints {
if mountPt.Src == "" {
return nil, errors.New("NsJailCmdBuilder: Src cannot be empty in a mount point")
}
if mountPt.Dst == "" {
mountPt.Dst = mountPt.Src
}
if mountPt.Writable {
cmd = append(cmd, "--bindmount", fmt.Sprintf("%s:%s", mountPt.Src, mountPt.Dst))
} else {
cmd = append(cmd, "--bindmount_ro", fmt.Sprintf("%s:%s", mountPt.Src, mountPt.Dst))
}
}
// Overwrite some default nsjail rlimits with our system soft maximums as
// the defaults are too restrictive. We should probably tune this a bit
// more in the future to absolute values.
cmd = append(
cmd,
"--rlimit_as", "soft",
"--rlimit_fsize", "soft",
"--rlimit_nofile", "soft",
"--rlimit_nproc", "soft",
)
for _, v := range n.Env {
cmd = append(cmd, "--env", v)
}
cmd = append(cmd, "--")
cmd = append(cmd, subcmd...)
return cmd, nil
}