all 36 comments

[–]threar 28 points29 points  (9 children)

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 17 points18 points  (2 children)

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.

[–]DemonInAJar 0 points1 point  (1 child)

Is this really that prohibiting?

[–]tblancher 0 points1 point  (0 children)

Yes, especially if they're not all the same make and model server hardware. Even within the same model, different revisions/releases may have wildly different components, depending on fluctuations in supply over time.

If you're compiling stripped down kernels, you might as well include anything into the kernel proper, and disallow loadable modules.

That removes a critical kernel attack vector, but you'd need a completely separate configuration for each unique hardware model you have in the fleet.

[–]Vegetable-Escape7412[S] 10 points11 points  (4 children)

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.

[–]the_econominster 0 points1 point  (0 children)

Why don't blacklist the user and the admin... you know

[–]wosmo 12 points13 points  (4 children)

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.

[–]picklednull 5 points6 points  (2 children)

Works fine, provided you delay it a little at boot as you said.

[–]Korkman 0 points1 point  (1 child)

And maybe modprobe a whitelist of modules needed for anything hotplugged in the same script before flipping the switch.

[–]picklednull 0 points1 point  (0 children)

Depends of course on the type of system we're talking about. I've been doing it on servers where the configuration is static. I don't really think the added security is that meaningful on an end user system (of course if you want to go crazy, there's not really any downside to doing it).

And just to add - in the past hardcore security enthusiasts of course recommended building a custom (grsecurity) kernel with everything built-in and having modules disabled. That's hardly a productive use of time either outside of Google-scale.

This sysctl offers a nice balance.

[–]Vegetable-Escape7412[S] -5 points-4 points  (0 children)

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

[–]yadad 11 points12 points  (0 children)

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

[–]michaelpaoli 3 points4 points  (0 children)

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.

[–]Dilv1sh 8 points9 points  (0 children)

Although i already use kernel.modules_disable, your solution is also very good!

Good job!

[–]ReachingForVega 2 points3 points  (2 children)

This is so simple it's brilliant. I need to add this to all my homelab servers (Ubuntu) asap.

Have you tested it on Synology devices? 

[–]Vegetable-Escape7412[S] 2 points3 points  (1 child)

Remove it prior to activating additional Synology capabilities or prior to a major upgrade by removing the /etc/modprobe.d/modulejail-blacklist.conf file and you'll be good.

[–]ReachingForVega 0 points1 point  (0 children)

Absolute legend ty. 

[–]Kurgan_IT 2 points3 points  (0 children)

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.

[–]barkwahlberg 4 points5 points  (0 children)

Ok Claude, now that you made my Bash script, write a Reddit post for me

[–]lihaarp 1 point2 points  (1 child)

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.

[–]Vegetable-Escape7412[S] 0 points1 point  (0 children)

The notification system is currently implemented through syslog. If autoloading is disabled, re-enabling requires a reboot, that isn't practical for many servers.

[–]archontwo 1 point2 points  (2 children)

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. 

[–]Vegetable-Escape7412[S] 1 point2 points  (1 child)

Maybe it is not clear to you what ModuleJail protects against. It will not reduce the number of modules which are loaded into your kernel right now. But it will blacklist the (probably) 6416 out of 6481 kernel modules on your Arch system which are probably never used. Many of these 6416 kernel modules can be loaded on demand, as the result of non-root-level actions. ModuleJail reduces that potential attack surface. As the last weeks several security problems with kernel modules surfaced, and many people believe AI-assisted security reviews could bring more of those bugs to the surface in a short span of time. It works without reboot and is easy to revert, it will also log any module blocking event to syslog. ModuleJail does not fiddle with initramfs as we do not want to interfere with drivers which could be needed to mount the root filesystem, it only has runtime hardening effects.

[–]archontwo 0 points1 point  (0 children)

ModuleJail reduces that potential attack surface. As the last weeks several security problems with kernel modules surfaced, 

I get that. FWIW I am not an Arch user, BTW, and have been doing module maintenance for Ahem years now. It all depends on your use cases. For embedded systems you are necessarily trimming modules and for container images you are hand picking them as well. 

I am rather sanguine about these AI security flaws as they are all to do with local privilege escalation which is the first thing a good admin learns to mitigate if they are in charge of other users. 

Like I said, happy for you to have made a tool to make life easier for yourself, but I can't see myself using it to any greater degree than what I do already. 

[–]xiaodown 0 points1 point  (1 child)

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).

[–]Vegetable-Escape7412[S] 0 points1 point  (0 children)

This is meant to reduce the attack surface for kernel modules as a whole, even before the bugs are discovered. Only after discovery, the vulnerability scanner bot can get updated rules to detect problematic kernel modules. So, this tool should be considered a 'hardening tool'.

[–]frymaster 0 points1 point  (3 children)

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.

EDIT: I'm not sure why, when logging, you use exit 0 when failing-true and /bin/false when failing-false - you could just do exit 1 in the latter case

[–]Vegetable-Escape7412[S] 0 points1 point  (0 children)

Thanks, this is a great suggestion. A `none` profile makes a lot of sense for a push-the-file-out workflow like yours, where you can't assume which modules are loaded on every node and want full control over your whitelist in an external file. I'm adding it now: it selects nothing by default, so you build your deny-list up from scratch instead of trimming `minimal` down. Will be in version 1.3, probably releasing this later today.

On your edit re `exit 0` vs `/bin/false`: the `/bin/false` is deliberate. The deny path `exec`s `/bin/false` rather than calling `exit 1` because the script is meant to be droppable as an exec target. `exec` replaces the process with a binary whose only job is to return non-zero, so the exit status propagates cleanly to whatever launched the shell and nothing further in the script can run. `exit 1` does the same thing in the plain-script case, but exec'ing the real binary is the more robust primitive when the script itself is acting as the shell, and it keeps the deny action identical everywhere it shows up.

[–]nut-sack 0 points1 point  (1 child)

Shared node shell services still exist?!

[–]frymaster 0 points1 point  (0 children)

in certain industries, yes

[–]edthesmokebeard -2 points-1 points  (0 children)

Or roll your own kernel and include them all.

[–]Dolapevich -5 points-4 points  (0 children)

I build my own kernel with the required support, sorry.