Delete devtmpfs and replace it with tmpfs.

Our devtmpfs implementation uses the same tmpfs filesystem instance for all
devtmpfs mounts in the sandbox. This would mean that devices mounted in a
container are visible and accessible to all other containers in the sandbox.

With GPU/TPU, the contents of devtmpfs can be different for different
containers within the same sandbox. So it is important to not share the same
devtmpfs contents.

It is better to drop support for devtmpfs, than to implement it incorrectly.
Instead, this change introduces a new dummy filesystem type named `dev`. This
filesystem can not be mounted or listed by the application. This filesystem
creates a new tmpfs instance on GetFilesystem() and populates it with all the
device files.

PiperOrigin-RevId: 578969556
This commit is contained in:
Ayush Ranjan
2023-11-02 13:58:58 -07:00
committed by gVisor bot
parent b92f900240
commit 9e66f710de
23 changed files with 459 additions and 698 deletions
-39
View File
@@ -1,39 +0,0 @@
load("//tools:defs.bzl", "go_library", "go_test")
package(default_applicable_licenses = ["//:license"])
licenses(["notice"])
go_library(
name = "devtmpfs",
srcs = [
"devtmpfs.go",
"save_restore.go",
],
visibility = ["//pkg/sentry:internal"],
deps = [
"//pkg/abi/linux",
"//pkg/context",
"//pkg/fspath",
"//pkg/sentry/fsimpl/tmpfs",
"//pkg/sentry/kernel/auth",
"//pkg/sentry/vfs",
"//pkg/sync",
],
)
go_test(
name = "devtmpfs_test",
size = "small",
srcs = ["devtmpfs_test.go"],
library = ":devtmpfs",
deps = [
"//pkg/abi/linux",
"//pkg/context",
"//pkg/fspath",
"//pkg/sentry/contexttest",
"//pkg/sentry/fsimpl/tmpfs",
"//pkg/sentry/kernel/auth",
"//pkg/sentry/vfs",
],
)
-231
View File
@@ -1,231 +0,0 @@
// Copyright 2020 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 devtmpfs provides an implementation of /dev based on tmpfs,
// analogous to Linux's devtmpfs.
package devtmpfs
import (
"fmt"
"path"
"gvisor.dev/gvisor/pkg/abi/linux"
"gvisor.dev/gvisor/pkg/context"
"gvisor.dev/gvisor/pkg/fspath"
"gvisor.dev/gvisor/pkg/sentry/fsimpl/tmpfs"
"gvisor.dev/gvisor/pkg/sentry/kernel/auth"
"gvisor.dev/gvisor/pkg/sentry/vfs"
"gvisor.dev/gvisor/pkg/sync"
)
// Name is the default filesystem name.
const Name = "devtmpfs"
// FilesystemType implements vfs.FilesystemType.
//
// +stateify savable
type FilesystemType struct {
initOnce sync.Once `state:"nosave"` // FIXME(gvisor.dev/issue/1663): not yet supported.
initErr error
// fs is the tmpfs filesystem that backs all mounts of this FilesystemType.
// root is fs' root. fs and root are immutable.
fs *vfs.Filesystem
root *vfs.Dentry
}
// Name implements vfs.FilesystemType.Name.
func (*FilesystemType) Name() string {
return Name
}
// GetFilesystem implements vfs.FilesystemType.GetFilesystem.
func (fst *FilesystemType) GetFilesystem(ctx context.Context, vfsObj *vfs.VirtualFilesystem, creds *auth.Credentials, source string, opts vfs.GetFilesystemOptions) (*vfs.Filesystem, *vfs.Dentry, error) {
fst.initOnce.Do(func() {
fs, root, err := tmpfs.FilesystemType{}.GetFilesystem(ctx, vfsObj, creds, "" /* source */, vfs.GetFilesystemOptions{
Data: "mode=0755", // opts from drivers/base/devtmpfs.c:devtmpfs_init()
})
if err != nil {
fst.initErr = err
return
}
fst.fs = fs
fst.root = root
})
if fst.initErr != nil {
return nil, nil, fst.initErr
}
fst.fs.IncRef()
fst.root.IncRef()
return fst.fs, fst.root, nil
}
// Release implements vfs.FilesystemType.Release.
func (fst *FilesystemType) Release(ctx context.Context) {
if fst.fs != nil {
// Release the original reference obtained when creating the filesystem.
fst.root.DecRef(ctx)
fst.fs.DecRef(ctx)
}
}
// Accessor allows devices to create device special files in devtmpfs.
type Accessor struct {
vfsObj *vfs.VirtualFilesystem
mntns *vfs.MountNamespace
root vfs.VirtualDentry
creds *auth.Credentials
}
// NewAccessor returns an Accessor that supports creation of device special
// files in the devtmpfs instance registered with name fsTypeName in vfsObj.
func NewAccessor(ctx context.Context, vfsObj *vfs.VirtualFilesystem, creds *auth.Credentials, fsTypeName string) (*Accessor, error) {
mntns, err := vfsObj.NewMountNamespace(ctx, creds, "devtmpfs" /* source */, fsTypeName, &vfs.MountOptions{}, nil)
if err != nil {
return nil, err
}
// Pass a reference on root to the Accessor.
root := mntns.Root(ctx)
return &Accessor{
vfsObj: vfsObj,
mntns: mntns,
root: root,
creds: creds,
}, nil
}
// Release must be called when a is no longer in use.
func (a *Accessor) Release(ctx context.Context) {
a.root.DecRef(ctx)
a.mntns.DecRef(ctx)
}
// accessorContext implements context.Context by extending an existing
// context.Context with an Accessor's values for VFS-relevant state.
type accessorContext struct {
context.Context
a *Accessor
}
func (a *Accessor) wrapContext(ctx context.Context) *accessorContext {
return &accessorContext{
Context: ctx,
a: a,
}
}
// Value implements context.Context.Value.
func (ac *accessorContext) Value(key any) any {
switch key {
case vfs.CtxMountNamespace:
ac.a.mntns.IncRef()
return ac.a.mntns
case vfs.CtxRoot:
ac.a.root.IncRef()
return ac.a.root
default:
return ac.Context.Value(key)
}
}
func (a *Accessor) pathOperationAt(pathname string) *vfs.PathOperation {
return &vfs.PathOperation{
Root: a.root,
Start: a.root,
Path: fspath.Parse(pathname),
}
}
// CreateDeviceFile creates a device special file at the given pathname in the
// devtmpfs instance accessed by the Accessor.
func (a *Accessor) CreateDeviceFile(ctx context.Context, pathname string, kind vfs.DeviceKind, major, minor uint32, perms uint16) error {
actx := a.wrapContext(ctx)
mode := (linux.FileMode)(perms)
switch kind {
case vfs.BlockDevice:
mode |= linux.S_IFBLK
case vfs.CharDevice:
mode |= linux.S_IFCHR
default:
panic(fmt.Sprintf("invalid vfs.DeviceKind: %v", kind))
}
// Create any parent directories. See
// devtmpfs.c:handle_create()=>path_create().
parent := path.Dir(pathname)
if err := a.vfsObj.MkdirAllAt(ctx, parent, a.root, a.creds, &vfs.MkdirOptions{
Mode: 0755,
}, true /* mustBeDir */); err != nil {
return fmt.Errorf("failed to create device parent directory %q: %v", parent, err)
}
// NOTE: Linux's devtmpfs refuses to automatically delete files it didn't
// create, which it recognizes by storing a pointer to the kdevtmpfs struct
// thread in struct inode::i_private. Accessor doesn't yet support deletion
// of files at all, and probably won't as long as we don't need to support
// kernel modules, so this is moot for now.
return a.vfsObj.MknodAt(actx, a.creds, a.pathOperationAt(pathname), &vfs.MknodOptions{
Mode: mode,
DevMajor: major,
DevMinor: minor,
})
}
// UserspaceInit creates symbolic links and mount points in the devtmpfs
// instance accessed by the Accessor that are created by userspace in Linux. It
// does not create mounts.
func (a *Accessor) UserspaceInit(ctx context.Context) error {
actx := a.wrapContext(ctx)
// Initialize symlinks.
for _, symlink := range []struct {
source string
target string
}{
// systemd: src/shared/dev-setup.c:dev_setup()
{source: "fd", target: "/proc/self/fd"},
{source: "stdin", target: "/proc/self/fd/0"},
{source: "stdout", target: "/proc/self/fd/1"},
{source: "stderr", target: "/proc/self/fd/2"},
// /proc/kcore is not implemented.
// Linux implements /dev/ptmx as a device node, but advises
// container implementations to create /dev/ptmx as a symlink
// to pts/ptmx (Documentation/filesystems/devpts.txt). Systemd
// follows this advice (src/nspawn/nspawn.c:setup_pts()), while
// LXC tries to create a bind mount and falls back to a symlink
// (src/lxc/conf.c:lxc_setup_devpts()).
{source: "ptmx", target: "pts/ptmx"},
} {
if err := a.vfsObj.SymlinkAt(actx, a.creds, a.pathOperationAt(symlink.source), symlink.target); err != nil {
return fmt.Errorf("failed to create symlink %q => %q: %v", symlink.source, symlink.target, err)
}
}
// systemd: src/core/mount-setup.c:mount_table
for _, dir := range []string{
"shm",
"pts",
} {
if err := a.vfsObj.MkdirAt(actx, a.creds, a.pathOperationAt(dir), &vfs.MkdirOptions{
// systemd: src/core/mount-setup.c:mount_one()
Mode: 0755,
}); err != nil {
return fmt.Errorf("failed to create directory %q: %v", dir, err)
}
}
return nil
}
-229
View File
@@ -1,229 +0,0 @@
// Copyright 2020 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 devtmpfs
import (
"path"
"testing"
"gvisor.dev/gvisor/pkg/abi/linux"
"gvisor.dev/gvisor/pkg/context"
"gvisor.dev/gvisor/pkg/fspath"
"gvisor.dev/gvisor/pkg/sentry/contexttest"
"gvisor.dev/gvisor/pkg/sentry/fsimpl/tmpfs"
"gvisor.dev/gvisor/pkg/sentry/kernel/auth"
"gvisor.dev/gvisor/pkg/sentry/vfs"
)
const devPath = "/dev"
func setupDevtmpfs(t *testing.T) (context.Context, *auth.Credentials, *vfs.VirtualFilesystem, vfs.VirtualDentry, func()) {
t.Helper()
ctx := contexttest.Context(t)
creds := auth.CredentialsFromContext(ctx)
vfsObj := &vfs.VirtualFilesystem{}
if err := vfsObj.Init(ctx); err != nil {
t.Fatalf("VFS init: %v", err)
}
// Register tmpfs just so that we can have a root filesystem that isn't
// devtmpfs.
vfsObj.MustRegisterFilesystemType("tmpfs", tmpfs.FilesystemType{}, &vfs.RegisterFilesystemTypeOptions{
AllowUserMount: true,
})
vfsObj.MustRegisterFilesystemType("devtmpfs", &FilesystemType{}, &vfs.RegisterFilesystemTypeOptions{
AllowUserMount: true,
})
// Create a test mount namespace with devtmpfs mounted at "/dev".
mntns, err := vfsObj.NewMountNamespace(ctx, creds, "tmpfs" /* source */, "tmpfs" /* fsTypeName */, &vfs.MountOptions{}, nil)
if err != nil {
t.Fatalf("failed to create tmpfs root mount: %v", err)
}
root := mntns.Root(ctx)
devpop := vfs.PathOperation{
Root: root,
Start: root,
Path: fspath.Parse(devPath),
}
if err := vfsObj.MkdirAt(ctx, creds, &devpop, &vfs.MkdirOptions{
Mode: 0755,
}); err != nil {
t.Fatalf("failed to create mount point: %v", err)
}
if _, err := vfsObj.MountAt(ctx, creds, "devtmpfs" /* source */, &devpop, "devtmpfs" /* fsTypeName */, &vfs.MountOptions{}); err != nil {
t.Fatalf("failed to mount devtmpfs: %v", err)
}
return ctx, creds, vfsObj, root, func() {
root.DecRef(ctx)
mntns.DecRef(ctx)
}
}
func TestUserspaceInit(t *testing.T) {
ctx, creds, vfsObj, root, cleanup := setupDevtmpfs(t)
defer cleanup()
a, err := NewAccessor(ctx, vfsObj, creds, "devtmpfs")
if err != nil {
t.Fatalf("failed to create devtmpfs.Accessor: %v", err)
}
defer a.Release(ctx)
// Create "userspace-initialized" files using a devtmpfs.Accessor.
if err := a.UserspaceInit(ctx); err != nil {
t.Fatalf("failed to userspace-initialize devtmpfs: %v", err)
}
// Created files should be visible in the test mount namespace.
links := []struct {
source string
target string
}{
{
source: "fd",
target: "/proc/self/fd",
},
{
source: "stdin",
target: "/proc/self/fd/0",
},
{
source: "stdout",
target: "/proc/self/fd/1",
},
{
source: "stderr",
target: "/proc/self/fd/2",
},
{
source: "ptmx",
target: "pts/ptmx",
},
}
for _, link := range links {
abspath := path.Join(devPath, link.source)
if gotTarget, err := vfsObj.ReadlinkAt(ctx, creds, &vfs.PathOperation{
Root: root,
Start: root,
Path: fspath.Parse(abspath),
}); err != nil || gotTarget != link.target {
t.Errorf("readlink(%q): got (%q, %v), wanted (%q, nil)", abspath, gotTarget, err, link.target)
}
}
dirs := []string{"shm", "pts"}
for _, dir := range dirs {
abspath := path.Join(devPath, dir)
statx, err := vfsObj.StatAt(ctx, creds, &vfs.PathOperation{
Root: root,
Start: root,
Path: fspath.Parse(abspath),
}, &vfs.StatOptions{
Mask: linux.STATX_MODE,
})
if err != nil {
t.Errorf("stat(%q): got error %v ", abspath, err)
continue
}
if want := uint16(0755) | linux.S_IFDIR; statx.Mode != want {
t.Errorf("stat(%q): got mode %x, want %x", abspath, statx.Mode, want)
}
}
}
func TestCreateDeviceFile(t *testing.T) {
ctx, creds, vfsObj, root, cleanup := setupDevtmpfs(t)
defer cleanup()
a, err := NewAccessor(ctx, vfsObj, creds, "devtmpfs")
if err != nil {
t.Fatalf("failed to create devtmpfs.Accessor: %v", err)
}
defer a.Release(ctx)
devFiles := []struct {
path string
kind vfs.DeviceKind
major uint32
minor uint32
perms uint16
}{
{
path: "dummy",
kind: vfs.CharDevice,
major: 12,
minor: 34,
perms: 0600,
},
{
path: "foo/bar",
kind: vfs.BlockDevice,
major: 13,
minor: 35,
perms: 0660,
},
{
path: "foo/baz",
kind: vfs.CharDevice,
major: 12,
minor: 40,
perms: 0666,
},
{
path: "a/b/c/d/e",
kind: vfs.BlockDevice,
major: 12,
minor: 34,
perms: 0600,
},
}
for _, f := range devFiles {
if err := a.CreateDeviceFile(ctx, f.path, f.kind, f.major, f.minor, f.perms); err != nil {
t.Fatalf("failed to create device file: %v", err)
}
// The device special file should be visible in the test mount namespace.
abspath := path.Join(devPath, f.path)
stat, err := vfsObj.StatAt(ctx, creds, &vfs.PathOperation{
Root: root,
Start: root,
Path: fspath.Parse(abspath),
}, &vfs.StatOptions{
Mask: linux.STATX_TYPE | linux.STATX_MODE,
})
if err != nil {
t.Fatalf("failed to stat device file at %q: %v", abspath, err)
}
if stat.RdevMajor != f.major {
t.Errorf("major device number: got %v, wanted %v", stat.RdevMajor, f.major)
}
if stat.RdevMinor != f.minor {
t.Errorf("minor device number: got %v, wanted %v", stat.RdevMinor, f.minor)
}
wantMode := f.perms
switch f.kind {
case vfs.CharDevice:
wantMode |= linux.S_IFCHR
case vfs.BlockDevice:
wantMode |= linux.S_IFBLK
}
if stat.Mode != wantMode {
t.Errorf("device file mode: got %v, wanted %v", stat.Mode, wantMode)
}
}
}
@@ -1,23 +0,0 @@
// Copyright 2020 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 devtmpfs
// afterLoad is invoked by stateify.
func (fst *FilesystemType) afterLoad() {
if fst.fs != nil {
// Ensure that we don't create another filesystem.
fst.initOnce.Do(func() {})
}
}