mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
seccomp: Don't treat variables that are optimized away as unused.
Prior to this change, the Sentry seccomp filters use the program's PID as filter to the `tgkill(2)` system call. However, some platforms expand this filter to allow any PID, which the optimizer detects and optimizes the initial filter away. So the PID ends up being treated as an unused variable. This change addresses this by detecting this situation and allowing unused variables in optimized bytecode, so long as they do show up in non-optimized bytecode. Also add some small helper functions for using 64-bit variables. PiperOrigin-RevId: 583222206
This commit is contained in:
committed by
gVisor bot
parent
7fd7e762f4
commit
2bc70b209b
@@ -54,8 +54,26 @@ type Program struct {
|
||||
// It is used when rendering seccomp-bpf program instructions.
|
||||
type Values map[string]uint32
|
||||
|
||||
const (
|
||||
uint64VarSuffixHigh = "_high32bits"
|
||||
uint64VarSuffixLow = "_low32bits"
|
||||
)
|
||||
|
||||
// SetUint64 sets the value of a 64-bit variable in `v`.
|
||||
// Under the hood, this is stored as two 32-bit variables.
|
||||
// Use `Values.GetUint64` to retrieve the 64-bit variable.
|
||||
func (v Values) SetUint64(varName string, value uint64) {
|
||||
v[varName+uint64VarSuffixHigh] = uint32(value >> 32)
|
||||
v[varName+uint64VarSuffixLow] = uint32(value)
|
||||
}
|
||||
|
||||
// GetUint64 retrieves the value of a 64-bit variable set using
|
||||
// `Values.SetUint64(varName)`.
|
||||
func (v Values) GetUint64(varName string) uint64 {
|
||||
return uint64(v[varName+"_high32bits"])<<32 | uint64(v[varName+"_low32bits"])
|
||||
}
|
||||
|
||||
// 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.
|
||||
@@ -168,6 +186,60 @@ func precompile(name string, values Values, fn func(Values) ProgramDesc) (Progra
|
||||
}
|
||||
}
|
||||
bytecode32 := instructionsToUint32Slice(insns)
|
||||
varOffsets := getVarOffsets(bytecode32, values)
|
||||
|
||||
// nonOptimizedOffsets stores the offsets at which each variable shows up
|
||||
// in the non-optimized version of the program. It is only computed when
|
||||
// a variable doesn't show up in the optimized version of the program.
|
||||
var nonOptimizedOffsets map[string][]int
|
||||
computeNonOptimizedOffsets := func() error {
|
||||
if nonOptimizedOffsets != nil {
|
||||
return nil
|
||||
}
|
||||
if !precompileOpts.SeccompOptions.Optimize {
|
||||
nonOptimizedOffsets = varOffsets
|
||||
return nil
|
||||
}
|
||||
nonOptimizedOpts := precompileOpts.SeccompOptions
|
||||
nonOptimizedOpts.Optimize = false
|
||||
nonOptInsns, _, err := seccomp.BuildProgram(precompileOpts.Rules, nonOptimizedOpts)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot build seccomp program with optimizations disabled: %w", err)
|
||||
}
|
||||
nonOptimizedOffsets = getVarOffsets(instructionsToUint32Slice(nonOptInsns), values)
|
||||
return nil
|
||||
}
|
||||
|
||||
for varName := range values {
|
||||
if len(varOffsets[varName]) == 0 {
|
||||
// If the variable doesn't show up in the optimized program but does
|
||||
// show up in the non-optimized program, then it is not unused.
|
||||
// It is being optimized away, e.g. as a result of being OR'd with a
|
||||
// `MatchAll` rule.
|
||||
// Only report an error if the variable shows up in neither optimized
|
||||
// nor non-optimized bytecode.
|
||||
if err := computeNonOptimizedOffsets(); err != nil {
|
||||
return Program{}, fmt.Errorf("cannot compute variable offsets for the non-optimized version of the program: %v", err)
|
||||
}
|
||||
if len(nonOptimizedOffsets[varName]) == 0 {
|
||||
return Program{}, fmt.Errorf("var %q does not show up in the BPF bytecode", varName)
|
||||
}
|
||||
// We set the offset slice for this variable to a nil slice, so that
|
||||
// it gets properly serialized (as opposed to omitted entirely) in the
|
||||
// generated Go code.
|
||||
varOffsets[varName] = nil
|
||||
}
|
||||
}
|
||||
return Program{
|
||||
Name: name,
|
||||
Bytecode32: bytecode32,
|
||||
VarOffsets: varOffsets,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// getVarOffsets returns the uint32-based offsets at which the values of each
|
||||
// variable in `values` shows up.
|
||||
func getVarOffsets(bytecode32 []uint32, values Values) map[string][]int {
|
||||
varOffsets := make(map[string][]int, len(values))
|
||||
for varName, value := range values {
|
||||
for i, v := range bytecode32 {
|
||||
@@ -176,16 +248,7 @@ func precompile(name string, values Values, fn func(Values) ProgramDesc) (Progra
|
||||
}
|
||||
}
|
||||
}
|
||||
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
|
||||
return varOffsets
|
||||
}
|
||||
|
||||
// Expr renders a Go expression encoding this `Program`.
|
||||
@@ -208,6 +271,10 @@ func (program Program) Expr(indentPrefix, pkgName string) string {
|
||||
}
|
||||
sort.Strings(varNames)
|
||||
for _, varName := range varNames {
|
||||
if len(program.VarOffsets[varName]) == 0 {
|
||||
sb.WriteString(fmt.Sprintf("%s\t\t%q: nil,\n", indentPrefix, varName))
|
||||
continue
|
||||
}
|
||||
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))
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
package precompiledseccomp
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"testing"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
@@ -115,6 +117,45 @@ func TestPrecompile(t *testing.T) {
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "variable that can be optimized away",
|
||||
vars: []string{"var1"},
|
||||
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.MatchAll{},
|
||||
},
|
||||
),
|
||||
Action: linux.SECCOMP_RET_ALLOW,
|
||||
}},
|
||||
SeccompOptions: seccomp.DefaultProgramOptions(),
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "64-bit variable",
|
||||
vars: []string{"var1" + uint64VarSuffixHigh, "var1" + uint64VarSuffixLow},
|
||||
fn: func(values Values) ProgramDesc {
|
||||
return ProgramDesc{
|
||||
Rules: []seccomp.RuleSet{{
|
||||
Rules: seccomp.NewSyscallRules().Add(
|
||||
unix.SYS_READ,
|
||||
seccomp.PerArg{
|
||||
seccomp.EqualTo(values.GetUint64("var1")),
|
||||
},
|
||||
),
|
||||
Action: linux.SECCOMP_RET_ALLOW,
|
||||
}},
|
||||
SeccompOptions: seccomp.DefaultProgramOptions(),
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "inconsistent offsets",
|
||||
vars: []string{"var1"},
|
||||
@@ -201,3 +242,20 @@ func TestPrecompile(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestUint64Var(t *testing.T) {
|
||||
vars := Values{}
|
||||
for _, v := range []uint64{
|
||||
0, 1,
|
||||
math.MaxInt,
|
||||
math.MaxInt16,
|
||||
math.MaxInt32,
|
||||
math.MaxInt64,
|
||||
math.MaxUint64,
|
||||
} {
|
||||
vars.SetUint64(fmt.Sprintf("var%d", v), v)
|
||||
if vars.GetUint64(fmt.Sprintf("var%d", v)) != v {
|
||||
t.Errorf("GetUint64(%q) = %d, want %d", fmt.Sprintf("var%d", v), v, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user