Skip to content

Instantly share code, notes, and snippets.

@devton
Created January 16, 2026 01:25
Show Gist options
  • Select an option

  • Save devton/71e6fb028b31c4d965c072c4faa48587 to your computer and use it in GitHub Desktop.

Select an option

Save devton/71e6fb028b31c4d965c072c4faa48587 to your computer and use it in GitHub Desktop.

Linux Systemd Hardening via Strace Profiling

This skill outlines a methodology for determining the minimal systemd security configuration for any application by profiling its runtime behavior using strace.

1. Filesystem Hardening (ReadWritePaths, ReadOnlyPaths)

Goal: Determine strictly which paths the application reads and writes to.

profiling

Run the application in the foreground (if possible) or attach to the process.

# Capture file operations
strace -ff -o /tmp/app_fs.strace -e trace=file,process <binary> <args>

Analysis

1. Identify Write Access (for ReadWritePaths) Filter for flags indicating write operations (O_WRONLY, O_RDWR, O_CREAT, O_TRUNC, O_APPEND).

grep -R --line-number 'openat(' /tmp/app_fs.strace* | \
grep -E 'O_WRONLY|O_RDWR|O_CREAT|O_TRUNC|O_APPEND' | \
sed -E 's/.*openat\([^,]+, "([^"]+)".*/\1/' | sort -u
  • Action: Add these paths to ReadWritePaths=.
  • Tip: Verify if the application does "lazy" writing (e.g., temp files for large uploads/buffers) by forcing load/traffic during the trace.

2. Identify Read Access (for ReadOnlyPaths / InaccessiblePaths) Filter for all openat calls to see what is touched.

grep -RhoE 'openat\([^,]+, "([^"]+)"' /tmp/app_fs.strace* | \
sed -E 's/.*"([^"]+)"/\1/' | sort -u
  • Noise Filtering:
    • ENOENT on /etc/ld.so.preload: Normal loader behavior.
    • ENOENT on /var/run/nscd/socket or /var/lib/sss/*: Normal glibc NSS checks. Not a failure if you don't use LDAP/SSSD.
  • Action: Ensure these paths are readable. Everything else can potentially be InaccessiblePaths.

2. Execution Restriction (ExecPaths, NoExecPaths)

Goal: Prevent the application from executing arbitrary binaries (shell injection mitigation).

Profiling

strace -ff -o /tmp/app_exec.strace -e trace=process <binary> <args>

Analysis

Look for execve calls. mmap of libraries does not count as execution for this directive.

grep -RhoE 'execve\("([^"]+)"' /tmp/app_exec.strace* | \
sed -E 's/.*"([^"]+)".*/\1/' | sort -u
  • Result:
    • If only the binary itself appears: ExecPaths=<path/to/binary> and NoExecPaths=/.
    • If other binaries appear (e.g., wrappers): Add them to ExecPaths.

3. Namespace Isolation (RestrictNamespaces)

Goal: Prevent the application from manipulating process namespaces (container breakouts, hidden processes).

Profiling

strace -ff -o /tmp/app_ns.strace -e trace=clone,unshare,setns <binary> <args>

Analysis

Check for usage of CLONE_NEW* flags, unshare(), or setns().

grep -R --line-number -E 'clone\(|unshare\(|setns\(' /tmp/app_ns.strace*
  • Interpretation:
    • Standard clone() for threads/processes is safe.
    • Look specifically for: CLONE_NEWNS, CLONE_NEWNET, CLONE_NEWUSER, CLONE_NEWPID, CLONE_NEWIPC, CLONE_NEWUTS.
  • Action:
    • If no namespace flags are seen: RestrictNamespaces=yes.
    • If specific flags are seen: RestrictNamespaces=~<namespace_type> (block others).

4. Capability Restriction (CapabilityBoundingSet)

Goal: Drop all unneeded Linux capabilities.

Profiling

strace -ff -o /tmp/app_caps.strace \
-e trace=capget,capset,setuid,setgid,setgroups,prctl,bind,chown,fchownat \
<binary> <args>

Analysis

1. Port Binding Check for bind() on ports < 1024.

grep 'bind(' /tmp/app_caps.strace*
  • Action: If found, keep CAP_NET_BIND_SERVICE.

2. Privilege Dropping Check for setuid, setgid, setgroups.

grep -E 'setuid|setgid|setgroups' /tmp/app_caps.strace*
  • Action: If the app starts as root and drops privileges, keep CAP_SETUID and CAP_SETGID.
  • Hardening Opportunity: If possible, configure systemd to start with User=<non-root> and use AmbientCapabilities=CAP_NET_BIND_SERVICE (if needed), removing the need for CAP_SETUID/GID entirely.

3. Other Capabilities

  • CAP_DAC_OVERRIDE: Often indicates bad file permissions/ownership rather than a real need. Fix permissions instead of granting this.
  • CAP_CHOWN: Only if the app explicitly runs chown.

Final Configuration: Start with:

CapabilityBoundingSet=

Add only what is strictly observed (e.g., CapabilityBoundingSet=CAP_NET_BIND_SERVICE).


5. Verification

After applying systemd changes:

  1. Reload & Restart:
    systemctl daemon-reload
    systemctl restart <service>
  2. Check for Crashes:
    journalctl -u <service> -b --no-pager | tail -200
  3. Audit Failures: If the service fails, re-run strace specifically looking for EACCES or EPERM errors.
    grep -R --line-number -E 'EACCES|EPERM' /tmp/debug.strace*
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment