OCI: Mount (accessible) host devices in privileged rootless containers
Allow rootless containers with privileged to mount devices that are accessible (ignore permission errors in rootless mode). This patch updates oci.getDevices() to ignore access denied errors on sub- directories and files within the given path if the container is running with userns enabled. Note that these errors are _only_ ignored on paths _under_ the specified path, and not the path itself, so if `HostDevices()` is used, and `/dev` itself is not accessible, or `WithDevices()` is used to specify a device that is not accessible, an error is still produced. Tests were added, which includes a temporary workaround for compatibility with Go 1.16 (we could decide to skip these tests on Go 1.16 instead). To verify the patch in a container: docker run --rm -v $(pwd):/go/src/github.com/containerd/containerd -w /go/src/github.com/containerd/containerd golang:1.17 sh -c 'go test -v -run TestHostDevices ./oci' === RUN TestHostDevicesOSReadDirFailure --- PASS: TestHostDevicesOSReadDirFailure (0.00s) === RUN TestHostDevicesOSReadDirFailureInUserNS --- PASS: TestHostDevicesOSReadDirFailureInUserNS (0.00s) === RUN TestHostDevicesDeviceFromPathFailure --- PASS: TestHostDevicesDeviceFromPathFailure (0.00s) === RUN TestHostDevicesDeviceFromPathFailureInUserNS --- PASS: TestHostDevicesDeviceFromPathFailureInUserNS (0.00s) === RUN TestHostDevicesAllValid --- PASS: TestHostDevicesAllValid (0.00s) PASS ok github.com/containerd/containerd/oci 0.006s Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
This commit is contained in:
@@ -23,6 +23,7 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/containerd/containerd/pkg/userns"
|
||||
specs "github.com/opencontainers/runtime-spec/specs-go"
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/sys/unix"
|
||||
@@ -31,6 +32,13 @@ import (
|
||||
// ErrNotADevice denotes that a file is not a valid linux device.
|
||||
var ErrNotADevice = errors.New("not a device node")
|
||||
|
||||
// Testing dependencies
|
||||
var (
|
||||
osReadDir = os.ReadDir
|
||||
usernsRunningInUserNS = userns.RunningInUserNS
|
||||
overrideDeviceFromPath func(path string) error
|
||||
)
|
||||
|
||||
// HostDevices returns all devices that can be found under /dev directory.
|
||||
func HostDevices() ([]specs.LinuxDevice, error) {
|
||||
return getDevices("/dev", "")
|
||||
@@ -53,7 +61,7 @@ func getDevices(path, containerPath string) ([]specs.LinuxDevice, error) {
|
||||
return []specs.LinuxDevice{*dev}, nil
|
||||
}
|
||||
|
||||
files, err := os.ReadDir(path)
|
||||
files, err := osReadDir(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -73,6 +81,12 @@ func getDevices(path, containerPath string) ([]specs.LinuxDevice, error) {
|
||||
}
|
||||
sub, err := getDevices(filepath.Join(path, f.Name()), cp)
|
||||
if err != nil {
|
||||
if errors.Is(err, os.ErrPermission) && usernsRunningInUserNS() {
|
||||
// ignore the "permission denied" error if running in userns.
|
||||
// This allows rootless containers to use devices that are
|
||||
// accessible, ignoring devices / subdirectories that are not.
|
||||
continue
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -90,6 +104,12 @@ func getDevices(path, containerPath string) ([]specs.LinuxDevice, error) {
|
||||
if os.IsNotExist(err) {
|
||||
continue
|
||||
}
|
||||
if errors.Is(err, os.ErrPermission) && usernsRunningInUserNS() {
|
||||
// ignore the "permission denied" error if running in userns.
|
||||
// This allows rootless containers to use devices that are
|
||||
// accessible, ignoring devices that are not.
|
||||
continue
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
if device.Type == fifoDevice {
|
||||
@@ -115,6 +135,12 @@ const (
|
||||
// DeviceFromPath takes the path to a device to look up the information about a
|
||||
// linux device and returns that information as a LinuxDevice struct.
|
||||
func DeviceFromPath(path string) (*specs.LinuxDevice, error) {
|
||||
if overrideDeviceFromPath != nil {
|
||||
if err := overrideDeviceFromPath(path); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
var stat unix.Stat_t
|
||||
if err := unix.Lstat(path, &stat); err != nil {
|
||||
return nil, err
|
||||
|
Reference in New Issue
Block a user