1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272
|
//go:build freebsd
package chroot
import (
"errors"
"fmt"
"io"
"io/fs"
"os"
"os/exec"
"path/filepath"
"strings"
"syscall"
"github.com/containers/buildah/pkg/jail"
"github.com/containers/storage/pkg/fileutils"
"github.com/containers/storage/pkg/mount"
"github.com/containers/storage/pkg/unshare"
"github.com/opencontainers/runtime-spec/specs-go"
"github.com/sirupsen/logrus"
"golang.org/x/sys/unix"
)
var (
rlimitsMap = map[string]int{
"RLIMIT_AS": unix.RLIMIT_AS,
"RLIMIT_CORE": unix.RLIMIT_CORE,
"RLIMIT_CPU": unix.RLIMIT_CPU,
"RLIMIT_DATA": unix.RLIMIT_DATA,
"RLIMIT_FSIZE": unix.RLIMIT_FSIZE,
"RLIMIT_MEMLOCK": unix.RLIMIT_MEMLOCK,
"RLIMIT_NOFILE": unix.RLIMIT_NOFILE,
"RLIMIT_NPROC": unix.RLIMIT_NPROC,
"RLIMIT_RSS": unix.RLIMIT_RSS,
"RLIMIT_STACK": unix.RLIMIT_STACK,
}
rlimitsReverseMap = map[int]string{}
)
type runUsingChrootSubprocOptions struct {
Spec *specs.Spec
BundlePath string
NoPivot bool
}
func setPlatformUnshareOptions(spec *specs.Spec, cmd *unshare.Cmd) error {
return nil
}
func setContainerHostname(name string) {
// On FreeBSD, we have to set this later when we create the
// jail below in createPlatformContainer
}
func setSelinuxLabel(spec *specs.Spec) error {
// Ignore this on FreeBSD
return nil
}
func setApparmorProfile(spec *specs.Spec) error {
// FreeBSD doesn't have apparmor`
return nil
}
func setCapabilities(spec *specs.Spec, keepCaps ...string) error {
// FreeBSD capabilities are nothing like Linux
return nil
}
func makeRlimit(limit specs.POSIXRlimit) unix.Rlimit {
return unix.Rlimit{Cur: int64(limit.Soft), Max: int64(limit.Hard)}
}
func createPlatformContainer(options runUsingChrootExecSubprocOptions) error {
path := options.Spec.Root.Path
jconf := jail.NewConfig()
jconf.Set("name", filepath.Base(path)+"-chroot")
jconf.Set("host.hostname", options.Spec.Hostname)
jconf.Set("persist", false)
jconf.Set("path", path)
jconf.Set("ip4", jail.INHERIT)
jconf.Set("ip6", jail.INHERIT)
jconf.Set("allow.raw_sockets", true)
jconf.Set("enforce_statfs", 1)
_, err := jail.CreateAndAttach(jconf)
if err != nil {
return fmt.Errorf("creating jail: %w", err)
}
return nil
}
// logNamespaceDiagnostics knows which namespaces we want to create.
// Output debug messages when that differs from what we're being asked to do.
func logNamespaceDiagnostics(spec *specs.Spec) {
// Nothing here for FreeBSD
}
func makeReadOnly(mntpoint string, flags uintptr) error {
var fs unix.Statfs_t
// Make sure it's read-only.
if err := unix.Statfs(mntpoint, &fs); err != nil {
return fmt.Errorf("checking if directory %q was bound read-only: %w", mntpoint, err)
}
return nil
}
func saveDir(spec *specs.Spec, path string) string {
id := filepath.Base(spec.Root.Path)
return filepath.Join(filepath.Dir(path), ".save-"+id)
}
func copyFile(source, dest string) error {
in, err := os.Open(source)
if err != nil {
return err
}
defer in.Close()
out, err := os.Create(dest)
if err != nil {
return err
}
defer out.Close()
_, err = io.Copy(out, in)
if err != nil {
return err
}
return out.Close()
}
type rename struct {
from, to string
}
// setupChrootBindMounts actually bind mounts things under the rootfs, and returns a
// callback that will clean up its work.
func setupChrootBindMounts(spec *specs.Spec, bundlePath string) (undoBinds func() error, err error) {
renames := []rename{}
unmounts := []string{}
removes := []string{}
undoBinds = func() error {
for _, r := range renames {
if err2 := os.Rename(r.to, r.from); err2 != nil {
logrus.Warnf("pkg/chroot: error renaming %q to %q: %v", r.to, r.from, err2)
if err == nil {
err = err2
}
}
}
for _, path := range unmounts {
if err2 := mount.Unmount(path); err2 != nil {
logrus.Warnf("pkg/chroot: error unmounting %q: %v", spec.Root.Path, err2)
if err == nil {
err = err2
}
}
}
for _, path := range removes {
if err2 := os.Remove(path); err2 != nil {
logrus.Warnf("pkg/chroot: error removing %q: %v", path, err2)
if err == nil {
err = err2
}
}
}
return err
}
// Now mount all of those things to be under the rootfs's location in this
// mount namespace.
for _, m := range spec.Mounts {
// If the target is there, we can just mount it.
var srcinfo os.FileInfo
switch m.Type {
case "nullfs":
srcinfo, err = os.Stat(m.Source)
if err != nil {
return undoBinds, fmt.Errorf("examining %q for mounting in mount namespace: %w", m.Source, err)
}
}
target := filepath.Join(spec.Root.Path, m.Destination)
if err := fileutils.Exists(target); err != nil {
// If the target can't be stat()ted, check the error.
if !errors.Is(err, fs.ErrNotExist) {
return undoBinds, fmt.Errorf("examining %q for mounting in mount namespace: %w", target, err)
}
// The target isn't there yet, so create it, and make a
// note to remove it later.
// XXX: This was copied from the linux version which supports bind mounting files.
// Leaving it here since I plan to add this to FreeBSD's nullfs.
if m.Type != "nullfs" || srcinfo.IsDir() {
if err = os.MkdirAll(target, 0o111); err != nil {
return undoBinds, fmt.Errorf("creating mountpoint %q in mount namespace: %w", target, err)
}
removes = append(removes, target)
} else {
if err = os.MkdirAll(filepath.Dir(target), 0o111); err != nil {
return undoBinds, fmt.Errorf("ensuring parent of mountpoint %q (%q) is present in mount namespace: %w", target, filepath.Dir(target), err)
}
// Don't do this until we can support file mounts in nullfs
/*var file *os.File
if file, err = os.OpenFile(target, os.O_WRONLY|os.O_CREATE, 0); err != nil {
return undoBinds, errors.Wrapf(err, "error creating mountpoint %q in mount namespace", target)
}
file.Close()
removes = append(removes, target)*/
}
}
logrus.Debugf("mount: %v", m)
switch m.Type {
case "nullfs":
// Do the bind mount.
if !srcinfo.IsDir() {
logrus.Debugf("emulating file mount %q on %q", m.Source, target)
err := fileutils.Exists(target)
if err == nil {
save := saveDir(spec, target)
if err := fileutils.Exists(save); err != nil {
if errors.Is(err, fs.ErrNotExist) {
err = os.MkdirAll(save, 0o111)
}
if err != nil {
return undoBinds, fmt.Errorf("creating file mount save directory %q: %w", save, err)
}
removes = append(removes, save)
}
savePath := filepath.Join(save, filepath.Base(target))
if err := fileutils.Exists(target); err == nil {
logrus.Debugf("moving %q to %q", target, savePath)
if err := os.Rename(target, savePath); err != nil {
return undoBinds, fmt.Errorf("moving %q to %q: %w", target, savePath, err)
}
renames = append(renames, rename{
from: target,
to: savePath,
})
}
} else {
removes = append(removes, target)
}
if err := copyFile(m.Source, target); err != nil {
return undoBinds, fmt.Errorf("copying %q to %q: %w", m.Source, target, err)
}
} else {
logrus.Debugf("bind mounting %q on %q", m.Destination, filepath.Join(spec.Root.Path, m.Destination))
if err := mount.Mount(m.Source, target, "nullfs", strings.Join(m.Options, ",")); err != nil {
return undoBinds, fmt.Errorf("bind mounting %q from host to %q in mount namespace (%q): %w", m.Source, m.Destination, target, err)
}
logrus.Debugf("bind mounted %q to %q", m.Source, target)
unmounts = append(unmounts, target)
}
case "devfs", "fdescfs", "tmpfs":
// Mount /dev, /dev/fd.
if err := mount.Mount(m.Source, target, m.Type, strings.Join(m.Options, ",")); err != nil {
return undoBinds, fmt.Errorf("mounting %q to %q in mount namespace (%q, %q): %w", m.Type, m.Destination, target, strings.Join(m.Options, ","), err)
}
logrus.Debugf("mounted a %q to %q", m.Type, target)
unmounts = append(unmounts, target)
}
}
return undoBinds, nil
}
// setPdeathsig sets a parent-death signal for the process
func setPdeathsig(cmd *exec.Cmd) {
if cmd.SysProcAttr == nil {
cmd.SysProcAttr = &syscall.SysProcAttr{}
}
cmd.SysProcAttr.Pdeathsig = syscall.SIGKILL
}
|