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

Estimated reading time: 4 minutes

Aug 6, 2026

Keywords: ebpf c 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 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, besides the man pages and the gazillion forks of “torvalds/linux” , but after 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. You write them in C (or Rust if you fancy) and they get verified for correctness. Anyway, they can hook into the kernel in many ways and basically extend the kernel much like loadable kernel modules (with limitations).

There are better resources than this unserious blog for this…

WTF is IMA?

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

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

What can bpf_ima_file_hash do?

bpf_ima_file_hash is an eBPF function (technically a “helper” but whatever) that lets you ask the kernel for a file (struct file*) hash.

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

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

Here’s a simple example:


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

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

Ignoring the funky BPF syntax (the SEC part is just the function we’re attaching to, the sleepable lsm hook “lsm.s” named “bprm_check_security”), we can use this to block any executable that’s in our naughty (hash) list (our bpf_map_lookup_elem looks it up).

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

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

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

Hope you found this interesting.

This post was made without AI slop. If I don’t take the time to write, I don’t expect you to read it