Skip to content

Improve haveBatchAPI() feature probing in BatchLookup() #2013

@lmtcf

Description

@lmtcf

Describe the bug

BatchLookup() calls into batchLookupCmd(), which in turn calls haveBatchAPI() indiscriminately, caching the result to avoid future calls. The problem here is that haveBatchAPI() creates a small temporary ebpf map which it then batch updates to verify that the system supports batching. However, the creation of the map implies CAP_BPF capabiliries.

BatchDelete (and BatchUpdate(), for that matter) was improved in #1071 to have the haveBatchAPI() being called only after a failure is observed while calling the underlying BPF syscall, to check if the cause was lack of batching support.

I'm suggesting the same for the same to be done for BatchLookup(), otherwise this can lead to awkward to debug errors like

batch lookup: map batch lookup: map batch api not supported (requires >= v5.6)".

Our binary is split in two - A and B, where A creates the maps and B reads and performs several read operations. B runs with minimal permissions, so suddenly having to require CAP_BPF is surprising and hard to justify.

How to reproduce

// Bellow BatchUpdate and BatchDelete work, but BatchLookup doesn't.

fun() {
    m, err := ebpf.LoadPinnedMap(path, nil)
    if err != nil {
        fmt.Fprintln(os.Stderr, "[run] LoadPinnedMap:", err)
        os.Exit(1)
    }
    defer m.Close() 

    keys := []uint32{1, 2}
    vals := []uint32{11, 22}

    // BatchUpdate first: populates entries we can later delete/lookup.
    n, err := m.BatchUpdate(keys, vals, nil)
    fmt.Printf("[run] BatchUpdate n=%d err=%v\n", n, err)

    // BatchLookup: this is the call cilium gates with the probe.
    var cursor ebpf.MapBatchCursor
    outKeys := make([]uint32, 2)
    outVals := make([]uint32, 2)
    n, err = m.BatchLookup(&cursor, outKeys, outVals, nil)
    fmt.Printf("[run] BatchLookup n=%d err=%v keys=%v vals=%v\n", n, err, outKeys, outVals)
    if errors.Is(err, ebpf.ErrKeyNotExist) {
        fmt.Println("[run] (ErrKeyNotExist is the normal end-of-iteration signal)")
    }

    // BatchDelete: should succeed even without caps on the happy path.
    n, err = m.BatchDelete(keys, nil)
    fmt.Printf("[run] BatchDelete n=%d err=%v\n", n, err)
}

Version information

All versions

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions