An eBPF program is loaded into the Linux kernel with a fixed program type such as BPF_PROG_TYPE_KPROBE or BPF_PROG_TYPE_XDP. What does that program type decide about the program, and why can't you attach one program anywhere you like?
answer
- one fixed type, chosen at load
- the ctx pointer differs per hook
- helpers are allowlisted, not global
- the return value means different things
- SEC() string is only a hint
basics
~20 sAn eBPF program's type fixes which kernel hooks it may attach to, the layout of the single context argument it receives, which BPF helper functions it may call, and how the kernel interprets its return value.
solid answer
~50 sThe type is set in the `bpf(BPF_PROG_LOAD)` call and it is not a label — it is a contract. It decides which hooks the program may attach to, and it decides the type of the one argument the program gets: a kprobe program receives `struct pt_regs *`, an XDP program `struct xdp_md *`, a tc or socket-filter program `struct __sk_buff *`, a cgroup connect hook `struct bpf_sock_addr *`. It decides which BPF helpers are callable, because the helper set is defined per type rather than globally — helpers that read the current task are meaningless in a softirq network path. And it decides what the return value means: ignored for a kprobe, an action code for XDP, allow/deny for cgroup and LSM programs. The kernel checks all of this at load time, so a program written for one hook is simply rejected at another.
code
c · 17 lines#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>
SEC("kprobe/do_unlinkat")
int BPF_KPROBE(on_unlink, int dfd, struct filename *name)
{
return 0;
}
SEC("cgroup/connect4")
int on_connect(struct bpf_sock_addr *ctx)
{
return 1;
}
char LICENSE[] SEC("license") = "GPL";go deeper
Be ready to say that an eBPF program is loaded with one fixed type, and that the type controls where it can attach and what its single ctx argument points at. Naming two examples, such as kprobe and XDP, is enough.
Explain all four consequences of the type: legal attach points, the context struct layout, the per-type helper allowlist, and how the return value is interpreted. Mention that expected_attach_type narrows the type further.
Show that you reason hook-first: the data you can see and the cost you impose are both decided by the attach point, so picking the type is a design decision made before any code is written. Be able to explain why a rejection happened at load rather than at run time.
Own the tradeoff of standardising hook choices across an eBPF-based agent fleet: hooks with narrow context and few helpers are cheaper to verify and safer to run everywhere, while richer hooks buy data at the cost of portability and privilege. Say how you would set that policy.
## What a "program type" actually is eBPF is not one language runtime that can be dropped anywhere in the kernel. Every program is loaded with a **program type** — an enum value passed in the `bpf(BPF_PROG_LOAD, ...)` system call, such as `BPF_PROG_TYPE_KPROBE`, `BPF_PROG_TYPE_TRACEPOINT`, `BPF_PROG_TYPE_XDP`, `BPF_PROG_TYPE_SCHED_CLS` (tc), `BPF_PROG_TYPE_SOCKET_FILTER`, `BPF_PROG_TYPE_CGROUP_SKB`, `BPF_PROG_TYPE_LSM` or `BPF_PROG_TYPE_TRACING` (fentry/fexit). That single choice is what turns a blob of bytecode into a program the kernel is willing to run at a specific place, and it is fixed for the life of the loaded program: you cannot load once and decide the type later. The type settles four separate things. ## 1. Where it may attach Each hook in the kernel accepts exactly one program type (sometimes narrowed further, see below). An XDP hook on a network device will not take a tracing program; a kprobe will not take a networking program. The attach call fails outright — this is not a runtime surprise but a load-time or attach-time rejection. Many types are narrowed further by an **expected_attach_type**, a second field supplied at load. `BPF_PROG_TYPE_CGROUP_SOCK_ADDR` is one type, but `BPF_CGROUP_INET4_CONNECT` and `BPF_CGROUP_INET4_BIND` are different attach types under it, and the kernel validates the program against the one you declared. `BPF_PROG_TYPE_TRACING` covers `BPF_TRACE_FENTRY`, `BPF_TRACE_FEXIT` and others in the same way. ## 2. The context argument An eBPF program is a function taking exactly one pointer argument, conventionally called `ctx`. What that pointer points at is decided entirely by the program type: | Program type | Context | |---|---| | kprobe | `struct pt_regs *` — the saved CPU registers at the probe point | | tracepoint | a per-tracepoint struct matching the event's format | | fentry/fexit (tracing) | typed function arguments, derived from BTF | | XDP | `struct xdp_md *` — a raw frame, before an skb exists | | tc / socket filter | `struct __sk_buff *` — a packet that already has an `sk_buff` | | cgroup connect/bind | `struct bpf_sock_addr *` | The verifier polices access to that struct field by field. Reading `ctx->data` makes sense for XDP and is nonsense for a kprobe, and the kernel knows which is which because it knows the type. This is also why the context is not "just memory": for some types the kernel rewrites your loads into different instructions at load time, so the struct you code against is a stable façade over internal kernel structures. ## 3. The helper allowlist BPF programs cannot call arbitrary kernel functions; they call **helpers** (`bpf_map_lookup_elem`, `bpf_ktime_get_ns`, `bpf_probe_read_kernel`, `bpf_perf_event_output`, and so on). The set of helpers available is defined per program type, not globally. That is a deliberate design: a helper that inspects the currently running task is meaningful in a tracing program that runs in the context of a syscall, and meaningless in an XDP program running in the NAPI softirq path where there is no relevant process. Calling a helper your type does not expose is a load-time rejection with a "unknown func" style error, not a crash. ## 4. The meaning of the return value The integer you return is interpreted by the caller of the hook, so its meaning is per type. For a kprobe or tracepoint it is effectively ignored — those are observation points, and returning 0 is the convention. For XDP and tc it is an action code the datapath obeys. For `BPF_PROG_TYPE_CGROUP_SKB` and the other cgroup types it is an allow/deny decision. For `BPF_PROG_TYPE_LSM` it is 0 for allow or a negative errno that the syscall then returns to user space. Getting this wrong is a silent behavioural bug rather than a load error, which makes it worth knowing cold. ## How you declare it in practice With libbpf you rarely write the enum yourself. You annotate each function with a `SEC()` string — `SEC("kprobe/do_unlinkat")`, `SEC("tp/syscalls/sys_enter_openat")`, `SEC("fentry/do_unlinkat")`, `SEC("xdp")`, `SEC("tc")`, `SEC("cgroup/connect4")`, `SEC("lsm/file_open")` — and libbpf maps that string to the program type and expected attach type when it loads the object. The section string is only a hint stored in the ELF; the kernel learns the type from the load call. ```c SEC("kprobe/do_unlinkat") int BPF_KPROBE(on_unlink, int dfd, struct filename *name) { return 0; } SEC("cgroup/connect4") int on_connect(struct bpf_sock_addr *ctx) { return 1; } ``` One object file can hold many programs of many types; each `SEC()`-annotated function is loaded as its own program with its own type. The practical consequence for an interview answer: "which hook do I want?" and "what data do I need?" are the same question, because the hook fixes the data.
- Two programs share a program type but only one attaches successfully at a given hook. What else is the kernel checking?The expected attach type, supplied at load alongside the program type. `BPF_PROG_TYPE_CGROUP_SOCK_ADDR` covers `BPF_CGROUP_INET4_CONNECT`, `BPF_CGROUP_INET4_BIND` and others; `BPF_PROG_TYPE_TRACING` covers `BPF_TRACE_FENTRY` and `BPF_TRACE_FEXIT`. The kernel validates context access against the declared attach type, then refuses to bind the program to a hook that does not match it.
- If the C source never names a program type, how does libbpf know which one to use?From the `SEC()` section string. libbpf keeps a table mapping section prefixes such as `kprobe/`, `tp/`, `fentry/`, `xdp`, `tc`, `cgroup/connect4` and `lsm/` to a program type and expected attach type, and passes those in the load call. An unrecognised section string is a libbpf error, not a silent default.
- Can a single compiled object contain programs of several different types?Yes, and it is normal. Every `SEC()`-annotated function in the ELF is loaded as an independent program with its own type, its own verification pass and its own attachment. They can share maps, which is the usual way an entry and an exit program, or a tracing and a networking program, cooperate.
A program type is like a job badge: it says which doors open for you, what desk you sit at, which internal tools you may call, and whose signature on your form actually counts.
saying these in an interview costs you the question
- Thinks any eBPF program can be attached to any hook
- Says the context argument is always a register dump
- Believes every BPF helper is callable everywhere
- Assumes the return value is always ignored
- Thinks the type is chosen when attaching, not when loading