diff --git a/libbpfgo.go b/libbpfgo.go index 9b1fa32f..a298964b 100644 --- a/libbpfgo.go +++ b/libbpfgo.go @@ -60,6 +60,18 @@ struct ring_buffer * init_ring_buf(int map_fd, uintptr_t ctx) return rb; } +void list_programs(struct bpf_object* obj) { + struct bpf_program *pos; + const char *cs; + const char *css; + + bpf_object__for_each_program(pos, obj) { + cs = bpf_program__section_name(pos); + css = bpf_program__name(pos); + printf("section: %s\tname: %s\n", cs, css); + } +} + struct perf_buffer * init_perf_buf(int map_fd, int page_cnt, uintptr_t ctx) { struct perf_buffer_opts pb_opts = {}; @@ -215,6 +227,7 @@ const ( PerfEvent Uprobe Uretprobe + Tracing ) type BPFLink struct { @@ -236,6 +249,26 @@ func (l *BPFLink) GetFd() int { return int(C.bpf_link__fd(l.link)) } +func (l *BPFLink) Pin(pinPath string) error { + path := C.CString(pinPath) + errC := C.bpf_link__pin(l.link, path) + C.free(unsafe.Pointer(path)) + if errC != 0 { + return fmt.Errorf("failed to pin link %s to path %s: %w", l.eventName, pinPath, syscall.Errno(-errC)) + } + return nil +} + +func (l *BPFLink) Unpin(pinPath string) error { + path := C.CString(pinPath) + errC := C.bpf_link__unpin(l.link) + C.free(unsafe.Pointer(path)) + if errC != 0 { + return fmt.Errorf("failed to unpin link %s from path %s: %w", l.eventName, pinPath, syscall.Errno(-errC)) + } + return nil +} + type PerfBuffer struct { pb *C.struct_perf_buffer bpfMap *BPFMap @@ -819,6 +852,10 @@ func (m *Module) GetProgram(progName string) (*BPFProg, error) { }, nil } +func (m *Module) ListProgramNames() { + C.list_programs(m.obj) +} + func (p *BPFProg) GetFd() int { return int(C.bpf_program__fd(p.prog)) } @@ -867,7 +904,7 @@ func (p *BPFProg) GetPinPath() string { type BPFProgType uint32 const ( - BPFProgTypeUnspec uint32 = iota + BPFProgTypeUnspec BPFProgType = iota BPFProgTypeSocketFilter BPFProgTypeKprobe BPFProgTypeSchedCls @@ -898,10 +935,101 @@ const ( BPFProgTypeExt BPFProgTypeLsm BPFProgTypeSkLookup + BPFProgTypeSyscall ) -func (p *BPFProg) GetType() uint32 { - return C.bpf_program__get_type(p.prog) +func (b BPFProgType) String() (str string) { + x := map[BPFProgType]string{ + BPFProgTypeUnspec: "BPF_PROG_TYPE_UNSPEC", + BPFProgTypeSocketFilter: "BPF_PROG_TYPE_SOCKET_FILTER", + BPFProgTypeKprobe: "BPF_PROG_TYPE_KPROBE", + BPFProgTypeSchedCls: "BPF_PROG_TYPE_SCHED_CLS", + BPFProgTypeSchedAct: "BPF_PROG_TYPE_SCHED_ACT", + BPFProgTypeTracepoint: "BPF_PROG_TYPE_TRACEPOINT", + BPFProgTypeXdp: "BPF_PROG_TYPE_XDP", + BPFProgTypePerfEvent: "BPF_PROG_TYPE_PERF_EVENT", + BPFProgTypeCgroupSkb: "BPF_PROG_TYPE_CGROUP_SKB", + BPFProgTypeCgroupSock: "BPF_PROG_TYPE_CGROUP_SOCK", + BPFProgTypeLwtIn: "BPF_PROG_TYPE_LWT_IN", + BPFProgTypeLwtOut: "BPF_PROG_TYPE_LWT_OUT", + BPFProgTypeLwtXmit: "BPF_PROG_TYPE_LWT_XMIT", + BPFProgTypeSockOps: "BPF_PROG_TYPE_SOCK_OPS", + BPFProgTypeSkSkb: "BPF_PROG_TYPE_SK_SKB", + BPFProgTypeCgroupDevice: "BPF_PROG_TYPE_CGROUP_DEVICE", + BPFProgTypeSkMsg: "BPF_PROG_TYPE_SK_MSG", + BPFProgTypeRawTracepoint: "BPF_PROG_TYPE_RAW_TRACEPOINT", + BPFProgTypeCgroupSockAddr: "BPF_PROG_TYPE_CGROUP_SOCK_ADDR", + BPFProgTypeLwtSeg6Local: "BPF_PROG_TYPE_LWT_SEG6LOCAL", + BPFProgTypeLircMode2: "BPF_PROG_TYPE_LIRC_MODE2", + BPFProgTypeSkReuseport: "BPF_PROG_TYPE_SK_REUSEPORT", + BPFProgTypeFlowDissector: "BPF_PROG_TYPE_FLOW_DISSECTOR", + BPFProgTypeCgroupSysctl: "BPF_PROG_TYPE_CGROUP_SYSCTL", + BPFProgTypeRawTracepointWritable: "BPF_PROG_TYPE_RAW_TRACEPOINT_WRITABLE", + BPFProgTypeCgroupSockopt: "BPF_PROG_TYPE_CGROUP_SOCKOPT", + BPFProgTypeTracing: "BPF_PROG_TYPE_TRACING", + BPFProgTypeStructOps: "BPF_PROG_TYPE_STRUCT_OPS", + BPFProgTypeExt: "BPF_PROG_TYPE_EXT", + BPFProgTypeLsm: "BPF_PROG_TYPE_LSM", + BPFProgTypeSkLookup: "BPF_PROG_TYPE_SK_LOOKUP", + BPFProgTypeSyscall: "BPF_PROG_TYPE_SYSCALL", + } + str = x[b] + if str == "" { + str = BPFProgTypeUnspec.String() + } + return str +} + +type BPFAttachType uint32 + +const ( + BPFAttachTypeCgroupInetIngress BPFAttachType = iota + BPFAttachTypeCgroupInetEgress + BPFAttachTypeCgroupInetSockCreate + BPFAttachTypeCgroupSockOps + BPFAttachTypeSKSKBStreamParser + BPFAttachTypeSKSKBStreamVerdict + BPFAttachTypeCgroupDevice + BPFAttachTypeSKMSGVerdict + BPFAttachTypeCgroupInet4Bind + BPFAttachTypeCgroupInet6Bind + BPFAttachTypeCgroupInet4Connect + BPFAttachTypeCgroupInet6Connect + BPFAttachTypeCgroupInet4PostBind + BPFAttachTypeCgroupInet6PostBind + BPFAttachTypeCgroupUDP4SendMsg + BPFAttachTypeCgroupUDP6SendMsg + BPFAttachTypeLircMode2 + BPFAttachTypeFlowDissector + BPFAttachTypeCgroupSysctl + BPFAttachTypeCgroupUDP4RecvMsg + BPFAttachTypeCgroupUDP6RecvMsg + BPFAttachTypeCgroupGetSockOpt + BPFAttachTypeCgroupSetSockOpt + BPFAttachTypeTraceRawTP + BPFAttachTypeTraceFentry + BPFAttachTypeTraceFexit + BPFAttachTypeModifyReturn + BPFAttachTypeLSMMac + BPFAttachTypeTraceIter + BPFAttachTypeCgroupInet4GetPeerName + BPFAttachTypeCgroupInet6GetPeerName + BPFAttachTypeCgroupInet4GetSockName + BPFAttachTypeCgroupInet6GetSockName + BPFAttachTypeXDPDevMap + BPFAttachTypeCgroupInetSockRelease + BPFAttachTypeXDPCPUMap + BPFAttachTypeSKLookup + BPFAttachTypeXDP + BPFAttachTypeSKSKBVerdict + BPFAttachTypeSKReusePortSelect + BPFAttachTypeSKReusePortSelectorMigrate + BPFAttachTypePerfEvent + BPFAttachTypeTraceKprobeMulti +) + +func (p *BPFProg) GetType() BPFProgType { + return BPFProgType(C.bpf_program__get_type(p.prog)) } func (p *BPFProg) SetAutoload(autoload bool) error { @@ -921,6 +1049,43 @@ func (p *BPFProg) SetTracepoint() error { return nil } +// AttachGeneric is used to attach the BPF program using autodetection +// for the attach target. You can specify the destination in BPF code +// via the SEC() such as `SEC("fentry/some_kernel_func")` +func (p *BPFProg) AttachGeneric() (*BPFLink, error) { + link, errno := C.bpf_program__attach(p.prog) + if C.IS_ERR_OR_NULL(unsafe.Pointer(link)) { + return nil, fmt.Errorf("failed to attach program: %w", errno) + } + bpfLink := &BPFLink{ + link: link, + prog: p, + linkType: Tracing, + eventName: fmt.Sprintf("tracing-%s", p.name), + } + return bpfLink, nil +} + +// SetAttachTarget can be used to specify the program and/or function to attach +// the BPF program to. To attach to a kernel function specify attachProgFD as 0 +func (p *BPFProg) SetAttachTarget(attachProgFD int, attachFuncName string) error { + cs := C.CString(attachFuncName) + errC := C.bpf_program__set_attach_target(p.prog, C.int(attachProgFD), cs) + C.free(unsafe.Pointer(cs)) + if errC != 0 { + return fmt.Errorf("failed to set attach target for program %s %s %w", p.name, attachFuncName, syscall.Errno(-errC)) + } + return nil +} + +func (p *BPFProg) SetProgramType(progType BPFProgType) { + C.bpf_program__set_type(p.prog, C.enum_bpf_prog_type(int(progType))) +} + +func (p *BPFProg) SetAttachType(attachType BPFAttachType) { + C.bpf_program__set_expected_attach_type(p.prog, C.enum_bpf_attach_type(int(attachType))) +} + func (p *BPFProg) AttachTracepoint(category, name string) (*BPFLink, error) { tpCategory := C.CString(category) tpName := C.CString(name) diff --git a/samples/fentry/main.bpf.c b/samples/fentry/main.bpf.c new file mode 100644 index 00000000..a8807980 --- /dev/null +++ b/samples/fentry/main.bpf.c @@ -0,0 +1,18 @@ +//+build ignore +#include "vmlinux.h" +#include +#include + +#ifdef asm_inline +#undef asm_inline +#define asm_inline asm +#endif + +SEC("fentry/commit_creds") +int BPF_PROG(commit_creds, struct cred *foobar) +{ + bpf_printk("%d", foobar->uid.val); + return 0; +} + +char LICENSE[] SEC("license") = "GPL"; diff --git a/samples/fentry/main.go b/samples/fentry/main.go new file mode 100644 index 00000000..ed39210e --- /dev/null +++ b/samples/fentry/main.go @@ -0,0 +1,44 @@ +package main + +import "C" + +import ( + "os" + + "fmt" + + "github.com/aquasecurity/libbpfgo" +) + +func main() { + + bpfModule, err := libbpfgo.NewModuleFromFile("main.bpf.o") + if err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(-1) + } + defer bpfModule.Close() + + err = bpfModule.BPFLoadObject() + if err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(-1) + } + + bpfModule.ListProgramNames() + prog1, err := bpfModule.GetProgram("commit_creds") + if err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(-1) + } + link1, err := prog1.AttachGeneric() + if err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(-1) + } + if link1.GetFd() == 0 { + os.Exit(-1) + } + + fmt.Println(prog1.GetType().String()) +} diff --git a/selftest/set-attach/Makefile b/selftest/set-attach/Makefile new file mode 120000 index 00000000..d981720c --- /dev/null +++ b/selftest/set-attach/Makefile @@ -0,0 +1 @@ +../common/Makefile \ No newline at end of file diff --git a/selftest/set-attach/go.mod b/selftest/set-attach/go.mod new file mode 100644 index 00000000..d282d08b --- /dev/null +++ b/selftest/set-attach/go.mod @@ -0,0 +1,7 @@ +module github.com/aquasecurity/libbpfgo/selftest/perfbuffers + +go 1.16 + +require github.com/aquasecurity/libbpfgo v0.2.1-libbpf-0.4.0 + +replace github.com/aquasecurity/libbpfgo => ../../ diff --git a/selftest/set-attach/go.sum b/selftest/set-attach/go.sum new file mode 100644 index 00000000..3b1886cc --- /dev/null +++ b/selftest/set-attach/go.sum @@ -0,0 +1,11 @@ +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/selftest/set-attach/main.bpf.c b/selftest/set-attach/main.bpf.c new file mode 100644 index 00000000..729f273e --- /dev/null +++ b/selftest/set-attach/main.bpf.c @@ -0,0 +1,32 @@ +//+build ignore +#include "vmlinux.h" +#include +#include + +#ifdef asm_inline +#undef asm_inline +#define asm_inline asm +#endif + +struct { + __uint(type, BPF_MAP_TYPE_RINGBUF); + __uint(max_entries, 1 << 24); +} events SEC(".maps"); +long ringbuffer_flags = 0; + +SEC("fentry/FUNC") +int BPF_PROG(foobar) +{ + int *process; + // Reserve space on the ringbuffer for the sample + process = bpf_ringbuf_reserve(&events, sizeof(int), ringbuffer_flags); + if (!process) { + return 0; + } + *process = 2021; + bpf_ringbuf_submit(process, ringbuffer_flags); + return 0; +} + +char LICENSE[] SEC("license") = "GPL"; + diff --git a/selftest/set-attach/main.go b/selftest/set-attach/main.go new file mode 100644 index 00000000..8d388891 --- /dev/null +++ b/selftest/set-attach/main.go @@ -0,0 +1,80 @@ +package main + +import "C" + +import ( + "encoding/binary" + "os" + "syscall" + "time" + + "fmt" + + bpf "github.com/aquasecurity/libbpfgo" +) + +func main() { + + bpfModule, err := bpf.NewModuleFromFile("main.bpf.o") + if err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(-1) + } + defer bpfModule.Close() + + bpfModule.ListProgramNames() + + prog, err := bpfModule.GetProgram("foobar") + if err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(-1) + } + + prog.SetProgramType(bpf.BPFProgTypeTracing) + prog.SetAttachType(bpf.BPFAttachTypeTraceFentry) + err = prog.SetAttachTarget(0, "__x64_sys_mmap") + if err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(-1) + } + err = bpfModule.BPFLoadObject() + if err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(-1) + } + _, err = prog.AttachGeneric() + if err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(-1) + } + + eventsChannel := make(chan []byte) + rb, err := bpfModule.InitRingBuf("events", eventsChannel) + if err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(-1) + } + + rb.Start() + numberOfEventsReceived := 0 + go func() { + for { + syscall.Mmap(999, 999, 999, 1, 1) + time.Sleep(time.Second / 100) + } + }() +recvLoop: + for { + b := <-eventsChannel + if binary.LittleEndian.Uint32(b) != 2021 { + fmt.Fprintf(os.Stderr, "invalid data retrieved\n") + os.Exit(-1) + } + numberOfEventsReceived++ + if numberOfEventsReceived > 5 { + break recvLoop + } + } + rb.Stop() + rb.Close() +} diff --git a/selftest/set-attach/run.sh b/selftest/set-attach/run.sh new file mode 120000 index 00000000..c1317de3 --- /dev/null +++ b/selftest/set-attach/run.sh @@ -0,0 +1 @@ +../common/run-5.8.sh \ No newline at end of file diff --git a/selftest/tracing/Makefile b/selftest/tracing/Makefile new file mode 120000 index 00000000..d981720c --- /dev/null +++ b/selftest/tracing/Makefile @@ -0,0 +1 @@ +../common/Makefile \ No newline at end of file diff --git a/selftest/tracing/go.mod b/selftest/tracing/go.mod new file mode 100644 index 00000000..d282d08b --- /dev/null +++ b/selftest/tracing/go.mod @@ -0,0 +1,7 @@ +module github.com/aquasecurity/libbpfgo/selftest/perfbuffers + +go 1.16 + +require github.com/aquasecurity/libbpfgo v0.2.1-libbpf-0.4.0 + +replace github.com/aquasecurity/libbpfgo => ../../ diff --git a/selftest/tracing/go.sum b/selftest/tracing/go.sum new file mode 100644 index 00000000..3b1886cc --- /dev/null +++ b/selftest/tracing/go.sum @@ -0,0 +1,11 @@ +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/selftest/tracing/main.bpf.c b/selftest/tracing/main.bpf.c new file mode 100644 index 00000000..063359e5 --- /dev/null +++ b/selftest/tracing/main.bpf.c @@ -0,0 +1,35 @@ +//+build ignore +#include "vmlinux.h" +#include +#include + +#ifdef asm_inline +#undef asm_inline +#define asm_inline asm +#endif + +struct { + __uint(type, BPF_MAP_TYPE_RINGBUF); + __uint(max_entries, 1 << 24); +} events SEC(".maps"); +long ringbuffer_flags = 0; + +SEC("fentry/__x64_sys_mmap") +int mmap_fentry(struct pt_regs *ctx) +{ + int *process; + + // Reserve space on the ringbuffer for the sample + process = bpf_ringbuf_reserve(&events, sizeof(int), ringbuffer_flags); + if (!process) { + return 0; + } + + *process = 2021; + + bpf_ringbuf_submit(process, ringbuffer_flags); + return 0; +} + +char LICENSE[] SEC("license") = "GPL"; + diff --git a/selftest/tracing/main.go b/selftest/tracing/main.go new file mode 100644 index 00000000..8acfe7ec --- /dev/null +++ b/selftest/tracing/main.go @@ -0,0 +1,77 @@ +package main + +import "C" + +import ( + "encoding/binary" + "os" + "syscall" + "time" + + "fmt" + + bpf "github.com/aquasecurity/libbpfgo" +) + +func main() { + + bpfModule, err := bpf.NewModuleFromFile("main.bpf.o") + if err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(-1) + } + defer bpfModule.Close() + + err = bpfModule.BPFLoadObject() + if err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(-1) + } + + bpfModule.ListProgramNames() + + prog, err := bpfModule.GetProgram("mmap_fentry") + if err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(-1) + } + link, err := prog.AttachGeneric() + if err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(-1) + } + if link.GetFd() == 0 { + os.Exit(-1) + } + + eventsChannel := make(chan []byte) + rb, err := bpfModule.InitRingBuf("events", eventsChannel) + if err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(-1) + } + + rb.Start() + numberOfEventsReceived := 0 + go func() { + for { + syscall.Mmap(999, 999, 999, 1, 1) + time.Sleep(time.Second / 100) + } + }() +recvLoop: + for { + b := <-eventsChannel + if binary.LittleEndian.Uint32(b) != 2021 { + fmt.Fprintf(os.Stderr, "invalid data retrieved\n") + os.Exit(-1) + } + numberOfEventsReceived++ + if numberOfEventsReceived > 5 { + break recvLoop + } + } + + rb.Stop() + rb.Close() +} diff --git a/selftest/tracing/run.sh b/selftest/tracing/run.sh new file mode 120000 index 00000000..c1317de3 --- /dev/null +++ b/selftest/tracing/run.sh @@ -0,0 +1 @@ +../common/run-5.8.sh \ No newline at end of file