Skip to content
Permalink
Browse files

cgroup2: port over eBPF device controller from crun

The implementation is based on https://github.com/containers/crun/blob/0.10.2/src/libcrun/ebpf.c

Although ebpf.c is originally licensed under LGPL-3.0-or-later, the author
Giuseppe Scrivano agreed to relicense the file in Apache License 2.0:
#2144 (comment)

See libcontainer/cgroups/ebpf/devicefilter/devicefilter_test.go for tested configurations.

Signed-off-by: Akihiro Suda <akihiro.suda.cz@hco.ntt.co.jp>
  • Loading branch information...
AkihiroSuda committed Oct 17, 2019
1 parent e57a774 commit faf673ee45bae8420f27d4e040579e6969cc009b
Showing with 5,564 additions and 3 deletions.
  1. +180 −0 libcontainer/cgroups/ebpf/devicefilter/devicefilter.go
  2. +258 −0 libcontainer/cgroups/ebpf/devicefilter/devicefilter_test.go
  3. +38 −0 libcontainer/cgroups/ebpf/ebpf.go
  4. +1 −0 libcontainer/cgroups/fs/apply_raw.go
  5. +85 −0 libcontainer/cgroups/fs/devices_v2.go
  6. +1 −0 libcontainer/cgroups/systemd/unified_hierarchy.go
  7. +4 −3 libcontainer/specconv/spec_linux.go
  8. +3 −0 vendor.conf
  9. +23 −0 vendor/github.com/cilium/ebpf/LICENSE
  10. +183 −0 vendor/github.com/cilium/ebpf/abi.go
  11. +149 −0 vendor/github.com/cilium/ebpf/asm/alu.go
  12. +107 −0 vendor/github.com/cilium/ebpf/asm/alu_string.go
  13. +2 −0 vendor/github.com/cilium/ebpf/asm/doc.go
  14. +143 −0 vendor/github.com/cilium/ebpf/asm/func.go
  15. +133 −0 vendor/github.com/cilium/ebpf/asm/func_string.go
  16. +416 −0 vendor/github.com/cilium/ebpf/asm/instruction.go
  17. +109 −0 vendor/github.com/cilium/ebpf/asm/jump.go
  18. +53 −0 vendor/github.com/cilium/ebpf/asm/jump_string.go
  19. +189 −0 vendor/github.com/cilium/ebpf/asm/load_store.go
  20. +80 −0 vendor/github.com/cilium/ebpf/asm/load_store_string.go
  21. +237 −0 vendor/github.com/cilium/ebpf/asm/opcode.go
  22. +38 −0 vendor/github.com/cilium/ebpf/asm/opcode_string.go
  23. +42 −0 vendor/github.com/cilium/ebpf/asm/register.go
  24. +148 −0 vendor/github.com/cilium/ebpf/collection.go
  25. +17 −0 vendor/github.com/cilium/ebpf/doc.go
  26. +392 −0 vendor/github.com/cilium/ebpf/elf_reader.go
  27. +19 −0 vendor/github.com/cilium/ebpf/feature.go
  28. +8 −0 vendor/github.com/cilium/ebpf/go.mod
  29. +64 −0 vendor/github.com/cilium/ebpf/internal/cpu.go
  30. +24 −0 vendor/github.com/cilium/ebpf/internal/endian.go
  31. +118 −0 vendor/github.com/cilium/ebpf/internal/unix/types_linux.go
  32. +183 −0 vendor/github.com/cilium/ebpf/internal/unix/types_other.go
  33. +58 −0 vendor/github.com/cilium/ebpf/linker.go
  34. +595 −0 vendor/github.com/cilium/ebpf/map.go
  35. +192 −0 vendor/github.com/cilium/ebpf/marshalers.go
  36. +523 −0 vendor/github.com/cilium/ebpf/prog.go
  37. +14 −0 vendor/github.com/cilium/ebpf/ptr_32_be.go
  38. +14 −0 vendor/github.com/cilium/ebpf/ptr_32_le.go
  39. +14 −0 vendor/github.com/cilium/ebpf/ptr_64.go
  40. +20 −0 vendor/github.com/cilium/ebpf/readme.md
  41. +420 −0 vendor/github.com/cilium/ebpf/syscalls.go
  42. +189 −0 vendor/github.com/cilium/ebpf/types.go
  43. +78 −0 vendor/github.com/cilium/ebpf/types_string.go
@@ -0,0 +1,180 @@
// Package devicefilter containes eBPF device filter program
//
// The implementation is based on https://github.com/containers/crun/blob/0.10.2/src/libcrun/ebpf.c
//
// Although ebpf.c is originally licensed under LGPL-3.0-or-later, the author (Giuseppe Scrivano)
// agreed to relicense the file in Apache License 2.0: https://github.com/opencontainers/runc/issues/2144#issuecomment-543116397
package devicefilter

import (
"fmt"
"math"

"github.com/cilium/ebpf/asm"
"github.com/opencontainers/runc/libcontainer/configs"
"github.com/pkg/errors"
"golang.org/x/sys/unix"
)

const (
// license string format is same as kernel MODULE_LICENSE macro
license = "Apache"
)

// DeviceFilter returns eBPF device filter program and its license string
func DeviceFilter(devices []*configs.Device) (asm.Instructions, string, error) {
p := &program{}
p.init()
for i := len(devices) - 1; i >= 0; i-- {
if err := p.appendDevice(devices[i]); err != nil {
return nil, "", err
}
}
insts, err := p.finalize()
return insts, license, err
}

type program struct {
insts asm.Instructions
hasWildCard bool
blockID int
}

func (p *program) init() {
// struct bpf_cgroup_dev_ctx: https://elixir.bootlin.com/linux/v5.3.6/source/include/uapi/linux/bpf.h#L3423
/*
u32 access_type
u32 major
u32 minor
*/
// R2 <- type (lower 16 bit of u32 access_type at R1[0])
p.insts = append(p.insts,
asm.LoadMem(asm.R2, asm.R1, 0, asm.Half))

// R3 <- access (upper 16 bit of u32 access_type at R1[0])
p.insts = append(p.insts,
asm.LoadMem(asm.R3, asm.R1, 0, asm.Word),
// RSh: bitwise shift right
asm.RSh.Imm32(asm.R3, 16))

// R4 <- major (u32 major at R1[4])
p.insts = append(p.insts,
asm.LoadMem(asm.R4, asm.R1, 4, asm.Word))

// R5 <- minor (u32 minor at R1[8])
p.insts = append(p.insts,
asm.LoadMem(asm.R5, asm.R1, 8, asm.Word))
}

// appendDevice needs to be called from the last element of OCI linux.resources.devices to the head element.
func (p *program) appendDevice(dev *configs.Device) error {
if p.blockID < 0 {
return errors.New("the program is finalized")
}
if p.hasWildCard {
// All entries after wildcard entry are ignored
return nil
}

bpfType := int32(-1)
hasType := true
switch dev.Type {
case 'c':
bpfType = int32(unix.BPF_DEVCG_DEV_CHAR)
case 'b':
bpfType = int32(unix.BPF_DEVCG_DEV_BLOCK)
case 'a':
hasType = false
default:
// if not specified in OCI json, typ is set to DeviceTypeAll
return errors.Errorf("invalid DeviceType %q", string(dev.Type))
}
if dev.Major > math.MaxUint32 {
return errors.Errorf("invalid major %d", dev.Major)
}
if dev.Minor > math.MaxUint32 {
return errors.Errorf("invalid minor %d", dev.Major)
}
hasMajor := dev.Major >= 0 // if not specified in OCI json, major is set to -1
hasMinor := dev.Minor >= 0
bpfAccess := int32(0)
for _, r := range dev.Permissions {
switch r {
case 'r':
bpfAccess |= unix.BPF_DEVCG_ACC_READ
case 'w':
bpfAccess |= unix.BPF_DEVCG_ACC_WRITE
case 'm':
bpfAccess |= unix.BPF_DEVCG_ACC_MKNOD
default:
return errors.Errorf("unknown device access %v", r)
}
}
// If the access is rwm, skip the check.
hasAccess := bpfAccess != (unix.BPF_DEVCG_ACC_READ | unix.BPF_DEVCG_ACC_WRITE | unix.BPF_DEVCG_ACC_MKNOD)

blockSym := fmt.Sprintf("block-%d", p.blockID)
nextBlockSym := fmt.Sprintf("block-%d", p.blockID+1)
prevBlockLastIdx := len(p.insts) - 1
if hasType {
p.insts = append(p.insts,
// if (R2 != bpfType) goto next
asm.JNE.Imm(asm.R2, bpfType, nextBlockSym),
)
}
if hasAccess {
p.insts = append(p.insts,
// if (R3 & bpfAccess == 0 /* use R1 as a temp var */) goto next
asm.Mov.Reg32(asm.R1, asm.R3),
asm.And.Imm32(asm.R1, bpfAccess),
asm.JEq.Imm(asm.R1, 0, nextBlockSym),
)
}
if hasMajor {
p.insts = append(p.insts,
// if (R4 != major) goto next
asm.JNE.Imm(asm.R4, int32(dev.Major), nextBlockSym),
)
}
if hasMinor {
p.insts = append(p.insts,
// if (R5 != minor) goto next
asm.JNE.Imm(asm.R5, int32(dev.Minor), nextBlockSym),
)
}
if !hasType && !hasAccess && !hasMajor && !hasMinor {
p.hasWildCard = true
}
p.insts = append(p.insts, acceptBlock(dev.Allow)...)
// set blockSym to the first instruction we added in this iteration
p.insts[prevBlockLastIdx+1] = p.insts[prevBlockLastIdx+1].Sym(blockSym)
p.blockID++
return nil
}

func (p *program) finalize() (asm.Instructions, error) {
if p.hasWildCard {
// acceptBlock with asm.Return() is already inserted
return p.insts, nil
}
blockSym := fmt.Sprintf("block-%d", p.blockID)
p.insts = append(p.insts,
// R0 <- 0
asm.Mov.Imm32(asm.R0, 0).Sym(blockSym),
asm.Return(),
)
p.blockID = -1
return p.insts, nil
}

func acceptBlock(accept bool) asm.Instructions {
v := int32(0)
if accept {
v = 1
}
return []asm.Instruction{
// R0 <- v
asm.Mov.Imm32(asm.R0, v),
asm.Return(),
}
}

0 comments on commit faf673e

Please sign in to comment.
You can’t perform that action at this time.