mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
runsc: umount /proc in the sandbox namespace
The sandbox process is started with the procfs mount and then it is umounted before executing guest processes. Here are a few reasons why we need /proc in the sandbox namespace: * /proc/self/exe is used to re-executed itself without capabilities and with a specific uid,gid, etc. execveat() can be protected by some security policies. For example, on my workstation, it fails tomoyo_bprm_check_security checks. * runsc reads /proc/cpuinfo, /proc/sys/vm/mmap_min_addr, /proc/self/auxv, /proc/sys/kernel/cap_last_cap. * The kvm platform reads /proc/self/maps to get current mappings. PiperOrigin-RevId: 488733897
This commit is contained in:
@@ -124,6 +124,9 @@ type Loader struct {
|
||||
// should be called when a sandbox is destroyed.
|
||||
stopProfiling func()
|
||||
|
||||
// PreSeccompCallback is called right before installing seccomp filters.
|
||||
PreSeccompCallback func()
|
||||
|
||||
// restore is set to true if we are restoring a container.
|
||||
restore bool
|
||||
|
||||
@@ -543,6 +546,9 @@ func createMemoryFile() (*pgalloc.MemoryFile, error) {
|
||||
|
||||
// installSeccompFilters installs sandbox seccomp filters with the host.
|
||||
func (l *Loader) installSeccompFilters() error {
|
||||
if l.PreSeccompCallback != nil {
|
||||
l.PreSeccompCallback()
|
||||
}
|
||||
if l.root.conf.DisableSeccomp {
|
||||
filter.Report("syscall filter is DISABLED. Running in less secure mode.")
|
||||
} else {
|
||||
|
||||
@@ -102,6 +102,7 @@ func Main(version string) {
|
||||
const internalGroup = "internal use only"
|
||||
subcommands.Register(new(cmd.Boot), internalGroup)
|
||||
subcommands.Register(new(cmd.Gofer), internalGroup)
|
||||
subcommands.Register(new(cmd.Umount), internalGroup)
|
||||
|
||||
// Register with the main command line.
|
||||
config.RegisterFlags(flag.CommandLine)
|
||||
|
||||
@@ -37,6 +37,7 @@ go_library(
|
||||
"statefile.go",
|
||||
"symbolize.go",
|
||||
"syscalls.go",
|
||||
"umount_unsafe.go",
|
||||
"usage.go",
|
||||
"wait.go",
|
||||
"write_control.go",
|
||||
@@ -45,6 +46,7 @@ go_library(
|
||||
"//runsc:__subpackages__",
|
||||
],
|
||||
deps = [
|
||||
"//pkg/abi/linux",
|
||||
"//pkg/coretag",
|
||||
"//pkg/coverage",
|
||||
"//pkg/log",
|
||||
|
||||
@@ -16,8 +16,10 @@ package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"runtime/debug"
|
||||
"strings"
|
||||
|
||||
@@ -101,6 +103,10 @@ type Boot struct {
|
||||
|
||||
// FDs for profile data.
|
||||
profileFDs profile.FDArgs
|
||||
|
||||
// procMountSyncFD is a file descriptor that has to be closed when the
|
||||
// procfs mount isn't needed anymore.
|
||||
procMountSyncFD int
|
||||
}
|
||||
|
||||
// Name implements subcommands.Command.Name.
|
||||
@@ -125,6 +131,7 @@ func (b *Boot) SetFlags(f *flag.FlagSet) {
|
||||
f.BoolVar(&b.setUpRoot, "setup-root", false, "if true, set up an empty root for the process")
|
||||
f.BoolVar(&b.pidns, "pidns", false, "if true, the sandbox is in its own PID namespace")
|
||||
f.IntVar(&b.cpuNum, "cpu-num", 0, "number of CPUs to create inside the sandbox")
|
||||
f.IntVar(&b.procMountSyncFD, "proc-mount-sync-fd", -1, "file descriptor that has to be closed when /proc isn't needed")
|
||||
f.Uint64Var(&b.totalMem, "total-memory", 0, "sets the initial amount of total memory to report back to the container")
|
||||
f.BoolVar(&b.attached, "attached", false, "if attached is true, kills the sandbox process when the parent process terminates")
|
||||
f.StringVar(&b.productName, "product-name", "", "value to show in /sys/devices/virtual/dmi/id/product_name")
|
||||
@@ -183,9 +190,24 @@ func (b *Boot) Execute(_ context.Context, f *flag.FlagSet, args ...any) subcomma
|
||||
}
|
||||
|
||||
if !b.applyCaps && !conf.Rootless {
|
||||
// /proc is umounted from a forked process, because the
|
||||
// current one is going to re-execute itself without
|
||||
// capabilities.
|
||||
cmd, w := b.execProcUmounter()
|
||||
defer w.Close()
|
||||
defer cmd.Wait()
|
||||
if b.procMountSyncFD != -1 {
|
||||
panic("procMountSyncFD is set")
|
||||
}
|
||||
b.procMountSyncFD = int(w.Fd())
|
||||
|
||||
// Remove --apply-caps arg to call myself. It has already been done.
|
||||
args := b.prepareArgs("setup-root")
|
||||
|
||||
// Clear FD_CLOEXEC.
|
||||
if _, _, errno := unix.RawSyscall(unix.SYS_FCNTL, w.Fd(), unix.F_SETFD, 0); errno != 0 {
|
||||
util.Fatalf("error clearing CLOEXEC: %v", errno)
|
||||
}
|
||||
// Note that we've already read the spec from the spec FD, and
|
||||
// we will read it again after the exec call. This works
|
||||
// because the ReadSpecFromFile function seeks to the beginning
|
||||
@@ -303,6 +325,28 @@ func (b *Boot) Execute(_ context.Context, f *flag.FlagSet, args ...any) subcomma
|
||||
// Fatalf exits the process and doesn't run defers.
|
||||
// 'l' must be destroyed explicitly after this point!
|
||||
|
||||
if b.procMountSyncFD != -1 {
|
||||
l.PreSeccompCallback = func() {
|
||||
syncFile := os.NewFile(uintptr(b.procMountSyncFD), "sync file")
|
||||
buf := make([]byte, 1)
|
||||
if w, err := syncFile.Write(buf); err != nil || w != 1 {
|
||||
util.Fatalf("unable to write into the proc umounter descriptor: %v", err)
|
||||
}
|
||||
syncFile.Close()
|
||||
|
||||
var waitStatus unix.WaitStatus
|
||||
if _, err := unix.Wait4(0, &waitStatus, 0, nil); err != nil {
|
||||
util.Fatalf("error waiting for the proc umounter process: %v", err)
|
||||
}
|
||||
if !waitStatus.Exited() || waitStatus.ExitStatus() != 0 {
|
||||
util.Fatalf("the proc umounter process failed: %v", waitStatus)
|
||||
}
|
||||
if err := unix.Access("/proc/self", unix.F_OK); err != unix.ENOENT {
|
||||
util.Fatalf("/proc is still accessible")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Notify the parent process the sandbox has booted (and that the controller
|
||||
// is up).
|
||||
startSyncFile := os.NewFile(uintptr(b.startSyncFD), "start-sync file")
|
||||
@@ -348,6 +392,9 @@ func (b *Boot) prepareArgs(exclude ...string) []string {
|
||||
// process terminates.
|
||||
args = append(args, "--attached")
|
||||
}
|
||||
if b.procMountSyncFD != -1 {
|
||||
args = append(args, fmt.Sprintf("--proc-mount-sync-fd=%d", b.procMountSyncFD))
|
||||
}
|
||||
if len(b.productName) > 0 {
|
||||
args = append(args, "--product-name", b.productName)
|
||||
}
|
||||
@@ -356,3 +403,24 @@ func (b *Boot) prepareArgs(exclude ...string) []string {
|
||||
}
|
||||
return args
|
||||
}
|
||||
|
||||
// execProcUmounter execute a child process that umounts /proc when the sks[1]
|
||||
// socket is closed.
|
||||
func (b *Boot) execProcUmounter() (*exec.Cmd, *os.File) {
|
||||
r, w, err := os.Pipe()
|
||||
if err != nil {
|
||||
util.Fatalf("error creating a pipe: %v", err)
|
||||
}
|
||||
defer r.Close()
|
||||
|
||||
cmd := exec.Command(specutils.ExePath)
|
||||
cmd.Args = append(cmd.Args, "umount", "--sync-fd=3", "/proc")
|
||||
cmd.ExtraFiles = append(cmd.ExtraFiles, r)
|
||||
cmd.Stdin = os.Stdin
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
if err := cmd.Start(); err != nil {
|
||||
util.Fatalf("error executing umounter: %v", err)
|
||||
}
|
||||
return cmd, w
|
||||
}
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
// Copyright 2022 The gVisor Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"github.com/google/subcommands"
|
||||
"golang.org/x/sys/unix"
|
||||
"gvisor.dev/gvisor/pkg/abi/linux"
|
||||
"gvisor.dev/gvisor/runsc/cmd/util"
|
||||
"gvisor.dev/gvisor/runsc/flag"
|
||||
)
|
||||
|
||||
// Umount implements subcommands.Command for the "kill" command.
|
||||
type Umount struct {
|
||||
syncFD int
|
||||
}
|
||||
|
||||
// Name implements subcommands.Command.Name.
|
||||
func (*Umount) Name() string {
|
||||
return "umount"
|
||||
}
|
||||
|
||||
// Synopsis implements subcommands.Command.Synopsis.
|
||||
func (*Umount) Synopsis() string {
|
||||
return "umount the specified directory when one byte is read from synd-fd"
|
||||
}
|
||||
|
||||
// Usage implements subcommands.Command.Usage.
|
||||
func (*Umount) Usage() string {
|
||||
return `umount --synd-fd=FD <directory path>`
|
||||
}
|
||||
|
||||
// SetFlags implements subcommands.Command.SetFlags.
|
||||
func (u *Umount) SetFlags(f *flag.FlagSet) {
|
||||
f.IntVar(&u.syncFD, "sync-fd", -1, "")
|
||||
}
|
||||
|
||||
// Execute implements subcommands.Command.Execute.
|
||||
func (u *Umount) Execute(_ context.Context, f *flag.FlagSet, args ...any) subcommands.ExitStatus {
|
||||
if f.NArg() == 0 || f.NArg() > 1 {
|
||||
f.Usage()
|
||||
return subcommands.ExitUsageError
|
||||
}
|
||||
|
||||
dirPath := f.Arg(0)
|
||||
|
||||
syncFile := os.NewFile(uintptr(u.syncFD), "sync file")
|
||||
defer syncFile.Close()
|
||||
|
||||
buf := make([]byte, 1)
|
||||
if l, err := syncFile.Read(buf); err != nil || l != 1 {
|
||||
util.Fatalf("unable to read from the sync descriptor: %v, error %v", l, err)
|
||||
}
|
||||
|
||||
if _, _, errno := unix.RawSyscall(
|
||||
unix.SYS_UMOUNT2,
|
||||
uintptr(unsafe.Pointer(syscall.StringBytePtr(dirPath))),
|
||||
uintptr(linux.MNT_DETACH), 0); errno != 0 {
|
||||
util.Fatalf("Unable to umount %s: errno %v", dirPath, errno)
|
||||
}
|
||||
|
||||
return subcommands.ExitSuccess
|
||||
}
|
||||
Reference in New Issue
Block a user