I want to learn how to write Linux Security Module to deny deleting of file.
From documentation https://www.kernel.org/doc/html/v5.4/security/lsm-development.html I can see that there are 2 functions: path_unlink
and inode_unlink
which seem to be related to unlinking. So I picked one of them and tried to hook that to see how and if it works.
lsm_hello.c
#include <linux/module.h>
#include <linux/init.h>
#include <linux/security.h>
#include <linux/lsm_hooks.h>
static int hello_lsm_inode_unlink(struct inode *dir, struct dentry *dentry) {
pr_info("LSM Hello World: Preventing unlink for file: %s\n", dentry->d_name.name);
return -EPERM; // Prevent the file from being deleted
}
static struct security_hook_list hello_hooks[] = {
LSM_HOOK_INIT(inode_unlink, hello_lsm_inode_unlink),
};
static int __init hello_lsm_init(void) {
pr_info("LSM Hello World: Initializing\n");
security_add_hooks(hello_hooks, ARRAY_SIZE(hello_hooks), "lsm_hello");
return 0;
}
static void __exit hello_lsm_exit(void) {
pr_info("LSM Hello World: Exiting\n");
}
module_init(hello_lsm_init);
module_exit(hello_lsm_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("You");
MODULE_DESCRIPTION("Minimal LSM with inode_unlink hook");
and Makefile:
EXTRA_CFLAGS += -DCONFIG_SECURITY_WRITABLE_HOOKS
obj-m := lsm_hello.o
all:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
clean:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
But when I compile it I get these errors and .ko file is not generated:
make[1]: Entering directory '/usr/src/linux-headers-6.1.0-31-amd64'
CC [M] /root/lsm_hello/lsm_hello.o
MODPOST /root/lsm_hello/Module.symvers
ERROR: modpost: "security_add_hooks" [/root/lsm_hello/lsm_hello.ko] undefined!
ERROR: modpost: "security_hook_heads" [/root/lsm_hello/lsm_hello.ko] undefined!
make[2]: *** [/usr/src/linux-headers-6.1.0-31-common/scripts/Makefile.modpost:127: /root/lsm_hello/Module.symvers] Error 1
make[1]: *** [/usr/src/linux-headers-6.1.0-31-common/Makefile:1986: modpost] Error 2
make[1]: Leaving directory '/usr/src/linux-headers-6.1.0-31-amd64'
make: *** [Makefile:6: all] Error 2
With some trial and error I found that problem comes from this line:
security_add_hooks(hello_hooks, ARRAY_SIZE(hello_hooks), "lsm_hello");
If I comment that out then it compiles fine and I can verify that it works:
root@debian:~/lsm_hello# insmod lsm_hello.ko
root@debian:~/lsm_hello# rmmod lsm_hello
root@debian:~/lsm_hello# journalctl --since "1 hour ago" | grep kernel.
Mar 16 15:21:33 debian kernel: LSM Hello World: Initializing
Mar 16 15:21:46 debian kernel: LSM Hello World: Exiting
What is the correct way of writing this as it seems that the correct way is different from version to version?
Looking in security/security.c
[for the latest kernel], we have:
void __init security_add_hooks(struct security_hook_list *hooks, int count,
const struct lsm_id *lsmid)
Note that __init
means that security_add_hooks
is used during an early initialization phase of the kernel during bootup. After that, the memory [for such functions] will be reclaimed and the corresponding functions made unavailable.
Thus, such functions can not be used by loadable modules. Modules that wish to use these functions must be built into the core kernel.
When you tried to build your [loadable] module, the build system recognized this and made security_add_hooks
unavailable for linking.
UPDATE:
Does that mean that I need to find something other than LSM or that I just need alternative for
security_add_hooks
?
Yes, you would have to rebuild the entire kernel or find an alternative.
Some ideas:
You might be able to do this with an existing client of LSM (e.g. bpf
, apparmor
, etc.) if they provide hooks.
You might be able to set attributes via selinux
to prevent a [single] file from being deleted.
IIRC, selinux
operates on top of linux file attributes (e.g. chattr
).
You could split your module into two parts:
security_add_hooks
code./dev/my_security
and use ioctl
calls. This saves the tedium of creating a new syscall.You could try the builtin access control list (ACL) facility: https://www.redhat.com/en/blog/linux-access-control-lists
You might be able to use fuse
(filesystem in user space):
I'd separate your need (preventing a file deletion) from the implementation (LSM, bpf
, etc.). I'd edit your question (vs. a bunch of comments), or ask a new one, with clearer description of needs (and what policies you want your module to enforce) (e.g.):
chattr
)?IMO, fuse
might be your best, easiest bet. No kernel driver (builtin or otherwise) needed. You could certainly use it as a starting point for experimentation. To flesh out how you'll implement the policies, even if you decide that you need a different implementation.
Your fuse FS handles the extra security, but the bulk of the FS duties are delegated to the underlying FS (e.g. zfs
, btrfs
, ext4
).
UPDATE #2:
What do you want to accomplish with "no delete":
For (1), it may be sufficient to remove write access to the file.
For (2), without "delete" privileges but, still having write access, a "bad" person could truncate the file. Or, overwrite the contents with zeroes, etc. They can't actually "delete" the file, but can [effectively] "delete" it (by destroying its contents).
A long time ago [in a galaxy far, far away :-)], I used a filesystem that had a delete permission/privilege that was separate from the write permission. To get around that, the file contents were simply overwritten. My takeaway from that experience was that a separate delete permission didn't help and that deletion should be controlled by the write permission. YMMV ...
UPDATE #3:
Thanks again. I don't understand one thing: If
__init
means that module needs to be loaded at boot time then what is the exact use of makingsecurity_add_hooks
unavailable for linking?
I glossed over this slightly, so let me be more clear.
From include/linux/init.h
[which you should look at in more detail], we have:
#define __init __section(".init.text") __cold __latent_entropy __noinitretpoline
#define __initdata __section(".init.data")
#define __initconst __section(".init.rodata")
.init.text
linker section..init.*
. All this memory will become available as kernel heap memory (i.e. part of the memory pool for kmalloc
et. al.)..c
file that defines it must export it explicitly with (e.g.) EXPORT_SYMBOL(a_symbol_name)
security/security.c
does not do EXPORT_SYMBOL(security_add_hooks)
because it makes no sense. By the time a module can be loaded, the .init.*
sections (and their code/data) have already been reclaimed..c
did not export it via EXPORT_SYMBOL
..init.*
trickery, without such an export directive, the symbol can not be linked to.If I somehow compiled myself .ko file for example by removing that restriction from linker wouldn't I then be able edit some config file to make my Debian 12 to load it at boot time? – u4963840
Again, the linker is not [artificially] restricting linking to the symbol. The core kernel simply does not export it. It's unavailable to the linker. So, you can't solve this with some linker trickery.
To use the aforementioned symbols, you would have to do one of the following:
security/security.c
to remove __init
from function definitions. Add EXPORT_SYMBOL(...)
for each symbol you needbpf
???).In either of the top two cases, you would have to ship a custom kernel (or, minimally, a kernel patch file for the source). You've stated that you don't want to do that (and I agree) because you'd be responsible for merging/updating each kernel version--you become your own linux distro.
Even with (3), you can't just ship a prebuilt .ko
file. That's because [in many cases], the .ko
is built against a specific kernel version. The kernel's module loader (built directly into the kernel) won't load a module built for an incompatible version. You could ship a .o
that could be linked into a .ko
by your customer. More likely, you'll have to ship your module source, as not everyone is willing to load a module into their kernel that they haven't rebuilt from source.
Let me state this with as much kindness as I can:
And, as I mentioned above, because you've not provided a detailed description of the problem, there are a number of unanswered questions [that I enumerated above]. You want to prevent a file from being deleted, but ... The LSM method (which, again, you can't do as stated with the given constraints) is but one solution. Even if you could [magically] do the LSM solution [as desired], without a full problem description, you can't state that it's the correct (i.e. best) solution.
I've been doing kernel [and standalone] programming for a number of decades. A hallmark of a good kernel programmer is knowing what to put in the kernel and what not to. And, remaining flexible with regard to possible solutions.