github.com/nicocha30/gvisor-ligolo@v0.0.0-20230726075806-989fa2c0a413/pkg/sentry/fsimpl/sys/pci.go (about)

     1  // Copyright 2023 The gVisor Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //	http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package sys
    16  
    17  import (
    18  	"fmt"
    19  	"path"
    20  	regex "regexp"
    21  
    22  	"golang.org/x/sys/unix"
    23  	"github.com/nicocha30/gvisor-ligolo/pkg/abi/linux"
    24  	"github.com/nicocha30/gvisor-ligolo/pkg/context"
    25  	"github.com/nicocha30/gvisor-ligolo/pkg/fsutil"
    26  	"github.com/nicocha30/gvisor-ligolo/pkg/sentry/fsimpl/kernfs"
    27  	"github.com/nicocha30/gvisor-ligolo/pkg/sentry/kernel/auth"
    28  )
    29  
    30  const (
    31  	pciMainBusDevicePath = "/sys/devices/pci0000:00"
    32  	// Size of the buffer that host file content will be read into. All relevant
    33  	// host files are smaller than this.
    34  	hostFileBufSize = 0x1000
    35  )
    36  
    37  var (
    38  	// Matches PCI device addresses in the main domain.
    39  	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}`)
    40  	// Matches the directories for the main bus (i.e. pci000:00), accel, and
    41  	// individual devices (e.g. 00:00:04.0)
    42  	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})`)
    43  	// Files allowlisted for host passthrough. These files are read-only.
    44  	sysDevicesFiles = map[string]any{
    45  		"vendor": nil, "device": nil, "subsystem_vendor": nil, "subsystem_device": nil,
    46  		"revision": nil, "class": nil, "numa_node": nil, "iommu_group": nil,
    47  		"resource": nil, "pci_address": nil, "dev": nil, "driver_version": nil,
    48  		"reset_count": nil, "write_open_count": nil, "status": nil,
    49  		"is_device_owned": nil, "device_owner": nil, "framework_version": nil,
    50  		"user_mem_ranges": nil, "interrupt_counts": nil, "chip_model": nil,
    51  		"bar_offsets": nil, "bar_sizes": nil, "resource0": nil, "resource1": nil,
    52  		"resource2": nil, "resource3": nil, "resource4": nil, "resource5": nil,
    53  	}
    54  )
    55  
    56  // Create /sys/class/accel/accel# symlinks.
    57  func (fs *filesystem) newAccelDir(ctx context.Context, creds *auth.Credentials) (map[string]kernfs.Inode, error) {
    58  	accelDirs := map[string]kernfs.Inode{}
    59  	pciDents, err := hostDirEntries(pciMainBusDevicePath)
    60  	if err != nil {
    61  		return nil, err
    62  	}
    63  	for _, pciDent := range pciDents {
    64  		accelDents, err := hostDirEntries(path.Join(pciMainBusDevicePath, pciDent, "accel"))
    65  		if err != nil {
    66  			return nil, err
    67  		}
    68  		if len(accelDents) != 1 {
    69  			return nil, fmt.Errorf("path %q should only have one entry", path.Join(pciMainBusDevicePath, pciDent, "accel"))
    70  		}
    71  		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]))
    72  	}
    73  
    74  	return accelDirs, nil
    75  }
    76  
    77  // Create /sys/bus/pci/devices symlinks.
    78  func (fs *filesystem) newPCIDevicesDir(ctx context.Context, creds *auth.Credentials) (map[string]kernfs.Inode, error) {
    79  	pciDevicesDir := map[string]kernfs.Inode{}
    80  	pciDents, err := hostDirEntries(pciMainBusDevicePath)
    81  	if err != nil {
    82  		return nil, err
    83  	}
    84  	for _, pciDent := range pciDents {
    85  		pciDevicesDir[pciDent] = kernfs.NewStaticSymlink(ctx, creds, linux.UNNAMED_MAJOR, fs.devMinor, fs.NextIno(), fmt.Sprintf("../../../devices/pci0000:00/%s", pciDent))
    86  	}
    87  
    88  	return pciDevicesDir, nil
    89  }
    90  
    91  // Recursively build out sysfs directories according to the allowlisted files,
    92  // directories, and symlinks defined in this package.
    93  func (fs *filesystem) mirrorPCIBusDeviceDir(ctx context.Context, creds *auth.Credentials, dir string) (map[string]kernfs.Inode, error) {
    94  	subs := map[string]kernfs.Inode{}
    95  	dents, err := hostDirEntries(dir)
    96  	if err != nil {
    97  		return nil, err
    98  	}
    99  	for _, dent := range dents {
   100  		dentPath := path.Join(dir, dent)
   101  		dentMode, err := hostFileMode(dentPath)
   102  		if err != nil {
   103  			return nil, err
   104  		}
   105  		switch dentMode {
   106  		case unix.S_IFDIR:
   107  			if match := sysDevicesDirRegex.MatchString(dent); !match {
   108  				continue
   109  			}
   110  			contents, err := fs.mirrorPCIBusDeviceDir(ctx, creds, dentPath)
   111  			if err != nil {
   112  				return nil, err
   113  			}
   114  			subs[dent] = fs.newDir(ctx, creds, defaultSysMode, contents)
   115  		case unix.S_IFREG:
   116  			if _, ok := sysDevicesFiles[dent]; ok {
   117  				subs[dent] = fs.newHostFile(ctx, creds, defaultSysMode, dentPath)
   118  			}
   119  		case unix.S_IFLNK:
   120  			// Both the device and PCI address entries are links to the original PCI
   121  			// device directory that's at the same place earlier in the dir tree.
   122  			if match := pciDeviceRegex.MatchString(dent); !(match || dent == "device") {
   123  				continue
   124  			}
   125  			pciDeviceName := pciDeviceRegex.FindString(dir)
   126  			if pciDeviceName == "" {
   127  				return nil, fmt.Errorf("could not populate sysfs pci symlink %s", dir)
   128  			}
   129  			linkContent := fmt.Sprintf("../../../%s", pciDeviceName)
   130  			subs[dent] = kernfs.NewStaticSymlink(ctx, creds, linux.UNNAMED_MAJOR, fs.devMinor, fs.NextIno(), linkContent)
   131  		}
   132  	}
   133  	return subs, nil
   134  }
   135  
   136  func hostFileMode(path string) (uint32, error) {
   137  	fd, err := unix.Openat(-1, path, unix.O_RDONLY|unix.O_NOFOLLOW|unix.O_PATH, 0)
   138  	if err != nil {
   139  		return 0, err
   140  	}
   141  	stat := unix.Stat_t{}
   142  	if err := unix.Fstat(fd, &stat); err != nil {
   143  		return 0, err
   144  	}
   145  	return stat.Mode & unix.S_IFMT, nil
   146  }
   147  
   148  func hostDirEntries(path string) ([]string, error) {
   149  	fd, err := unix.Openat(-1, path, unix.O_RDONLY|unix.O_NOFOLLOW, 0)
   150  	if err != nil {
   151  		return nil, err
   152  	}
   153  	var buf [hostFileBufSize]byte
   154  	n, err := unix.Getdents(fd, buf[:])
   155  	if err != nil {
   156  		return nil, err
   157  	}
   158  	var dents []string
   159  	fsutil.ParseDirents(buf[:n], func(_ uint64, _ int64, _ uint8, name string, _ uint16) bool {
   160  		dents = append(dents, name)
   161  		return true
   162  	})
   163  	return dents, nil
   164  }