From e4ca94a13916a955fc7592c2c84f63437174824c Mon Sep 17 00:00:00 2001 From: Raphael Campos Date: Thu, 16 Jan 2025 08:39:39 -0600 Subject: [PATCH 1/2] fix: probe bpf func - Improved BPFHelperIsSupported by enhancing error reporting to include detailed messages for unsupported helpers and unexpected errno values returned by libbpf; - Added logic to handle unexpected errno states and clarified support assumptions based on retC == 1 when running with capabilities. --- libbpfgo.go | 39 ++++++++++++++++++++++++++++++++------- 1 file changed, 32 insertions(+), 7 deletions(-) diff --git a/libbpfgo.go b/libbpfgo.go index e42657d..f6dabf2 100644 --- a/libbpfgo.go +++ b/libbpfgo.go @@ -96,21 +96,46 @@ func BPFMapTypeIsSupported(mapType MapType) (bool, error) { return supportedC == 1, nil } -// BPFHelperIsSupported checks if a BPF helper function is supported for a given program type. -// Specific capabilities are required depending on the program type to probe the bpf helper function. +// BPFHelperIsSupported checks if a specific BPF helper function is supported for a given program type. +// This function probes the BPF helper using libbpf and returns whether the helper is supported. +// +// Important Notes for the Caller: +// +// 1. libbpf probes may return success (`true`) even if the BPF program load would fail due to permission issues (EPERM). +// To ensure reliability, it is necessary to either run with sufficient capabilities or explicitly check for EPERM. +// Reference: https://github.com/libbpf/bpftool/blob/a5c058054cc71836930e232162e8bd1ec6705eaf/src/feature.c#L694-L701 +// +// 2. libbpf does not always clear `errno` in certain scenarios. For example, if the file `/proc/version_signature` +// is missing, libbpf may set `errno` to ENOENT (errno=2) and leave it uncleared, even if the helper is supported. +// Reference: https://github.com/libbpf/libbpf/blob/09b9e83102eb8ab9e540d36b4559c55f3bcdb95d/src/libbpf_probes.c#L33-L39 +// +// 3. If the function returns `true` while running with appropriate capabilities, the helper is assumed to be supported. +// This behavior is documented in libbpf: +// Reference: https://github.com/libbpf/libbpf/blob/09b9e83102eb8ab9e540d36b4559c55f3bcdb95d/src/libbpf_probes.c#L448-L464 +// +// Caveats: +// - A return value of `true` does not guarantee that the BPF program will load successfully. It is critical to verify +// permissions or run with sufficient capabilities for accurate results. +// - If `retC < 0`, the helper is not supported. In such cases, additional details can be found in `errno`. func BPFHelperIsSupported(progType BPFProgType, funcId BPFFunc) (bool, error) { retC, errno := C.libbpf_probe_bpf_helper(C.enum_bpf_prog_type(int(progType)), C.enum_bpf_func_id(int(funcId)), nil) - if errno != nil { - return false, fmt.Errorf("operation failed for function `%s` with program type `%s`: %w", funcId, progType, errno) - } + var innerErr error // helper not supported if retC < 0 { - return false, fmt.Errorf("operation failed for function `%s` with program type `%s`: %w", funcId, progType, syscall.Errno(-retC)) + return false, fmt.Errorf("operation failed for function `%s` with program type `%s`: %w. (errno: %v)", funcId, progType, syscall.Errno(-retC), errno) + } + + // Handle unexpected errno values returned by libbpf. For example, errno may still + // contain a previous value like ENOENT, even when the helper is supported. + if errno != nil { + innerErr = fmt.Errorf("unexpected errno for function `%s` with program type `%s`. (errno: %v)", funcId, progType, errno) } - return retC == 1, nil + // If running with capabilities and retC==1 its assumed the helper is supported. Reference: + // https://github.com/libbpf/libbpf/blob/09b9e83102eb8ab9e540d36b4559c55f3bcdb95d/src/libbpf_probes.c#L448-L464 + return retC == 1, innerErr } // From 9aead273966d669fd4f60b30e1fd8516a62dd74b Mon Sep 17 00:00:00 2001 From: Raphael Campos Date: Thu, 16 Jan 2025 10:48:03 -0600 Subject: [PATCH 2/2] fix(test): update tests to align with the helper - align the err msg return; - remove one test case - the func helper is enabled for the type for new kernels. --- helpers_test.go | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/helpers_test.go b/helpers_test.go index 9181126..ed84c2a 100644 --- a/helpers_test.go +++ b/helpers_test.go @@ -1,8 +1,8 @@ package libbpfgo import ( - "errors" "fmt" + "strings" "syscall" "testing" @@ -69,10 +69,12 @@ func TestFuncSupportbyType(t *testing.T) { errMsg error }{ // func available but not enough permission (permission denied) + // May return success (`true`) even if the BPF program load would fail due to permission issues (EPERM). + // Check BPFHelperIsSupported for more info. { progType: BPFProgTypeKprobe, funcId: BPFFuncGetCurrentUidGid, - supported: false, + supported: true, capability: []string{}, errMsg: syscall.EPERM, }, @@ -87,17 +89,12 @@ func TestFuncSupportbyType(t *testing.T) { // func unavailable and enough permission // When the function is unavailable, BPF returns "Invalid Argument". // Therefore, ignore the error and proceed with validation. + // May return success (`true`) even if the BPF program load would fail due to permission issues (EPERM). + // Check BPFHelperIsSupported for more info. { progType: BPFProgTypeSkLookup, funcId: BPFFuncGetCurrentCgroupId, - supported: false, - capability: []string{"cap_sys_admin"}, - errMsg: syscall.EINVAL, - }, - { - progType: BPFProgTypeSkLookup, - funcId: BPFFuncGetCurrentCgroupId, - supported: false, + supported: true, capability: []string{}, errMsg: syscall.EPERM, }, @@ -153,7 +150,6 @@ func TestFuncSupportbyType(t *testing.T) { errMsg: syscall.EINVAL, }, } - for _, tc := range tt { // reset all current effective capabilities resetEffectiveCapabilities() @@ -169,8 +165,8 @@ func TestFuncSupportbyType(t *testing.T) { t.Errorf("expected no error, got %v", err) } } else { - if !errors.Is(err, tc.errMsg) { - t.Errorf("expected error %v, got %v", tc.errMsg, err) + if err == nil || !strings.Contains(err.Error(), tc.errMsg.Error()) { + t.Errorf("expected error containing %q, got %v", tc.errMsg.Error(), err) } }