devices.go 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109
  1. package fs
  2. import (
  3. "bytes"
  4. "errors"
  5. "reflect"
  6. "github.com/opencontainers/runc/libcontainer/cgroups"
  7. cgroupdevices "github.com/opencontainers/runc/libcontainer/cgroups/devices"
  8. "github.com/opencontainers/runc/libcontainer/configs"
  9. "github.com/opencontainers/runc/libcontainer/devices"
  10. "github.com/opencontainers/runc/libcontainer/userns"
  11. )
  12. type DevicesGroup struct {
  13. TestingSkipFinalCheck bool
  14. }
  15. func (s *DevicesGroup) Name() string {
  16. return "devices"
  17. }
  18. func (s *DevicesGroup) Apply(path string, r *configs.Resources, pid int) error {
  19. if r.SkipDevices {
  20. return nil
  21. }
  22. if path == "" {
  23. // Return error here, since devices cgroup
  24. // is a hard requirement for container's security.
  25. return errSubsystemDoesNotExist
  26. }
  27. return apply(path, pid)
  28. }
  29. func loadEmulator(path string) (*cgroupdevices.Emulator, error) {
  30. list, err := cgroups.ReadFile(path, "devices.list")
  31. if err != nil {
  32. return nil, err
  33. }
  34. return cgroupdevices.EmulatorFromList(bytes.NewBufferString(list))
  35. }
  36. func buildEmulator(rules []*devices.Rule) (*cgroupdevices.Emulator, error) {
  37. // This defaults to a white-list -- which is what we want!
  38. emu := &cgroupdevices.Emulator{}
  39. for _, rule := range rules {
  40. if err := emu.Apply(*rule); err != nil {
  41. return nil, err
  42. }
  43. }
  44. return emu, nil
  45. }
  46. func (s *DevicesGroup) Set(path string, r *configs.Resources) error {
  47. if userns.RunningInUserNS() || r.SkipDevices {
  48. return nil
  49. }
  50. // Generate two emulators, one for the current state of the cgroup and one
  51. // for the requested state by the user.
  52. current, err := loadEmulator(path)
  53. if err != nil {
  54. return err
  55. }
  56. target, err := buildEmulator(r.Devices)
  57. if err != nil {
  58. return err
  59. }
  60. // Compute the minimal set of transition rules needed to achieve the
  61. // requested state.
  62. transitionRules, err := current.Transition(target)
  63. if err != nil {
  64. return err
  65. }
  66. for _, rule := range transitionRules {
  67. file := "devices.deny"
  68. if rule.Allow {
  69. file = "devices.allow"
  70. }
  71. if err := cgroups.WriteFile(path, file, rule.CgroupString()); err != nil {
  72. return err
  73. }
  74. }
  75. // Final safety check -- ensure that the resulting state is what was
  76. // requested. This is only really correct for white-lists, but for
  77. // black-lists we can at least check that the cgroup is in the right mode.
  78. //
  79. // This safety-check is skipped for the unit tests because we cannot
  80. // currently mock devices.list correctly.
  81. if !s.TestingSkipFinalCheck {
  82. currentAfter, err := loadEmulator(path)
  83. if err != nil {
  84. return err
  85. }
  86. if !target.IsBlacklist() && !reflect.DeepEqual(currentAfter, target) {
  87. return errors.New("resulting devices cgroup doesn't precisely match target")
  88. } else if target.IsBlacklist() != currentAfter.IsBlacklist() {
  89. return errors.New("resulting devices cgroup doesn't match target mode")
  90. }
  91. }
  92. return nil
  93. }
  94. func (s *DevicesGroup) GetStats(path string, stats *cgroups.Stats) error {
  95. return nil
  96. }