Skip to content

Commit

Permalink
restructure i386 syscalls code in syscall lists
Browse files Browse the repository at this point in the history
syscall lists are used so that we can have a common place to define:
syscall ids for the tracepoint hook filtering, and syscall function
symbols for the enforcer.

The previous implementation of i386 syscalls in these lists was using
the fact that (most) of the i386 syscalls are implemented using
functions prefixed by __ia32_sys. This, however, is not true for ARM
where most system calls have the same implementation for aarch32 and
aarch64 and their prefix is __arm64. This means that for arm, we cannot
determine the ABI of a system call from its function.

Morover, x86_64 has two 32-bit abis: i386 and x32, and we might,
eventually, want to add supoprt the latter.

This patch reworks the code of the syscall lists. In syscall lists,
users can now use the abi/ prefix (e.g., i386/) to specify system calls
under a specific ABI.

The commits adds a new SyscallVal type which represents values in a
system call list. For this type, we imlement the two required methods:
 - ID(): get the id of the system call to pass in BPF
 - Symbol(): get the symbol that corresponds to the implementation of
   the syscall

Signed-off-by: Kornilios Kourtis <[email protected]>
  • Loading branch information
kkourt committed Sep 10, 2024
1 parent 94022ca commit b751ed7
Show file tree
Hide file tree
Showing 10 changed files with 271 additions and 123 deletions.
8 changes: 4 additions & 4 deletions docs/content/en/docs/concepts/tracing-policy/hooks.md
Original file line number Diff line number Diff line change
Expand Up @@ -525,8 +525,8 @@ spec:

Syscalls specified with `sys_` prefix are translated to their 64 bit equivalent function names.

It's possible to specify 32 bit syscall by using its full function name that
includes specific architecture native prefix (like `__ia32_` for `x86`):
It's possible to specify a syscall for an alternative ABI by using the ABI name as a prefix.
For example:

```yaml
spec:
Expand All @@ -535,7 +535,7 @@ spec:
type: "syscalls"
values:
- "sys_dup"
- "__ia32_sys_dup"
- "i386/sys_dup"
name: "another"
- "sys_open"
- "sys_close"
Expand Down Expand Up @@ -665,7 +665,7 @@ spec:
type: "syscalls"
values:
- "sys_dup"
- "__ia32_sys_dup"
- "i386/sys_dup"
tracepoints:
- subsystem: "raw_syscalls"
event: "sys_enter"
Expand Down
28 changes: 19 additions & 9 deletions pkg/arch/arch.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,21 +58,31 @@ func AddSyscallPrefixTestHelper(t *testing.T, symbol string) string {
return syscallName
}

// CutSyscallPrefix removes a potential arch specific prefix from the symbol
// and returns true in second return argument if the prefix is 32 bits
func CutSyscallPrefix(symbol string) (string, bool) {
is32BitArch := func(arch string) bool {
return arch == "i386"
// CutSyscallPrefix removes a potential arch specific prefix from the symbol.
// If a prefix was removed, it retuns the corresponding arch as a first argument.
func CutSyscallPrefix(symbol string) (arch string, name string) {
for a, p := range supportedArchPrefix {
if rest, ok := strings.CutPrefix(symbol, p); ok {
arch = a
name = rest
return
}
}
for arch, prefix := range supportedArchPrefix {

name = symbol
return
}

func HasSyscallPrefix(symbol string) bool {
for _, prefix := range supportedArchPrefix {
if strings.HasPrefix(symbol, prefix) {
return symbol[len(prefix):], is32BitArch(arch)
return true
}
}
return symbol, false
return false
}

func HasSyscallPrefix(symbol string) bool {
func HasValidSyscallPrefix(abi, symbol string) bool {
for _, prefix := range supportedArchPrefix {
if strings.HasPrefix(symbol, prefix) {
return true
Expand Down
2 changes: 1 addition & 1 deletion pkg/encoder/encoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -285,7 +285,7 @@ func (p *CompactEncoder) EventToString(response *tetragon.GetEventsResponse) (st
return "", ErrMissingProcessInfo
}
processInfo, caps := p.Colorer.ProcessInfo(response.NodeName, kprobe.Process)
sc, _ := arch.CutSyscallPrefix(kprobe.FunctionName)
_, sc := arch.CutSyscallPrefix(kprobe.FunctionName)
switch sc {
case "sys_write":
event := p.Colorer.Blue.Sprintf("📝 %-7s", "write")
Expand Down
71 changes: 38 additions & 33 deletions pkg/sensors/tracing/enforcer.go
Original file line number Diff line number Diff line change
Expand Up @@ -202,47 +202,52 @@ func (kp *enforcerPolicy) createEnforcerSensor(
)

kh := &enforcerHandler{}

// get all the syscalls
for idx := range enforcer.Calls {
sym := enforcer.Calls[idx]
if strings.HasPrefix(sym, "list:") {
listName := sym[len("list:"):]

for _, call := range enforcer.Calls {
var symsToAdd []string
if strings.HasPrefix(call, "list:") {
listName := call[len("list:"):]
list := getList(listName, lists)
if list == nil {
return nil, fmt.Errorf("Error list '%s' not found", listName)
}

kh.syscallsSyms = append(kh.syscallsSyms, list.Values...)
continue
}

kh.syscallsSyms = append(kh.syscallsSyms, sym)
}

var err error

// fix syscalls
for idx, sym := range kh.syscallsSyms {
isPrefix := arch.HasSyscallPrefix(sym)
isSyscall := strings.HasPrefix(sym, "sys_")
isSecurity := strings.HasPrefix(sym, "security_")

if !isSyscall && !isSecurity && !isPrefix {
return nil, fmt.Errorf("enforcer sensor requires either syscall or security_ functions")
switch list.Type {
case "syscalls":
// syscalls list values require special interpretation
for _, val := range list.Values {
symbol, err := SyscallVal(val).Symbol()
if err != nil {
return nil, fmt.Errorf("failed to parse list element of syscall list '%s': %w", val, err)
}
kh.syscallsSyms = append(kh.syscallsSyms, symbol)
}
hasSyscall = true
continue
default:
// for everything else, we just append the symbols
symsToAdd = list.Values
}
} else {
symsToAdd = []string{call}
}

if isSyscall {
sym, err = arch.AddSyscallPrefix(sym)
if err != nil {
return nil, err
// check and add the rest of the symbols
for _, sym := range symsToAdd {
if arch.HasSyscallPrefix(sym) {
hasSyscall = true
} else if strings.HasPrefix(sym, "sys_") {
hasSyscall = true
var err error
sym, err = arch.AddSyscallPrefix(sym)
if err != nil {
return nil, err
}
} else if strings.HasPrefix(sym, "security_") {
hasSecurity = true
} else {
return nil, fmt.Errorf("enforcer sensor requires either syscall or security_ functions and symbol '%s' appears to be neither", sym)
}
kh.syscallsSyms[idx] = sym
kh.syscallsSyms = append(kh.syscallsSyms, sym)
}

hasSyscall = hasSyscall || isSyscall || isPrefix
hasSecurity = hasSecurity || isSecurity
}

// register enforcer sensor
Expand Down
6 changes: 3 additions & 3 deletions pkg/sensors/tracing/enforcer_amd64_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ func TestEnforcerOverride32(t *testing.T) {

test := testutils.RepoRootPath("contrib/tester-progs/enforcer-tester-32")
yaml := NewEnforcerSpecBuilder("enforcer-override").
WithSyscallList("__ia32_sys_prctl").
WithSyscallList("i386/sys_prctl").
WithMatchBinaries(test).
WithOverrideValue(-17). // EEXIST
MustYAML()
Expand Down Expand Up @@ -52,7 +52,7 @@ func TestEnforcerSignal32(t *testing.T) {

test := testutils.RepoRootPath("contrib/tester-progs/enforcer-tester-32")
yaml := NewEnforcerSpecBuilder("enforcer-signal").
WithSyscallList("__ia32_sys_prctl").
WithSyscallList("i386/sys_prctl").
WithMatchBinaries(test).
WithOverrideValue(-17). // EEXIST
WithKill(9). // SigKill
Expand Down Expand Up @@ -84,7 +84,7 @@ func TestEnforcerOverrideBothBits(t *testing.T) {
test64 := testutils.RepoRootPath("contrib/tester-progs/enforcer-tester")

yaml := NewEnforcerSpecBuilder("enforcer-override").
WithSyscallList("__ia32_sys_prctl", "sys_prctl").
WithSyscallList("i386/sys_prctl", "sys_prctl").
WithMatchBinaries(test32, test64).
WithOverrideValue(-17). // EEXIST
MustYAML()
Expand Down
43 changes: 5 additions & 38 deletions pkg/sensors/tracing/lists.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,10 @@ import (
"fmt"
"strings"

"github.com/cilium/tetragon/pkg/arch"
"github.com/cilium/tetragon/pkg/btf"
"github.com/cilium/tetragon/pkg/ftrace"
gt "github.com/cilium/tetragon/pkg/generictypes"
"github.com/cilium/tetragon/pkg/k8s/apis/cilium.io/v1alpha1"
"github.com/cilium/tetragon/pkg/syscallinfo"
)

func getList(name string, lists []v1alpha1.ListSpec) *v1alpha1.ListSpec {
Expand Down Expand Up @@ -60,22 +58,6 @@ func validateList(list *v1alpha1.ListSpec) (err error) {
return fmt.Errorf("Invalid list type: %s", list.Type)
}

// Add prefix to syscalls list
if listTypeFromString(list.Type) == ListTypeSyscalls {
for idx := range list.Values {
// keep symbols with '__' prefix
if strings.HasPrefix(list.Values[idx], "__") {
continue
}
symbol, err := arch.AddSyscallPrefix(list.Values[idx])
if err != nil {
return err
}
list.Values[idx] = symbol
}
return nil
}

// Generate syscalls list
if listTypeFromString(list.Type) == ListTypeGeneratedSyscalls {
if len(list.Values) != 0 {
Expand Down Expand Up @@ -144,26 +126,11 @@ func (lr *listReader) Read(name string, ty uint32) ([]uint32, error) {
return []uint32{}, fmt.Errorf("Error list '%s' argument type is not syscall64", name)
}

var (
res []uint32
id int
)

for idx := range list.Values {
sc, is32 := arch.CutSyscallPrefix(list.Values[idx])
sc = strings.TrimPrefix(sc, "sys_")

if is32 {
id = syscallinfo.GetSyscallID32(sc)
} else {
id = syscallinfo.GetSyscallID(sc)
}

if id == -1 {
return []uint32{}, fmt.Errorf("failed list '%s' cannot translate syscall '%s'", name, sc)
}
if is32 {
id |= Is32Bit
var res []uint32
for _, val := range list.Values {
id, err := SyscallVal(val).ID()
if err != nil {
return nil, err
}
res = append(res, uint32(id))
}
Expand Down
131 changes: 131 additions & 0 deletions pkg/sensors/tracing/syscall_list.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of Tetragon

package tracing

import (
"fmt"
"runtime"
"strings"

"github.com/cilium/tetragon/pkg/arch"
"github.com/cilium/tetragon/pkg/syscallinfo"
)

// The code in this file deals with values found in syscall lists (type: "syscalls")
// We need the following type of information for the values:
// - ID(): a system call id
// - Symbol(): the kernel function symbol that implements the syscall

type SyscallVal string

// return syscall id for value
func (value SyscallVal) ID() (int, error) {
abi, sc, err := parseSyscallValue(value)
if err != nil {
return -1, err
}

sc = strings.TrimPrefix(sc, "sys_")
id, err := syscallinfo.SyscallID(sc, abi)
if err != nil {
return -1, fmt.Errorf("failed list '%s' cannot translate syscall '%s': %w", value, sc, err)
}
if abi == "i386" {
id |= Is32Bit
}
return id, nil
}

func (v SyscallVal) Symbol() (string, error) {
abi, sc, err := parseSyscallValue(v)
if err != nil {
return "", err
}

var prefix string
switch abi {
case "x64":
prefix = "__x64_"
case "arm64":
prefix = "__arm64_"
case "i386":
prefix = "__ia32_"
default:
return "", fmt.Errorf("unexpected error, unknown ABI: '%s'", abi)
}

return prefix + sc, nil
}

func validateABI(xarg, abi string) error {
switch xarg {
case "":
// no arch
if abi != "x64" && abi != "i386" && abi != "arm64" {
return fmt.Errorf("invalid ABI: %s", abi)
}
case "amd64":
if abi != "x64" && abi != "i386" {
return fmt.Errorf("invalid ABI (%s) for arch (%s)", abi, xarg)
}
case "i386":
if abi != "i386" {
return fmt.Errorf("invalid ABI (%s) for arch (%s)", abi, xarg)
}
case "arm64":
if abi != "arm64" {
return fmt.Errorf("invalid ABI (%s) for arch (%s)", abi, xarg)
}
}

return nil
}

func defaultABI() (string, error) {
switch a := runtime.GOARCH; a {
case "amd64":
return "x64", nil
case "arm64":
return "arm64", nil
default:
return "", fmt.Errorf("unsupported arch: %s", a)
}

}

func parseSyscallValue(value SyscallVal) (abi string, name string, err error) {
val := string(value)
arr := strings.Split(string(val), "/")
switch len(arr) {
case 1:
// Original version of this code tried to determine the abi by looking at the
// prefix, so we maintain this behavior although it will not work for ARM32.
var xarch string
xarch, name = arch.CutSyscallPrefix(val)
switch xarch {
case "":
abi, err = defaultABI()
case "amd64":
abi = "x64"
case "i386":
abi = "i386"
case "arm64":
abi = "arm64"
}
return

case 2:
xabi := arr[0]
xarch, xname := arch.CutSyscallPrefix(arr[1])
if err = validateABI(xarch, xabi); err != nil {
return
}
abi = xabi
name = xname

default:
err = fmt.Errorf("invalid syscall value: '%s'", value)
}
return
}
Loading

0 comments on commit b751ed7

Please sign in to comment.