Skip to content

Commit 457489d

Browse files
committed
Ensure that filemode is set for device nodes
This change ensures that the filemode for a CDI device is properly set from the host device node when adding devices to the CDI spec. Without this the device node permissions are 000 in the container under certain conditions (such as Kata). Signed-off-by: Evan Lezar <[email protected]>
1 parent 97e1067 commit 457489d

File tree

2 files changed

+77
-25
lines changed

2 files changed

+77
-25
lines changed

pkg/cdi/container-edits_test.go

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package cdi
1818

1919
import (
20+
"os"
2021
"runtime"
2122
"testing"
2223

@@ -314,6 +315,9 @@ func TestValidateContainerEdits(t *testing.T) {
314315
func TestApplyContainerEdits(t *testing.T) {
315316
nullDeviceMajor := int64(1)
316317
nullDeviceMinor := int64(3)
318+
319+
mode := uint32(0666)
320+
nullDeviceFileMode := (*os.FileMode)(&mode)
317321
if runtime.GOOS == "darwin" {
318322
nullDeviceMajor = 3
319323
nullDeviceMinor = 2
@@ -360,10 +364,11 @@ func TestApplyContainerEdits(t *testing.T) {
360364
Linux: &oci.Linux{
361365
Devices: []oci.LinuxDevice{
362366
{
363-
Path: "/dev/null",
364-
Type: "c",
365-
Major: nullDeviceMajor,
366-
Minor: nullDeviceMinor,
367+
Path: "/dev/null",
368+
Type: "c",
369+
Major: nullDeviceMajor,
370+
Minor: nullDeviceMinor,
371+
FileMode: nullDeviceFileMode,
367372
},
368373
},
369374
Resources: &oci.LinuxResources{
@@ -403,10 +408,11 @@ func TestApplyContainerEdits(t *testing.T) {
403408
Linux: &oci.Linux{
404409
Devices: []oci.LinuxDevice{
405410
{
406-
Path: "/dev/null",
407-
Type: "c",
408-
Major: nullDeviceMajor,
409-
Minor: nullDeviceMinor,
411+
Path: "/dev/null",
412+
Type: "c",
413+
Major: nullDeviceMajor,
414+
Minor: nullDeviceMinor,
415+
FileMode: nullDeviceFileMode,
410416
},
411417
},
412418
Resources: &oci.LinuxResources{

pkg/cdi/container-edits_unix.go

Lines changed: 63 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ package cdi
2121
import (
2222
"errors"
2323
"fmt"
24+
"os"
2425

2526
"golang.org/x/sys/unix"
2627
)
@@ -31,16 +32,28 @@ const (
3132
fifoDevice = "p"
3233
)
3334

35+
type deviceInfo struct {
36+
// cgroup properties
37+
deviceType string
38+
major int64
39+
minor int64
40+
41+
// device node properties
42+
fileMode os.FileMode
43+
}
44+
3445
// deviceInfoFromPath takes the path to a device and returns its type,
3546
// major and minor device numbers.
3647
//
3748
// It was adapted from https://github.com/opencontainers/runc/blob/v1.1.9/libcontainer/devices/device_unix.go#L30-L69
38-
func deviceInfoFromPath(path string) (devType string, major, minor int64, _ error) {
49+
func deviceInfoFromPath(path string) (*deviceInfo, error) {
3950
var stat unix.Stat_t
4051
err := unix.Lstat(path, &stat)
4152
if err != nil {
42-
return "", 0, 0, err
53+
return nil, err
4354
}
55+
56+
var devType string
4457
switch stat.Mode & unix.S_IFMT {
4558
case unix.S_IFBLK:
4659
devType = blockDevice
@@ -49,38 +62,71 @@ func deviceInfoFromPath(path string) (devType string, major, minor int64, _ erro
4962
case unix.S_IFIFO:
5063
devType = fifoDevice
5164
default:
52-
return "", 0, 0, errors.New("not a device node")
65+
return nil, errors.New("not a device node")
5366
}
5467
devNumber := uint64(stat.Rdev) //nolint:unconvert // Rdev is uint32 on e.g. MIPS.
55-
return devType, int64(unix.Major(devNumber)), int64(unix.Minor(devNumber)), nil
68+
69+
di := deviceInfo{
70+
deviceType: devType,
71+
major: int64(unix.Major(devNumber)),
72+
minor: int64(unix.Minor(devNumber)),
73+
fileMode: os.FileMode(stat.Mode &^ unix.S_IFMT),
74+
}
75+
76+
return &di, nil
5677
}
5778

5879
// fillMissingInfo fills in missing mandatory attributes from the host device.
5980
func (d *DeviceNode) fillMissingInfo() error {
81+
hasMinimalSpecification := d.Type != "" && (d.Major != 0 || d.Type == fifoDevice)
82+
83+
// Ensure that the host path and the container path match.
6084
if d.HostPath == "" {
6185
d.HostPath = d.Path
6286
}
6387

64-
if d.Type != "" && (d.Major != 0 || d.Type == fifoDevice) {
88+
// Try to extract the device info from the host path.
89+
di, err := deviceInfoFromPath(d.HostPath)
90+
if err != nil {
91+
// The error is only considered fatal if the device is not already
92+
// minimally specified since it is allowed for a device vendor to fully
93+
// specify a device node specification.
94+
if !hasMinimalSpecification {
95+
return fmt.Errorf("failed to stat CDI host device %q: %w", d.HostPath, err)
96+
}
6597
return nil
6698
}
6799

68-
deviceType, major, minor, err := deviceInfoFromPath(d.HostPath)
69-
if err != nil {
70-
return fmt.Errorf("failed to stat CDI host device %q: %w", d.HostPath, err)
100+
// Even for minimally-specified device nodes, we update the file mode if
101+
// required. This is useful for rootless containers where device node
102+
// requests may be treated as bind mounts.
103+
if d.FileMode == nil {
104+
d.FileMode = &di.fileMode
105+
}
106+
107+
// If the device is minimally specified, we make no further updates and
108+
// don't perform additional checks.
109+
if hasMinimalSpecification {
110+
return nil
71111
}
72112

73113
if d.Type == "" {
74-
d.Type = deviceType
75-
} else {
76-
if d.Type != deviceType {
77-
return fmt.Errorf("CDI device (%q, %q), host type mismatch (%s, %s)",
78-
d.Path, d.HostPath, d.Type, deviceType)
79-
}
114+
d.Type = di.deviceType
80115
}
81-
if d.Major == 0 && d.Type != fifoDevice {
82-
d.Major = major
83-
d.Minor = minor
116+
if d.Type != di.deviceType {
117+
return fmt.Errorf("CDI device (%q, %q), host type mismatch (%s, %s)",
118+
d.Path, d.HostPath, d.Type, di.deviceType)
119+
}
120+
121+
// For a fifoDevice, we do not update the major and minor number.
122+
if d.Type == fifoDevice {
123+
return nil
124+
}
125+
126+
// Update the major and minor number for the device node if required.
127+
if d.Major == 0 {
128+
d.Major = di.major
129+
d.Minor = di.minor
84130
}
85131

86132
return nil

0 commit comments

Comments
 (0)