mirror of https://github.com/k3s-io/k3s
111 lines
2.9 KiB
Go
111 lines
2.9 KiB
Go
// +build linux
|
|
|
|
package fs
|
|
|
|
import (
|
|
"bytes"
|
|
"errors"
|
|
"reflect"
|
|
|
|
"github.com/opencontainers/runc/libcontainer/cgroups"
|
|
cgroupdevices "github.com/opencontainers/runc/libcontainer/cgroups/devices"
|
|
"github.com/opencontainers/runc/libcontainer/configs"
|
|
"github.com/opencontainers/runc/libcontainer/devices"
|
|
"github.com/opencontainers/runc/libcontainer/userns"
|
|
)
|
|
|
|
type DevicesGroup struct {
|
|
testingSkipFinalCheck bool
|
|
}
|
|
|
|
func (s *DevicesGroup) Name() string {
|
|
return "devices"
|
|
}
|
|
|
|
func (s *DevicesGroup) Apply(path string, d *cgroupData) error {
|
|
if d.config.SkipDevices {
|
|
return nil
|
|
}
|
|
if path == "" {
|
|
// Return error here, since devices cgroup
|
|
// is a hard requirement for container's security.
|
|
return errSubsystemDoesNotExist
|
|
}
|
|
return join(path, d.pid)
|
|
}
|
|
|
|
func loadEmulator(path string) (*cgroupdevices.Emulator, error) {
|
|
list, err := cgroups.ReadFile(path, "devices.list")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return cgroupdevices.EmulatorFromList(bytes.NewBufferString(list))
|
|
}
|
|
|
|
func buildEmulator(rules []*devices.Rule) (*cgroupdevices.Emulator, error) {
|
|
// This defaults to a white-list -- which is what we want!
|
|
emu := &cgroupdevices.Emulator{}
|
|
for _, rule := range rules {
|
|
if err := emu.Apply(*rule); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
return emu, nil
|
|
}
|
|
|
|
func (s *DevicesGroup) Set(path string, r *configs.Resources) error {
|
|
if userns.RunningInUserNS() || r.SkipDevices {
|
|
return nil
|
|
}
|
|
|
|
// Generate two emulators, one for the current state of the cgroup and one
|
|
// for the requested state by the user.
|
|
current, err := loadEmulator(path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
target, err := buildEmulator(r.Devices)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Compute the minimal set of transition rules needed to achieve the
|
|
// requested state.
|
|
transitionRules, err := current.Transition(target)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for _, rule := range transitionRules {
|
|
file := "devices.deny"
|
|
if rule.Allow {
|
|
file = "devices.allow"
|
|
}
|
|
if err := cgroups.WriteFile(path, file, rule.CgroupString()); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
// Final safety check -- ensure that the resulting state is what was
|
|
// requested. This is only really correct for white-lists, but for
|
|
// black-lists we can at least check that the cgroup is in the right mode.
|
|
//
|
|
// This safety-check is skipped for the unit tests because we cannot
|
|
// currently mock devices.list correctly.
|
|
if !s.testingSkipFinalCheck {
|
|
currentAfter, err := loadEmulator(path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !target.IsBlacklist() && !reflect.DeepEqual(currentAfter, target) {
|
|
return errors.New("resulting devices cgroup doesn't precisely match target")
|
|
} else if target.IsBlacklist() != currentAfter.IsBlacklist() {
|
|
return errors.New("resulting devices cgroup doesn't match target mode")
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *DevicesGroup) GetStats(path string, stats *cgroups.Stats) error {
|
|
return nil
|
|
}
|