mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
Add methods for generating PCI sysfs paths and registering accel devices.
The TPU userspace driver needs access to specific PCI device information located in Linux sysfs. We mirror the sysfs paths the driver reads on the host in the Sentry sysfs. This way we can ensure we only expose the host device information that's strictly necessary for TPU to run. PiperOrigin-RevId: 550005271
This commit is contained in:
committed by
gVisor bot
parent
f3e4a1fc3b
commit
19e04218b9
@@ -6,6 +6,7 @@ go_library(
|
||||
name = "accel",
|
||||
srcs = [
|
||||
"accel.go",
|
||||
"device.go",
|
||||
"seccomp_filters.go",
|
||||
],
|
||||
visibility = ["//pkg/sentry:internal"],
|
||||
@@ -16,6 +17,7 @@ go_library(
|
||||
"//pkg/errors/linuxerr",
|
||||
"//pkg/seccomp",
|
||||
"//pkg/sentry/arch",
|
||||
"//pkg/sentry/fsimpl/devtmpfs",
|
||||
"//pkg/sentry/vfs",
|
||||
"//pkg/usermem",
|
||||
"//pkg/waiter",
|
||||
|
||||
@@ -33,6 +33,8 @@ type accelFD struct {
|
||||
vfs.FileDescriptionDefaultImpl
|
||||
vfs.DentryMetadataFileDescriptionImpl
|
||||
vfs.NoLockFD
|
||||
|
||||
hostFD int32
|
||||
}
|
||||
|
||||
// Release implements vfs.FileDescriptionImpl.Release.
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
// Copyright 2023 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 accel
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
"gvisor.dev/gvisor/pkg/abi/linux"
|
||||
"gvisor.dev/gvisor/pkg/context"
|
||||
"gvisor.dev/gvisor/pkg/sentry/fsimpl/devtmpfs"
|
||||
"gvisor.dev/gvisor/pkg/sentry/vfs"
|
||||
)
|
||||
|
||||
// accelDevice implements vfs.Device for /dev/accel[0-9]+.
|
||||
//
|
||||
// +stateify savable
|
||||
type accelDevice struct {
|
||||
minor uint32
|
||||
}
|
||||
|
||||
func (dev *accelDevice) Open(ctx context.Context, mnt *vfs.Mount, vfsd *vfs.Dentry, opts vfs.OpenOptions) (*vfs.FileDescription, error) {
|
||||
hostPath := fmt.Sprintf("/dev/accel%d", dev.minor)
|
||||
hostFD, err := unix.Openat(-1, hostPath, int((opts.Flags&unix.O_ACCMODE)|unix.O_NOFOLLOW), 0)
|
||||
if err != nil {
|
||||
ctx.Warningf("accelDevice: failed to open host %s: %v", hostPath, err)
|
||||
return nil, err
|
||||
}
|
||||
fd := &accelFD{
|
||||
hostFD: int32(hostFD),
|
||||
}
|
||||
if err := fd.vfsfd.Init(fd, opts.Flags, mnt, vfsd, &vfs.FileDescriptionOptions{
|
||||
UseDentryMetadata: true,
|
||||
}); err != nil {
|
||||
unix.Close(hostFD)
|
||||
return nil, err
|
||||
}
|
||||
return &fd.vfsfd, nil
|
||||
}
|
||||
|
||||
// CreateDevtmpfsFile creates a /dev/accel[0-9]+ device file.
|
||||
func CreateDevtmpfsFile(ctx context.Context, dev *devtmpfs.Accessor, num uint32) error {
|
||||
return dev.CreateDeviceFile(ctx, fmt.Sprintf("accel%d", num), vfs.CharDevice, linux.ACCEL_MAJOR, num, 0666)
|
||||
}
|
||||
|
||||
// Register registers all devices implemented by this package in vfsObj.
|
||||
func Register(vfsObj *vfs.VirtualFilesystem, minor uint32) error {
|
||||
return vfsObj.RegisterDevice(vfs.CharDevice, linux.ACCEL_MAJOR, minor, &accelDevice{
|
||||
minor: minor,
|
||||
}, &vfs.RegisterDeviceOptions{
|
||||
GroupName: "accel",
|
||||
})
|
||||
}
|
||||
@@ -22,6 +22,7 @@ go_library(
|
||||
"dir_refs.go",
|
||||
"kcov.go",
|
||||
"net.go",
|
||||
"pci.go",
|
||||
"sys.go",
|
||||
],
|
||||
visibility = ["//pkg/sentry:internal"],
|
||||
@@ -31,9 +32,11 @@ go_library(
|
||||
"//pkg/context",
|
||||
"//pkg/coverage",
|
||||
"//pkg/errors/linuxerr",
|
||||
"//pkg/fsutil",
|
||||
"//pkg/log",
|
||||
"//pkg/refs",
|
||||
"//pkg/sentry/arch",
|
||||
"//pkg/sentry/fsimpl/host",
|
||||
"//pkg/sentry/fsimpl/kernfs",
|
||||
"//pkg/sentry/inet",
|
||||
"//pkg/sentry/kernel",
|
||||
@@ -41,6 +44,7 @@ go_library(
|
||||
"//pkg/sentry/memmap",
|
||||
"//pkg/sentry/vfs",
|
||||
"//pkg/usermem",
|
||||
"@org_golang_x_sys//unix:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
@@ -0,0 +1,164 @@
|
||||
// Copyright 2023 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 sys
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path"
|
||||
regex "regexp"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
"gvisor.dev/gvisor/pkg/abi/linux"
|
||||
"gvisor.dev/gvisor/pkg/context"
|
||||
"gvisor.dev/gvisor/pkg/fsutil"
|
||||
"gvisor.dev/gvisor/pkg/sentry/fsimpl/kernfs"
|
||||
"gvisor.dev/gvisor/pkg/sentry/kernel/auth"
|
||||
)
|
||||
|
||||
const (
|
||||
pciMainBusDevicePath = "/sys/devices/pci0000:00"
|
||||
// Size of the buffer that host file content will be read into. All relevant
|
||||
// host files are smaller than this.
|
||||
hostFileBufSize = 0x1000
|
||||
)
|
||||
|
||||
var (
|
||||
// Matches PCI device addresses in the main domain.
|
||||
pciDeviceRegex = regex.MustCompile(`0000:([a-fA-F0-9]{2}|[a-fA-F0-9]{4}):[a-fA-F0-9]{2}\.[a-fA-F0-9]{1,2}`)
|
||||
// Matches the directories for the main bus (i.e. pci000:00), accel, and
|
||||
// individual devices (e.g. 00:00:04.0)
|
||||
sysDevicesDirRegex = regex.MustCompile(`pci0000:00|accel|(0000:([a-fA-F0-9]{2}|[a-fA-F0-9]{4}):[a-fA-F0-9]{2}\.[a-fA-F0-9]{1,2})`)
|
||||
// Files allowlisted for host passthrough. These files are read-only.
|
||||
sysDevicesFiles = map[string]any{
|
||||
"vendor": nil, "device": nil, "subsystem_vendor": nil, "subsystem_device": nil,
|
||||
"revision": nil, "class": nil, "numa_node": nil, "iommu_group": nil,
|
||||
"resource": nil, "pci_address": nil, "dev": nil, "driver_version": nil,
|
||||
"reset_count": nil, "write_open_count": nil, "status": nil,
|
||||
"is_device_owned": nil, "device_owner": nil, "framework_version": nil,
|
||||
"user_mem_ranges": nil, "interrupt_counts": nil, "chip_model": nil,
|
||||
"bar_offsets": nil, "bar_sizes": nil, "resource0": nil, "resource1": nil,
|
||||
"resource2": nil, "resource3": nil, "resource4": nil, "resource5": nil,
|
||||
}
|
||||
)
|
||||
|
||||
// Create /sys/class/accel/accel# symlinks.
|
||||
func (fs *filesystem) newAccelDir(ctx context.Context, creds *auth.Credentials) (map[string]kernfs.Inode, error) {
|
||||
accelDirs := map[string]kernfs.Inode{}
|
||||
pciDents, err := hostDirEntries(pciMainBusDevicePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, pciDent := range pciDents {
|
||||
accelDents, err := hostDirEntries(path.Join(pciMainBusDevicePath, pciDent, "accel"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(accelDents) != 1 {
|
||||
return nil, fmt.Errorf("path %q should only have one entry", path.Join(pciMainBusDevicePath, pciDent, "accel"))
|
||||
}
|
||||
accelDirs[accelDents[0]] = kernfs.NewStaticSymlink(ctx, creds, linux.UNNAMED_MAJOR, fs.devMinor, fs.NextIno(), fmt.Sprintf("../../devices/pci0000:00/%s/accel/%s", pciDent, accelDents[0]))
|
||||
}
|
||||
|
||||
return accelDirs, nil
|
||||
}
|
||||
|
||||
// Create /sys/bus/pci/devices symlinks.
|
||||
func (fs *filesystem) newPCIDevicesDir(ctx context.Context, creds *auth.Credentials) (map[string]kernfs.Inode, error) {
|
||||
pciDevicesDir := map[string]kernfs.Inode{}
|
||||
pciDents, err := hostDirEntries(pciMainBusDevicePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, pciDent := range pciDents {
|
||||
pciDevicesDir[pciDent] = kernfs.NewStaticSymlink(ctx, creds, linux.UNNAMED_MAJOR, fs.devMinor, fs.NextIno(), fmt.Sprintf("../../../devices/pci0000:00/%s", pciDent))
|
||||
}
|
||||
|
||||
return pciDevicesDir, nil
|
||||
}
|
||||
|
||||
// Recursively build out sysfs directories according to the allowlisted files,
|
||||
// directories, and symlinks defined in this package.
|
||||
func (fs *filesystem) mirrorPCIBusDeviceDir(ctx context.Context, creds *auth.Credentials, dir string) (map[string]kernfs.Inode, error) {
|
||||
subs := map[string]kernfs.Inode{}
|
||||
dents, err := hostDirEntries(dir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, dent := range dents {
|
||||
dentPath := path.Join(dir, dent)
|
||||
dentMode, err := hostFileMode(dentPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch dentMode {
|
||||
case unix.S_IFDIR:
|
||||
if match := sysDevicesDirRegex.MatchString(dent); !match {
|
||||
continue
|
||||
}
|
||||
contents, err := fs.mirrorPCIBusDeviceDir(ctx, creds, dentPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
subs[dent] = fs.newDir(ctx, creds, defaultSysMode, contents)
|
||||
case unix.S_IFREG:
|
||||
if _, ok := sysDevicesFiles[dent]; ok {
|
||||
subs[dent] = fs.newHostFile(ctx, creds, defaultSysMode, dentPath)
|
||||
}
|
||||
case unix.S_IFLNK:
|
||||
// Both the device and PCI address entries are links to the original PCI
|
||||
// device directory that's at the same place earlier in the dir tree.
|
||||
if match := pciDeviceRegex.MatchString(dent); !(match || dent == "device") {
|
||||
continue
|
||||
}
|
||||
pciDeviceName := pciDeviceRegex.FindString(dir)
|
||||
if pciDeviceName == "" {
|
||||
return nil, fmt.Errorf("could not populate sysfs pci symlink %s", dir)
|
||||
}
|
||||
linkContent := fmt.Sprintf("../../../%s", pciDeviceName)
|
||||
subs[dent] = kernfs.NewStaticSymlink(ctx, creds, linux.UNNAMED_MAJOR, fs.devMinor, fs.NextIno(), linkContent)
|
||||
}
|
||||
}
|
||||
return subs, nil
|
||||
}
|
||||
|
||||
func hostFileMode(path string) (uint32, error) {
|
||||
fd, err := unix.Openat(-1, path, unix.O_RDONLY|unix.O_NOFOLLOW|unix.O_PATH, 0)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
stat := unix.Stat_t{}
|
||||
if err := unix.Fstat(fd, &stat); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return stat.Mode & unix.S_IFMT, nil
|
||||
}
|
||||
|
||||
func hostDirEntries(path string) ([]string, error) {
|
||||
fd, err := unix.Openat(-1, path, unix.O_RDONLY|unix.O_NOFOLLOW, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var buf [hostFileBufSize]byte
|
||||
n, err := unix.Getdents(fd, buf[:])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var dents []string
|
||||
fsutil.ParseDirents(buf[:n], func(_ uint64, _ int64, _ uint8, name string, _ uint16) bool {
|
||||
dents = append(dents, name)
|
||||
return true
|
||||
})
|
||||
return dents, nil
|
||||
}
|
||||
@@ -20,6 +20,7 @@ import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
"gvisor.dev/gvisor/pkg/abi/linux"
|
||||
"gvisor.dev/gvisor/pkg/context"
|
||||
"gvisor.dev/gvisor/pkg/coverage"
|
||||
@@ -51,6 +52,9 @@ type FilesystemType struct{}
|
||||
type InternalData struct {
|
||||
// ProductName is the value to be set to devices/virtual/dmi/id/product_name.
|
||||
ProductName string
|
||||
// EnableAccelSysfs is whether to populate sysfs paths used by hardware
|
||||
// accelerators.
|
||||
EnableAccelSysfs bool
|
||||
}
|
||||
|
||||
// filesystem implements vfs.FilesystemImpl.
|
||||
@@ -117,10 +121,35 @@ func (fsType FilesystemType) GetFilesystem(ctx context.Context, vfsObj *vfs.Virt
|
||||
}
|
||||
|
||||
productName := ""
|
||||
var busSub map[string]kernfs.Inode
|
||||
if opts.InternalData != nil {
|
||||
data := opts.InternalData.(*InternalData)
|
||||
productName = data.ProductName
|
||||
idata := opts.InternalData.(*InternalData)
|
||||
productName = idata.ProductName
|
||||
if idata.EnableAccelSysfs {
|
||||
pciMainBusSub, err := fs.mirrorPCIBusDeviceDir(ctx, creds, pciMainBusDevicePath)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
devicesSub["pci0000:00"] = fs.newDir(ctx, creds, defaultSysDirMode, pciMainBusSub)
|
||||
|
||||
accelSub, err := fs.newAccelDir(ctx, creds)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
classSub["accel"] = fs.newDir(ctx, creds, defaultSysDirMode, accelSub)
|
||||
|
||||
pciDevicesSub, err := fs.newPCIDevicesDir(ctx, creds)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
busSub = map[string]kernfs.Inode{
|
||||
"pci": fs.newDir(ctx, creds, defaultSysDirMode, map[string]kernfs.Inode{
|
||||
"devices": fs.newDir(ctx, creds, defaultSysDirMode, pciDevicesSub),
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(productName) > 0 {
|
||||
log.Debugf("Setting product_name: %q", productName)
|
||||
classSub["dmi"] = fs.newDir(ctx, creds, defaultSysDirMode, map[string]kernfs.Inode{
|
||||
@@ -136,7 +165,7 @@ func (fsType FilesystemType) GetFilesystem(ctx context.Context, vfsObj *vfs.Virt
|
||||
}
|
||||
root := fs.newDir(ctx, creds, defaultSysDirMode, map[string]kernfs.Inode{
|
||||
"block": fs.newDir(ctx, creds, defaultSysDirMode, nil),
|
||||
"bus": fs.newDir(ctx, creds, defaultSysDirMode, nil),
|
||||
"bus": fs.newDir(ctx, creds, defaultSysDirMode, busSub),
|
||||
"class": fs.newDir(ctx, creds, defaultSysDirMode, classSub),
|
||||
"dev": fs.newDir(ctx, creds, defaultSysDirMode, nil),
|
||||
"devices": fs.newDir(ctx, creds, defaultSysDirMode, devicesSub),
|
||||
@@ -287,3 +316,33 @@ func (fs *filesystem) newStaticFile(ctx context.Context, creds *auth.Credentials
|
||||
s.Init(ctx, creds, linux.UNNAMED_MAJOR, fs.devMinor, fs.NextIno(), s, mode)
|
||||
return s
|
||||
}
|
||||
|
||||
// hostFile is an inode whose contents are generated by reading from the
|
||||
// host.
|
||||
//
|
||||
// +stateify savable
|
||||
type hostFile struct {
|
||||
kernfs.DynamicBytesFile
|
||||
hostPath string
|
||||
}
|
||||
|
||||
func (hf *hostFile) Generate(ctx context.Context, buf *bytes.Buffer) error {
|
||||
fd, err := unix.Openat(-1, hf.hostPath, unix.O_RDONLY|unix.O_NOFOLLOW, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var data [hostFileBufSize]byte
|
||||
n, err := unix.Read(fd, data[:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
unix.Close(fd)
|
||||
buf.Write(data[:n])
|
||||
return nil
|
||||
}
|
||||
|
||||
func (fs *filesystem) newHostFile(ctx context.Context, creds *auth.Credentials, mode linux.FileMode, hostPath string) kernfs.Inode {
|
||||
hf := &hostFile{hostPath: hostPath}
|
||||
hf.Init(ctx, creds, linux.UNNAMED_MAJOR, fs.devMinor, fs.NextIno(), hf, mode)
|
||||
return hf
|
||||
}
|
||||
|
||||
@@ -50,6 +50,7 @@ go_library(
|
||||
"//pkg/sentry/arch",
|
||||
"//pkg/sentry/arch:registers_go_proto",
|
||||
"//pkg/sentry/control",
|
||||
"//pkg/sentry/devices/accel",
|
||||
"//pkg/sentry/devices/memdev",
|
||||
"//pkg/sentry/devices/nvproxy",
|
||||
"//pkg/sentry/devices/ttydev",
|
||||
|
||||
+34
-1
@@ -18,6 +18,7 @@ import (
|
||||
"fmt"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -30,6 +31,7 @@ import (
|
||||
"gvisor.dev/gvisor/pkg/fd"
|
||||
"gvisor.dev/gvisor/pkg/fspath"
|
||||
"gvisor.dev/gvisor/pkg/log"
|
||||
"gvisor.dev/gvisor/pkg/sentry/devices/accel"
|
||||
"gvisor.dev/gvisor/pkg/sentry/devices/memdev"
|
||||
"gvisor.dev/gvisor/pkg/sentry/devices/nvproxy"
|
||||
"gvisor.dev/gvisor/pkg/sentry/devices/ttydev"
|
||||
@@ -173,6 +175,10 @@ func registerFilesystems(k *kernel.Kernel, info *containerInfo) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := tpuProxyRegisterDevicesAndCreateFiles(ctx, info, k, vfsObj, a); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -784,9 +790,11 @@ func (c *containerMounter) getMountNameAndOptions(conf *config.Config, m *mountI
|
||||
fsName = sys.Name
|
||||
|
||||
case sys.Name:
|
||||
sysData := &sys.InternalData{EnableAccelSysfs: conf.TPUProxy}
|
||||
if len(c.productName) > 0 {
|
||||
internalData = &sys.InternalData{ProductName: c.productName}
|
||||
sysData.ProductName = c.productName
|
||||
}
|
||||
internalData = sysData
|
||||
|
||||
case tmpfs.Name:
|
||||
var err error
|
||||
@@ -1113,6 +1121,31 @@ func createDeviceFiles(ctx context.Context, creds *auth.Credentials, info *conta
|
||||
return nil
|
||||
}
|
||||
|
||||
func tpuProxyRegisterDevicesAndCreateFiles(ctx context.Context, info *containerInfo, k *kernel.Kernel, vfsObj *vfs.VirtualFilesystem, a *devtmpfs.Accessor) error {
|
||||
if !info.conf.TPUProxy {
|
||||
return nil
|
||||
}
|
||||
// At this point /dev/accel just contains the TPU devices have been mounted
|
||||
// into the sandbox chroot. Enumerate all of them and create sentry devices.
|
||||
paths, err := filepath.Glob("/dev/accel*")
|
||||
if err != nil {
|
||||
return fmt.Errorf("enumerating accel device files: %w", err)
|
||||
}
|
||||
for _, path := range paths {
|
||||
accelDeviceRegex := regexp.MustCompile(`^/dev/accel(\d+)$`)
|
||||
if ms := accelDeviceRegex.FindStringSubmatch(path); ms != nil {
|
||||
deviceNum, _ := strconv.ParseUint(ms[1], 10, 32)
|
||||
if err := accel.Register(vfsObj, uint32(deviceNum)); err != nil {
|
||||
return fmt.Errorf("registering accel driver: %w", err)
|
||||
}
|
||||
if err := accel.CreateDevtmpfsFile(ctx, a, uint32(deviceNum)); err != nil {
|
||||
return fmt.Errorf("creating accel device file %q: %w", deviceNum, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func nvproxyRegisterDevicesAndCreateFiles(ctx context.Context, info *containerInfo, k *kernel.Kernel, vfsObj *vfs.VirtualFilesystem, a *devtmpfs.Accessor) error {
|
||||
if !specutils.GPUFunctionalityRequested(info.spec, info.conf) {
|
||||
return nil
|
||||
|
||||
Reference in New Issue
Block a user