Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: probe bpf func #475

Merged
merged 2 commits into from
Jan 17, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 9 additions & 13 deletions helpers_test.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package libbpfgo

import (
"errors"
"fmt"
"strings"
"syscall"
"testing"

Expand Down Expand Up @@ -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,
},
Expand All @@ -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,
},
Expand Down Expand Up @@ -153,7 +150,6 @@ func TestFuncSupportbyType(t *testing.T) {
errMsg: syscall.EINVAL,
},
}

for _, tc := range tt {
// reset all current effective capabilities
resetEffectiveCapabilities()
Expand All @@ -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)
}
}

Expand Down
39 changes: 32 additions & 7 deletions libbpfgo.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

//
Expand Down
Loading