Add support for OCI seccomp filters in the sandbox.

OCI configuration includes support for specifying seccomp filters. In runc,
these filter configurations are converted into seccomp BPF programs and loaded
into the kernel via libseccomp. runsc needs to be a static binary so, for
runsc, we cannot rely on a C library and need to implement the functionality
in Go.

The generator added here implements basic support for taking OCI seccomp
configuration and converting it into a seccomp BPF program with the same
behavior as a program generated by libseccomp.

- New conditional operations were added to pkg/seccomp to support operations
  available in OCI.
- AllowAny and AllowValue were renamed to MatchAny and EqualTo to better reflect
  that syscalls matching the conditionals result in the provided action not
  simply SCMP_RET_ALLOW.
- BuildProgram in pkg/seccomp no longer panics if provided an empty list of
  rules. It now builds a program with the architecture sanity check only.
- ProgramBuilder now allows adding labels that are unused. However, backwards
  jumps are still not permitted.

Fixes #510

PiperOrigin-RevId: 331938697
This commit is contained in:
Ian Lewis
2020-09-15 23:19:17 -07:00
committed by gVisor bot
parent c053c4bb03
commit dcd532e2e4
27 changed files with 1946 additions and 458 deletions
+18 -5
View File
@@ -34,11 +34,11 @@ type BPFAction uint32
const (
SECCOMP_RET_KILL_PROCESS BPFAction = 0x80000000
SECCOMP_RET_KILL_THREAD = 0x00000000
SECCOMP_RET_TRAP = 0x00030000
SECCOMP_RET_ERRNO = 0x00050000
SECCOMP_RET_TRACE = 0x7ff00000
SECCOMP_RET_ALLOW = 0x7fff0000
SECCOMP_RET_KILL_THREAD BPFAction = 0x00000000
SECCOMP_RET_TRAP BPFAction = 0x00030000
SECCOMP_RET_ERRNO BPFAction = 0x00050000
SECCOMP_RET_TRACE BPFAction = 0x7ff00000
SECCOMP_RET_ALLOW BPFAction = 0x7fff0000
)
func (a BPFAction) String() string {
@@ -64,6 +64,19 @@ func (a BPFAction) Data() uint16 {
return uint16(a & SECCOMP_RET_DATA)
}
// WithReturnCode sets the lower 16 bits of the SECCOMP_RET_ERRNO or
// SECCOMP_RET_TRACE actions to the provided return code, overwriting the previous
// action, and returns a new BPFAction. If not SECCOMP_RET_ERRNO or
// SECCOMP_RET_TRACE then this panics.
func (a BPFAction) WithReturnCode(code uint16) BPFAction {
// mask out the previous return value
baseAction := a & SECCOMP_RET_ACTION_FULL
if baseAction == SECCOMP_RET_ERRNO || baseAction == SECCOMP_RET_TRACE {
return BPFAction(uint32(baseAction) | uint32(code))
}
panic("WithReturnCode only valid for SECCOMP_RET_ERRNO and SECCOMP_RET_TRACE")
}
// SockFprog is sock_fprog taken from <linux/filter.h>.
type SockFprog struct {
Len uint16
+9 -4
View File
@@ -21,10 +21,15 @@ import (
"gvisor.dev/gvisor/pkg/abi/linux"
)
// DecodeProgram translates an array of BPF instructions into text format.
func DecodeProgram(program []linux.BPFInstruction) (string, error) {
// DecodeProgram translates a compiled BPF program into text format.
func DecodeProgram(p Program) (string, error) {
return DecodeInstructions(p.instructions)
}
// DecodeInstructions translates an array of BPF instructions into text format.
func DecodeInstructions(instns []linux.BPFInstruction) (string, error) {
var ret bytes.Buffer
for line, s := range program {
for line, s := range instns {
ret.WriteString(fmt.Sprintf("%v: ", line))
if err := decode(s, line, &ret); err != nil {
return "", err
@@ -34,7 +39,7 @@ func DecodeProgram(program []linux.BPFInstruction) (string, error) {
return ret.String(), nil
}
// Decode translates BPF instruction into text format.
// Decode translates a single BPF instruction into text format.
func Decode(inst linux.BPFInstruction) (string, error) {
var ret bytes.Buffer
err := decode(inst, -1, &ret)
+2 -2
View File
@@ -93,7 +93,7 @@ func TestDecode(t *testing.T) {
}
}
func TestDecodeProgram(t *testing.T) {
func TestDecodeInstructions(t *testing.T) {
for _, test := range []struct {
name string
program []linux.BPFInstruction
@@ -126,7 +126,7 @@ func TestDecodeProgram(t *testing.T) {
program: []linux.BPFInstruction{Stmt(Ld+Abs+W, 10), Stmt(Ld+Len+Mem, 0)},
fail: true},
} {
got, err := DecodeProgram(test.program)
got, err := DecodeInstructions(test.program)
if test.fail {
if err == nil {
t.Errorf("%s: Decode(...) failed, expected: 'error', got: %q", test.name, got)
+19 -4
View File
@@ -32,13 +32,21 @@ type ProgramBuilder struct {
// Maps label names to label objects.
labels map[string]*label
// unusableLabels are labels that are added before being referenced in a
// jump. Any labels added this way cannot be referenced later in order to
// avoid backwards references.
unusableLabels map[string]bool
// Array of BPF instructions that makes up the program.
instructions []linux.BPFInstruction
}
// NewProgramBuilder creates a new ProgramBuilder instance.
func NewProgramBuilder() *ProgramBuilder {
return &ProgramBuilder{labels: map[string]*label{}}
return &ProgramBuilder{
labels: map[string]*label{},
unusableLabels: map[string]bool{},
}
}
// label contains information to resolve a label to an offset.
@@ -108,9 +116,12 @@ func (b *ProgramBuilder) AddJumpLabels(code uint16, k uint32, jtLabel, jfLabel s
func (b *ProgramBuilder) AddLabel(name string) error {
l, ok := b.labels[name]
if !ok {
// This is done to catch jump backwards cases, but it's not strictly wrong
// to have unused labels.
return fmt.Errorf("Adding a label that hasn't been used is not allowed: %v", name)
if _, ok = b.unusableLabels[name]; ok {
return fmt.Errorf("label %q already set", name)
}
// Mark the label as unusable. This is done to catch backwards jumps.
b.unusableLabels[name] = true
return nil
}
if l.target != -1 {
return fmt.Errorf("label %q target already set: %v", name, l.target)
@@ -141,6 +152,10 @@ func (b *ProgramBuilder) addLabelSource(labelName string, t jmpType) {
func (b *ProgramBuilder) resolveLabels() error {
for key, v := range b.labels {
if _, ok := b.unusableLabels[key]; ok {
return fmt.Errorf("backwards reference detected for label: %q", key)
}
if v.target == -1 {
return fmt.Errorf("label target not set: %v", key)
}
+35 -7
View File
@@ -26,16 +26,16 @@ func validate(p *ProgramBuilder, expected []linux.BPFInstruction) error {
if err != nil {
return fmt.Errorf("Instructions() failed: %v", err)
}
got, err := DecodeProgram(instructions)
got, err := DecodeInstructions(instructions)
if err != nil {
return fmt.Errorf("DecodeProgram('instructions') failed: %v", err)
return fmt.Errorf("DecodeInstructions('instructions') failed: %v", err)
}
expectedDecoded, err := DecodeProgram(expected)
expectedDecoded, err := DecodeInstructions(expected)
if err != nil {
return fmt.Errorf("DecodeProgram('expected') failed: %v", err)
return fmt.Errorf("DecodeInstructions('expected') failed: %v", err)
}
if got != expectedDecoded {
return fmt.Errorf("DecodeProgram() failed, expected: %q, got: %q", expectedDecoded, got)
return fmt.Errorf("DecodeInstructions() failed, expected: %q, got: %q", expectedDecoded, got)
}
return nil
}
@@ -124,10 +124,38 @@ func TestProgramBuilderLabelWithNoInstruction(t *testing.T) {
}
}
// TestProgramBuilderUnusedLabel tests that adding an unused label doesn't
// cause program generation to fail.
func TestProgramBuilderUnusedLabel(t *testing.T) {
p := NewProgramBuilder()
if err := p.AddLabel("unused"); err == nil {
t.Errorf("AddLabel(unused) should have failed")
p.AddStmt(Ld+Abs+W, 10)
p.AddJump(Jmp+Ja, 10, 0, 0)
expected := []linux.BPFInstruction{
Stmt(Ld+Abs+W, 10),
Jump(Jmp+Ja, 10, 0, 0),
}
if err := p.AddLabel("unused"); err != nil {
t.Errorf("AddLabel(unused) should have succeeded")
}
if err := validate(p, expected); err != nil {
t.Errorf("Validate() failed: %v", err)
}
}
// TestProgramBuilderBackwardsReference tests that including a backwards
// reference to a label in a program causes a failure.
func TestProgramBuilderBackwardsReference(t *testing.T) {
p := NewProgramBuilder()
if err := p.AddLabel("bw_label"); err != nil {
t.Errorf("failed to add label")
}
p.AddStmt(Ld+Abs+W, 10)
p.AddJumpTrueLabel(Jmp+Jeq+K, 10, "bw_label", 0)
if _, err := p.Instructions(); err == nil {
t.Errorf("Instructions() should have failed")
}
}
+153 -26
View File
@@ -12,7 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
// Package seccomp provides basic seccomp filters for x86_64 (little endian).
// Package seccomp provides generation of basic seccomp filters. Currently,
// only little endian systems are supported.
package seccomp
import (
@@ -64,9 +65,9 @@ func Install(rules SyscallRules) error {
Rules: rules,
Action: linux.SECCOMP_RET_ALLOW,
},
}, defaultAction)
}, defaultAction, defaultAction)
if log.IsLogging(log.Debug) {
programStr, errDecode := bpf.DecodeProgram(instrs)
programStr, errDecode := bpf.DecodeInstructions(instrs)
if errDecode != nil {
programStr = fmt.Sprintf("Error: %v\n%s", errDecode, programStr)
}
@@ -117,7 +118,7 @@ var SyscallName = func(sysno uintptr) string {
// BuildProgram builds a BPF program from the given map of actions to matching
// SyscallRules. The single generated program covers all provided RuleSets.
func BuildProgram(rules []RuleSet, defaultAction linux.BPFAction) ([]linux.BPFInstruction, error) {
func BuildProgram(rules []RuleSet, defaultAction, badArchAction linux.BPFAction) ([]linux.BPFInstruction, error) {
program := bpf.NewProgramBuilder()
// Be paranoid and check that syscall is done in the expected architecture.
@@ -128,7 +129,7 @@ func BuildProgram(rules []RuleSet, defaultAction linux.BPFAction) ([]linux.BPFIn
// defaultLabel is at the bottom of the program. The size of program
// may exceeds 255 lines, which is the limit of a condition jump.
program.AddJump(bpf.Jmp|bpf.Jeq|bpf.K, LINUX_AUDIT_ARCH, skipOneInst, 0)
program.AddDirectJumpLabel(defaultLabel)
program.AddStmt(bpf.Ret|bpf.K, uint32(badArchAction))
if err := buildIndex(rules, program); err != nil {
return nil, err
}
@@ -144,6 +145,11 @@ func BuildProgram(rules []RuleSet, defaultAction linux.BPFAction) ([]linux.BPFIn
// buildIndex builds a BST to quickly search through all syscalls.
func buildIndex(rules []RuleSet, program *bpf.ProgramBuilder) error {
// Do nothing if rules is empty.
if len(rules) == 0 {
return nil
}
// Build a list of all application system calls, across all given rule
// sets. We have a simple BST, but may dispatch individual matchers
// with different actions. The matchers are evaluated linearly.
@@ -216,42 +222,163 @@ func addSyscallArgsCheck(p *bpf.ProgramBuilder, rules []Rule, action linux.BPFAc
labelled := false
for i, arg := range rule {
if arg != nil {
// Break out early if using MatchAny since no further
// instructions are required.
if _, ok := arg.(MatchAny); ok {
continue
}
// Determine the data offset for low and high bits of input.
dataOffsetLow := seccompDataOffsetArgLow(i)
dataOffsetHigh := seccompDataOffsetArgHigh(i)
if i == RuleIP {
dataOffsetLow = seccompDataOffsetIPLow
dataOffsetHigh = seccompDataOffsetIPHigh
}
// Add the conditional operation. Input values to the BPF
// program are 64bit values. However, comparisons in BPF can
// only be done on 32bit values. This means that we need to do
// multiple BPF comparisons in order to do one logical 64bit
// comparison.
switch a := arg.(type) {
case AllowAny:
case AllowValue:
dataOffsetLow := seccompDataOffsetArgLow(i)
dataOffsetHigh := seccompDataOffsetArgHigh(i)
if i == RuleIP {
dataOffsetLow = seccompDataOffsetIPLow
dataOffsetHigh = seccompDataOffsetIPHigh
}
case EqualTo:
// EqualTo checks that both the higher and lower 32bits are equal.
high, low := uint32(a>>32), uint32(a)
// assert arg_low == low
// Assert that the lower 32bits are equal.
// arg_low == low ? continue : violation
p.AddStmt(bpf.Ld|bpf.Abs|bpf.W, dataOffsetLow)
p.AddJumpFalseLabel(bpf.Jmp|bpf.Jeq|bpf.K, low, 0, ruleViolationLabel(ruleSetIdx, sysno, ruleidx))
// assert arg_high == high
// Assert that the lower 32bits are also equal.
// arg_high == high ? continue/success : violation
p.AddStmt(bpf.Ld|bpf.Abs|bpf.W, dataOffsetHigh)
p.AddJumpFalseLabel(bpf.Jmp|bpf.Jeq|bpf.K, high, 0, ruleViolationLabel(ruleSetIdx, sysno, ruleidx))
labelled = true
case GreaterThan:
dataOffsetLow := seccompDataOffsetArgLow(i)
dataOffsetHigh := seccompDataOffsetArgHigh(i)
if i == RuleIP {
dataOffsetLow = seccompDataOffsetIPLow
dataOffsetHigh = seccompDataOffsetIPHigh
}
labelGood := fmt.Sprintf("gt%v", i)
case NotEqual:
// NotEqual checks that either the higher or lower 32bits
// are *not* equal.
high, low := uint32(a>>32), uint32(a)
// assert arg_high < high
labelGood := fmt.Sprintf("ne%v", i)
// Check if the higher 32bits are (not) equal.
// arg_low == low ? continue : success
p.AddStmt(bpf.Ld|bpf.Abs|bpf.W, dataOffsetLow)
p.AddJumpFalseLabel(bpf.Jmp|bpf.Jeq|bpf.K, low, 0, ruleLabel(ruleSetIdx, sysno, ruleidx, labelGood))
// Assert that the lower 32bits are not equal (assuming
// higher bits are equal).
// arg_high == high ? violation : continue/success
p.AddStmt(bpf.Ld|bpf.Abs|bpf.W, dataOffsetHigh)
p.AddJumpTrueLabel(bpf.Jmp|bpf.Jeq|bpf.K, high, ruleViolationLabel(ruleSetIdx, sysno, ruleidx), 0)
p.AddLabel(ruleLabel(ruleSetIdx, sysno, ruleidx, labelGood))
labelled = true
case GreaterThan:
// GreaterThan checks that the higher 32bits is greater
// *or* that the higher 32bits are equal and the lower
// 32bits are greater.
high, low := uint32(a>>32), uint32(a)
labelGood := fmt.Sprintf("gt%v", i)
// Assert the higher 32bits are greater than or equal.
// arg_high >= high ? continue : violation (arg_high < high)
p.AddStmt(bpf.Ld|bpf.Abs|bpf.W, dataOffsetHigh)
p.AddJumpFalseLabel(bpf.Jmp|bpf.Jge|bpf.K, high, 0, ruleViolationLabel(ruleSetIdx, sysno, ruleidx))
// arg_high > high
// Assert that the lower 32bits are greater.
// arg_high == high ? continue : success (arg_high > high)
p.AddJumpFalseLabel(bpf.Jmp|bpf.Jeq|bpf.K, high, 0, ruleLabel(ruleSetIdx, sysno, ruleidx, labelGood))
// arg_low < low
// arg_low > low ? continue/success : violation (arg_high == high and arg_low <= low)
p.AddStmt(bpf.Ld|bpf.Abs|bpf.W, dataOffsetLow)
p.AddJumpFalseLabel(bpf.Jmp|bpf.Jgt|bpf.K, low, 0, ruleViolationLabel(ruleSetIdx, sysno, ruleidx))
p.AddLabel(ruleLabel(ruleSetIdx, sysno, ruleidx, labelGood))
labelled = true
case GreaterThanOrEqual:
// GreaterThanOrEqual checks that the higher 32bits is
// greater *or* that the higher 32bits are equal and the
// lower 32bits are greater than or equal.
high, low := uint32(a>>32), uint32(a)
labelGood := fmt.Sprintf("ge%v", i)
// Assert the higher 32bits are greater than or equal.
// arg_high >= high ? continue : violation (arg_high < high)
p.AddStmt(bpf.Ld|bpf.Abs|bpf.W, dataOffsetHigh)
p.AddJumpFalseLabel(bpf.Jmp|bpf.Jge|bpf.K, high, 0, ruleViolationLabel(ruleSetIdx, sysno, ruleidx))
// arg_high == high ? continue : success (arg_high > high)
p.AddJumpFalseLabel(bpf.Jmp|bpf.Jeq|bpf.K, high, 0, ruleLabel(ruleSetIdx, sysno, ruleidx, labelGood))
// Assert that the lower 32bits are greater (assuming the
// higher bits are equal).
// arg_low >= low ? continue/success : violation (arg_high == high and arg_low < low)
p.AddStmt(bpf.Ld|bpf.Abs|bpf.W, dataOffsetLow)
p.AddJumpFalseLabel(bpf.Jmp|bpf.Jge|bpf.K, low, 0, ruleViolationLabel(ruleSetIdx, sysno, ruleidx))
p.AddLabel(ruleLabel(ruleSetIdx, sysno, ruleidx, labelGood))
labelled = true
case LessThan:
// LessThan checks that the higher 32bits is less *or* that
// the higher 32bits are equal and the lower 32bits are
// less.
high, low := uint32(a>>32), uint32(a)
labelGood := fmt.Sprintf("lt%v", i)
// Assert the higher 32bits are less than or equal.
// arg_high > high ? violation : continue
p.AddStmt(bpf.Ld|bpf.Abs|bpf.W, dataOffsetHigh)
p.AddJumpTrueLabel(bpf.Jmp|bpf.Jgt|bpf.K, high, ruleViolationLabel(ruleSetIdx, sysno, ruleidx), 0)
// arg_high == high ? continue : success (arg_high < high)
p.AddJumpFalseLabel(bpf.Jmp|bpf.Jeq|bpf.K, high, 0, ruleLabel(ruleSetIdx, sysno, ruleidx, labelGood))
// Assert that the lower 32bits are less (assuming the
// higher bits are equal).
// arg_low >= low ? violation : continue
p.AddStmt(bpf.Ld|bpf.Abs|bpf.W, dataOffsetLow)
p.AddJumpTrueLabel(bpf.Jmp|bpf.Jge|bpf.K, low, ruleViolationLabel(ruleSetIdx, sysno, ruleidx), 0)
p.AddLabel(ruleLabel(ruleSetIdx, sysno, ruleidx, labelGood))
labelled = true
case LessThanOrEqual:
// LessThan checks that the higher 32bits is less *or* that
// the higher 32bits are equal and the lower 32bits are
// less than or equal.
high, low := uint32(a>>32), uint32(a)
labelGood := fmt.Sprintf("le%v", i)
// Assert the higher 32bits are less than or equal.
// assert arg_high > high ? violation : continue
p.AddStmt(bpf.Ld|bpf.Abs|bpf.W, dataOffsetHigh)
p.AddJumpTrueLabel(bpf.Jmp|bpf.Jgt|bpf.K, high, ruleViolationLabel(ruleSetIdx, sysno, ruleidx), 0)
// arg_high == high ? continue : success
p.AddJumpFalseLabel(bpf.Jmp|bpf.Jeq|bpf.K, high, 0, ruleLabel(ruleSetIdx, sysno, ruleidx, labelGood))
// Assert the lower bits are less than or equal (assuming
// the higher bits are equal).
// arg_low > low ? violation : success
p.AddStmt(bpf.Ld|bpf.Abs|bpf.W, dataOffsetLow)
p.AddJumpTrueLabel(bpf.Jmp|bpf.Jgt|bpf.K, low, ruleViolationLabel(ruleSetIdx, sysno, ruleidx), 0)
p.AddLabel(ruleLabel(ruleSetIdx, sysno, ruleidx, labelGood))
labelled = true
case maskedEqual:
// MaskedEqual checks that the bitwise AND of the value and
// mask are equal for both the higher and lower 32bits.
high, low := uint32(a.value>>32), uint32(a.value)
maskHigh, maskLow := uint32(a.mask>>32), uint32(a.mask)
// Assert that the lower 32bits are equal when masked.
// A <- arg_low.
p.AddStmt(bpf.Ld|bpf.Abs|bpf.W, dataOffsetLow)
// A <- arg_low & maskLow
p.AddStmt(bpf.Alu|bpf.And|bpf.K, maskLow)
// Assert that arg_low & maskLow == low.
p.AddJumpFalseLabel(bpf.Jmp|bpf.Jeq|bpf.K, low, 0, ruleViolationLabel(ruleSetIdx, sysno, ruleidx))
// Assert that the higher 32bits are equal when masked.
// A <- arg_high
p.AddStmt(bpf.Ld|bpf.Abs|bpf.W, dataOffsetHigh)
// A <- arg_high & maskHigh
p.AddStmt(bpf.Alu|bpf.And|bpf.K, maskHigh)
// Assert that arg_high & maskHigh == high.
p.AddJumpFalseLabel(bpf.Jmp|bpf.Jeq|bpf.K, high, 0, ruleViolationLabel(ruleSetIdx, sysno, ruleidx))
labelled = true
default:
return fmt.Errorf("unknown syscall rule type: %v", reflect.TypeOf(a))
}
+63 -12
View File
@@ -39,28 +39,79 @@ func seccompDataOffsetArgHigh(i int) uint32 {
return seccompDataOffsetArgLow(i) + 4
}
// AllowAny is marker to indicate any value will be accepted.
type AllowAny struct{}
// MatchAny is marker to indicate any value will be accepted.
type MatchAny struct{}
func (a AllowAny) String() (s string) {
func (a MatchAny) String() (s string) {
return "*"
}
// AllowValue specifies a value that needs to be strictly matched.
type AllowValue uintptr
// EqualTo specifies a value that needs to be strictly matched.
type EqualTo uintptr
func (a EqualTo) String() (s string) {
return fmt.Sprintf("== %#x", uintptr(a))
}
// NotEqual specifies a value that is strictly not equal.
type NotEqual uintptr
func (a NotEqual) String() (s string) {
return fmt.Sprintf("!= %#x", uintptr(a))
}
// GreaterThan specifies a value that needs to be strictly smaller.
type GreaterThan uintptr
func (a AllowValue) String() (s string) {
return fmt.Sprintf("%#x ", uintptr(a))
func (a GreaterThan) String() (s string) {
return fmt.Sprintf("> %#x", uintptr(a))
}
// GreaterThanOrEqual specifies a value that needs to be smaller or equal.
type GreaterThanOrEqual uintptr
func (a GreaterThanOrEqual) String() (s string) {
return fmt.Sprintf(">= %#x", uintptr(a))
}
// LessThan specifies a value that needs to be strictly greater.
type LessThan uintptr
func (a LessThan) String() (s string) {
return fmt.Sprintf("< %#x", uintptr(a))
}
// LessThanOrEqual specifies a value that needs to be greater or equal.
type LessThanOrEqual uintptr
func (a LessThanOrEqual) String() (s string) {
return fmt.Sprintf("<= %#x", uintptr(a))
}
type maskedEqual struct {
mask uintptr
value uintptr
}
func (a maskedEqual) String() (s string) {
return fmt.Sprintf("& %#x == %#x", a.mask, a.value)
}
// MaskedEqual specifies a value that matches the input after the input is
// masked (bitwise &) against the given mask. Can be used to verify that input
// only includes certain approved flags.
func MaskedEqual(mask, value uintptr) interface{} {
return maskedEqual{
mask: mask,
value: value,
}
}
// Rule stores the allowed syscall arguments.
//
// For example:
// rule := Rule {
// AllowValue(linux.ARCH_GET_FS | linux.ARCH_SET_FS), // arg0
// EqualTo(linux.ARCH_GET_FS | linux.ARCH_SET_FS), // arg0
// }
type Rule [7]interface{} // 6 arguments + RIP
@@ -89,12 +140,12 @@ func (r Rule) String() (s string) {
// rules := SyscallRules{
// syscall.SYS_FUTEX: []Rule{
// {
// AllowAny{},
// AllowValue(linux.FUTEX_WAIT | linux.FUTEX_PRIVATE_FLAG),
// MatchAny{},
// EqualTo(linux.FUTEX_WAIT | linux.FUTEX_PRIVATE_FLAG),
// }, // OR
// {
// AllowAny{},
// AllowValue(linux.FUTEX_WAKE | linux.FUTEX_PRIVATE_FLAG),
// MatchAny{},
// EqualTo(linux.FUTEX_WAKE | linux.FUTEX_PRIVATE_FLAG),
// },
// },
// syscall.SYS_GETPID: []Rule{},
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -100,7 +100,7 @@ func main() {
if !die {
syscalls[syscall.SYS_OPENAT] = []seccomp.Rule{
{
seccomp.AllowValue(10),
seccomp.EqualTo(10),
},
}
}
+10
View File
@@ -348,6 +348,16 @@ func (s *SyscallTable) LookupName(sysno uintptr) string {
return fmt.Sprintf("sys_%d", sysno) // Unlikely.
}
// LookupNo looks up a syscall number by name.
func (s *SyscallTable) LookupNo(name string) (uintptr, error) {
for i, syscall := range s.Table {
if syscall.Name == name {
return uintptr(i), nil
}
}
return 0, fmt.Errorf("syscall %q not found", name)
}
// LookupEmulate looks up an emulation syscall number.
func (s *SyscallTable) LookupEmulate(addr usermem.Addr) (uintptr, bool) {
sysno, ok := s.Emulate[addr]
@@ -201,7 +201,7 @@ func appendArchSeccompRules(rules []seccomp.RuleSet, defaultAction linux.BPFActi
seccomp.RuleSet{
Rules: seccomp.SyscallRules{
syscall.SYS_ARCH_PRCTL: []seccomp.Rule{
{seccomp.AllowValue(linux.ARCH_SET_CPUID), seccomp.AllowValue(0)},
{seccomp.EqualTo(linux.ARCH_SET_CPUID), seccomp.EqualTo(0)},
},
},
Action: linux.SECCOMP_RET_ALLOW,
@@ -80,9 +80,9 @@ func attachedThread(flags uintptr, defaultAction linux.BPFAction) (*thread, erro
Rules: seccomp.SyscallRules{
syscall.SYS_CLONE: []seccomp.Rule{
// Allow creation of new subprocesses (used by the master).
{seccomp.AllowValue(syscall.CLONE_FILES | syscall.SIGKILL)},
{seccomp.EqualTo(syscall.CLONE_FILES | syscall.SIGKILL)},
// Allow creation of new threads within a single address space (used by addresss spaces).
{seccomp.AllowValue(
{seccomp.EqualTo(
syscall.CLONE_FILES |
syscall.CLONE_FS |
syscall.CLONE_SIGHAND |
@@ -97,14 +97,14 @@ func attachedThread(flags uintptr, defaultAction linux.BPFAction) (*thread, erro
// For the stub prctl dance (all).
syscall.SYS_PRCTL: []seccomp.Rule{
{seccomp.AllowValue(syscall.PR_SET_PDEATHSIG), seccomp.AllowValue(syscall.SIGKILL)},
{seccomp.EqualTo(syscall.PR_SET_PDEATHSIG), seccomp.EqualTo(syscall.SIGKILL)},
},
syscall.SYS_GETPPID: {},
// For the stub to stop itself (all).
syscall.SYS_GETPID: {},
syscall.SYS_KILL: []seccomp.Rule{
{seccomp.AllowAny{}, seccomp.AllowValue(syscall.SIGSTOP)},
{seccomp.MatchAny{}, seccomp.EqualTo(syscall.SIGSTOP)},
},
// Injected to support the address space operations.
@@ -115,7 +115,7 @@ func attachedThread(flags uintptr, defaultAction linux.BPFAction) (*thread, erro
})
}
rules = appendArchSeccompRules(rules, defaultAction)
instrs, err := seccomp.BuildProgram(rules, defaultAction)
instrs, err := seccomp.BuildProgram(rules, defaultAction, defaultAction)
if err != nil {
return nil, err
}
+2
View File
@@ -26,6 +26,7 @@ go_library(
deps = [
"//pkg/abi",
"//pkg/abi/linux",
"//pkg/bpf",
"//pkg/context",
"//pkg/control/server",
"//pkg/cpuid",
@@ -107,6 +108,7 @@ go_library(
"//runsc/boot/pprof",
"//runsc/config",
"//runsc/specutils",
"//runsc/specutils/seccomp",
"@com_github_golang_protobuf//proto:go_default_library",
"@com_github_opencontainers_runtime_spec//specs-go:go_default_library",
"@org_golang_x_sys//unix:go_default_library",
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -25,7 +25,7 @@ import (
func init() {
allowedSyscalls[syscall.SYS_ARCH_PRCTL] = append(allowedSyscalls[syscall.SYS_ARCH_PRCTL],
seccomp.Rule{seccomp.AllowValue(linux.ARCH_GET_FS)},
seccomp.Rule{seccomp.AllowValue(linux.ARCH_SET_FS)},
seccomp.Rule{seccomp.EqualTo(linux.ARCH_GET_FS)},
seccomp.Rule{seccomp.EqualTo(linux.ARCH_SET_FS)},
)
}
+3 -3
View File
@@ -25,9 +25,9 @@ func profileFilters() seccomp.SyscallRules {
return seccomp.SyscallRules{
syscall.SYS_OPENAT: []seccomp.Rule{
{
seccomp.AllowAny{},
seccomp.AllowAny{},
seccomp.AllowValue(syscall.O_RDONLY | syscall.O_LARGEFILE | syscall.O_CLOEXEC),
seccomp.MatchAny{},
seccomp.MatchAny{},
seccomp.EqualTo(syscall.O_RDONLY | syscall.O_LARGEFILE | syscall.O_CLOEXEC),
},
},
}
+29
View File
@@ -27,6 +27,7 @@ import (
specs "github.com/opencontainers/runtime-spec/specs-go"
"golang.org/x/sys/unix"
"gvisor.dev/gvisor/pkg/abi/linux"
"gvisor.dev/gvisor/pkg/bpf"
"gvisor.dev/gvisor/pkg/context"
"gvisor.dev/gvisor/pkg/cpuid"
"gvisor.dev/gvisor/pkg/fd"
@@ -70,6 +71,7 @@ import (
"gvisor.dev/gvisor/runsc/boot/pprof"
"gvisor.dev/gvisor/runsc/config"
"gvisor.dev/gvisor/runsc/specutils"
"gvisor.dev/gvisor/runsc/specutils/seccomp"
// Include supported socket providers.
"gvisor.dev/gvisor/pkg/sentry/socket/hostinet"
@@ -507,6 +509,7 @@ func createMemoryFile() (*pgalloc.MemoryFile, error) {
return mf, nil
}
// installSeccompFilters installs sandbox seccomp filters with the host.
func (l *Loader) installSeccompFilters() error {
if l.root.conf.DisableSeccomp {
filter.Report("syscall filter is DISABLED. Running in less secure mode.")
@@ -577,6 +580,7 @@ func (l *Loader) run() error {
if _, err := l.createContainerProcess(true, l.sandboxID, &l.root, ep); err != nil {
return err
}
}
ep.tg = l.k.GlobalInit()
@@ -764,6 +768,31 @@ func (l *Loader) createContainerProcess(root bool, cid string, info *containerIn
}
}
// Install seccomp filters with the new task if there are any.
if info.conf.OCISeccomp {
if info.spec.Linux != nil && info.spec.Linux.Seccomp != nil {
program, err := seccomp.BuildProgram(info.spec.Linux.Seccomp)
if err != nil {
return nil, fmt.Errorf("building seccomp program: %v", err)
}
if log.IsLogging(log.Debug) {
out, _ := bpf.DecodeProgram(program)
log.Debugf("Installing OCI seccomp filters\nProgram:\n%s", out)
}
task := tg.Leader()
// NOTE: It seems Flags are ignored by runc so we ignore them too.
if err := task.AppendSyscallFilter(program, true); err != nil {
return nil, fmt.Errorf("appending seccomp filters: %v", err)
}
}
} else {
if info.spec.Linux != nil && info.spec.Linux.Seccomp != nil {
log.Warningf("Seccomp spec is being ignored")
}
}
return tg, nil
}
+4
View File
@@ -157,8 +157,12 @@ type Config struct {
// Enables FUSE usage.
FUSE bool `flag:"fuse"`
// Allows overriding of flags in OCI annotations.
AllowFlagOverride bool `flag:"allow-flag-override"`
// Enables seccomp inside the sandbox.
OCISeccomp bool `flag:"oci-seccomp"`
// TestOnlyAllowRunAsCurrentUserWithoutChroot should only be used in
// tests. It allows runsc to start the sandbox process as the current
// user, and without chrooting the sandbox process. This can be
+1
View File
@@ -63,6 +63,7 @@ func RegisterFlags() {
flag.Bool("rootless", false, "it allows the sandbox to be started with a user that is not root. Sandbox and Gofer processes may run with same privileges as current user.")
flag.Var(leakModePtr(refs.NoLeakChecking), "ref-leak-mode", "sets reference leak check mode: disabled (default), log-names, log-traces.")
flag.Bool("cpu-num-from-quota", false, "set cpu number to cpu quota (least integer greater or equal to quota value, but not less than 2)")
flag.Bool("oci-seccomp", false, "Enables loading OCI seccomp filters inside the sandbox.")
// Flags that control sandbox runtime behavior: FS related.
flag.Var(fileAccessTypePtr(FileAccessExclusive), "file-access", "specifies which filesystem to use for the root mount: exclusive (default), shared. Volume mounts are always shared.")
+77 -77
View File
@@ -29,7 +29,7 @@ var allowedSyscalls = seccomp.SyscallRules{
syscall.SYS_CLOCK_GETTIME: {},
syscall.SYS_CLONE: []seccomp.Rule{
{
seccomp.AllowValue(
seccomp.EqualTo(
syscall.CLONE_VM |
syscall.CLONE_FS |
syscall.CLONE_FILES |
@@ -43,46 +43,46 @@ var allowedSyscalls = seccomp.SyscallRules{
syscall.SYS_EPOLL_CTL: {},
syscall.SYS_EPOLL_PWAIT: []seccomp.Rule{
{
seccomp.AllowAny{},
seccomp.AllowAny{},
seccomp.AllowAny{},
seccomp.AllowAny{},
seccomp.AllowValue(0),
seccomp.MatchAny{},
seccomp.MatchAny{},
seccomp.MatchAny{},
seccomp.MatchAny{},
seccomp.EqualTo(0),
},
},
syscall.SYS_EVENTFD2: []seccomp.Rule{
{
seccomp.AllowValue(0),
seccomp.AllowValue(0),
seccomp.EqualTo(0),
seccomp.EqualTo(0),
},
},
syscall.SYS_EXIT: {},
syscall.SYS_EXIT_GROUP: {},
syscall.SYS_FALLOCATE: []seccomp.Rule{
{
seccomp.AllowAny{},
seccomp.AllowValue(0),
seccomp.MatchAny{},
seccomp.EqualTo(0),
},
},
syscall.SYS_FCHMOD: {},
syscall.SYS_FCHOWNAT: {},
syscall.SYS_FCNTL: []seccomp.Rule{
{
seccomp.AllowAny{},
seccomp.AllowValue(syscall.F_GETFL),
seccomp.MatchAny{},
seccomp.EqualTo(syscall.F_GETFL),
},
{
seccomp.AllowAny{},
seccomp.AllowValue(syscall.F_SETFL),
seccomp.MatchAny{},
seccomp.EqualTo(syscall.F_SETFL),
},
{
seccomp.AllowAny{},
seccomp.AllowValue(syscall.F_GETFD),
seccomp.MatchAny{},
seccomp.EqualTo(syscall.F_GETFD),
},
// Used by flipcall.PacketWindowAllocator.Init().
{
seccomp.AllowAny{},
seccomp.AllowValue(unix.F_ADD_SEALS),
seccomp.MatchAny{},
seccomp.EqualTo(unix.F_ADD_SEALS),
},
},
syscall.SYS_FSTAT: {},
@@ -91,31 +91,31 @@ var allowedSyscalls = seccomp.SyscallRules{
syscall.SYS_FTRUNCATE: {},
syscall.SYS_FUTEX: {
seccomp.Rule{
seccomp.AllowAny{},
seccomp.AllowValue(linux.FUTEX_WAIT | linux.FUTEX_PRIVATE_FLAG),
seccomp.AllowAny{},
seccomp.AllowAny{},
seccomp.AllowValue(0),
seccomp.MatchAny{},
seccomp.EqualTo(linux.FUTEX_WAIT | linux.FUTEX_PRIVATE_FLAG),
seccomp.MatchAny{},
seccomp.MatchAny{},
seccomp.EqualTo(0),
},
seccomp.Rule{
seccomp.AllowAny{},
seccomp.AllowValue(linux.FUTEX_WAKE | linux.FUTEX_PRIVATE_FLAG),
seccomp.AllowAny{},
seccomp.AllowAny{},
seccomp.AllowValue(0),
seccomp.MatchAny{},
seccomp.EqualTo(linux.FUTEX_WAKE | linux.FUTEX_PRIVATE_FLAG),
seccomp.MatchAny{},
seccomp.MatchAny{},
seccomp.EqualTo(0),
},
// Non-private futex used for flipcall.
seccomp.Rule{
seccomp.AllowAny{},
seccomp.AllowValue(linux.FUTEX_WAIT),
seccomp.AllowAny{},
seccomp.AllowAny{},
seccomp.MatchAny{},
seccomp.EqualTo(linux.FUTEX_WAIT),
seccomp.MatchAny{},
seccomp.MatchAny{},
},
seccomp.Rule{
seccomp.AllowAny{},
seccomp.AllowValue(linux.FUTEX_WAKE),
seccomp.AllowAny{},
seccomp.AllowAny{},
seccomp.MatchAny{},
seccomp.EqualTo(linux.FUTEX_WAKE),
seccomp.MatchAny{},
seccomp.MatchAny{},
},
},
syscall.SYS_GETDENTS64: {},
@@ -137,28 +137,28 @@ var allowedSyscalls = seccomp.SyscallRules{
// TODO(b/148688965): Remove once this is gone from Go.
syscall.SYS_MLOCK: []seccomp.Rule{
{
seccomp.AllowAny{},
seccomp.AllowValue(4096),
seccomp.MatchAny{},
seccomp.EqualTo(4096),
},
},
syscall.SYS_MMAP: []seccomp.Rule{
{
seccomp.AllowAny{},
seccomp.AllowAny{},
seccomp.AllowAny{},
seccomp.AllowValue(syscall.MAP_SHARED),
seccomp.MatchAny{},
seccomp.MatchAny{},
seccomp.MatchAny{},
seccomp.EqualTo(syscall.MAP_SHARED),
},
{
seccomp.AllowAny{},
seccomp.AllowAny{},
seccomp.AllowAny{},
seccomp.AllowValue(syscall.MAP_PRIVATE | syscall.MAP_ANONYMOUS),
seccomp.MatchAny{},
seccomp.MatchAny{},
seccomp.MatchAny{},
seccomp.EqualTo(syscall.MAP_PRIVATE | syscall.MAP_ANONYMOUS),
},
{
seccomp.AllowAny{},
seccomp.AllowAny{},
seccomp.AllowAny{},
seccomp.AllowValue(syscall.MAP_PRIVATE | syscall.MAP_ANONYMOUS | syscall.MAP_FIXED),
seccomp.MatchAny{},
seccomp.MatchAny{},
seccomp.MatchAny{},
seccomp.EqualTo(syscall.MAP_PRIVATE | syscall.MAP_ANONYMOUS | syscall.MAP_FIXED),
},
},
syscall.SYS_MPROTECT: {},
@@ -172,14 +172,14 @@ var allowedSyscalls = seccomp.SyscallRules{
syscall.SYS_READLINKAT: {},
syscall.SYS_RECVMSG: []seccomp.Rule{
{
seccomp.AllowAny{},
seccomp.AllowAny{},
seccomp.AllowValue(syscall.MSG_DONTWAIT | syscall.MSG_TRUNC),
seccomp.MatchAny{},
seccomp.MatchAny{},
seccomp.EqualTo(syscall.MSG_DONTWAIT | syscall.MSG_TRUNC),
},
{
seccomp.AllowAny{},
seccomp.AllowAny{},
seccomp.AllowValue(syscall.MSG_DONTWAIT | syscall.MSG_TRUNC | syscall.MSG_PEEK),
seccomp.MatchAny{},
seccomp.MatchAny{},
seccomp.EqualTo(syscall.MSG_DONTWAIT | syscall.MSG_TRUNC | syscall.MSG_PEEK),
},
},
syscall.SYS_RENAMEAT: {},
@@ -190,33 +190,33 @@ var allowedSyscalls = seccomp.SyscallRules{
syscall.SYS_SENDMSG: []seccomp.Rule{
// Used by fdchannel.Endpoint.SendFD().
{
seccomp.AllowAny{},
seccomp.AllowAny{},
seccomp.AllowValue(0),
seccomp.MatchAny{},
seccomp.MatchAny{},
seccomp.EqualTo(0),
},
// Used by unet.SocketWriter.WriteVec().
{
seccomp.AllowAny{},
seccomp.AllowAny{},
seccomp.AllowValue(syscall.MSG_DONTWAIT | syscall.MSG_NOSIGNAL),
seccomp.MatchAny{},
seccomp.MatchAny{},
seccomp.EqualTo(syscall.MSG_DONTWAIT | syscall.MSG_NOSIGNAL),
},
},
syscall.SYS_SHUTDOWN: []seccomp.Rule{
{seccomp.AllowAny{}, seccomp.AllowValue(syscall.SHUT_RDWR)},
{seccomp.MatchAny{}, seccomp.EqualTo(syscall.SHUT_RDWR)},
},
syscall.SYS_SIGALTSTACK: {},
// Used by fdchannel.NewConnectedSockets().
syscall.SYS_SOCKETPAIR: {
{
seccomp.AllowValue(syscall.AF_UNIX),
seccomp.AllowValue(syscall.SOCK_SEQPACKET | syscall.SOCK_CLOEXEC),
seccomp.AllowValue(0),
seccomp.EqualTo(syscall.AF_UNIX),
seccomp.EqualTo(syscall.SOCK_SEQPACKET | syscall.SOCK_CLOEXEC),
seccomp.EqualTo(0),
},
},
syscall.SYS_SYMLINKAT: {},
syscall.SYS_TGKILL: []seccomp.Rule{
{
seccomp.AllowValue(uint64(os.Getpid())),
seccomp.EqualTo(uint64(os.Getpid())),
},
},
syscall.SYS_UNLINKAT: {},
@@ -227,24 +227,24 @@ var allowedSyscalls = seccomp.SyscallRules{
var udsSyscalls = seccomp.SyscallRules{
syscall.SYS_SOCKET: []seccomp.Rule{
{
seccomp.AllowValue(syscall.AF_UNIX),
seccomp.AllowValue(syscall.SOCK_STREAM),
seccomp.AllowValue(0),
seccomp.EqualTo(syscall.AF_UNIX),
seccomp.EqualTo(syscall.SOCK_STREAM),
seccomp.EqualTo(0),
},
{
seccomp.AllowValue(syscall.AF_UNIX),
seccomp.AllowValue(syscall.SOCK_DGRAM),
seccomp.AllowValue(0),
seccomp.EqualTo(syscall.AF_UNIX),
seccomp.EqualTo(syscall.SOCK_DGRAM),
seccomp.EqualTo(0),
},
{
seccomp.AllowValue(syscall.AF_UNIX),
seccomp.AllowValue(syscall.SOCK_SEQPACKET),
seccomp.AllowValue(0),
seccomp.EqualTo(syscall.AF_UNIX),
seccomp.EqualTo(syscall.SOCK_SEQPACKET),
seccomp.EqualTo(0),
},
},
syscall.SYS_CONNECT: []seccomp.Rule{
{
seccomp.AllowAny{},
seccomp.MatchAny{},
},
},
}

Some files were not shown because too many files have changed in this diff Show More