Skip to content

Commit

Permalink
logger: introduce tracee logger callbacks
Browse files Browse the repository at this point in the history
loggerCallback() calls log and filter callbacks, Log and logFnFilters,
which must be set by the libbpfgo consumer via SetLoggerCbs().

This moves output filtering from C libbpf_print_fn() to
Go filterOutput() which can be set as one of the slice of filter funcs.
  • Loading branch information
geyslan committed Jan 31, 2023
1 parent 8b1513e commit 093e05c
Show file tree
Hide file tree
Showing 2 changed files with 159 additions and 29 deletions.
42 changes: 13 additions & 29 deletions libbpfgo.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,46 +15,30 @@
#include <bpf/bpf.h>
#include <bpf/libbpf.h>

extern void loggerCallback(enum libbpf_print_level level, char *output);

int libbpf_print_fn(enum libbpf_print_level level, // libbpf print level
const char *format, // format used for the msg
va_list args) { // args used by format

if (level != LIBBPF_WARN)
return 0;

int ret;
char str[300];
int ret = 0;
char *out;
va_list check;

out = (char *)calloc(1, 300);
if (!out)
return -ENOMEM;

va_copy(check, args);
ret = vsnprintf(str, sizeof(str), format, check);
ret = vsnprintf(out, 300, format, check);
va_end(check);

if (ret <= 0) {
goto done;
}
if (ret > 0)
loggerCallback(level, out);

// BUG: https:/github.com/aquasecurity/tracee/issues/1676
if (strstr(str, "Exclusivity flag on") != NULL) {
return 0;
}

// BUG: https://github.com/aquasecurity/tracee/issues/2446
if (strstr(str, "failed to create kprobe") != NULL) {
if (strstr(str, "trace_check_map_func_compatibility") != NULL)
return 0;
}

// AttachCgroupLegacy() will first try AttachCgroup() and it might fail. This
// is not an error and is the best way of probing for eBPF cgroup attachment
// link existence.
if (strstr(str, "cgroup") != NULL) {
if (strstr(str, "Invalid argument") != NULL)
return 0;
}
free(out);

done:
return vfprintf(stderr, format, args);
return ret;
}

void set_print_fn() { libbpf_set_print(libbpf_print_fn); }
Expand Down
146 changes: 146 additions & 0 deletions logger_cb.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
package libbpfgo

/*
#include <bpf/libbpf.h>
*/
import "C"

import (
"fmt"
"os"
"regexp"
"strings"
)

// This callback definition needs to be in a different file from where it is declared in C
// Otherwise, multiple definition compilation error will occur

func init() {
if Log == nil {
Log = logFallback
}
}

// loggerCallback is called by libbpf_print_fn() which in turn is called by libbpf
//
//export loggerCallback
func loggerCallback(libbpfPrintLevel int, libbpfOutput *C.char) {
var (
level int
goOutput string
)

goOutput = C.GoString(libbpfOutput)
goOutput = strings.TrimSuffix(goOutput, "\n")

for _, fnFilterOut := range logFnFilters {
if fnFilterOut != nil {
if fnFilterOut(libbpfPrintLevel, goOutput) {
return
}
}
}

Log(level, goOutput)
}

var Log func(level int, msg string, keyValues ...interface{})
var logFnFilters []func(libLevel int, msg string) bool

// SetLoggerCbs receives a function to be used to log libbpf outputs and
// a list of functions to filter this output out
func SetLoggerCbs(
fnLog func(level int, msg string, keyValues ...interface{}), // Log callback
fnFilters []func(libLevel int, msg string) bool, // Filter out callbacks
) {
Log = fnLog
logFnFilters = fnFilters
}

// logFallback:
// - level is ignored in this stage
// - type coercion only takes care of string types
// - keyValues is not required to contain pairs
// - outputs all to stderr
func logFallback(level int, msg string, keyValues ...interface{}) {
var (
args = make([]string, 0)
outMsg = msg
)

for _, v := range keyValues {
if s, ok := v.(string); ok {
fmt.Println("string:", s)
outMsg += " [%s]"
args = append(args, s)
}
}

outMsg += "\n"
if len(keyValues) > 0 {
fmt.Fprintf(os.Stderr, outMsg, args)
} else {
fmt.Fprint(os.Stderr, outMsg)
}
}

// LogFilterOutput filters some errors out
// The consumer can use it by passing it as a filter function via SetLoggerCbs
func LogFilterOutput(libbpfPrintLevel int, output string) bool {
var (
matched bool
debugLevel int
err error
)

debugLevel = int(C.LIBBPF_DEBUG)

// BUG: https:/github.com/aquasecurity/tracee/issues/1676

// triggered by: libbpf/src/nlattr.c->libbpf_nla_dump_errormsg()
// "libbpf: Kernel error message: %s\n"
// 1. %s = "Exclusivity flag on"

matched, err = regexp.MatchString(`libbpf:.*Kernel error message:.*Exclusivity flag on`, output)
if err != nil {
Log(debugLevel, "Filtering libbpf print output", "error", err)
} else if matched {
return true
}

// BUG: https://github.com/aquasecurity/tracee/issues/2446

// triggered by: libbpf/src/libbpf.c->bpf_program__attach_kprobe_opts()
// "libbpf: prog '%s': failed to create %s '%s+0x%zx' perf event: %s\n"
// 1. %s = trace_check_map_func_compatibility
// 2. %s = kretprobe or kprobe
// 3. %s = check_map_func_compatibility (function name)
// 4. %x = offset (ignored in this check)
// 5. %s = No such file or directory

matched, err = regexp.MatchString(`libbpf:.*prog 'trace_check_map_func_compatibility'.*failed to create kprobe.*perf event: No such file or directory`, output)
if err != nil {
Log(debugLevel, "Filtering libbpf print output", "error", err)
} else if matched {
return true
}

// AttachCgroupLegacy() will first try AttachCgroup() and it might fail. This
// is not an error and is the best way of probing for eBPF cgroup attachment
// link existence.

// triggered by: libbpf/src/libbpf.c->bpf_program__attach_fd()
// "libbpf: prog '%s': failed to attach to %s: %s\n"
// 1. %s = cgroup_skb_ingress or cgroup_skb_egress
// 2. %s = cgroup
// 3. %s = Invalid argument

matched, err = regexp.MatchString(`libbpf:.*prog 'cgroup_skb_ingress|cgroup_skb_egress'.*failed to attach to cgroup.*Invalid argument`, output)
if err != nil {
Log(debugLevel, "Filtering libbpf print output", "error", err)
} else if matched {
return true
}

return false
}

0 comments on commit 093e05c

Please sign in to comment.