Debug, Trace, and Profiling Authority: Prior-Art Survey
Survey of how existing systems scope and gate debug, trace, and profiling access. Each section states the verified fact and the lesson it carries for a capability OS.
1. GDB Remote Serial Protocol (gdbstub)
The GDB Remote Serial Protocol (RSP) is the wire protocol between a GDB client and a gdbstub running on or alongside the target. A stub exposes the target’s entire register file and address space to the connected client via a small set of packet types:
g/G— read and write all general-purpose registers.p/P— read and write individual registers.m/M/X— read and write arbitrary memory ranges.Z/z— set and clear software breakpoints, hardware breakpoints, and hardware watchpoints (read, write, or access).s/c— single-step and continue execution.
Feature negotiation (qSupported) lets client and stub advertise extensions,
but the baseline packet set already provides full read/write authority over the
target’s memory and execution state.
Lesson for capOS. A DebugSession capability is not a read-only observer
— it is a read/write authority over the target’s registers, memory, and control
flow. Attaching the stub to a process is itself the high-privilege act; the
session object must be issued by an explicit grant (e.g., a ProcessSpawner
debug grant or a ThreadControl-derived debug capability) rather than derived
from any lesser handle. The gdbstub pattern shows that the session boundary is
the right chokepoint: once a client holds the session capability the protocol
can proceed without further kernel checks.
2. Linux ptrace and the Yama LSM
Ambient-authority problem
Linux ptrace originally allowed any process to PTRACE_ATTACH to any other
process running under the same UID that was marked as dumpable. The kernel docs
summarize the risk: “a single user is able to examine the memory and running
state of any of their processes. For example, if one application (e.g. Pidgin)
was compromised, it would be possible for an attacker to attach to other running
processes (e.g. Firefox, SSH sessions, GPG agent, etc) to extract additional
credentials and continue to expand the scope of their attack.”
Yama ptrace_scope levels
The Yama Linux Security Module adds a sysctl kernel.yama.ptrace_scope with
four levels to progressively restrict this ambient authority:
| Level | Behaviour |
|---|---|
| 0 | Classic: PTRACE_ATTACH to any same-UID dumpable process. |
| 1 | Restricted: only descendants (or processes that have called prctl(PR_SET_TRACER, ...)) may be attached. |
| 2 | Admin-only: only processes holding CAP_SYS_PTRACE may attach. |
| 3 | No attach: PTRACE_ATTACH and PTRACE_TRACEME are blocked system-wide; the setting is irreversible once applied. |
Most Linux distributions now ship with level 1 as the default, but level 0 remains the kernel default if Yama is not loaded.
Lesson for capOS. Yama exists solely because ambient-authority ptrace is a
privilege-escalation footgun. The correct model is the inverse: no process
should be able to attach to another without an explicit, pre-granted capability.
In capOS terms, DebugSession attach must require a pre-issued debug capability
(analogous to level 3 everywhere, not level 0 with an opt-out). The parent
process or init can hold a ThreadControl-derived debug grant; a
RestrictedLauncher can be configured to never issue one. There is no ambient
fallback.
3. Linux perf_events and eBPF gating
perf_event_paranoid
/proc/sys/kernel/perf_event_paranoid is the sysctl controlling
what unprivileged processes may sample:
| Value | Effect |
|---|---|
| -1 | No scope or access restrictions; most permissive. |
| ≥ 0 | Raw tracepoints blocked for unprivileged users. |
| ≥ 1 | CPU-level (system-wide) profiling blocked; per-process only. |
| ≥ 2 | Kernel profiling blocked; user-space events only. |
Debian-based distributions additionally define 4 (block all perf for unprivileged users) and use it as the distro default.
CAP_PERFMON and CAP_BPF
Linux 5.8 introduced CAP_PERFMON to separate performance-monitoring authority
from the broad CAP_SYS_ADMIN. Holding CAP_PERFMON lets a process bypass
perf_event_paranoid scope checks. Similarly, CAP_BPF gates loading BPF
programs that have performance or tracing implications (e.g., kprobes, uprobes,
perf maps); attaching BPF to a kprobe tracepoint requires CAP_PERFMON or
CAP_SYS_ADMIN.
The split reflects the principle of least privilege: a profiling daemon should
not require CAP_SYS_ADMIN merely to sample hardware counters.
Lesson for capOS. Read-only sampling (hardware counters, ring buffer) is a
distinct authority from read/write debugging. capOS should issue a
Sampler capability (read-only, non-interrupting, no memory write) separately
from a DebugSession (register/memory read-write, breakpoints). The sampler
does not stop the target and transfers no writable authority; the
perf/CAP_PERFMON split is the prior-art justification for keeping these two
surfaces apart.
4. Fuchsia / Zircon: handle-scoped debug authority
debug_agent and zxdb
On Fuchsia the debugger is split into two components: debug_agent, a
component running on the target that holds process handles and communicates
with the kernel, and zxdb, the developer-facing client that connects to
debug_agent over a socket. The fuchsia.debugger FIDL library defines the
boundary:
DebugAgent— core protocol;AttachToaccepts a name pattern andFilterTypeto select which processes to attach to.ProcessInfoIterator,AttachedProcessIterator— read access to thread and process state.Launcher— creates newDebugAgentinstances.
The debug_agent acquires process handles from the kernel by being granted
them through the Zircon job/process handle tree. Zircon’s handle model means
that process operations (reading memory, setting breakpoints, receiving
exceptions) all require the caller to hold a process handle with the
appropriate ZX_RIGHT_* bits. A process that does not hold a handle to another
process cannot inspect or modify it, regardless of UID. The zxdb UI can
inspect handle tables of attached processes and displays their
ZX_RIGHT_READ/ZX_RIGHT_WRITE/ZX_RIGHT_INSPECT/ZX_RIGHT_SIGNAL rights
to the developer.
Exception delivery in Zircon is also capability-scoped: zx_task_create_exception_channel
creates a channel on a task (thread, process, or job) object; the caller must
hold that task handle. The resulting channel is read-only and can only receive
exception messages, not issue commands, which means observing crashes requires
the task handle but does not by itself grant write authority.
Lesson for capOS. Fuchsia demonstrates that a production debugger can be
built entirely on object-handle authority without ambient attach. The debug_agent
component acts like a bounded debug authority domain: it holds process handles
for the processes it is authorized to debug, and zxdb interacts only through
the FIDL protocol that debug_agent exposes. The capOS equivalent is a
DebugSession capability issued per target process, scoped to a running
session, with a separate read-only ExceptionChannel cap for crash observation.
5. seL4: TCB capability and debug-build gate
Hardware debug API
seL4 exposes hardware breakpoints, watchpoints, and single-stepping to userspace
via TCB object methods, but only when the kernel is built with
KernelDebugBuild (equivalently, HardwareDebugAPI=1 in the CMake config).
The available TCB invocations are:
seL4_TCB_SetBreakpoint— configure a breakpoint or watchpoint (virtual address, access type: read/write/exec, size).seL4_TCB_GetBreakpoint— read the current configuration.seL4_TCB_UnsetBreakpoint— disable a slot.seL4_TCB_ConfigureSingleStepping— break on every N-th instruction.
Each invocation takes a capability to the target TCB. Only a holder of that TCB capability can manipulate the thread’s debug registers.
Debug-only kernel syscalls
KernelDebugBuild also enables:
seL4_DebugSnapshot— outputs a CapDL dump of the current kernel capability state to the serial console.seL4_DebugDumpScheduler— dumps TCB addresses, thread names, instruction pointers, priorities, and scheduler states.
These syscalls expose global kernel state and are intentionally excluded from verified (proof) builds where the information flow would violate the formal security model.
Lesson for capOS. seL4 gates per-thread debug authority on possession of
the TCB capability, which is the right model. capOS’s DebugSession should
similarly be derived from ThreadControl so that only the process or entity
that holds ThreadControl for a thread can open a debug session on it.
The seL4_DebugSnapshot pattern also shows that a system-wide cap-table
snapshot is a separate, higher-privilege operation from per-thread debug access;
in capOS a read-only CapTableSnapshot authority can be issued for audit
purposes without granting register/memory write access.
6. Genode: capability-session GDB monitor
Architecture
Genode implements user-level debugging via a GDB monitor component that interposes between a target application and its parent. The GDB monitor:
- Intercepts session requests from the target before they reach the parent.
- Provides local virtual implementations of the CPU service, RM (region-map/address-space) service, and ROM service, wrapping the real core implementations.
- Exposes a gdbserver protocol endpoint over a terminal session (TCP or UART).
This gives the GDB monitor “full control over all threads and memory objects (dataspace) and the address space of the target.” The monitor holds real capabilities to the target’s CPU and address-space sessions; the target’s own session handles are virtualized stubs that forward to the monitor.
Capability session scoping
Genode’s Cpu_session interface allows retrieving and modifying thread register
and execution state. The API comment in the framework explicitly notes that
these operations are “primarily designated for realizing user-level debuggers.”
Because the monitor interposes the CPU session, it holds the same authority the
parent would hold, but the target holds only stubs — the target cannot see or
touch its own debug registers directly.
Lesson for capOS. The Genode monitor pattern reinforces that debugging
authority flows from capability delegation, not from process identity. The
interposition model also clarifies the ring-trace design decision: debug_tap
in capOS captures SQE/CQE ring records passively and does not require
interposing a CPU session, which keeps ring-trace authority weaker and
non-interrupting by construction. A full DebugSession (register read/write,
breakpoints) requires explicit session acquisition from the parent or init,
matching the Genode monitor’s explicit CPU-session grant.
Applicability to capOS
The cross-system survey points to a consistent set of design invariants:
-
DebugSessionattach is an explicit, audited capability grant, not ambient. The anti-pattern is Linux ptrace at level 0; Yama level 3 is the correct default posture. In capOS no process inherits the ability to debug another: aDebugSessionis derived fromThreadControl, issued by the process’s parent or init, and recorded in the audit log. -
Read-only cap-table snapshots transfer no authority. seL4’s
seL4_DebugSnapshotis a separate, opt-in, debug-build-only facility. In capOS aCapTableSnapshotcap can be issued for audit visibility without granting any write access to the observed process. -
Ring-trace builds on
debug_tapand does not stop the target.perf/CAP_PERFMONshows that sampling is a distinct authority class from full debugging. capOSdebug_tapring records are append-only, non-interrupting, and do not feed back into the target’s execution — matching the sampler authority class, not theDebugSessionclass. -
Sampler does not stop the target. Hardware performance counter sampling (
CAP_PERFMONsemantics) and ring-record sampling (debug_tap) are passive read surfaces. ADebugSessionthat can set breakpoints, modify registers, or write memory is a distinct, higher-privilege capability and must not be conflated with passive tracing. -
Exception observation is weaker than debug write authority. Zircon’s
zx_task_create_exception_channelreturns a read-only channel. capOS should provide a similarExceptionObservercapability (receive crash notifications, no write access) independent ofDebugSession.
Sources
- GDB Remote Serial Protocol (Embecosm application note): https://www.embecosm.com/appnotes/ean4/embecosm-howto-rsp-server-ean4-issue-2.html
- GDB remote protocol packet reference (Apple/GNU): https://developer.apple.com/library/archive/documentation/DeveloperTools/gdb/gdb/gdb_33.html
- Linux kernel Yama documentation: https://docs.kernel.org/admin-guide/LSM/Yama.html
- Linux perf events security (kernel docs): https://docs.kernel.org/admin-guide/perf-security.html
perf_event_open(2)man page: https://man7.org/linux/man-pages/man2/perf_event_open.2.html- LWN: Introducing
CAP_PERFMON: https://lwn.net/Articles/816647/ - Fuchsia
fuchsia.debuggerFIDL reference: https://fuchsia.dev/reference/fidl/fuchsia.debugger - Fuchsia debugger (zxdb) overview: https://fuchsia.dev/fuchsia-src/development/debugger
- Fuchsia Zircon handles and rights: https://fuchsia.dev/fuchsia-src/concepts/kernel/handles
- Fuchsia exception handling: https://fuchsia.dev/fuchsia-src/concepts/kernel/exceptions
- seL4 API reference (debug syscalls and TCB hardware debug): https://docs.sel4.systems/projects/sel4/api-doc.html
- seL4 hardware debug tutorial: https://docs.sel4.systems/projects/sel4-tutorials/debugging-userspace.html
- seL4 kernel configurations (
KernelDebugBuild): https://docs.sel4.systems/projects/sel4/configurations.html - Genode GDB developer resources: https://genode.org/documentation/developer-resources/gdb
- Genode session interfaces (CPU session): https://genode.org/documentation/genode-foundations/20.05/functional_specification/Session_interfaces_of_the_base_API.html