mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
Add tooling to compile seccomp-bpf programs at bazel build time.
This adds a `precompiledseccomp` library which provides tooling to compile `seccomp-bpf` programs and generate Go source code that contains the resulting bytecode embedded into it. In turn, this bytecode can be used in Go libraries. This avoids spending time compiling and optimizing `seccomp-bpf` programs at runsc container creation time. This library also contains support for "variables", which are `uint32`s whose values are part of the seccomp filters but only known at runtime. To support this, the program is compiled twice with placeholder values for these variables, and we verify that the offsets at which these values show up in the bytecode is consistent across these two compilation attempts. PiperOrigin-RevId: 583117683
This commit is contained in:
committed by
gVisor bot
parent
e60464fdfa
commit
0a3bced479
@@ -19,10 +19,17 @@ import (
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// sizeOfInstruction is the size of a BPF instruction struct.
|
||||
const sizeOfInstruction = int(unsafe.Sizeof(Instruction{}))
|
||||
|
||||
// ToBytecode converts BPF instructions into raw BPF bytecode.
|
||||
func ToBytecode(insns []Instruction) []byte {
|
||||
return ([]byte)(unsafe.Slice((*byte)(unsafe.Pointer(&insns[0])), len(insns)*sizeOfInstruction))
|
||||
}
|
||||
|
||||
// ParseBytecode converts raw BPF bytecode into BPF instructions.
|
||||
// It verifies that the resulting set of instructions is a valid program.
|
||||
func ParseBytecode(bytecode []byte) ([]Instruction, error) {
|
||||
sizeOfInstruction := int(unsafe.Sizeof(Instruction{}))
|
||||
if len(bytecode)%sizeOfInstruction != 0 {
|
||||
return nil, fmt.Errorf("bytecode size (%d bytes) is not a multiple of BPF instruction size of %d bytes", len(bytecode), sizeOfInstruction)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
load("//tools:defs.bzl", "bzl_library", "go_library", "go_test")
|
||||
|
||||
package(
|
||||
default_applicable_licenses = ["//:license"],
|
||||
licenses = ["notice"],
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "precompiledseccomp",
|
||||
srcs = ["precompiledseccomp.go"],
|
||||
visibility = ["//:sandbox"],
|
||||
deps = [
|
||||
"//pkg/bpf",
|
||||
"//pkg/log",
|
||||
"//pkg/seccomp",
|
||||
],
|
||||
)
|
||||
|
||||
# @unused
|
||||
glaze_ignore = [
|
||||
"precompiled_lib.tmpl.go",
|
||||
"precompile_gen.go",
|
||||
]
|
||||
|
||||
exports_files(
|
||||
srcs = [
|
||||
"precompile_gen.go",
|
||||
"precompiled_lib.tmpl.go",
|
||||
],
|
||||
visibility = [
|
||||
"//:sandbox",
|
||||
],
|
||||
)
|
||||
|
||||
bzl_library(
|
||||
name = "defs_bzl",
|
||||
srcs = ["defs.bzl"],
|
||||
visibility = [
|
||||
"//:sandbox",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "precompiledseccomp_test",
|
||||
srcs = ["precompiledseccomp_test.go"],
|
||||
library = ":precompiledseccomp",
|
||||
deps = [
|
||||
"//pkg/abi/linux",
|
||||
"//pkg/seccomp",
|
||||
"@org_golang_x_sys//unix:go_default_library",
|
||||
],
|
||||
)
|
||||
@@ -0,0 +1,62 @@
|
||||
# precompiledseccomp
|
||||
|
||||
This package provides build tooling to embed precompiled seccomp-bpf programs
|
||||
inside Go binaries. Within gVisor, this is useful to keep startup time fast.
|
||||
|
||||
It also features basic support for runtime-modifiable `uint32` variables within
|
||||
embedded programs. This allows having values that are only known at runtime
|
||||
(e.g. FD numbers) to remain usable within seccomp filtering rules.
|
||||
|
||||
## Usage
|
||||
|
||||
You will need two Go libraries: One where you'll list the seccomp-bpf programs
|
||||
that you want embedded, and one where those precompiled programs will be
|
||||
embedded. This allows you to define the list of seccomp-bpf programs
|
||||
programmatically.
|
||||
|
||||
### 1: Define a Go library returning a set of programs to precompile
|
||||
|
||||
You need to define a `go_library` target which declares a package-level
|
||||
function:
|
||||
|
||||
```go
|
||||
func PrecompiledPrograms() ([]precompiledseccomp.Program, error)
|
||||
```
|
||||
|
||||
Look at [example.go](example/example.go) for a documented example.
|
||||
|
||||
### 2: Call `precompiled_seccomp_rules`
|
||||
|
||||
The [`precompiled_seccomp_rules`](defs.bzl) BUILD macro will auto-generate a
|
||||
`.go` file which contains the precompiled seccomp-bpf binary that your first Go
|
||||
library specifies.
|
||||
|
||||
Look at [example/usage/BUILD](example/usage/BUILD) for an example.
|
||||
|
||||
### 3: Use the generated library to access embedded programs
|
||||
|
||||
Use the auto-generated `.go` file from step 2 in the second `go_library`. A new
|
||||
package-level function will be defined:
|
||||
|
||||
```go
|
||||
func GetPrecompiled(programName string) (precompiledseccomp.Program, bool)
|
||||
```
|
||||
|
||||
You can call it to get the precompiled seccomp-bpf program.
|
||||
|
||||
See [example/usage/usage.go](example/usage/usage.go) for a documented example.
|
||||
|
||||
## How does it work?
|
||||
|
||||
See the [`precompiled_seccomp_rules`](defs.bzl) BUILD macro for the precise
|
||||
logic. At a high level, it generates a `go_binary` target that imports your
|
||||
first `go_library` (expressing your desired seccomp-bpf program). When this Go
|
||||
binary runs, it will compile the programs and output Go code that contains the
|
||||
compiled programs. Lastly, we use a `genrule` to execute this generated program
|
||||
and direct its output to a file of your choosing, which you can now embed in
|
||||
your second `go_library`.
|
||||
|
||||
In order to support variables, the compilation step actually compiles the
|
||||
program twice, using different placeholder values for all variables. It looks at
|
||||
the places in the BPF bytecode where these values show up, and ensures that
|
||||
these offsets are consistent across both compilation attempts.
|
||||
@@ -0,0 +1,89 @@
|
||||
"""Macro for precompiling seccomp-bpf programs."""
|
||||
|
||||
load("//tools:defs.bzl", "go_binary")
|
||||
|
||||
def precompiled_seccomp_rules(
|
||||
name,
|
||||
programs_to_compile_go_library,
|
||||
programs_to_compile_go_import,
|
||||
out,
|
||||
out_package_name):
|
||||
"""Generates a Go source file containing precompiled seccomp-bpf programs.
|
||||
|
||||
Args:
|
||||
name: Name of the final genrule.
|
||||
programs_to_compile_go_library: go_library target which describes the
|
||||
set of seccomp-bpf programs that you wish to precompile. This must
|
||||
define the following package-level function:
|
||||
func PrecompiledPrograms() ([]precompiledseccomp.Program, error)
|
||||
programs_to_compile_go_import: Go-style import path to
|
||||
`programs_to_compile_go_library`.
|
||||
out: Name of the Go source file (with the precompiled seccomp-bpf
|
||||
programs embedded in it) to generate. You can add this file as
|
||||
source to a `go_library` rule. This will define a package-level
|
||||
function:
|
||||
GetPrecompiled(programName string) (precompiledseccomp.Program, bool)
|
||||
out_package_name: Go package name that `out` belongs to.
|
||||
"""
|
||||
|
||||
# This genrule copies precompiled_lib.tmpl.go to the directory of wherever
|
||||
# `precompiled_seccomp_rules` is called.
|
||||
# This allows the go:embed directive inside the `.gen.go` file below to
|
||||
# work without rewriting the full path.
|
||||
native.genrule(
|
||||
name = name + "_gen_lib",
|
||||
outs = [out + ".gen.lib.tmpl.go"],
|
||||
cmd = "cat < $(SRCS) > $@",
|
||||
srcs = [
|
||||
"//pkg/seccomp/precompiledseccomp:precompiled_lib.tmpl.go",
|
||||
],
|
||||
)
|
||||
|
||||
# This genrule generates the Go file of the binary that, when run,
|
||||
# precompiles rules and writes them to a designated file.
|
||||
native.genrule(
|
||||
name = name + "_gen",
|
||||
outs = [out + ".gen.go"],
|
||||
cmd = (
|
||||
" while IFS= read -r line; do" +
|
||||
" if echo \"$$line\" | grep -q 'REPLACED_IMPORT_THIS_IS_A_LOAD_BEARING_COMMENT'; then" +
|
||||
" echo -e \"\\\\trules \\\"" + programs_to_compile_go_import + "\\\"\";" +
|
||||
" elif echo \"$$line\" | grep -q 'PROGRAMS_FUNC_THIS_IS_A_LOAD_BEARING_COMMENT'; then" +
|
||||
" echo -e \"var loadProgramsFn = rules.PrecompiledPrograms\";" +
|
||||
" elif echo \"$$line\" | grep -q 'go:embed precompiled_lib.tmpl.go'; then" +
|
||||
" echo -e \"//go:embed " + out + ".gen.lib.tmpl.go\";" +
|
||||
" else" +
|
||||
" echo \"$$line\";" +
|
||||
" fi;" +
|
||||
" done" +
|
||||
" < $(SRCS)" +
|
||||
" > $@"
|
||||
),
|
||||
srcs = [
|
||||
"//pkg/seccomp/precompiledseccomp:precompile_gen.go",
|
||||
],
|
||||
)
|
||||
|
||||
# This defines the go_binary for the Go file we just generated.
|
||||
go_binary(
|
||||
name = name + "_gen_bin",
|
||||
srcs = [out + ".gen.go"],
|
||||
deps = [
|
||||
programs_to_compile_go_library,
|
||||
"//runsc/flag",
|
||||
],
|
||||
embedsrcs = [
|
||||
":" + out + ".gen.lib.tmpl.go",
|
||||
],
|
||||
)
|
||||
|
||||
# This genrule actually runs the go_binary we just declared, and writes
|
||||
# its output (containing the precompiled rules) to the desired `out` file.
|
||||
native.genrule(
|
||||
name = name,
|
||||
outs = [out],
|
||||
cmd = (
|
||||
"$(location :" + name + "_gen_bin) --package='" + out_package_name + "' --out=$@"
|
||||
),
|
||||
tools = [":" + name + "_gen_bin"],
|
||||
)
|
||||
@@ -0,0 +1,20 @@
|
||||
load("//tools:defs.bzl", "go_library")
|
||||
|
||||
package(
|
||||
default_applicable_licenses = ["//:license"],
|
||||
licenses = ["notice"],
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "example",
|
||||
srcs = ["example.go"],
|
||||
visibility = [
|
||||
"//pkg/seccomp/precompiledseccomp/example:__subpackages__",
|
||||
],
|
||||
deps = [
|
||||
"//pkg/abi/linux",
|
||||
"//pkg/seccomp",
|
||||
"//pkg/seccomp/precompiledseccomp",
|
||||
"@org_golang_x_sys//unix:go_default_library",
|
||||
],
|
||||
)
|
||||
@@ -0,0 +1,103 @@
|
||||
// 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 example defines two seccomp programs ("example_program1" and
|
||||
// "example_program2") to be embedded in the `usage` package in this
|
||||
// directory.
|
||||
package example
|
||||
|
||||
import (
|
||||
"golang.org/x/sys/unix"
|
||||
"gvisor.dev/gvisor/pkg/abi/linux"
|
||||
"gvisor.dev/gvisor/pkg/seccomp"
|
||||
"gvisor.dev/gvisor/pkg/seccomp/precompiledseccomp"
|
||||
)
|
||||
|
||||
// Variable names used in the precompiled programs.
|
||||
// In this example, we have two file descriptors, which fit in 32 bits.
|
||||
// If you need a 64-bit variable, simply declare two 32-bit variables and
|
||||
// concatenate them to a single 64-bit number in the function that
|
||||
// generates the `ProgramDesc`.
|
||||
const (
|
||||
FD1 = "fd1"
|
||||
FD2 = "fd2"
|
||||
)
|
||||
|
||||
// Name of the example programs.
|
||||
const (
|
||||
// Program1Name is the name of the first example program.
|
||||
// It allows reading from `FD1` and `FD2`, but writing only to `FD1`.
|
||||
Program1Name = "example_program1"
|
||||
|
||||
// Program2Name is the name of the second example program.
|
||||
// It allows reading from `FD1` and `FD2`, but writing only to `FD2`.
|
||||
Program2Name = "example_program2"
|
||||
)
|
||||
|
||||
// Program1 returns a program that allows reading from FDs `FD1` and `FD2`,
|
||||
// but writing only to FD `FD1`.
|
||||
func Program1(values precompiledseccomp.Values) precompiledseccomp.ProgramDesc {
|
||||
return precompiledseccomp.ProgramDesc{
|
||||
Rules: []seccomp.RuleSet{{
|
||||
Rules: seccomp.NewSyscallRules().Add(
|
||||
unix.SYS_READ,
|
||||
seccomp.Or{
|
||||
seccomp.PerArg{seccomp.EqualTo(values[FD1])},
|
||||
seccomp.PerArg{seccomp.EqualTo(values[FD2])},
|
||||
},
|
||||
).Add(
|
||||
unix.SYS_WRITE,
|
||||
seccomp.PerArg{seccomp.EqualTo(values[FD1])},
|
||||
),
|
||||
Action: linux.SECCOMP_RET_ALLOW,
|
||||
}},
|
||||
SeccompOptions: seccomp.DefaultProgramOptions(),
|
||||
}
|
||||
}
|
||||
|
||||
// Program2 returns a program that allows reading from FDs `FD1` and `FD2`,
|
||||
// but writing only to FD `FD2`.
|
||||
func Program2(values precompiledseccomp.Values) precompiledseccomp.ProgramDesc {
|
||||
return precompiledseccomp.ProgramDesc{
|
||||
Rules: []seccomp.RuleSet{{
|
||||
Rules: seccomp.NewSyscallRules().Add(
|
||||
unix.SYS_READ,
|
||||
seccomp.Or{
|
||||
seccomp.PerArg{seccomp.EqualTo(values[FD1])},
|
||||
seccomp.PerArg{seccomp.EqualTo(values[FD2])},
|
||||
},
|
||||
).Add(
|
||||
unix.SYS_WRITE,
|
||||
seccomp.PerArg{seccomp.EqualTo(values[FD2])},
|
||||
),
|
||||
Action: linux.SECCOMP_RET_ALLOW,
|
||||
}},
|
||||
SeccompOptions: seccomp.DefaultProgramOptions(),
|
||||
}
|
||||
}
|
||||
|
||||
// PrecompiledPrograms defines the seccomp-bpf programs to precompile.
|
||||
// This function is called by the generated `go_binary` rule.
|
||||
func PrecompiledPrograms() ([]precompiledseccomp.Program, error) {
|
||||
vars := []string{FD1, FD2}
|
||||
example1, err := precompiledseccomp.Precompile(Program1Name, vars, Program1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
example2, err := precompiledseccomp.Precompile(Program2Name, vars, Program2)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return []precompiledseccomp.Program{example1, example2}, nil
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
load("//pkg/seccomp/precompiledseccomp:defs.bzl", "precompiled_seccomp_rules")
|
||||
load("//tools:defs.bzl", "go_library", "go_test")
|
||||
|
||||
package(
|
||||
default_applicable_licenses = ["//:license"],
|
||||
licenses = ["notice"],
|
||||
)
|
||||
|
||||
precompiled_seccomp_rules(
|
||||
name = "example_precompiled",
|
||||
out = "usage_embedded.go",
|
||||
out_package_name = "usage",
|
||||
programs_to_compile_go_import = "gvisor.dev/gvisor/pkg/seccomp/precompiledseccomp/example",
|
||||
programs_to_compile_go_library = "//pkg/seccomp/precompiledseccomp/example",
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "usage",
|
||||
srcs = [
|
||||
"usage.go",
|
||||
"usage_embedded.go",
|
||||
],
|
||||
deps = [
|
||||
"//pkg/bpf",
|
||||
"//pkg/seccomp/precompiledseccomp",
|
||||
"//pkg/seccomp/precompiledseccomp/example",
|
||||
"//pkg/sync",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "usage_test",
|
||||
srcs = ["usage_test.go"],
|
||||
library = ":usage",
|
||||
deps = [
|
||||
"//pkg/bpf",
|
||||
"//pkg/seccomp",
|
||||
"//pkg/seccomp/precompiledseccomp",
|
||||
"//pkg/seccomp/precompiledseccomp/example",
|
||||
],
|
||||
)
|
||||
@@ -0,0 +1,56 @@
|
||||
// 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 usage shows how to use precompiled seccomp-bpf programs.
|
||||
package usage
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"gvisor.dev/gvisor/pkg/bpf"
|
||||
"gvisor.dev/gvisor/pkg/seccomp/precompiledseccomp"
|
||||
"gvisor.dev/gvisor/pkg/seccomp/precompiledseccomp/example"
|
||||
)
|
||||
|
||||
// LoadProgram1 loads the program1 program with the given FDs.
|
||||
func LoadProgram1(fd1, fd2 uint32) []bpf.Instruction {
|
||||
program, ok := GetPrecompiled(example.Program1Name)
|
||||
if !ok {
|
||||
panic(fmt.Sprintf("precompiled program %q not found", example.Program1Name))
|
||||
}
|
||||
insns, err := program.RenderInstructions(precompiledseccomp.Values{
|
||||
example.FD1: fd1,
|
||||
example.FD2: fd2,
|
||||
})
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("failed to render instructions of precompiled program %q: %v", example.Program1Name, err))
|
||||
}
|
||||
return insns
|
||||
}
|
||||
|
||||
// LoadProgram2 loads the program2 program with the given FDs.
|
||||
func LoadProgram2(fd1, fd2 uint32) []bpf.Instruction {
|
||||
program, ok := GetPrecompiled(example.Program2Name)
|
||||
if !ok {
|
||||
panic(fmt.Sprintf("precompiled program %q not found", example.Program2Name))
|
||||
}
|
||||
insns, err := program.RenderInstructions(precompiledseccomp.Values{
|
||||
example.FD1: fd1,
|
||||
example.FD2: fd2,
|
||||
})
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("failed to render instructions of precompiled program %q: %v", example.Program2Name, err))
|
||||
}
|
||||
return insns
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
// 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 usage
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"gvisor.dev/gvisor/pkg/bpf"
|
||||
"gvisor.dev/gvisor/pkg/seccomp"
|
||||
"gvisor.dev/gvisor/pkg/seccomp/precompiledseccomp"
|
||||
"gvisor.dev/gvisor/pkg/seccomp/precompiledseccomp/example"
|
||||
)
|
||||
|
||||
// comparePrograms verifies that the precompiled and freshly-compiled programs
|
||||
// match byte-for-byte. If not, it prints them side-by-side.
|
||||
func comparePrograms(t *testing.T, precompiled, freshlyCompiled []bpf.Instruction) {
|
||||
t.Helper()
|
||||
if !reflect.DeepEqual(precompiled, freshlyCompiled) {
|
||||
t.Error("Precompiled and freshly-compiled versions of the program do not match:")
|
||||
t.Errorf(" Offset | %-32s | %-32s", "Freshly-compiled", "Compiled")
|
||||
for i := 0; i < max(len(precompiled), len(freshlyCompiled)); i++ {
|
||||
switch {
|
||||
case i < len(precompiled) && i < len(freshlyCompiled):
|
||||
if reflect.DeepEqual(precompiled[i], freshlyCompiled[i]) {
|
||||
t.Errorf(" OK %04d | %-32s | %-32s", i, freshlyCompiled[i].String(), precompiled[i].String())
|
||||
} else {
|
||||
t.Errorf(" DIFF %04d | %-32s | %-32s", i, freshlyCompiled[i].String(), precompiled[i].String())
|
||||
}
|
||||
case i < len(precompiled):
|
||||
t.Errorf(" DIFF %04d | %-32s | %-32s", i, "(end)", precompiled[i].String())
|
||||
case i < len(freshlyCompiled):
|
||||
t.Errorf(" DIFF %04d | %-32s | %-32s", i, freshlyCompiled[i].String(), "(end)")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestProgram1 verifies that the precompiled version of the Program1 program
|
||||
// matches a freshly-compiled version byte-for-byte.
|
||||
func TestProgram1(t *testing.T) {
|
||||
fd1 := rand.Uint32()
|
||||
fd2 := fd1 + 1
|
||||
precompiled := LoadProgram1(fd1, fd2)
|
||||
prog := example.Program1(precompiledseccomp.Values{
|
||||
example.FD1: fd1,
|
||||
example.FD2: fd2,
|
||||
})
|
||||
freshlyCompiled, _, err := seccomp.BuildProgram(prog.Rules, prog.SeccompOptions)
|
||||
if err != nil {
|
||||
t.Fatalf("cannot freshly compile the program: %v", err)
|
||||
}
|
||||
comparePrograms(t, precompiled, freshlyCompiled)
|
||||
}
|
||||
|
||||
// TestProgram2 verifies that the precompiled version of the Program2 program
|
||||
// matches a freshly-compiled version byte-for-byte.
|
||||
func TestProgram2(t *testing.T) {
|
||||
fd1 := rand.Uint32()
|
||||
fd2 := fd1 + 1
|
||||
precompiled := LoadProgram2(fd1, fd2)
|
||||
prog := example.Program2(precompiledseccomp.Values{
|
||||
example.FD1: fd1,
|
||||
example.FD2: fd2,
|
||||
})
|
||||
freshlyCompiled, _, err := seccomp.BuildProgram(prog.Rules, prog.SeccompOptions)
|
||||
if err != nil {
|
||||
t.Fatalf("cannot freshly compile the program: %v", err)
|
||||
}
|
||||
comparePrograms(t, precompiled, freshlyCompiled)
|
||||
}
|
||||
|
||||
// TestNonExistentProgram verifies that invalid program names don't exist.
|
||||
func TestNonExistentProgram(t *testing.T) {
|
||||
const nonExistentProgram = "this program name does not exist"
|
||||
got, found := GetPrecompiled(nonExistentProgram)
|
||||
if found {
|
||||
t.Fatalf("unexpectedly found program named %q: %v", nonExistentProgram, got)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
// 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.
|
||||
|
||||
// precompile_gen generates a Go library that contains precompiled seccomp
|
||||
// programs.
|
||||
package main
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"fmt"
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"gvisor.dev/gvisor/runsc/flag"
|
||||
|
||||
// This import will be replaced by the one specified in the genrule.
|
||||
"gvisor.dev/gvisor/pkg/seccomp/precompiledseccomp/example" // REPLACED_IMPORT_THIS_IS_A_LOAD_BEARING_COMMENT
|
||||
)
|
||||
|
||||
//go:embed precompiled_lib.tmpl.go
|
||||
var precompiledLibTemplate []byte
|
||||
|
||||
// Constants referring to how things are named in precompiled_lib.tmpl.go.
|
||||
const (
|
||||
packageNameStandin = "precompiled"
|
||||
precompiledseccompPackageName = "precompiledseccomp"
|
||||
registrationComment = "PROGRAM_REGISTRATION_GOES_HERE_THIS_IS_A_LOAD_BEARING_COMMENT"
|
||||
programsMapVarName = "programs"
|
||||
)
|
||||
|
||||
// Flags.
|
||||
var (
|
||||
output = flag.String("out", "/dev/stdout", "output file")
|
||||
packageName = flag.String("package", "", "output package name")
|
||||
)
|
||||
|
||||
// loadProgramsFn loads seccomp programs to be precompiled.
|
||||
var loadProgramsFn = example.PrecompiledPrograms // PROGRAMS_FUNC_THIS_IS_A_LOAD_BEARING_COMMENT
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
// Get a sorted list of programs.
|
||||
programs, err := loadProgramsFn()
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Cannot get list of programs to precompile: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
programNames := make(map[string]struct{}, len(programs))
|
||||
for _, program := range programs {
|
||||
if _, alreadySeen := programNames[program.Name]; alreadySeen {
|
||||
fmt.Fprintf(os.Stderr, "duplicate program name %q", program.Name)
|
||||
os.Exit(1)
|
||||
}
|
||||
programNames[program.Name] = struct{}{}
|
||||
}
|
||||
sort.Slice(programs, func(i, j int) bool {
|
||||
return programs[i].Name < programs[j].Name
|
||||
})
|
||||
|
||||
// Open the output file.
|
||||
outFile, err := os.Create(*output)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Cannot open output file %q: %v\n", *output, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
defer outFile.Close()
|
||||
|
||||
// Write Go code to the output file.
|
||||
processedPackageComment := false
|
||||
packageStandinLine := fmt.Sprintf("package %s", packageNameStandin)
|
||||
packageCommentPrefix := fmt.Sprintf("// Package %s ", packageNameStandin)
|
||||
lines := strings.Split(string(precompiledLibTemplate), "\n")
|
||||
for i := 0; i < len(lines); i++ {
|
||||
line := lines[i]
|
||||
switch {
|
||||
case line == packageStandinLine:
|
||||
fmt.Fprintf(outFile, "package %s\n", *packageName)
|
||||
case !processedPackageComment && strings.HasPrefix(line, packageCommentPrefix):
|
||||
// Do not output package comment, as this would conflict with
|
||||
// other package comments from other files in the same package.
|
||||
// Skip over all the next lines until we get to the "package" line.
|
||||
for ; i+1 < len(lines) && !strings.HasPrefix(lines[i+1], "package "); i++ {
|
||||
}
|
||||
processedPackageComment = true
|
||||
case strings.Contains(line, registrationComment):
|
||||
var indent string
|
||||
for {
|
||||
var found bool
|
||||
if line, found = strings.CutPrefix(line, "\t"); !found {
|
||||
break
|
||||
}
|
||||
indent += "\t"
|
||||
}
|
||||
for _, program := range programs {
|
||||
fmt.Fprint(outFile, program.Registration(indent, precompiledseccompPackageName, programsMapVarName))
|
||||
}
|
||||
default:
|
||||
fmt.Fprintf(outFile, "%s\n", line)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
// 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 precompiled does not exist. This file is used in a go:embed
|
||||
// directive inside `precompile_gen.go`.
|
||||
package precompiled
|
||||
|
||||
import (
|
||||
"gvisor.dev/gvisor/pkg/seccomp/precompiledseccomp"
|
||||
"gvisor.dev/gvisor/pkg/sync"
|
||||
)
|
||||
|
||||
var (
|
||||
// precompiledPrograms holds registered programs.
|
||||
// It is populated in `registerPrograms`.
|
||||
precompiledPrograms map[string]precompiledseccomp.Program = nil
|
||||
|
||||
// registerPrecompiledProgramsOnce ensures that program registration
|
||||
// happens only once.
|
||||
registerPrecompiledProgramsOnce sync.Once
|
||||
)
|
||||
|
||||
// GetPrecompiled returns the precompiled program for the given name,
|
||||
// and whether that program name exists.
|
||||
func GetPrecompiled(programName string) (precompiledseccomp.Program, bool) {
|
||||
registerPrecompiledProgramsOnce.Do(registerPrograms)
|
||||
program, ok := precompiledPrograms[programName]
|
||||
return program, ok
|
||||
}
|
||||
|
||||
// registerPrograms registers available programs inside `precompiledPrograms`.
|
||||
func registerPrograms() {
|
||||
programs := make(map[string]precompiledseccomp.Program)
|
||||
// PROGRAM_REGISTRATION_GOES_HERE_THIS_IS_A_LOAD_BEARING_COMMENT
|
||||
precompiledPrograms = programs
|
||||
}
|
||||
@@ -0,0 +1,269 @@
|
||||
// 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 precompiledseccomp provides tooling to precompile seccomp-bpf
|
||||
// programs that can be embedded inside Go source code.
|
||||
package precompiledseccomp
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"gvisor.dev/gvisor/pkg/bpf"
|
||||
"gvisor.dev/gvisor/pkg/log"
|
||||
"gvisor.dev/gvisor/pkg/seccomp"
|
||||
)
|
||||
|
||||
// ProgramDesc describes a program to be compiled.
|
||||
type ProgramDesc struct {
|
||||
// Rules contains the seccomp-bpf rulesets to compile.
|
||||
Rules []seccomp.RuleSet
|
||||
|
||||
// SeccompOptions is the seccomp-bpf program options used in compilation.
|
||||
SeccompOptions seccomp.ProgramOptions
|
||||
}
|
||||
|
||||
// Program is a precompiled seccomp-bpf program.
|
||||
// To get actual BPF instructions, call the `RenderInstructions` function.
|
||||
type Program struct {
|
||||
// Name is the name of this program within a set of embedded programs.
|
||||
Name string
|
||||
|
||||
// Bytecode32 is the raw BPF bytecode represented as a sequence of uint32s.
|
||||
Bytecode32 []uint32
|
||||
|
||||
// VarOffsets maps variable names to the uint32-based offsets where these
|
||||
// variables show up in `Bytecode32`.
|
||||
VarOffsets map[string][]int
|
||||
}
|
||||
|
||||
// Values is an assignment of variables to uint32 values.
|
||||
// It is used when rendering seccomp-bpf program instructions.
|
||||
type Values map[string]uint32
|
||||
|
||||
// Precompile compiles a `ProgramDesc` with the given values.
|
||||
//
|
||||
// It supports the notion of "variables", which are named in `vars`.
|
||||
// Variables are uint32s which are only known at runtime, and whose value
|
||||
// shows up in the BPF bytecode.
|
||||
//
|
||||
// `fn` takes in a mapping of variable names to their assigned values,
|
||||
// and should return a `ProgramDesc` describing the seccomp-bpf program
|
||||
// to be compiled.
|
||||
//
|
||||
// Precompile verifies that all variables in `vars` show up consistently in
|
||||
// the bytecode by compiling the program twice, ensures that the offsets at
|
||||
// which some stand-in values is consistent across these two compilation
|
||||
// attempts, and that nothing else about the BPF bytecode is different.
|
||||
func Precompile(name string, varNames []string, fn func(Values) ProgramDesc) (Program, error) {
|
||||
vars := make(map[string]struct{}, len(varNames))
|
||||
for _, varName := range varNames {
|
||||
vars[varName] = struct{}{}
|
||||
}
|
||||
if len(vars) != len(varNames) {
|
||||
return Program{}, fmt.Errorf("non-unique variable names: %q", varNames)
|
||||
}
|
||||
|
||||
// These constants are chosen to be recognizable and unique within
|
||||
// seccomp-bpf programs.
|
||||
// These could of course show up in seccomp-bpf programs for legitimate
|
||||
// reasons other than being part the variable being matched against (e.g. a
|
||||
// jump of this many instructions forward, or a static equality match that
|
||||
// happens to check against this exact value), but it is very unlikely that
|
||||
// integers this large actually occur.
|
||||
// If it does happen, we'll catch it here because one compilation attempt
|
||||
// will find its placeholder values show up less often than the other.
|
||||
// Assuming that the reason this occurred is legitimate, update these
|
||||
// constants to even-less-likely values in order to fix this issue.
|
||||
const (
|
||||
varStart1 uint32 = 0x13371337
|
||||
varStart2 uint32 = 0x42424243
|
||||
)
|
||||
|
||||
// Render the program with one set of values.
|
||||
// Remember at which offsets we saw these values show up in the bytecode.
|
||||
values1 := Values(make(map[string]uint32, len(vars)))
|
||||
v := varStart1
|
||||
for varName := range vars {
|
||||
values1[varName] = v
|
||||
v += 2
|
||||
}
|
||||
program1, err := precompile(name, values1, fn)
|
||||
if err != nil {
|
||||
return Program{}, err
|
||||
}
|
||||
|
||||
// Do the same, but with a different set of values.
|
||||
values2 := Values(make(map[string]uint32, len(vars)))
|
||||
v = varStart2
|
||||
for _, varName := range varNames {
|
||||
values2[varName] = v
|
||||
v += 2
|
||||
}
|
||||
program2, err := precompile(name, values2, fn)
|
||||
if err != nil {
|
||||
return Program{}, err
|
||||
}
|
||||
|
||||
// Ensure that the offsets we got is consistent.
|
||||
for _, varName := range varNames {
|
||||
offsets1 := program1.VarOffsets[varName]
|
||||
offsets2 := program2.VarOffsets[varName]
|
||||
if len(offsets1) != len(offsets2) {
|
||||
return Program{}, fmt.Errorf("var %q has different number of offsets depending on its value: with value 0x%08x it showed up %d times, but with value %d it showed up %d times", varName, values1[varName], len(offsets1), values2[varName], len(offsets2))
|
||||
}
|
||||
for i := 0; i < len(offsets1); i++ {
|
||||
if offsets1[i] != offsets2[i] {
|
||||
return Program{}, fmt.Errorf("var %q has different offsets depending on its value: with value 0x%08x it showed up at offsets %v, but with value %d it showed up at offsets %v", varName, values1[varName], offsets1, values2[varName], offsets2)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure that the rest of the bytecode is exactly equal.
|
||||
if len(program1.Bytecode32) != len(program2.Bytecode32) {
|
||||
return Program{}, fmt.Errorf("compiled programs do not have the same bytecode size: %d vs %d", len(program1.Bytecode32), len(program2.Bytecode32))
|
||||
}
|
||||
knownOffsets := map[int]struct{}{}
|
||||
for _, varName := range varNames {
|
||||
for _, offset := range program1.VarOffsets[varName] {
|
||||
knownOffsets[offset] = struct{}{}
|
||||
}
|
||||
}
|
||||
for i := 0; i < len(program1.Bytecode32); i++ {
|
||||
if _, isVarOffset := knownOffsets[i]; isVarOffset {
|
||||
continue
|
||||
}
|
||||
if program1.Bytecode32[i] != program2.Bytecode32[i] {
|
||||
return Program{}, fmt.Errorf("compiled programs do not have the same bytecode at uint32 offset %d (which is not any of the offsets where a variable shows up: %v)", i, knownOffsets)
|
||||
}
|
||||
}
|
||||
|
||||
return program1, nil
|
||||
}
|
||||
|
||||
// precompile compiles a `ProgramDesc` with the given values.
|
||||
func precompile(name string, values Values, fn func(Values) ProgramDesc) (Program, error) {
|
||||
precompileOpts := fn(values)
|
||||
insns, _, err := seccomp.BuildProgram(precompileOpts.Rules, precompileOpts.SeccompOptions)
|
||||
if err != nil {
|
||||
return Program{}, err
|
||||
}
|
||||
if log.IsLogging(log.Debug) {
|
||||
log.Debugf("Compiled program with values %v (%d instructions):", values, len(insns))
|
||||
for i, insn := range insns {
|
||||
log.Debugf(" %04d: %s\n", i, insn.String())
|
||||
}
|
||||
}
|
||||
bytecode32 := instructionsToUint32Slice(insns)
|
||||
varOffsets := make(map[string][]int, len(values))
|
||||
for varName, value := range values {
|
||||
for i, v := range bytecode32 {
|
||||
if v == value {
|
||||
varOffsets[varName] = append(varOffsets[varName], i)
|
||||
}
|
||||
}
|
||||
}
|
||||
for varName := range values {
|
||||
if len(varOffsets[varName]) == 0 {
|
||||
return Program{}, fmt.Errorf("var %q does not show up in the BPF bytecode", varName)
|
||||
}
|
||||
}
|
||||
return Program{
|
||||
Name: name,
|
||||
Bytecode32: bytecode32,
|
||||
VarOffsets: varOffsets,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Expr renders a Go expression encoding this `Program`.
|
||||
// It is used when embedding a precompiled `Program` into a Go library file.
|
||||
// `pkgName` is the package name under which the precompiledseccomp package is
|
||||
// imported.
|
||||
func (program Program) Expr(indentPrefix, pkgName string) string {
|
||||
var sb strings.Builder
|
||||
sb.WriteString(fmt.Sprintf("%s.Program{\n", pkgName))
|
||||
sb.WriteString(fmt.Sprintf("%s\tName: %q,\n", indentPrefix, program.Name))
|
||||
sb.WriteString(fmt.Sprintf("%s\tBytecode32: []uint32{\n", indentPrefix))
|
||||
for _, v := range program.Bytecode32 {
|
||||
sb.WriteString(fmt.Sprintf("%s\t\t0x%08x,\n", indentPrefix, v))
|
||||
}
|
||||
sb.WriteString(fmt.Sprintf("%s\t},\n", indentPrefix))
|
||||
sb.WriteString(fmt.Sprintf("%s\tVarOffsets: map[string][]int{\n", indentPrefix))
|
||||
varNames := make([]string, 0, len(program.VarOffsets))
|
||||
for varName := range program.VarOffsets {
|
||||
varNames = append(varNames, varName)
|
||||
}
|
||||
sort.Strings(varNames)
|
||||
for _, varName := range varNames {
|
||||
sb.WriteString(fmt.Sprintf("%s\t\t%q: []int{\n", indentPrefix, varName))
|
||||
for _, v := range program.VarOffsets[varName] {
|
||||
sb.WriteString(fmt.Sprintf("%s\t\t\t%d,\n", indentPrefix, v))
|
||||
}
|
||||
sb.WriteString(fmt.Sprintf("%s\t\t},\n", indentPrefix))
|
||||
}
|
||||
sb.WriteString(fmt.Sprintf("%s\t},\n", indentPrefix))
|
||||
sb.WriteString(fmt.Sprintf("%s}", indentPrefix))
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
// RenderInstructions builds the set of precompiled BPF instructions,
|
||||
// replacing the variables with their values as given in `values`.
|
||||
// This must be called with the exact same set of variable names as was used
|
||||
// during `Precompile`.
|
||||
func (program Program) RenderInstructions(values Values) ([]bpf.Instruction, error) {
|
||||
if len(values) != len(program.VarOffsets) {
|
||||
return nil, fmt.Errorf("called with inconsistent vars: got %v expected %v", values, program.VarOffsets)
|
||||
}
|
||||
for varName, value := range values {
|
||||
offsets, found := program.VarOffsets[varName]
|
||||
if !found {
|
||||
return nil, fmt.Errorf("var %q was not defined in precompiled instructions (defined: %v)", varName, program.VarOffsets)
|
||||
}
|
||||
for _, offset := range offsets {
|
||||
program.Bytecode32[offset] = value
|
||||
}
|
||||
}
|
||||
return uint32SliceToInstructions(program.Bytecode32)
|
||||
}
|
||||
|
||||
// instructionsToUint32Slice converts a slice of BPF instructions into a slice
|
||||
// of uint32s containing the same binary data.
|
||||
func instructionsToUint32Slice(insns []bpf.Instruction) []uint32 {
|
||||
bytecode := bpf.ToBytecode(insns)
|
||||
bytecode32 := make([]uint32, len(bytecode)/4)
|
||||
for i := 0; i < len(bytecode); i += 4 {
|
||||
bytecode32[i/4] = binary.NativeEndian.Uint32(bytecode[i : i+4])
|
||||
}
|
||||
return bytecode32
|
||||
}
|
||||
|
||||
// uint32SliceToInstructions converts a slice of uint32s into a slice of
|
||||
// BPF instructions containing the same binary data.
|
||||
func uint32SliceToInstructions(bytecode32 []uint32) ([]bpf.Instruction, error) {
|
||||
bytecode := make([]byte, len(bytecode32)*4)
|
||||
for i, v := range bytecode32 {
|
||||
binary.NativeEndian.PutUint32(bytecode[i*4:], v)
|
||||
}
|
||||
return bpf.ParseBytecode(bytecode)
|
||||
}
|
||||
|
||||
// Registration outputs Go code that registers this programs in a
|
||||
// `map[string]Program` variable named `programsMapVarName` which maps
|
||||
// programs names to their `Program` struct.
|
||||
// It is used when embedding precompiled programs into a Go library file.
|
||||
func (program Program) Registration(indentPrefix, pkgName, programsMapVarName string) string {
|
||||
return fmt.Sprintf("%s%s[%q] = %s\n", indentPrefix, programsMapVarName, program.Name, program.Expr(indentPrefix, pkgName))
|
||||
}
|
||||
@@ -0,0 +1,203 @@
|
||||
// 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 precompiledseccomp
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
"gvisor.dev/gvisor/pkg/abi/linux"
|
||||
"gvisor.dev/gvisor/pkg/seccomp"
|
||||
)
|
||||
|
||||
// TestPrecompile verifies that precompilation works and verifies that variable
|
||||
// offsets are verified across compilation attempts.
|
||||
func TestPrecompile(t *testing.T) {
|
||||
// Used in some tests below that need statefulness in order to return
|
||||
// purposefully-inconsistent results across calls.
|
||||
counter := 0
|
||||
|
||||
for _, test := range []struct {
|
||||
name string
|
||||
vars []string
|
||||
fn func(Values) ProgramDesc
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "simple case",
|
||||
fn: func(Values) ProgramDesc {
|
||||
return ProgramDesc{
|
||||
Rules: []seccomp.RuleSet{{
|
||||
Rules: seccomp.NewSyscallRules().Add(
|
||||
unix.SYS_READ,
|
||||
seccomp.MatchAll{},
|
||||
),
|
||||
Action: linux.SECCOMP_RET_ALLOW,
|
||||
}},
|
||||
SeccompOptions: seccomp.DefaultProgramOptions(),
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "one variable",
|
||||
vars: []string{"var1"},
|
||||
fn: func(values Values) ProgramDesc {
|
||||
return ProgramDesc{
|
||||
Rules: []seccomp.RuleSet{{
|
||||
Rules: seccomp.NewSyscallRules().Add(
|
||||
unix.SYS_READ,
|
||||
seccomp.PerArg{
|
||||
seccomp.EqualTo(values["var1"]),
|
||||
},
|
||||
),
|
||||
Action: linux.SECCOMP_RET_ALLOW,
|
||||
}},
|
||||
SeccompOptions: seccomp.DefaultProgramOptions(),
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "duplicate variable name",
|
||||
vars: []string{"var1", "var1"},
|
||||
fn: func(values Values) ProgramDesc {
|
||||
return ProgramDesc{}
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "multiple variables showing up multiple times",
|
||||
vars: []string{"var1", "var2"},
|
||||
fn: func(values Values) ProgramDesc {
|
||||
return ProgramDesc{
|
||||
Rules: []seccomp.RuleSet{{
|
||||
Rules: seccomp.NewSyscallRules().Add(
|
||||
unix.SYS_READ,
|
||||
seccomp.Or{
|
||||
seccomp.PerArg{seccomp.EqualTo(values["var1"])},
|
||||
seccomp.PerArg{seccomp.EqualTo(values["var2"])},
|
||||
},
|
||||
).Add(
|
||||
unix.SYS_WRITE,
|
||||
seccomp.PerArg{seccomp.EqualTo(values["var1"])},
|
||||
),
|
||||
Action: linux.SECCOMP_RET_ALLOW,
|
||||
}},
|
||||
SeccompOptions: seccomp.DefaultProgramOptions(),
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "unused variable",
|
||||
vars: []string{"var1"},
|
||||
fn: func(values Values) ProgramDesc {
|
||||
return ProgramDesc{
|
||||
Rules: []seccomp.RuleSet{{
|
||||
Rules: seccomp.NewSyscallRules().Add(
|
||||
unix.SYS_READ,
|
||||
seccomp.MatchAll{},
|
||||
),
|
||||
Action: linux.SECCOMP_RET_ALLOW,
|
||||
}},
|
||||
SeccompOptions: seccomp.DefaultProgramOptions(),
|
||||
}
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "inconsistent offsets",
|
||||
vars: []string{"var1"},
|
||||
fn: func(values Values) ProgramDesc {
|
||||
var pa seccomp.PerArg
|
||||
if counter == 0 {
|
||||
pa[0] = seccomp.EqualTo(values["var1"])
|
||||
}
|
||||
if counter == 1 {
|
||||
pa[0] = seccomp.EqualTo(values["var1"])
|
||||
pa[1] = seccomp.EqualTo(values["var1"])
|
||||
}
|
||||
counter++
|
||||
return ProgramDesc{
|
||||
Rules: []seccomp.RuleSet{{
|
||||
Rules: seccomp.NewSyscallRules().Add(
|
||||
unix.SYS_READ,
|
||||
pa,
|
||||
),
|
||||
Action: linux.SECCOMP_RET_ALLOW,
|
||||
}},
|
||||
SeccompOptions: seccomp.DefaultProgramOptions(),
|
||||
}
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "inconsistent program size",
|
||||
vars: []string{"var1"},
|
||||
fn: func(values Values) ProgramDesc {
|
||||
pa := seccomp.PerArg{seccomp.EqualTo(values["var1"])}
|
||||
if counter == 1 {
|
||||
pa[1] = seccomp.EqualTo(123)
|
||||
}
|
||||
counter++
|
||||
return ProgramDesc{
|
||||
Rules: []seccomp.RuleSet{{
|
||||
Rules: seccomp.NewSyscallRules().Add(
|
||||
unix.SYS_READ,
|
||||
pa,
|
||||
),
|
||||
Action: linux.SECCOMP_RET_ALLOW,
|
||||
}},
|
||||
SeccompOptions: seccomp.DefaultProgramOptions(),
|
||||
}
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "inconsistent program bytecode",
|
||||
vars: []string{"var1"},
|
||||
fn: func(values Values) ProgramDesc {
|
||||
pa := seccomp.PerArg{seccomp.EqualTo(values["var1"])}
|
||||
if counter == 0 {
|
||||
pa[1] = seccomp.EqualTo(1337)
|
||||
}
|
||||
if counter == 1 {
|
||||
pa[1] = seccomp.EqualTo(42)
|
||||
}
|
||||
counter++
|
||||
return ProgramDesc{
|
||||
Rules: []seccomp.RuleSet{{
|
||||
Rules: seccomp.NewSyscallRules().Add(
|
||||
unix.SYS_READ,
|
||||
pa,
|
||||
),
|
||||
Action: linux.SECCOMP_RET_ALLOW,
|
||||
}},
|
||||
SeccompOptions: seccomp.DefaultProgramOptions(),
|
||||
}
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
} {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
counter = 0
|
||||
_, err := Precompile("", test.vars, test.fn)
|
||||
if err != nil && !test.wantErr {
|
||||
t.Fatalf("Precompile failed: %v", err)
|
||||
}
|
||||
if err == nil && test.wantErr {
|
||||
t.Fatal("Precompile succeeded but want error")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user