Build a crappy ring-0 toy antivirus in eBPF with the IMA LSM

razighter7771 pts0 comments

prizrak.me blog

GitHub

← Main Page

Table of Contents

Build a crappy ring-0 toy antivirus in eBPF with the IMA LSM

Estimated reading time: 4 minutes

Aug 6, 2026

Keywords:

ebpf

lsm

dev

security

Table of Contents

Build a crappy ring-0 toy antivirus in eBPF with the IMA LSM

In significant boredom, I wandered into the eBPF documentation and stumbled across<br>this sick function:

long bpf_ima_file_hash(struct file *file, void *dst, u32 size)

Searching for references in github code search returns only a few results,<br>besides the man pages and the gazillion forks of &ldquo;torvalds/linux&rdquo; , but after<br>digging into what this actually does I think this deserves a little more spotlight…

WTF is eBPF?

eBPF are basically little bytecode programs that you can load into the kernel.<br>You write them in C (or Rust if you fancy) and<br>they get verified for correctness. Anyway, they can hook into the kernel in many<br>ways and basically extend the kernel much like loadable kernel modules (with limitations).

There are better resources than this unserious<br>blog for this…

WTF is IMA?

The Linux Integrity Measurement Architecture (IMA) is a Linux Security Module<br>designed to check and secure file integrity. It&rsquo;s technically in the same<br>class of as SELinux / AppArmor, but basically it&rsquo;s main job is to hash files<br>that get loaded into kernel space for verification against things like the TPM<br>(mitigating evil-maid attacks). You don&rsquo;t need a TPM to use it, and in fact<br>it gets enabled on most modern linux distros:

# make sure you have bpf and ima in the list.<br>cat /sys/kernel/security/lsm

What can bpf_ima_file_hash do?

bpf_ima_file_hash is an eBPF function (technically a &ldquo;helper&rdquo; but whatever) that<br>lets you ask the kernel for a file (struct file*) hash.

It&rsquo;s on-demand too. So the hash isn&rsquo;t computed until you actually need it.<br>(Without an IMA policy this re-hashes on every exec. Boot with ima_policy=tcb to<br>get real caching)

Because you can call this from eBPF, you can hook into the kernel at interesting points,<br>like bprm_check_security (which runs when any binary is executed), which gives you<br>access to a struct file* through the struct linux_binprm* param.

Here&rsquo;s a simple example:

SEC("lsm.s/bprm_check_security") /* must be the lsm.s (sleepable) not just lsm */<br>int BPF_PROG(my_l337_ant1v1rus, struct linux_binprm *bprm, int ret)<br>__u8 digest[IMA_MAX_DIGEST_SIZE] = {};<br>struct file *file = bprm->file;<br>int algo = 0;<br>if (ret)<br>return ret;

algo = bpf_ima_file_hash(file, digest, sizeof(digest));

/* default hash is sha1. yours may vary... see ima_hash boot param */<br>if (algo != HASH_ALGO_SHA1) {<br>return 0;<br>/* check if our hash is in the naughty list.<br>* denied_hashes is our map, defined elsewhere, with<br>* our forbidden hashes.<br>* Add a "!" do do whitelisting if you're a security psychopath...<br>*/<br>if (bpf_map_lookup_elem(&denied_hashes, digest))<br>return -EPERM;<br>return 0;

Ignoring the funky BPF syntax (the SEC part is just the function we&rsquo;re attaching to,<br>the sleepable lsm hook &ldquo;lsm.s&rdquo; named &ldquo;bprm_check_security&rdquo;), we can use this to<br>block any executable that&rsquo;s in our naughty (hash) list (our bpf_map_lookup_elem<br>looks it up).

The effect of this: we can block any executable in the naughty list from ever<br>running systemwide. And do it BlAzInGlY FaST (🤡) in ring 0.

IMA will do the heaving lifting and caching for you. In practice it&rsquo;s pretty fast.<br>I loaded it up with 1,116,433 hashes (the entire MalwareBazaar database) without a sweat.<br>It&rsquo;s smart about lazily calculating the hashes and will avoid recalculating it unless<br>the pages are dirty. It&rsquo;s almost certainly faster than a userspace implementation of<br>the same thing… no userspace round-trip cost or TOCTOU (cough Windows Defender<br>cough) issues.

I&rsquo;ll spare you the rest of my garbage code. But basically this is all you need to<br>make a ring-0 toy little &ldquo;antivirus&rdquo; thingy. You can load your map up with a<br>gazillion malware hashes from some Kaggle dataset and block all those hashes<br>pretty trivially. Not saying that it&rsquo;s an effective solution, but it&rsquo;s certainly<br>a building block for a proper implementation… a real antivirus should be a bit<br>more than just a file hash blocker. But I&rsquo;ll take this anyday before Norton and<br>Kapersky shitware.

Hope you found this interesting.

This post was made without AI slop. If I don&rsquo;t take the time to write, I don&rsquo;t expect<br>you to read it

rsquo file ebpf kernel hash struct

Related Articles