
Kernel-runtime defense framework for AF_ALG vulnerabilities, featuring eBPF socket tracing, Ansible hardening, and a crypto auditor for drift detection.
A kernel-runtime defense framework for the Linux AF_ALG (Address Family
Algorithm, family 38) subsystem. Built for Security Operations Centers
running enterprise Linux fleets where Zero Trust must extend into the
kernel, not stop at the network edge.
AF_ALG exposes the kernel crypto API to userspace through a socket
interface (socket(AF_ALG, SOCK_SEQPACKET, 0)). It was originally added
for embedded systems without /dev/crypto and has since accumulated a
disproportionate share of kernel CVEs because it presents kernel-mode
crypto code to unprivileged callers — a classic surface-area mismatch.
In a typical enterprise build:
connect(),
bind(), or DNS see nothing — AF_ALG traffic never leaves the kernel.This framework treats every AF_ALG socket creation as a high-signal
event and reduces the surface that makes those events exploitable.
| Zero Trust principle | Control in this framework |
|---|---|
| Never trust, always verify | eBPF tracer logs every AF_ALG socket-create attempt with pid/uid/comm |
| Assume breach | Crypto auditor diffs kernel posture against a signed baseline |
| Least privilege | systemd RestrictAddressFamilies + capability bounding on managed units |
| Microsegmentation (kernel-side) | unprivileged_userns_clone=0 removes the userns pivot used by exploits |
| Continuous validation | CI validates audit reports against a versioned schema on every change |
.
├── ebpf/ Runtime observability (BCC tracer + allowlist)
├── ansible/ Configuration-as-Code (sysctl + systemd drop-ins)
├── systemd/ Standalone systemd drop-in for non-Ansible hosts
├── auditor/ Kernel state auditor (Python)
├── schemas/ JSON Schema for audit-report ingestion
├── scripts/ Helper shell scripts (linted by CI)
├── tests/ Unit tests + report fixtures
└── .github/workflows/ CI: shellcheck + JSON schema validation + lint
ebpf/af_alg_tracer.py attaches a kprobe to security_socket_create.
The probe filters on family == 38 at the BPF program level so the
verifier prunes unrelated socket creations and the per-event overhead
stays in nanoseconds. It emits one JSON record per attempt:
{
"@timestamp": "2026-05-02T09:14:11.412041+00:00",
"event": {"category": "kernel", "action": "af_alg_socket_create", "severity": "high"},
"process": {"pid": 1394, "tgid": 1394, "comm": "suspicious_bin"},
"user": {"uid": 1000, "gid": 1000},
"socket": {"family": 38, "family_name": "AF_ALG", "type": 5, "protocol": 0},
"host": {"name": "web-prod-04"}
}
Pipe stdout into Vector, Fluent Bit, or journald (via systemd-cat).
A comm-name allowlist (/etc/af-alg-defense/allow.list) suppresses
known-good consumers without losing the ability to detect deviations.
The kprobe target is the LSM hook, so events fire on intent —
even attempts that would be denied by seccomp or RestrictAddressFamilies
still produce a record. That is exactly what a SOC wants for behavioral
baselining.
ansible/roles/af_alg_hardening/ applies two hardening layers:
Sysctl drop-in (/etc/sysctl.d/90-af-alg-defense.conf):
kernel.unprivileged_userns_clone=0 — removes the userns pivot used
by most AF_ALG escalation chains.user.max_user_namespaces=0 — distro-portable defence-in-depth.Systemd drop-in (/etc/systemd/system/<unit>.d/50-af-alg-restrict.conf):
Uses RestrictAddressFamilies as an allow-list (not deny-list). The
unit is permitted AF_UNIX AF_INET AF_INET6 AF_NETLINK; any other
family — AF_ALG included — fails with EAFNOSUPPORT because systemd
enforces it through cgroup-attached BPF that the application cannot
disable. The drop-in also strips CAP_SYS_ADMIN and applies
ProtectKernel* to close the most common escalation paths.
Apply with:
ansible-playbook -i inventory ansible/site.yml --check --diff # preview
ansible-playbook -i inventory ansible/site.yml # enforce
For hosts without Ansible, drop the standalone file in place:
sudo ./scripts/deploy_dropin.sh nginx.service
auditor/crypto_auditor.py produces a JSON security-posture report by
inspecting:
/proc/crypto — every registered cipher / hash / aead, with
FIPS flags and self-test status./sys/module/ — loaded modules in the crypto subtree, with taint
flags and parameter snapshots./proc/sys/kernel/, /proc/sys/user/ — sysctls that gate AF_ALG
attack paths./sys/kernel/security/lockdown — kernel lockdown mode.The report is keyed by stable finding IDs (FND-001 through FND-005
at present) so SIEM rules can suppress individual findings without
dropping the whole document. Drift detection compares posture against
a baseline:
sudo ./auditor/crypto_auditor.py --output /var/log/af-alg-defense/today.json
sudo ./auditor/crypto_auditor.py \
--baseline /var/log/af-alg-defense/baseline.json \
--fail-on-drift
The schema lives in schemas/audit_report.schema.json (Draft 2020-12)
and is validated in CI on every push.
.github/workflows/ci.yml runs four jobs on every push and PR:
*.sh and shebang-bearing script.audit_report.schema.json,
then runs the auditor live on the GH runner kernel and validates
the resulting report. Fixtures in tests/fixtures/ are also checked.ruff check .).A failed schema check blocks merges, which prevents downstream SIEM parsers from breaking on a silently-renamed field.
af_alg_socket_create event from a non-allowlisted comm — page
on first occurrence, do not aggregate.crypto_modules between consecutive auditor runs on a
host where module loading should be frozen.hardened=false after a hardening playbook run —
indicates manual tampering or drift from a parallel config system.lockdown field transitions from integrity/confidentiality to
none — strong indicator of kernel-state tampering.allow.list for known-good
consumers (cryptsetup at boot is the usual one).baseline.json.af_alg_systemd_services set to one low-risk unit. Watch for
EAFNOSUPPORT errors in journald.systemd-analyze security <unit> should show the restriction is enforced.--fail-on-drift and route
non-zero exits into the on-call queue.af_alg if it's already in use. Module unloading
is out of scope because legitimate boot-time consumers may still be
running. Use modprobe.blacklist=af_alg on the kernel command line
if you've confirmed nothing on the host needs it.security_socket_create kprobe target).uname -r.CAP_BPF (or root) to load the tracer; read access to /proc/crypto
for the auditor (no privileges required to read it).Apache-2.0. See LICENSE.