Skip to content
mdspanPublic

About

CHERI-inspired C++ library that labels pointers as secret, pii, or untrusted using Intel LAM, and enforces label policies on syscalls with seccomp-BPF.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

lamprov

lamprov is a CHERI-inspired C++ library that stores a provenance label in the upper bits of a pointer, using Intel's Linear Address Masking (LAM) feature. A label marks memory as secret, pii, or untrusted, and adds an instance number. Pointers derived from a labeled pointer keep its label, and a seccomp-BPF filter reads the label when a labeled pointer is passed to a syscall.

Layouts

A layout sets where the label goes. init() selects the layout (InitOptions::layout) and turns on LAM with ARCH_ENABLE_TAGGED_ADDR. Call init() before starting any threads, because the kernel refuses to turn on LAM once the process has started a thread. Create arenas after calling init().

Layout Label Requires
U57 (LayoutKind::lam, the default) pointer bits 62:57: one bit each for secret, pii, and untrusted in bits 57 to 59, and an instance from 0 to 7 in bits 62:60 LAM_U57, kernel tier u57 or u48
U48 (LayoutKind::u48) pointer bits 62:48: the same fields, with values up to 32767 LAM_U48, kernel tier u48

init() and lamprov probe find the kernel tier by passing a labeled buffer to read and write:

Tier Meaning
none LAM is not available
rejects_labels a syscall fails with EFAULT on a labeled buffer, even when the filter allows the call
u57 syscalls accept labeled pointers, with 6 tag bits
u48 as u57, and LAM_U48 is available, with 15 tag bits

Without LAM, init() fails if InitOptions::require_lam is set. Otherwise arenas return unlabeled pointers, or, with InitOptions::label_without_lam, labeled pointers that the filter can read but that fault when dereferenced.

Library

  • Arena allocates from mmap memory, and every pointer it returns carries the arena's label. It has size classes, per-thread caches, and guard pages. LabeledAllocator, LabeledVector, and LabeledString put container storage in an arena.
  • policy::parse() and policy::compile() turn a .lamp policy file into a chain of seccomp filters. seccomp::install() installs the chain.
  • seccomp::install_report_handler() installs a SIGSYS handler that reports calls caught by trap and report clauses, with a backtrace.
  • supervisor::Supervisor reports calls caught by notify clauses and checks the buffers of syscalls that take an array of buffers, such as writev and sendmsg.
  • audit::Session (in the lamprov_ebpf library) uses eBPF to report labeled pointers passed to syscalls and to library functions such as printf.
  • trace::Session uses eBPF to record where labeled pointers go, from an allocation site or label to a syscall or function argument.
  • liblamprov_shim.so is a library for LD_PRELOAD. It turns on LAM before main and labels a thread's heap allocations while a Scope is active. In site mode, it labels each heap allocation by its malloc call site. It also counts copies from labeled into unlabeled buffers.

Policies

examples/policy.lamp is an example policy that uses every rule kind:

label secret = class(secret)     label pii = class(pii)     label untrusted = class(untrusted)
egress    write pwrite64 send sendto : buffer arg 1
    any(secret) -> errno(EPERM) report       any(pii) -> notify
name-sink openat execve unlink connect       # pointer args per signature table
    any(untrusted) -> trap
indirect  writev pwritev pwritev2 sendmsg sendmmsg process_vm_writev vmsplice -> notify
forbid    io_uring_setup io_uring_enter io_uring_register

A label line names a set of labels, selected by class(C | ...), instance(N), or value(V). The rule kinds are:

  • egress: checks a buffer argument.
  • name-sink: checks path name and socket address arguments.
  • indirect: sends the call to the supervisor, which reads the buffer pointers from the array.
  • forbid: gives every call the same action, errno(ENOSYS) by default.

A clause has the form any(LABEL) -> ACTION, and the actions are errno(E), errno(E) report, trap, kill, and notify. The first matching clause decides the action, and calls that match no clause are allowed. The filter kills the process on ia32 and x32 syscalls, whose 32-bit pointers carry no labels.

Tools

Command Effect
lamprov compile [--stats] POLICY prints the BPF program of a policy, or its instruction counts and longest path
lamprov run [--report-loss] --policy POLICY -- COMMAND runs COMMAND under the filter with the shim preloaded, and supervises it when the policy has notify clauses or indirect rules
lamprov audit [--sinks FILE] --policy POLICY -- COMMAND reports labeled pointers that COMMAND passes to syscalls and library functions, when the policy selects their label
lamprov trace [--dot FILE] [--json FILE] -- COMMAND runs COMMAND in site mode and writes a graph of where its labeled pointers go
lamprov probe [--json] checks LAM support, how the kernel handles labeled pointers, and the kernel tier
lamprov-bench [--quick] BENCHMARK|all runs the filter, alloc, and observers benchmarks

lamprov probe takes these options for further checks:

  • --observers: what eBPF and /proc see of a labeled pointer.
  • --propagation: whether libc and libstdc++ functions return pointers with the label intact.
  • --filter-cost: write latency with and without a labeled filter.
  • --fwrite: which fwrite sizes pass the caller's pointer to write.

The lamprov commands except probe take --layout u57|u48; probe runs its checks under U57. audit, trace, and probe --observers require eBPF support, Linux 5.17 or later, kernel BTF, CAP_BPF, and CAP_PERFMON.

Build

Requirements: CMake 3.22 or later, Ninja, and GCC 11+ or Clang 14+ on x86-64 Linux. Catch2 v3 and nlohmann_json come from the system if find_package finds them, and from GitHub otherwise.

cmake --preset gcc      # or: clang
cmake --build --preset gcc
ctest --preset gcc

The gcc and clang presets build in Debug mode into build-gcc/ and build-clang/ with -Wall -Wextra -Werror. The bench preset builds lamprov-bench in Release mode into build-bench/.

eBPF support requires libbpf 0.7.0 or later, a Clang with the BPF target, and bpftool. tools/bootstrap installs them on Debian. The LAMPROV_BPF cache variable is AUTO (the default), ON, or OFF.

The project has no install target. lamprov run and lamprov trace load liblamprov_shim.so from the directory of the lamprov executable, so copy both files from build-gcc/tools/lamprov/ into the same directory.

Tests

Tests that call init() or install a filter run in forked children. Each test belongs to a lane, a condition that the test requires. If the condition fails, the test prints a line starting with LAMPROV_SKIPPED:, and CTest reports it as skipped.

Lane Condition
host x86-64 Linux with seccomp
lam ARCH_ENABLE_TAGGED_ADDR with 6 bits succeeds
uaccess lam, and the kernel tier is u57 or u48
u48 the kernel tier is u48, and ARCH_ENABLE_TAGGED_ADDR with 15 bits succeeds
bpf eBPF support was built, and the process has CAP_BPF, CAP_PERFMON, kernel BTF, and the raw_syscalls tracepoints

Threat model

The checks catch accidental leaks by code that does not handle labels, such as third-party libraries, legacy modules, and bugs. They do not contain hostile code: any code in the process can clear label bits or copy data into unlabeled memory.

About

CHERI-inspired C++ library that labels pointers as secret, pii, or untrusted using Intel LAM, and enforces label policies on syscalls with seccomp-BPF.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Contributors

Languages