Most runtime security tools quietly assume the target hardware was built after 2018. For teams deploying boards with kernels from the 3.x/4.x era, or earlier, such as ARM32, “modern” security tooling treats the fleet like it doesn’t exist.
This post is about that gap, and about how Exein closes it with the Runtime Linux Lite agent, an agent built specifically for legacy embedded Linux.
The fleet nobody’s protecting
There’s a whole class of Linux devices the security industry has effectively given up on. ARMv6 and ARMv7 boards. 128 to 256 MB of RAM. Kernels older than 2.6.37, often a vendor-locked build that hasn’t been touched in years. Industrial controllers. Edge gateways. Routers. Medical equipment. The kind of hardware that ships once, runs for a decade, and gets a firmware update only if the operator is lucky.
These devices share a few traits. They handle data that matters. They sit on networks that matter. And the runtime security stack that works on a modern Kubernetes cluster won’t run on them, not even close.
If your job is to secure legacy embedded Linux devices, this conversation will sound familiar:
- A security vendor pitches an EDR agent. It needs 100+ MB of RAM and a recent kernel. The device has neither.
- Someone suggests “just use eBPF”. The kernel is 3.10. eBPF in any useful form arrived around 4.18+.
- A consultant proposes container-based isolation. There are no containers on the device. There never will be.
- Cloud-native runtime security assumes elastic resources and constant connectivity. The device has 32 MB free and a flaky cellular link.
So the device stays exposed. And the IoT botnets keep finding new recruits.
The eBPF problem
To be clear: eBPF is great. On a modern kernel it’s probably the right tool for in-kernel observability and security enforcement. The argument here isn’t against eBPF.
The argument is that eBPF doesn’t work for the fleet described above. Here’s why, in concrete terms:
Kernel version. The genuinely useful eBPF features for security (LSM hooks, BTF, CO-RE) landed in 5.x. An industrial gateway shipped in 2014 with kernel 3.10 is permanently outside that window.
Architecture support. The eBPF JIT on ARMv6 is incomplete or missing depending on kernel version. Without JIT, the interpreter pays a real performance cost on already-slow hardware.
Memory budget. The verifier, the maps, the loaders, none of these are free. Adding several MB of BPF runtime is a non-starter.
Toolchain. Building eBPF programs needs LLVM with the BPF backend, BTF headers from the target kernel, libbpf. None of that closes cleanly against a 2.6 kernel.
Recompiling the kernel isn’t an option. Rebuilding with the right CONFIG flags sounds easy in theory. But on a real fleet, kernel sources may not be available, vendor support may forbid it, certifications may depend on the exact build, and the engineers who shipped the device may not work there anymore.
The honest summary: when Linux runtime security for ARM32 has to work on devices currently in the field, eBPF is a roadmap to a future that’s not under anyone’s control. What’s needed is something that works against the kernel that’s already there.
Linux Lite agent: kernel APIs only
Our Runtime Linux Lite agent takes the opposite approach. Instead of requiring modern in-kernel infrastructure, it uses two APIs that have been in mainline Linux for over a decade:
- Fanotify, available since 2.6.37 (released January 2011), for synchronous file access control.
- Netlink Connector, available since 2.6.15 (2006), for process execution monitoring. Specifically, the agent subscribes to the kernel’s process events subsystem (the
CONFIG_PROC_EVENTSbuild option) and listens forPROC_EVENT_EXEC, the per-event notification fired on everyexecve().
That’s the whole kernel-side story. No custom modules. No BPF programs. No kernel rebuild. If the kernel was built with CONFIG_FANOTIFY=y, CONFIG_FANOTIFY_ACCESS_PERMISSIONS=y, CONFIG_CONNECTOR=y, and CONFIG_PROC_EVENTS=y, and most distributions enable these by default — the Linux Lite agent runs.
Here’s what the resulting agent looks like in practice:

A 6 MB static binary. Five megabytes of resident memory. That fits into the slack space of devices already in production, with no hardware refresh, no kernel changes, no OS upgrade cycle.
Two engines, two jobs
The architecture splits cleanly along the grain of the problem.
Fanotify for file access
Files get blocked before they open. The Linux Lite agent uses FAN_OPEN_PERM in synchronous permission mode: when a process tries to open a marked file, the kernel asks the agent for a verdict, and the agent has microseconds to say allow or deny. The syscall doesn’t return until that decision is made.
Two design choices matter here. First, marks are placed on specific inodes, not on the whole filesystem, there’s no per-syscall overhead for files nobody cares about. Second, the blocking is genuinely synchronous: if policy says deny, the file does not open. The process gets EACCES and moves on (or doesn’t).
Netlink Connector for process execution
Process exec events are handled differently, and the reason is worth understanding.
The intuitive choice would be to block exec the same way file opens are blocked — synchronously, with FAN_OPEN_EXEC_PERM. The problem is that during execve() itself, /proc/[pid]/cmdline is empty. The binary path is visible, but the arguments aren’t. So a rule like “kill gdb if it’s attaching to PID 1” can’t be evaluated, because at the moment the kernel asks for a verdict, the arguments gdb was invoked with aren’t yet known.
Exein's Runtime Linux Lite agent takes a different route. Netlink notifies the agent asynchronously after exec. The agent reads /proc/[pid]/cmdline, evaluates the rule, and if it matches, sends SIGKILL. The kill window is 1–10 ms on ARM32. That’s not zero, but it’s short enough that the process hasn’t done anything meaningful, it hasn’t opened sockets, hasn’t read sensitive files, often hasn’t even finished its own initialization.
For the use cases that matter (blocking malware execution, killing exploit chains, stopping unauthorized debuggers), 1–10 ms is the right tradeoff. Reactive, but reactive fast enough to count as prevention.

What rules look like
The configuration is declarative TOML. Three real examples that handle three real problems.
Block execution from /tmp. A huge fraction of IoT malware drops itself into /tmp and runs from there. One rule kills the entire pattern:
[[policies]]
id = "BLOCK-TMP-EXEC"
trigger = "process_exec"
action = "block"
severity = "high"
category = "execution"
condition = "process.path starts_with '/tmp/'"
Lock down a credentials file so only legitimate processes can read it, even as root:
[[policies]]
id = "PROTECT-DEVICE-KEYS"
trigger = "file_open"
action = "block"
severity = "high"
category = "credential_access"
condition = "target.path eq '/etc/device_keys.bin' and process.name not in ['firmware-updater', 'auth-daemon']"
Detect a known CVE pattern. vsftpd 2.3.4 had a backdoor (CVE-2011-2523) that spawned a shell as a child of vsftpd. No signature update is needed to catch it, the behavioral pattern is enough:
[[policies]]
id = "VSFTPD-BACKDOOR"
trigger = "process_exec"
action = "block"
severity = "critical"
category = "initial_access"
condition = "process.name in ['sh', 'bash', 'dash'] and process.parent.name eq 'vsftpd'"
The point isn’t the specific rules. The point is that catching real attacks on real devices doesn’t require a signature feed or a heavyweight ML model. It requires the ability to express a few behavioral patterns and enforce them at the kernel boundary. Exein's Linux Lite agent does that in 5 MB of RAM.
Forensics you can actually use
One thing that gets overlooked in lightweight agents: when something does fire, the alert needs enough context to act on it.
Every alert from Exein's Linux Lite agent includes the full process hierarchy up to PID 1. So instead of a notification that says “process 12345 was blocked”, the operator gets this:
[12345] malware (/tmp/malware)
└─ [1000] bash (/usr/bin/bash)
└─ [500] sshd (/usr/sbin/sshd)
└─ [1] systemd (/usr/lib/systemd/systemd)
The malicious process was launched from a bash shell that came from an SSH session. Three logs don’t need to be correlated to figure out the entry vector, it’s right there in the alert. On a device, where rich endpoint telemetry usually isn’t available, this single piece of context turns “we noticed something” into “we know what happened.”
Alerts are sent to the Exein platform, with optional forwarding to a SIEM such as Splunk.
Deployment, briefly
Our Runtime Linux Lite agent ships as a static musl binary, pre-built for the relevant ARM targets. No toolchain, no compiler, no dependencies, a single file gets copied over and runs. Two things to check on the device side.
Confirm the kernel has the right options:
zcat /proc/config.gz | grep -E "FANOTIFY|CONNECTOR|PROC_EVENTS"
The required entries are CONFIG_FANOTIFY=y, CONFIG_FANOTIFY_ACCESS_PERMISSIONS=y, CONFIG_CONNECTOR=y, CONFIG_PROC_EVENTS=y. These are standard in Yocto, Buildroot, and most OpenWrt builds with full kernels.
Don’t run as root. Use capabilities instead:
sudo setcap 'cap_sys_admin,cap_kill=+ep' /usr/local/bin/runtime-lite
CAP_SYS_ADMIN is required for fanotify_init() with FAN_CLASS_CONTENT. CAP_KILL is needed to terminate processes the agent doesn’t own. That’s the entire privilege footprint.
The systemd unit, the SIEM integration, the centralized config rollout, all of it works the same way it would on any other Linux service. Running on a 2.6-era kernel doesn’t add operational weirdness.
Where Exein's Linux Lite agent fits in the bigger picture
Linux Lite agent is the answer to one specific problem: securing devices the rest of the industry has written off. But that’s not the whole shape of runtime security on Linux, and it’s not the whole shape of what Exein does.
A realistic Linux fleet often spans three pretty different worlds:
- Legacy embedded: ARM32, kernel version 2.6 and above, limited resources. This is the scope of Runtime Lite.
- Modern embedded: ARM64, kernel 5.x and above, where eBPF actually makes sense. A different agent (Exein Runtime/Pulsar), different trade-offs.
- Servers and cloud workloads: x86_64, recent kernels, containers, orchestration. Here, the priority is proactivity via LSM hooks, which provide deeper visibility into system calls. Here too, a different agent (Photon) with different requirements.
Exein has agents covering all three. The reason that matters is consistency: the same security posture, the same policy language where possible, and the same alert pipeline whether the target is a Raspberry Pi Zero W in the field or a fleet of cloud servers behind a load balancer. A single-environment fleet only needs the matching agent. A mixed environment, which is the common case, gets its actual value from having all three under one roof.
The takeaway
For a long time, runtime security on Linux implicitly meant runtime security on modern Linux. Legacy embedded devices got skipped not for any deep technical reason, but because the dominant tools assumed dominant infrastructure.
Linux Lite agent is what happens when that assumption gets flipped. Use kernel APIs that have been around since 2011. Ship a single static binary. Block files synchronously, kill processes reactively but fast, write rules in TOML, send alerts to the Exein platform and optionally on to a SIEM.
To see Exein's Linux Lite agent running on your hardware, contact the Exein team for a demo or to discuss rolling it out across your fleet.



