Files
Etienne PerotandgVisor bot 3495c7e7d5 Use clear on map types wherever possible.
This is similar as pull request #9749 but for maps rather than slices.

PiperOrigin-RevId: 586504320
2023-11-29 18:00:07 -08:00

373 lines
10 KiB
Go

// Copyright 2023 The gVisor Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package cgroupfs
import (
"bytes"
"fmt"
"strconv"
"strings"
"gvisor.dev/gvisor/pkg/context"
"gvisor.dev/gvisor/pkg/errors/linuxerr"
"gvisor.dev/gvisor/pkg/hostarch"
"gvisor.dev/gvisor/pkg/sentry/fsimpl/kernfs"
"gvisor.dev/gvisor/pkg/sentry/kernel"
"gvisor.dev/gvisor/pkg/sentry/kernel/auth"
"gvisor.dev/gvisor/pkg/sentry/vfs"
"gvisor.dev/gvisor/pkg/sync"
"gvisor.dev/gvisor/pkg/usermem"
)
const (
canRead = 1 << iota
canWrite
canMknod
)
const (
allowedDevices = "devices.allow"
controlledDevices = "devices.list"
deniedDevices = "devices.deny"
wildcardDeviceNumber = -1
)
const (
blockDevice deviceType = "b"
charDevice deviceType = "c"
wildcardDevice deviceType = "a"
)
// type denotes a device's type.
type deviceType string
func (d deviceType) valid() bool {
switch d {
case wildcardDevice, charDevice, blockDevice:
return true
default:
return false
}
}
// permission represents a device access, read, write, and mknod.
type permission string
func (p permission) valid() bool {
for _, c := range p {
switch c {
case 'r', 'w', 'm':
continue
default:
return false
}
}
return true
}
// toBinary converts permission to its binary representation.
func (p permission) toBinary() int {
var perm int
for _, c := range p {
switch c {
case 'r':
perm |= canRead
case 'w':
perm |= canWrite
case 'm':
perm |= canMknod
}
}
return perm
}
// union returns a permission which unions p and perm.
func (p permission) union(perm permission) permission {
return fromBinary(p.toBinary() | perm.toBinary())
}
// difference returns a permission which consists of accesses in p and not in perm.
func (p permission) difference(perm permission) permission {
return fromBinary(p.toBinary() & ^perm.toBinary())
}
// fromBinary converts permission to its string representation.
func fromBinary(i int) permission {
var perm permission
if i&canRead == canRead {
perm += "r"
}
if i&canWrite == canWrite {
perm += "w"
}
if i&canMknod == canMknod {
perm += "m"
}
return perm
}
// +stateify savable
type deviceID struct {
// Device type, when the type is all, the following fields are ignored.
controllerType deviceType
// The device's major number.
major int64
// The device's minor number.
minor int64
}
// +stateify savable
type devicesController struct {
controllerCommon
controllerStateless
controllerNoResource
// mu protects the fields below.
mu sync.Mutex `state:"nosave"`
// Allow or deny the device rules below.
defaultAllow bool
deviceRules map[deviceID]permission
}
// +stateify savable
type allowedDevicesData struct {
c *devicesController
}
// Generate implements vfs.DynamicBytesSource.Generate. The devices.allow shows nothing.
func (d *allowedDevicesData) Generate(ctx context.Context, buf *bytes.Buffer) error {
return nil
}
// Write implements vfs.WritableDynamicBytesSource.Write.
func (d *allowedDevicesData) Write(ctx context.Context, _ *vfs.FileDescription, src usermem.IOSequence, offset int64) (int64, error) {
return d.c.write(ctx, src, offset, true)
}
// +stateify savable
type deniedDevicesData struct {
c *devicesController
}
// Generate implements vfs.DynamicBytesSource.Generate. The devices.deny shows nothing.
func (d *deniedDevicesData) Generate(ctx context.Context, buf *bytes.Buffer) error {
return nil
}
// Write implements vfs.WritableDynamicBytesSource.Write.
func (d *deniedDevicesData) Write(ctx context.Context, _ *vfs.FileDescription, src usermem.IOSequence, offset int64) (int64, error) {
return d.c.write(ctx, src, offset, false)
}
// +stateify savable
type controlledDevicesData struct {
c *devicesController
}
// Generate implements vfs.DynamicBytesSource.Generate.
//
// The corresponding devices.list shows devices for which access control is set.
func (d *controlledDevicesData) Generate(ctx context.Context, buf *bytes.Buffer) error {
return d.c.generate(ctx, buf)
}
func (c *devicesController) addRule(id deviceID, newPermission permission) error {
existingPermission := c.deviceRules[id]
c.deviceRules[id] = existingPermission.union(newPermission)
return nil
}
func (c *devicesController) removeRule(id deviceID, p permission) error {
// cgroupv1 ignores silently requests to remove a partially-matching wildcard rule,
// which are {majorDevice:wildcardDevice}, {wildcardDevice:minorDevice}, and {wildcardDevice:wildcardDevice}
for _, wildcardDeviceID := range []deviceID{
{controllerType: id.controllerType, major: id.major, minor: wildcardDeviceNumber},
{controllerType: id.controllerType, major: wildcardDeviceNumber, minor: id.minor},
{controllerType: id.controllerType, major: wildcardDeviceNumber, minor: wildcardDeviceNumber},
} {
// If there is a exact match, the permission needs to be updated.
if id == wildcardDeviceID {
continue
}
if _, exist := c.deviceRules[wildcardDeviceID]; exist {
return nil
}
}
if existingPermission, exist := c.deviceRules[id]; exist {
if newPermission := existingPermission.difference(p); len(newPermission) == 0 {
delete(c.deviceRules, id)
} else {
c.deviceRules[id] = newPermission
}
}
return nil
}
func (c *devicesController) applyRule(id deviceID, p permission, allow bool) error {
if !id.controllerType.valid() {
return linuxerr.EINVAL
}
// If the device type is all, it will reset the rules for all.
if id.controllerType == wildcardDevice {
c.defaultAllow = allow
clear(c.deviceRules)
return nil
}
if !p.valid() {
return linuxerr.EINVAL
}
if len(c.deviceRules) == 0 {
c.defaultAllow = allow
clear(c.deviceRules)
}
if allow == c.defaultAllow {
return c.addRule(id, p)
}
return c.removeRule(id, p)
}
func (c *devicesController) generate(ctx context.Context, buf *bytes.Buffer) error {
c.mu.Lock()
defer c.mu.Unlock()
switch {
case c.defaultAllow && len(c.deviceRules) > 0:
for id, p := range c.deviceRules {
buf.WriteString(deviceRuleString(id, p))
// It lists one rule per line.
buf.WriteRune('\n')
}
case c.defaultAllow && len(c.deviceRules) == 0:
buf.WriteString(deviceRuleString(deviceID{controllerType: wildcardDevice, major: wildcardDeviceNumber, minor: wildcardDeviceNumber}, "rwm"))
case !c.defaultAllow && len(c.deviceRules) == 0:
buf.WriteString("")
default:
// When allow-all rule presents at devices.list, it actually indicates that
// the cgroup is in black-list mode.
buf.WriteString(deviceRuleString(deviceID{controllerType: wildcardDevice, major: wildcardDeviceNumber, minor: wildcardDeviceNumber}, "rwm"))
}
return nil
}
func (c *devicesController) write(ctx context.Context, src usermem.IOSequence, offset int64, allow bool) (int64, error) {
c.mu.Lock()
defer c.mu.Unlock()
if src.NumBytes() > hostarch.PageSize {
return 0, linuxerr.EINVAL
}
buf := copyScratchBufferFromContext(ctx, hostarch.PageSize)
n, err := src.CopyIn(ctx, buf)
if err != nil {
return 0, err
}
rule := string(buf[:n])
fields := strings.FieldsFunc(rule, func(r rune) bool {
return r == ' ' || r == ':'
})
switch {
case len(fields) != 1 && len(fields) != 4:
return 0, linuxerr.EINVAL
case len(fields) == 4:
controllerType := deviceType(fields[0])
perm := permission(fields[3])
if i := strings.IndexFunc(fields[3], func(r rune) bool { return r == '\n' }); i != -1 {
perm = perm[:i]
}
if len(perm) > 3 {
perm = perm[:3]
}
majorDevice, err := toDeviceNumber(fields[1])
if err != nil {
return 0, err
}
minorDevice, err := toDeviceNumber(fields[2])
if err != nil {
return 0, err
}
id := deviceID{
controllerType: controllerType,
major: majorDevice,
minor: minorDevice,
}
if err := c.applyRule(id, perm, allow); err != nil {
return 0, err
}
case len(fields) == 1:
if deviceType(fields[0]) != wildcardDevice {
return 0, linuxerr.EINVAL
}
if err := c.applyRule(deviceID{controllerType: wildcardDevice}, permission(""), allow); err != nil {
return 0, err
}
}
return int64(n), nil
}
var _ controller = (*devicesController)(nil)
func newDevicesController(fs *filesystem) *devicesController {
// The root device cgroup starts with rwm to all.
c := &devicesController{
defaultAllow: true,
deviceRules: make(map[deviceID]permission),
}
c.controllerCommon.init(kernel.CgroupControllerDevices, fs)
return c
}
// Clone implements controller.Clone.
func (c *devicesController) Clone() controller {
c.mu.Lock()
defer c.mu.Unlock()
newRules := make(map[deviceID]permission)
for id, p := range c.deviceRules {
newRules[id] = p
}
new := &devicesController{
defaultAllow: c.defaultAllow,
deviceRules: newRules,
}
new.controllerCommon.cloneFromParent(c)
return new
}
// AddControlFiles implements controller.AddControlFiles.
func (c *devicesController) AddControlFiles(ctx context.Context, creds *auth.Credentials, _ *cgroupInode, contents map[string]kernfs.Inode) {
contents[allowedDevices] = c.fs.newControllerWritableFile(ctx, creds, &allowedDevicesData{c: c}, true)
contents[deniedDevices] = c.fs.newControllerWritableFile(ctx, creds, &deniedDevicesData{c: c}, true)
contents[controlledDevices] = c.fs.newControllerFile(ctx, creds, &controlledDevicesData{c: c}, true)
}
func deviceRuleString(id deviceID, p permission) string {
return fmt.Sprintf("%s %s:%s %s", id.controllerType, deviceNumber(id.major), deviceNumber(id.minor), p)
}
// deviceNumber converts a device number to string.
func deviceNumber(number int64) string {
if number == wildcardDeviceNumber {
return "*"
}
return fmt.Sprint(number)
}
func toDeviceNumber(s string) (int64, error) {
if s == "*" {
return wildcardDeviceNumber, nil
}
val, err := strconv.ParseInt(s, 10, 64)
if err != nil {
return 0, linuxerr.EINVAL
}
return val, nil
}