platform: Pass device fd into platform constructor.

We were previously openining the platform device (i.e. /dev/kvm) inside the
platfrom constructor (i.e. kvm.New).  This requires that we have RW access to
the platform device when constructing the platform.

However, now that the runsc sandbox process runs as user "nobody", it is not
able to open the platform device.

This CL changes the kvm constructor to take the platform device FD, rather than
opening the device file itself. The device file is opened outside of the
sandbox and passed to the sandbox process.

PiperOrigin-RevId: 212505804
Change-Id: I427e1d9de5eb84c84f19d513356e1bb148a52910
This commit is contained in:
Nicolas Lacasse
2018-09-11 13:09:46 -07:00
committed by Shentubot
parent c44bc6612f
commit 6cc9b311af
9 changed files with 96 additions and 59 deletions
+16 -9
View File
@@ -17,6 +17,7 @@ package kvm
import (
"fmt"
"os"
"sync"
"syscall"
@@ -44,25 +45,29 @@ var (
globalErr error
)
// OpenDevice opens the KVM device at /dev/kvm and returns the File.
func OpenDevice() (*os.File, error) {
f, err := os.OpenFile("/dev/kvm", syscall.O_RDWR, 0)
if err != nil {
return nil, fmt.Errorf("error opening /dev/kvm: %v", err)
}
return f, nil
}
// New returns a new KVM-based implementation of the platform interface.
func New() (*KVM, error) {
func New(deviceFile *os.File) (*KVM, error) {
// Allocate physical memory for the vCPUs.
fm, err := filemem.New("kvm-memory")
if err != nil {
return nil, err
}
// Try opening KVM.
fd, err := syscall.Open("/dev/kvm", syscall.O_RDWR, 0)
if err != nil {
return nil, fmt.Errorf("opening /dev/kvm: %v", err)
}
defer syscall.Close(fd)
fd := deviceFile.Fd()
// Ensure global initialization is done.
globalOnce.Do(func() {
physicalInit()
globalErr = updateSystemValues(fd)
globalErr = updateSystemValues(int(fd))
ring0.Init(cpuid.HostFeatureSet())
})
if globalErr != nil {
@@ -70,10 +75,12 @@ func New() (*KVM, error) {
}
// Create a new VM fd.
vm, _, errno := syscall.RawSyscall(syscall.SYS_IOCTL, uintptr(fd), _KVM_CREATE_VM, 0)
vm, _, errno := syscall.RawSyscall(syscall.SYS_IOCTL, fd, _KVM_CREATE_VM, 0)
if errno != 0 {
return nil, fmt.Errorf("creating VM: %v", errno)
}
// We are done with the device file.
deviceFile.Close()
// Create a VM context.
machine, err := newMachine(int(vm))
+5 -1
View File
@@ -39,7 +39,11 @@ type testHarness interface {
func kvmTest(t testHarness, setup func(*KVM), fn func(*vCPU) bool) {
// Create the machine.
k, err := New()
deviceFile, err := OpenDevice()
if err != nil {
t.Fatalf("error opening device file: %v", err)
}
k, err := New(deviceFile)
if err != nil {
t.Fatalf("error creating KVM instance: %v", err)
}
+19 -5
View File
@@ -17,6 +17,7 @@ package boot
import (
"errors"
"fmt"
"os"
"path"
specs "github.com/opencontainers/runtime-spec/specs-go"
@@ -287,7 +288,8 @@ func (cm *containerManager) WaitForLoader(_, _ *struct{}) error {
// RestoreOpts contains options related to restoring a container's file system.
type RestoreOpts struct {
// FilePayload contains the state file to be restored.
// FilePayload contains the state file to be restored, followed by the
// platform device file if necessary.
urpc.FilePayload
// SandboxID contains the ID of the sandbox.
@@ -300,16 +302,28 @@ type RestoreOpts struct {
// signal to start.
func (cm *containerManager) Restore(o *RestoreOpts, _ *struct{}) error {
log.Debugf("containerManager.Restore")
if len(o.FilePayload.Files) != 1 {
return fmt.Errorf("exactly one file must be provided")
var specFile, deviceFile *os.File
switch numFiles := len(o.FilePayload.Files); numFiles {
case 2:
// The device file is donated to the platform, so don't Close
// it here.
deviceFile = o.FilePayload.Files[1]
fallthrough
case 1:
specFile = o.FilePayload.Files[0]
defer specFile.Close()
case 0:
return fmt.Errorf("at least one file must be passed to Restore")
default:
return fmt.Errorf("at most two files may be passed to Restore")
}
defer o.FilePayload.Files[0].Close()
// Destroy the old kernel and create a new kernel.
cm.l.k.Pause()
cm.l.k.Destroy()
p, err := createPlatform(cm.l.conf)
p, err := createPlatform(cm.l.conf, int(deviceFile.Fd()))
if err != nil {
return fmt.Errorf("error creating platform: %v", err)
}
+7 -4
View File
@@ -122,9 +122,9 @@ func init() {
// New initializes a new kernel loader configured by spec.
// New also handles setting up a kernel for restoring a container.
func New(spec *specs.Spec, conf *Config, controllerFD int, ioFDs []int, console bool) (*Loader, error) {
func New(spec *specs.Spec, conf *Config, controllerFD, deviceFD int, ioFDs []int, console bool) (*Loader, error) {
// Create kernel and platform.
p, err := createPlatform(conf)
p, err := createPlatform(conf, deviceFD)
if err != nil {
return nil, fmt.Errorf("error creating platform: %v", err)
}
@@ -301,14 +301,17 @@ func (l *Loader) Destroy() {
l.watchdog.Stop()
}
func createPlatform(conf *Config) (platform.Platform, error) {
func createPlatform(conf *Config, deviceFD int) (platform.Platform, error) {
switch conf.Platform {
case PlatformPtrace:
log.Infof("Platform: ptrace")
return ptrace.New()
case PlatformKVM:
log.Infof("Platform: kvm")
return kvm.New()
if deviceFD < 0 {
return nil, fmt.Errorf("kvm device fd must be provided")
}
return kvm.New(os.NewFile(uintptr(deviceFD), "kvm device"))
default:
return nil, fmt.Errorf("invalid platform %v", conf.Platform)
}
+1 -1
View File
@@ -101,7 +101,7 @@ func createLoader() (*Loader, func(), error) {
return nil, nil, err
}
l, err := New(spec, conf, fd, []int{sandEnd}, false)
l, err := New(spec, conf, fd, -1 /* device fd */, []int{sandEnd}, false)
if err != nil {
cleanup()
return nil, nil, err
+5 -1
View File
@@ -42,6 +42,9 @@ type Boot struct {
// control server that is donated to this process.
controllerFD int
// deviceFD is the file descriptor for the platform device file.
deviceFD int
// ioFDs is the list of FDs used to connect to FS gofers.
ioFDs intFlags
@@ -74,6 +77,7 @@ func (b *Boot) SetFlags(f *flag.FlagSet) {
f.StringVar(&b.bundleDir, "bundle", "", "required path to the root of the bundle directory")
f.IntVar(&b.specFD, "spec-fd", -1, "required fd with the container spec")
f.IntVar(&b.controllerFD, "controller-fd", -1, "required FD of a stream socket for the control server that must be donated to this process")
f.IntVar(&b.deviceFD, "device-fd", -1, "FD for the platform device file")
f.Var(&b.ioFDs, "io-fds", "list of FDs to connect 9P clients. They must follow this order: root first, then mounts as defined in the spec")
f.BoolVar(&b.console, "console", false, "set to true if the sandbox should allow terminal ioctl(2) syscalls")
f.BoolVar(&b.applyCaps, "apply-caps", false, "if true, apply capabilities defined in the spec to the process")
@@ -134,7 +138,7 @@ func (b *Boot) Execute(_ context.Context, f *flag.FlagSet, args ...interface{})
}
// Create the loader.
l, err := boot.New(spec, conf, b.controllerFD, b.ioFDs.GetArray(), b.console)
l, err := boot.New(spec, conf, b.controllerFD, b.deviceFD, b.ioFDs.GetArray(), b.console)
if err != nil {
Fatalf("error creating loader: %v", err)
}
+1
View File
@@ -18,6 +18,7 @@ go_library(
"//pkg/control/server",
"//pkg/log",
"//pkg/sentry/control",
"//pkg/sentry/platform/kvm",
"//pkg/urpc",
"//runsc/boot",
"//runsc/console",
+3 -37
View File
@@ -22,7 +22,6 @@ import (
"syscall"
"gvisor.googlesource.com/gvisor/pkg/log"
"gvisor.googlesource.com/gvisor/runsc/boot"
"gvisor.googlesource.com/gvisor/runsc/specutils"
)
@@ -39,18 +38,12 @@ func mountInChroot(chroot, src, dst, typ string, flags uint32) error {
if err := specutils.Mount(src, chrootDst, typ, flags); err != nil {
return fmt.Errorf("error mounting %q at %q: %v", src, chrootDst, err)
}
// Make sure the mount is accessible to all users, since we will be
// running as nobody inside the chroot.
if err := os.Chmod(chrootDst, 0777); err != nil {
return fmt.Errorf("Chmod(%q) failed: %v", chroot, err)
}
return nil
}
// setUpChroot creates an empty directory with runsc mounted at /runsc, proc
// mounted at /proc, and any dev files needed for the platform.
func setUpChroot(platform boot.PlatformType) (string, error) {
// setUpChroot creates an empty directory with runsc mounted at /runsc and proc
// mounted at /proc.
func setUpChroot() (string, error) {
// Create the chroot directory and make it accessible to all users.
chroot, err := ioutil.TempDir("", "runsc-sandbox-chroot-")
if err != nil {
@@ -75,18 +68,6 @@ func setUpChroot(platform boot.PlatformType) (string, error) {
return "", fmt.Errorf("error mounting runsc in chroot: %v", err)
}
// Mount dev files needed for platform.
var devMount string
switch platform {
case boot.PlatformKVM:
devMount = "/dev/kvm"
}
if devMount != "" {
if err := mountInChroot(chroot, devMount, devMount, "bind", syscall.MS_BIND); err != nil {
return "", fmt.Errorf("error mounting platform device in chroot: %v", err)
}
}
return chroot, nil
}
@@ -105,21 +86,6 @@ func tearDownChroot(chroot string) error {
return fmt.Errorf("error unmounting %q: %v", exe, err)
}
// Unmount platform dev files.
devFiles := []string{"dev/kvm"}
for _, f := range devFiles {
devPath := filepath.Join(chroot, f)
if _, err := os.Stat(devPath); err != nil {
if os.IsNotExist(err) {
continue
}
return fmt.Errorf("Stat(%q) failed: %v", devPath, err)
}
if err := syscall.Unmount(devPath, 0); err != nil {
return fmt.Errorf("error unmounting %q: %v", devPath, err)
}
}
// Remove chroot directory.
if err := os.RemoveAll(chroot); err != nil {
return fmt.Errorf("error removing %q: %v", chroot, err)
+39 -1
View File
@@ -29,6 +29,7 @@ import (
"gvisor.googlesource.com/gvisor/pkg/control/server"
"gvisor.googlesource.com/gvisor/pkg/log"
"gvisor.googlesource.com/gvisor/pkg/sentry/control"
"gvisor.googlesource.com/gvisor/pkg/sentry/platform/kvm"
"gvisor.googlesource.com/gvisor/pkg/urpc"
"gvisor.googlesource.com/gvisor/runsc/boot"
"gvisor.googlesource.com/gvisor/runsc/console"
@@ -140,6 +141,14 @@ func (s *Sandbox) Restore(cid string, spec *specs.Spec, conf *boot.Config, f str
SandboxID: s.ID,
}
// If the platform needs a device fd we must pass it in.
if deviceFile, err := deviceFileForPlatform(conf.Platform); err != nil {
return err
} else if deviceFile != nil {
defer deviceFile.Close()
opt.FilePayload.Files = append(opt.FilePayload.Files, deviceFile)
}
conn, err := s.sandboxConnect()
if err != nil {
return err
@@ -315,6 +324,16 @@ func (s *Sandbox) createSandboxProcess(spec *specs.Spec, conf *boot.Config, bund
nextFD++
}
// If the platform needs a device fd we must pass it in.
if deviceFile, err := deviceFileForPlatform(conf.Platform); err != nil {
return err
} else if deviceFile != nil {
defer deviceFile.Close()
cmd.ExtraFiles = append(cmd.ExtraFiles, deviceFile)
cmd.Args = append(cmd.Args, "--device-fd="+strconv.Itoa(nextFD))
nextFD++
}
// Sandbox stdio defaults to current process stdio.
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
@@ -428,7 +447,7 @@ func (s *Sandbox) createSandboxProcess(spec *specs.Spec, conf *boot.Config, bund
log.Warningf("Running sandbox in test mode without chroot. This is only safe in tests!")
} else if specutils.HasCapSysAdmin() {
log.Infof("Sandbox will be started in minimal chroot")
chroot, err := setUpChroot(conf.Platform)
chroot, err := setUpChroot()
if err != nil {
return fmt.Errorf("error setting up chroot: %v", err)
}
@@ -660,3 +679,22 @@ func signalProcess(pid int, sig syscall.Signal) error {
}
return nil
}
// deviceFileForPlatform opens the device file for the given platform. If the
// platform does not need a device file, then nil is returned.
func deviceFileForPlatform(p boot.PlatformType) (*os.File, error) {
var (
f *os.File
err error
)
switch p {
case boot.PlatformKVM:
f, err = kvm.OpenDevice()
default:
return nil, nil
}
if err != nil {
return nil, fmt.Errorf("error opening device file for platform %q: %v", p, err)
}
return f, err
}