microsoft / microsoft/ebpf-for-windows

Support for Atomically Replacing BPF Programs Attached to Hooks

Open
#4,503 0 comments 0 reactions 1 assignee Claimed by @Alan-Jowett View on GitHub
enhancement P3 triaged
Dominant language
C
Stars
3.6k
Forks
311
Avg merge
6d 10h
Merged PRs (30d)
21

Description

Description:

Currently, eBPF for Windows supports attaching and detaching BPF programs to hooks via link objects. However, it is unclear whether the platform supports atomic replacement of a BPF program already attached to a hook—similar to the BPF_LINK_UPDATE functionality available in Linux.

This capability is critical for production-grade systems where minimizing downtime and ensuring consistency during program updates is essential.

Request:

Please clarify whether eBPF for Windows supports atomic replacement of BPF programs at hook points. If not currently supported, are there plans to introduce this functionality? Specifically:

Is there a mechanism to replace a program without a detach/attach cycle?
Can this be done without disrupting in-flight execution or requiring re-verification of the hook state?
Are there any workarounds or best practices for achieving near-atomic replacement behavior?

References:

https://man7.org/linux/man-pages/man2/bpf.2.html

---

## Feature Spec: Atomic replacement of a program attached via bpf_link (BPF_LINK_UPDATE)

### Problem statement
Today, when a program is attached to a hook via a link object, updating the behavior typically requires a detach + attach cycle. This introduces a window of time where:
- no program is attached (downtime), or
- a replacement is racy across concurrent traffic/events.

We need a first-class, Linux-compatible mechanism to atomically swap the program behind an existing link, similar to Linux `bpf_link_update()` / `BPF_LINK_UPDATE`.

There is already a TODO in user-mode XDP helper logic noting that Linux performs replace atomically in-kernel and that eBPF for Windows should consider moving the logic to the execution context (see `libs\\api\\libbpf_program.cpp` around `__bpf_set_link_xdp_fd_replace`).

### Goals
- Provide **atomic** replacement of the program associated with an existing link (no detach/attach cycle).
- Be compatible with Linux user-mode API shape:
- `bpf(BPF_LINK_UPDATE, ...)`
- `bpf_link_update(link_fd, new_prog_fd, opts)`
- Ensure safety for in-flight invocations:
- Calls already executing may continue running the old program.
- All invocations after the update must run the new program.
- Avoid re-registering/re-binding the hook provider (keep the same link/NMR registration).

### Non-goals (initial scope)
- No support for changing attach parameters (attach_data) of an existing link.
- No support for changing attach type / hook type / link type.
- No support for converting a legacy attach (no link handle/fd returned) into an updatable link.
- No new multi-program chaining semantics (FIFO lists, priorities, etc.).

### User scenarios
1. **XDP hot update**: Replace an attached XDP program with zero downtime.
2. **CGROUP connect hooks**: Update enforcement/telemetry logic without gaps.
3. **Operational rollouts**: Canary program updates by swapping program pointers while preserving link identity and pin path.

### Proposed external API
#### Linux-compatible syscall surface
- Implement `BPF_LINK_UPDATE` in `bpf()` syscall shim (`libs\\api\\bpf_syscall.cpp`).
- Implement libbpf API entrypoint:
- `int bpf_link_update(int link_fd, int new_prog_fd, const struct bpf_link_update_opts *opts);`

#### Semantics (match Linux as closely as practical)
- `link_fd`: existing link.
- `new_prog_fd`: new program.
- `opts`:
- `opts->flags`: support `BPF_F_REPLACE`.
- `opts->old_prog_fd`: optional expected current program.

Behavior:
- If `opts == NULL`, treat as flags=0 and old_prog_fd=0.
- If `opts->old_prog_fd != 0`, require `opts->flags & BPF_F_REPLACE` (else fail with `-EINVAL`).
- If `BPF_F_REPLACE` is set and `old_prog_fd` is supplied, only succeed if the link’s currently attached program matches `old_prog_fd` (else fail with `-EPERM`).
- On success, program swap is atomic.

Error mapping guidelines:
- Invalid fds -> `-EBADF`
- Unsupported command/flags -> `-EINVAL` or `-EOPNOTSUPP` (prefer Linux-aligned `-EINVAL` for unsupported flags, `-EOPNOTSUPP` if the whole feature is missing)
- Program type mismatch / attach incompatibility -> `-EINVAL`

### Required internal changes
#### Execution context / protocol
Add a new execution-context protocol operation to update a link:
- `EBPF_OPERATION_LINK_UPDATE`
- Request fields:
- `ebpf_handle_t link_handle`
- `ebpf_handle_t new_program_handle`
- `uint32_t flags` (Linux BPF_F_* subset)
- `ebpf_handle_t old_program_handle` (optional; `ebpf_handle_invalid` if not provided)
- Reply: header only (or include the old program id for diagnostics; optional).

#### Kernel/link implementation approach
Current design facts (today):
- `ebpf_link_t` stores a raw `ebpf_program_t* program`.
- Invoke path (`_ebpf_link_instance_invoke_batch`) reads `link->program` **without acquiring `link->lock`**.
- Invocation is wrapped in epoch enter/exit (`_ebpf_link_instance_invoke_batch_begin/end`).
- Program lifetime is protected by ref-counting and epoch-based final free.

Proposed algorithm (atomic swap):
1. Validate link handle resolves to an `EBPF_OBJECT_LINK` and is in `EBPF_LINK_STATE_ATTACHED`.
2. Resolve `new_program_handle` to `EBPF_OBJECT_PROGRAM` and validate:
- code type != `EBPF_CODE_NONE`
- new program type UUID equals the existing link’s `program_type` (and therefore the provider’s supported type)
3. If `BPF_F_REPLACE` + `old_program_handle` provided:
- resolve old program handle and verify it matches the currently attached program on the link.
4. Atomically swap `link->program` to the new program:
- Acquire reference/link association for new program (call `ebpf_program_attach_link(new_program)`)
- Under `link->lock`, update:
- `link->program` pointer (use `InterlockedExchangePointer` or equivalent to be explicit)
- `link->program_type` (should be unchanged if validated)
- `link->client_data.prog_attach_flags`
- Detach link association from old program (call `ebpf_program_detach_link(old_program)`)

Safety notes:
- In-flight invocations may have already read the old pointer; that remains valid until the end of the current epoch.
- Releasing the old program reference is safe because object destruction is epoch-deferred.

### Concurrency and locking
- Serialize updates and detaches via `link->lock`:
- `ebpf_link_detach_program()` already takes `link->lock`; link update must also.
- Invoke path remains lock-free; update uses atomic pointer swap with proper memory ordering.

### Compatibility / versioning
- Older user-mode components should see `-EOPNOTSUPP` for `BPF_LINK_UPDATE` if the kernel doesn’t support it.
- Document support in `docs\\BpfSyscallCompatibility.md`.

### Workarounds (until implemented)
- For XDP only, current user-mode logic emulates “replace” by enumerating links and detaching matching ones, then attaching the new program (non-atomic and has a race window).
- For other attach types, detach/attach is the only option.

---

## Implementation Plan
1. **Protocol surface**
- Add `EBPF_OPERATION_LINK_UPDATE` to `libs\\execution_context\\ebpf_protocol.h`.
- Add request/reply structs.
2. **Execution context handler**
- Implement `_ebpf_core_protocol_link_update` in `libs\\execution_context\\ebpf_core.c`.
- Validate inputs and call a new helper `ebpf_link_update_program(...)`.
3. **Link object support**
- Add `ebpf_link_update_program()` to `libs\\execution_context\\ebpf_link.c/.h`.
- Implement atomic pointer swap + reference updates as described above.
4. **User-mode API plumbing (ebpfapi)**
- Add `ebpf_link_update_by_fd()` (or similar) in `libs\\api\\ebpf_api.cpp` to issue the IOCTL for `EBPF_OPERATION_LINK_UPDATE`.
5. **Linux syscall shim + libbpf wrapper**
- Add `case BPF_LINK_UPDATE:` to `libs\\api\\bpf_syscall.cpp`.
- Implement `bpf_link_update()` in `libs\\api\\libbpf_link.cpp` (exported symbol) to call into ebpfapi.
6. **Tests**
- Add execution-context unit tests:
- success path swaps program and updates `bpf_link_info.prog_id`
- `BPF_F_REPLACE` + wrong `old_prog_fd` fails
- type mismatch fails
- detach concurrent with update is serialized (no crashes)
- Add end-to-end test (optional) that repeatedly updates a link while invoking it to validate stability.
7. **Docs**
- Update `docs\\BpfSyscallCompatibility.md` to list `BPF_LINK_UPDATE` supported.

---

## Acceptance Criteria
- A new link update API exists and is wired end-to-end:
- `bpf(BPF_LINK_UPDATE, ...)` works.
- `bpf_link_update()` works.
- Update is atomic:
- No detach/attach cycle is performed.
- After success, subsequent invocations always execute the new program.
- In-flight invocations may finish on the old program, but there are no crashes/UAFs.
- Correct validation:
- Fails if link not attached.
- Fails if new program type is incompatible with the link/hook.
- Honors `BPF_F_REPLACE` + `old_prog_fd` semantics.
- `bpf_obj_get_info_by_fd(link_fd, ...)` reflects the new `prog_id` after update.
- Unit tests cover success and failure cases.

---

## Open questions / follow-ups (non-blocking)
- Do we want to support updating attach flags/metadata beyond `prog_attach_flags`?
- Should we expose a Windows-native IOCTL-only API separate from the Linux-compatible surface?
- Do we need to gate this feature by a capability/version query so libbpf can probe support cleanly?

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.