Skip to content

v0.2.0

Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 28 Oct 05:05
· 14 commits to main since this release
Immutable release. Only release title and notes can be modified.
v0.2.0
266b018

v0.1 watched and wrote things down. v0.2.0 is the first release that
acts. Audit mode is still the default and still changes nothing, so an
upgrade alone won't touch a single process. Set mode = "enforce" and
the tiers you've been reading about become real: a throttle-tier
incident is slowed down, and a quarantine-tier one is held in the
kernel, frozen and cut off from the network, where it waits until you
release it or kill it.

Added

  • Enforce mode. mode = "enforce" is no longer refused outright.
    aegisd now refuses it only when the BPF LSM is missing from
    /sys/kernel/security/lsm or nft isn't installed, and points at the
    mode line when it does. (#29, #30)
  • Throttle. At the throttle tier the process tree moves into
    aegis-incident-<id>.scope under aegis-throttle.slice, limited by
    the new [throttle] settings: cpu_quota_percent (25% of one CPU)
    and memory_high_mb (256 MiB). It keeps running, and keeps being
    scored. (#29)
  • Quarantine. At the quarantine tier the tree's members are held in
    the kernel, then moved into aegis-incident-<id>-q.scope under
    aegis-quarantine.slice with any children aegisd finds under them in
    /proc, frozen, and cut off by an nftables rule. A second walk after
    the freeze attaches anything new. For 30 seconds after the decision,
    each child the tree forks gets its own job: held and frozen under a
    quarantine, moved into the scope under a throttle. Pid 1, kthreadd,
    aegisd and the tree's ancestors are never touched. (#29)
  • Delegated incident scopes. Both incident scopes are started with
    Delegate=yes, because systemd only lets a process join a running
    scope that way. Their cgroups stay root-owned, so a tree that isn't
    root gains nothing, and limits, freezing and kill still cover
    everything below the scope. The end-to-end suite now fails any
    enforce-mode scenario whose incident records a failed action.
  • The hold. Thirteen BPF LSM programs refuse a held process fork and
    clone, connect, send, mount, bpf(), module loading, ptrace, writes
    through any file descriptor, new user namespaces, and any signal to
    aegisd. A process that isn't held costs one map lookup per hook.
    Separately, in audit mode too, no other process can ptrace aegisd,
    root included: strace -p, gdb -p and /proc/<pid>/mem get
    EPERM. Root can still stop it. (#26)
  • aegisctl incidents release and kill. Release lifts a
    quarantined tree's holds while it's still frozen, thaws it and
    restores its network, or lifts a throttle's limits. Kill sends
    SIGKILL to a quarantined tree, frozen or not. Both work from the
    recorded actions, so they still work after a daemon restart and in
    audit mode. After a reboot there's nothing left to release: either
    one, on an incident from an earlier boot, touches nothing on the host
    and records it as ended. (#30)
  • Actions on the record. Each incident now carries an actions
    list: what was done, to which pids, with the unit name or nft handle
    needed to undo it, and any error. Verdicts gain throttled and
    quarantined, which an incident only reaches once the action worked.
    Reaching one sends a record to telemetry with a reason starting
    enforcement done:. Incidents written by v0.1 still load. (#28)
  • Webhooks. [[telemetry.webhook]] posts JSON or a Slack message
    each time an incident's verdict rises (would_throttle, throttled,
    would_quarantine, quarantined), and on every enforcement failure,
    including a job dropped because the enforcer's queue was full. They
    fire in audit mode too. (#33)
  • OTLP metrics. Every 60 seconds aegisd sends its event, drop,
    enforcement and telemetry counters, and its open incidents by
    verdict, to <endpoint>/v1/metrics. (#34)
  • Signed releases. Releases are published with a cosign signature
    over SHA256SUMS, build provenance attestations, and SPDX and
    CycloneDX SBOMs. Installation
    shows how to verify them. (#38)

Changed

  • SELinux policy 0.2.0. aegisd_t may now run busctl,
    systemctl and nft, talk to systemd over D-Bus, and hand nft to
    its own domain. Each rule fixes a denial seen on Rocky 10 while a
    throttle, a quarantine, a release and a kill ran. The unit gains
    CAP_NET_ADMIN and AF_NETLINK for nft, which makes six
    capabilities. (#31)
  • Dependencies. which 8, toml 1.1 and sha2 0.11. (#24)

Fixed

  • A preloaded library could scrub LD_PRELOAD before the check.
    v0.1 read /proc/<pid>/environ a few milliseconds after each exec,
    and that's the process's own memory. The environment is now read in
    the kernel at exec, before any of the new image's code runs.
    Processes that were already running when aegisd started still get
    the /proc read. (#25)
  • Uninstalling left systemd holding on. After dnf remove,
    aegis.slice stayed active and the removed unit stayed loaded. On
    erase the package now reloads systemd, and stops the aegis slices and
    deletes the inet aegis table when no incident scope is left. If any
    tree aegisd moved is still there, released ones included, it keeps
    all of that, warns you, and the
    uninstall steps
    say how to end it.
  • The end-to-end SELinux check never looked. Over ssh, ausearch
    read its stdin instead of the audit log and answered <no matches>
    every time, so the suite's "no denials" result for v0.1 proved
    nothing. It now reads the log and fails if it finds no events at all.
    The suite also gains eight enforce-mode scenarios, all run on Rocky
    10 with SELinux enforcing. (#49)

Security

  • OTLP failures could log a credential. The first failed export
    logged the HTTP client's error text, which can quote the request URL,
    and a URL can carry a token. aegisd now logs the status code or the
    kind of error instead, for OTLP and webhooks alike. (#33)

Known limits

  • The hold starts when aegisd writes the process into the kernel's
    map, not when the score crosses the line, so a few milliseconds of
    activity can land first.
  • Writes made before the hold, including through a shared memory
    mapping, reach the disk. The overlay that would catch them arrives in
    v0.4.0.
  • Holds don't survive an aegisd restart. The freeze and the network
    rule do.
  • A child aegisd finds in /proc but never saw fork is frozen and cut
    off, not held, so it can act until the freeze. If the enforcer falls
    more than 30 seconds behind, children a throttled tree starts in that
    time can stay outside its limits.
  • If the move into the quarantine scope fails, aegisd doesn't retry it.
    The tree stays held but not frozen, at would_quarantine. A restart
    doesn't retry the move either, and it lifts the holds, so after one
    the tree runs free until you kill or release it.
  • systemctl reload nftables, or any full ruleset flush, deletes the
    inet aegis table and with it every quarantined tree's drop rule.
    They stay frozen. After that, rule handles can be reused
    within the boot, so release affected incidents one at a time. A fix
    that puts the table back is planned for v0.2.1.
  • A frozen process looks frozen to anything that inspects it, and a
    released one stays under aegis-quarantine.slice.
  • An environment longer than 32 KiB counts as unreadable, so the
    process gets no allowlist credit.
  • The quarantined tree's traffic is dropped, not answered. The honeypot,
    and the nftables redirection that feeds it, are planned for v0.3.0.

The full list is in the
threat model.