Skip to content

Pod state map update fails with EINVAL after a partial BPF load is accepted as success #674

Description

@viveksb007

Version: v1.4.1 (aws-ebpf-sdk-go v1.0.15) · kernel 5.10 (AL2)


Behavior

A pod occasionally fails network policy enforcement with:

ERROR  Ingress Pod State Map update failed: unable to update map: invalid argument
ERROR  bpf_client.go:1272  unable to create/update map entry and ret -1 and err invalid argument

Just before it, the same pod's program load was logged as a success but with a short map set:

Prog Load Succeeded for ingress, progFD: N, pinpath: .../<podIdentifier>_handle_ingress, maps: 2

Root cause

Three NPA-side gaps. The map is dropped inside aws-ebpf-sdk-go (filed separately:
aws/aws-ebpf-sdk-go#167); NPA turns that into a pod-startup failure.

1. A partial map set is accepted as a successful load. loadBPFProgram only rejects an
empty set, so a 2-of-3 result passes and is logged as success:

bpf_client.go:994   if len(bpfData.Maps) == 0 { ... }
bpf_client.go:999   log().Infof("Prog Load Succeeded for %s, ... maps: %d", ..., len(bpfData.Maps))

2. No MapFD == 0 guard before the update. The missing key yields a zero-value BpfMap,
and the syscall is issued against fd 0:

bpf_client.go:1269   mapToUpdate = ingressProgInfo.Maps[utils.TC_INGRESS_POD_STATE_MAP]  // absent → BpfMap{MapFD:0}
bpf_client.go:1272   mapToUpdate.CreateUpdateMapEntry(...)                               // fd 0 → EINVAL

3. DeleteBPFProbes deletes the per-identifier mutex while holding it, which lets two
attaches for the same identifier run concurrently and trigger the SDK bug:

bpf_client.go:870-872   lock := podIdentifierLock.LoadOrStore(X, &sync.Mutex{}); lock.Lock()
bpf_client.go:915         policyEndpointeBPFContext.Delete(X)   // clears the program reuse cache
bpf_client.go:887       l.podIdentifierLock.Delete(X)           // removes the mutex entry, still holding it
bpf_client.go:873       (deferred) lock.Unlock()

Deleting the entry while a waiter is parked on that mutex orphans it — the next caller's
LoadOrStore creates a different mutex. So AttacheBPFProbes(X) from
rpc_handler.EnforceNpToPod (rpc_handler.go:86) and from a reconciler
(policyendpoints_controller.go:336) can be inside the function at the same time. The reuse
cache was just cleared, so the gate at bpf_client.go:788 no longer dedupes and both call
LoadBpfFile with the same identifier — same pin paths, concurrently.

This affects a whole cohort at once: GetPodIdentifier (pkg/utils/utils.go:176-187) strips the
pod name's last -suffix, so all pods of a CronJob or ReplicaSet share one identifier and one
set of pin paths.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions