Background

systemd​ is the default service manager on most major Linux distributions. In systemd so-called ‘unit’ files define services that are loaded upon startup, when a certain event occurs or on a timer. Most system services have a large attack surface through the syscalls they can issue, the files they can read or write, the capabilities they have been assigned or the network endpoints it can reach. systemd ships with a rich set of sandboxing and security directives to limit the access a service has. Examples of such directives are:

  • ProtectSystem=
  • SystemCallFilter=
  • CapabilityBoundingSet=
  • RestrictAddressFamilies=
  • PrivateTmp=
  • NoNewPrivileges= To measure how many of these directives are used by a particular unit file, systemd comes bundled with systemd-analyze security which gives a score from 0.0 (completely isolated) to 10.0 (exposed).

In practice, many unit-files shipped by major distributions have scores of 8–10. Such a high score can be due to the broad access a service needs, but often it is clear that the distribution did not attempt any hardening. One example would be the unit-file for the SSH Daemon on Ubuntu 24.04 LTS which has a score of 9.6:

[Unit]
Description=OpenBSD Secure Shell server
Documentation=man:sshd(8) man:sshd_config(5)
After=network.target auditd.service
ConditionPathExists=!/etc/ssh/sshd_not_to_be_run

[Service]
EnvironmentFile=-/etc/default/ssh
ExecStartPre=/usr/sbin/sshd -t
ExecStart=/usr/sbin/sshd -D $SSHD_OPTS
ExecReload=/usr/sbin/sshd -t
ExecReload=/bin/kill -HUP $MAINPID
KillMode=process
Restart=on-failure
RestartPreventExitStatus=255
Type=notify
RuntimeDirectory=sshd
RuntimeDirectoryMode=0755

[Install]
WantedBy=multi-user.target
Alias=sshd.service

This service unit-file does not include any of the hardening directives systemd comes bundled with. To improve this, the operator must:

  1. discover, by reading documentation and source code, what the service actually needs,
  2. translate that into the correct combination of systemd directives, optionally a MAC policy (SELinux/AppArmor),
  3. validate that the resulting configuration does not break the service under realistic workloads, and
  4. re-validate after every package upgrade, because new code paths may need new syscalls, paths, or capabilities.

No existing tool performs this entire loop. The closest, shh​ (“Systemd Hardening Helper”), wraps a service with strace​, observes its behaviour, and emits a drop-in hardening config. It is explicitly non-portable across kernels, libc versions, and systemd releases, and it only produces systemd​ directives. Hence, it does not generate complementary SELinux or AppArmor policy. Upstream systemd has an open feature request for an aa-genprof​-equivalent mode for systemd-analyze security (systemd/systemd#26994). However, it remains unimplemented.

The gap this project addresses is the absence of a cross-layer hardening pipeline that combines static and dynamic analysis, generates a coherent confinement stack across systemd, MAC (SELinux/AppArmor), validates the result with LLM-synthesised functional tests, and is portable across distributions.

Objectives

The aim of the work is to design, implement, and evaluate a tool that automatically hardens systemd services while guaranteeing functionality.

Specifically:

  1. Multi-source profiling. Combine static analysis of the unit file and package contents with dynamic tracing (strace​, auditd, etc.) to capture syscalls, file accesses, capabilities, network endpoints, and IPC usage.
  2. Cross-layer hardening generation. From profiling passes, produce a coherent confinement stack:
    • a systemd​ drop-in (ProtectSystem=​, ReadWritePaths=​, SystemCallFilter=​, CapabilityBoundingSet=​, RestrictAddressFamilies=, etc.),
    • optionally, a complementary SELinux CIL module or AppArmor profile that confines the service domain to the observed labels/paths/ports, and
    • extra rules for confinement technologies like Landlock, bwrap or similar.
  3. LLM-validated functional testing. Use a local LLM to synthesise runNixOSTest-style or similar standalone tests from the service’s declared behaviour (unit file, man page, package metadata). Run each candidate hardening config in an isolated VM, execute the functional tests, and select the most restrictive config that passes. The LLM is also used to diagnose failures and propose minimal relaxations.

Requirements

  • Strong understanding of systemd, Linux namespaces, capabilities, cgroups, MAC (SELinux/AppArmor), etc.
  • Proficiency in at least one of Python, Rust, or Go for tooling.
  • Experience with VMs or containers (QEMU/KVM, LXC, Docker/Podman) for isolated testing.
  • Familiarity with NixOS is a plus, given the runNixOSTest​ framework and nix flake check integration.
  • Familiarity with LLMs and prompt engineering is a plus, given the LLM-based test synthesis and failure diagnosis.
  • Mindset to learn the additional skills.

References and sources

Existing tools

Relevant research