Your Linux system has +6,000 kernel modules which can be autoloaded. You use 80 of them. ModuleJail blacklist all of the unused ones. Server and desktop profiles and much more in a simple shell script.
Posted by Vegetable-Escape7412@reddit | linuxadmin | View on Reddit | 19 comments
Hey r/linuxadmin. I'm the author of this so I'm flagging that up front - this is a "would love feedback from people running real fleets" post.
The problem. Modern distro kernels ship with thousands of loadable modules. Almost all of them are attack surface that you're paying for in availability (autoload via udev, hotplug, dependency resolution) but not using. With AI-assisted kernel vulnerability discovery accelerating, every module a host can load but doesn't need to load is a problem you'd rather not have.
ModuleJail walks lsmod, treats whatever is loaded right now as "necessary," and writes a modprobe.d blacklist file for everything else. Optionally adds a --whitelist-file for modules you want preserved even if they're not currently loaded (think: rarely-used filesystem drivers you mount once a quarter).
What it isn't.
- Not a vulnerability scanner. The model is "unused, therefore blacklisted," not "vulnerable, therefore blacklisted."
- Not a defense against an attacker who already has root - they can rm the file. It's about reducing the unprivileged-trigger / autoload paths.
- Not initramfs-aware. Modules baked into the initrd are out of scope.
- Not a daemon, not a monitor. Single POSIX shell script, runs once, writes one file in /etc/modprobe.d/.
Revert.
rm /etc/modprobe.d/modulejail-blacklist.conf
and you're back. No reboot needed - the kernel reads modprobe.d at load time. Explicit sudo modprobe foo always wins over the blacklist, by design.
What I want feedback on. What does this need before you'd run it across a fleet? Things I've heard so far: an Ansible role, a --dry-run flag, JSON output for diff-friendly state tracking, kernel-version pinning in the generated file header. What else?
Repo: github.com/jnuyens/modulejail
License: GPL-3.0
Packaging: .deb and .rpm on the releases page; AUR package today.
frymaster@reddit
We run shared-node shell services for 5000+ users, on a variety of distros. Our use-case is going to be running the script, generating a deny-list file, and then pushing that out to our nodes, rather than installing anything
that "minimal" is the most-minimal profile is a problem for us. Not all of the modules in the list are loaded on all of our systems. Obviously the file is easy to edit, but it'd be nice if we didn't have to i.e. if there was a "none" profile.
xiaodown@reddit
We have an automated vulnerability scanner bot that raises (and depending on configuration, optionally merges) security fixes.
Plus, our software gets built into docker images and run in containers anyway, and we deploy at least once per day. If there’s no merges to main, there’s an automated build that kicks off a fresh build and deploy.
It’s been forever since I’ve logged into a production system. I don’t even have access beyond dev environments (local -> dev -> merge to main -> staging -> soak -> prod progressive rollout).
Kurgan_IT@reddit
Nice idea, not invasive, easy to revert, easy to disable, then do the actions that load needed new modules, and then re-run to get a new configuration. Nice.
lihaarp@reddit
Feels like just disabling autoloading would be more sensible.
Also this would need some form of notification system for attempted module loads, as otherwise you'll be very confused when your new USB gadget just won't work.
ReachingForVega@reddit
This is so simple it's brilliant. I need to add this to all my homelab servers asap.
archontwo@reddit
Ahem. Not to be that guy. But when initramd is built you can choose which modules are included. The default option is 'most' which includes some common ones plus whatever you are currently running.
Still nice you found an itch to scratch, so far be it to say it is wasted effort, but you might have learned the boot process a little better to use the tooling already available to you.
yadad@reddit
Once your system has booted with all required services running, there's no more need to load more modules. Also, your script doesn't stop any new modules, only blacklists known existing modules.
echo 1 > /proc/sys/kernel/modules_disabled
echo confidentiality > /sys/kernel/security/lockdown
Use this first line to do what you need - disable new modules entirely. The 2nd line is equally good.
https://linuxsecurity.com/howtos/learn-tips-and-tricks/lockdown-mode-kernel-self-protection
threar@reddit
If you're going this far why blacklist over just removing (or moving them elsewhere) the unused modules? Another option if this is "across a fleet" is to recompile and rebuild the RPM (or deb) and push a stripped down kernel and then you keep a closer watch on what's actually available.
tblancher@reddit
Recompiling the kernel doesn't scale when your fleet is heterogeneous, which it most likely is if it's been up for any length of time.
Vegetable-Escape7412@reddit (OP)
RHEL doesn't support stripped down or recompiled kernels. Yep. Sad, isn't it? For many linux distributions it's also not very practical, instead of deep diving for hours or days into kernel compilation - which can be great too - this is an easy script to get the job done quickly. Some servers have different hardware or different needs for cryptographic modules, sorting that out manually is very time consuming. ModuleJail defines it per system based upon what's already loaded.
cereal7802@reddit
Huh? you can compile your own kernel on RHEL
shulemaker@reddit
And you won’t get support from RH, is what he’s saying.
At my work this applies. We’re not running a custom kernel. We’re using the RH tested and approved kernels.
And these recent vulns are killing us.
We’re pushing our our own config managed modprobe.d files on standalone hosts, and running the mitigation daemonsets on OpenShift and AKS that basically do these thing on immutable images.
michaelpaoli@reddit
Don't forget about proc/sys/kernel/modules_disabled
Set that, and no loading of additional modules, nor unloading of loaded modules, and that can't be changed, even by root, short of a reboot.
If one merely blacklists/whitelists, root can change/bypass that, so it's not so securely locked in.
Hmmm, I was under the impression, that, at least once-upon-a-time Linux had same or similar mechanism, but when activating, one could optionally set a password at that time, and then that password was the only way to revert that setting short of a reboot - and that this capability/idea had at least originally come from BSD (and that it might've been Linux that added the capability of setting a password at that time to allow it to later be reverted short of a reboot). But at least at present with very quick search I'm not finding references to such password capability.
edthesmokebeard@reddit
Or roll your own kernel and include them all.
Dolapevich@reddit
I build my own kernel with the required support, sorry.
Dilv1sh@reddit
Although i already use kernel.modules_disable, your solution is also very good!
Good job!
wosmo@reddit
I've been wondering about the feasibility of just setting kernel.modules_disabled=1. Obviously it'd need to be done post-boot, if it's done too early it could be problematic. But from my understanding, it'd stop all module loading without affecting modules already loaded.
Vegetable-Escape7412@reddit (OP)
Granularity / hot-plug. modules_disabled=1 is binary: all future module loading off, period. Great for a stable-hardware server where genuinely nothing should ever load post-boot. But it breaks anything depending on lazy/triggered loading: USB peripherals that need their driver, filesystem mounts when the FS module wasn't preloaded, USB, ethernet/WiFi, encrypted-disk subsystems triggered on mount, etc.
ModuleJail keeps explicit modprobe foo working for the currently-loaded set + your --whitelist-file, and kills only the autoload-via-udev / dependency-resolution / alias paths.
Reversibility without reboot. modules_disabled=1 is one-way - you can't flip it back to 0 without a reboot. modulejail's blacklist is just 1 textfile in /etc/modprobe.d/; rm it or edit it to permit a specific module without rebooting. Not needing to reboot is crucial in large environments.
Debugging. Loading nfsv4 or some diagnostic module post-incident is modprobe nfsv4 away with modulejail; with modules_disabled=1 it requires a reboot. And all blocked module loading shows up in syslog.
You're right that modules_disabled=1 doesn't touch already-loaded modules - same is true of modulejail. Both reduce the future-load surface, not the running surface. CVE on an already-loaded module: neither helps.
Re: the 5-minute delay u/pickednull mentions - typical pattern is a systemd oneshot service with After=multi-user.target or an OnBootSec=5min timer that flips the sysctl. Same shape that's planned for modulejail itself in v1.5 for periodic re-runs after kernel updates, so it's funny you mentioned it 😄
In the mean time the ModuleJail Arch package is available from AUR
picklednull@reddit
Works fine, provided you delay it a little at boot as you said.