├── .github └── FUNDING.yml ├── .gitignore ├── README.md └── security ├── Kconfig ├── Makefile ├── can-exec ├── Kconfig ├── Makefile ├── README.md ├── can_exec_lsm.c └── samples │ ├── Makefile │ └── can-exec.c ├── hashcheck ├── Kconfig ├── Makefile ├── README.md └── hashcheck_lsm.c └── whitelist ├── Kconfig ├── Makefile ├── README.md ├── samples └── whitelist.c └── whitelist_lsm.c /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: skx 4 | custom: https://steve.fi/donate/ 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.a 2 | *.o 3 | *.builtin 4 | *.order 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Linux Security Modules 2 | 3 | This repository contains a small collection of linux security modules, which were written as a part of a learning/experimentation process. 4 | 5 | The code present has been compiled and tested against the most recent long-term kernel, at the time of writing that is __5.10.17__. 6 | 7 | If you want to port this code to a newer kernel, in the future, then the following bug-report is a good overview of how I approach things: 8 | 9 | * https://github.com/skx/linux-security-modules/issues/13 10 | 11 | 12 | 13 | ## Included Modules 14 | 15 | There are three modules contained within this repository, two of which are simple tests and one of which is more "real". 16 | 17 | The only real/useful module is: 18 | 19 | * [can-exec](security/can-exec) 20 | * The user-space helper `/sbin/can-exec` is invoked to determine whether a user can execute a specific command. 21 | * Because user-space controls execution policies can be written/updated dynamically. 22 | 23 | The following two modules were written as I started the learning-process, and demonstrate creating simple standalone modules, albeit ones which do not actually provide any significant security benefit: 24 | 25 | * [whitelist](security/whitelist/) 26 | * Only allow execution of binaries which have a specific `xattr` present. 27 | * [hashcheck](security/hashcheck/) 28 | * Only allow execution of commands with `xattr` containing valid SHA1sum of binaries. 29 | * This builds upon the previous module. 30 | 31 | 32 | 33 | 34 | ## Compilation 35 | 36 | Copy the contents of `security/` into your local Kernel-tree, and run `make menuconfig` to enable the appropriate options. 37 | 38 | Further notes are available within the appropriate module subdirectories. 39 | 40 | For a Debian GNU/Linux host, these are the kernel build-dependencies you'll need to install, if they're not already present: 41 | 42 | # apt-get install flex bison bc libelf-dev libssl-dev \ 43 | build-essential make libncurses5-dev \ 44 | git-core 45 | 46 | 47 | 48 | ### Tracking Kernel Changes 49 | 50 | As new kernels are released it is possible the two files `security/Kconfig` & `security/Makefile` might need resyncing with the base versions installed with the Linux source-tree. 51 | 52 | You should be able to update them just by running `diff` and copying any lines referring to the modules `CAN_EXEC`, `HASH_CHECK`, & `WHITELIST` into place. 53 | -------------------------------------------------------------------------------- /security/Kconfig: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-2.0-only 2 | # 3 | # Security configuration 4 | # 5 | 6 | menu "Security options" 7 | 8 | source "security/keys/Kconfig" 9 | 10 | config SECURITY_DMESG_RESTRICT 11 | bool "Restrict unprivileged access to the kernel syslog" 12 | default n 13 | help 14 | This enforces restrictions on unprivileged users reading the kernel 15 | syslog via dmesg(8). 16 | 17 | If this option is not selected, no restrictions will be enforced 18 | unless the dmesg_restrict sysctl is explicitly set to (1). 19 | 20 | If you are unsure how to answer this question, answer N. 21 | 22 | config SECURITY 23 | bool "Enable different security models" 24 | depends on SYSFS 25 | depends on MULTIUSER 26 | help 27 | This allows you to choose different security modules to be 28 | configured into your kernel. 29 | 30 | If this option is not selected, the default Linux security 31 | model will be used. 32 | 33 | If you are unsure how to answer this question, answer N. 34 | 35 | config SECURITY_WRITABLE_HOOKS 36 | depends on SECURITY 37 | bool 38 | default n 39 | 40 | config SECURITYFS 41 | bool "Enable the securityfs filesystem" 42 | help 43 | This will build the securityfs filesystem. It is currently used by 44 | various security modules (AppArmor, IMA, SafeSetID, TOMOYO, TPM). 45 | 46 | If you are unsure how to answer this question, answer N. 47 | 48 | config SECURITY_NETWORK 49 | bool "Socket and Networking Security Hooks" 50 | depends on SECURITY 51 | help 52 | This enables the socket and networking security hooks. 53 | If enabled, a security module can use these hooks to 54 | implement socket and networking access controls. 55 | If you are unsure how to answer this question, answer N. 56 | 57 | config PAGE_TABLE_ISOLATION 58 | bool "Remove the kernel mapping in user mode" 59 | default y 60 | depends on (X86_64 || X86_PAE) && !UML 61 | help 62 | This feature reduces the number of hardware side channels by 63 | ensuring that the majority of kernel addresses are not mapped 64 | into userspace. 65 | 66 | See Documentation/x86/pti.rst for more details. 67 | 68 | config SECURITY_INFINIBAND 69 | bool "Infiniband Security Hooks" 70 | depends on SECURITY && INFINIBAND 71 | help 72 | This enables the Infiniband security hooks. 73 | If enabled, a security module can use these hooks to 74 | implement Infiniband access controls. 75 | If you are unsure how to answer this question, answer N. 76 | 77 | config SECURITY_NETWORK_XFRM 78 | bool "XFRM (IPSec) Networking Security Hooks" 79 | depends on XFRM && SECURITY_NETWORK 80 | help 81 | This enables the XFRM (IPSec) networking security hooks. 82 | If enabled, a security module can use these hooks to 83 | implement per-packet access controls based on labels 84 | derived from IPSec policy. Non-IPSec communications are 85 | designated as unlabelled, and only sockets authorized 86 | to communicate unlabelled data can send without using 87 | IPSec. 88 | If you are unsure how to answer this question, answer N. 89 | 90 | config SECURITY_PATH 91 | bool "Security hooks for pathname based access control" 92 | depends on SECURITY 93 | help 94 | This enables the security hooks for pathname based access control. 95 | If enabled, a security module can use these hooks to 96 | implement pathname based access controls. 97 | If you are unsure how to answer this question, answer N. 98 | 99 | config INTEL_TXT 100 | bool "Enable Intel(R) Trusted Execution Technology (Intel(R) TXT)" 101 | depends on HAVE_INTEL_TXT 102 | help 103 | This option enables support for booting the kernel with the 104 | Trusted Boot (tboot) module. This will utilize 105 | Intel(R) Trusted Execution Technology to perform a measured launch 106 | of the kernel. If the system does not support Intel(R) TXT, this 107 | will have no effect. 108 | 109 | Intel TXT will provide higher assurance of system configuration and 110 | initial state as well as data reset protection. This is used to 111 | create a robust initial kernel measurement and verification, which 112 | helps to ensure that kernel security mechanisms are functioning 113 | correctly. This level of protection requires a root of trust outside 114 | of the kernel itself. 115 | 116 | Intel TXT also helps solve real end user concerns about having 117 | confidence that their hardware is running the VMM or kernel that 118 | it was configured with, especially since they may be responsible for 119 | providing such assurances to VMs and services running on it. 120 | 121 | See for more information 122 | about Intel(R) TXT. 123 | See for more information about tboot. 124 | See Documentation/x86/intel_txt.rst for a description of how to enable 125 | Intel TXT support in a kernel boot. 126 | 127 | If you are unsure as to whether this is required, answer N. 128 | 129 | config LSM_MMAP_MIN_ADDR 130 | int "Low address space for LSM to protect from user allocation" 131 | depends on SECURITY && SECURITY_SELINUX 132 | default 32768 if ARM || (ARM64 && COMPAT) 133 | default 65536 134 | help 135 | This is the portion of low virtual memory which should be protected 136 | from userspace allocation. Keeping a user from writing to low pages 137 | can help reduce the impact of kernel NULL pointer bugs. 138 | 139 | For most ia64, ppc64 and x86 users with lots of address space 140 | a value of 65536 is reasonable and should cause no problems. 141 | On arm and other archs it should not be higher than 32768. 142 | Programs which use vm86 functionality or have some need to map 143 | this low address space will need the permission specific to the 144 | systems running LSM. 145 | 146 | config HAVE_HARDENED_USERCOPY_ALLOCATOR 147 | bool 148 | help 149 | The heap allocator implements __check_heap_object() for 150 | validating memory ranges against heap object sizes in 151 | support of CONFIG_HARDENED_USERCOPY. 152 | 153 | config HARDENED_USERCOPY 154 | bool "Harden memory copies between kernel and userspace" 155 | depends on HAVE_HARDENED_USERCOPY_ALLOCATOR 156 | imply STRICT_DEVMEM 157 | help 158 | This option checks for obviously wrong memory regions when 159 | copying memory to/from the kernel (via copy_to_user() and 160 | copy_from_user() functions) by rejecting memory ranges that 161 | are larger than the specified heap object, span multiple 162 | separately allocated pages, are not on the process stack, 163 | or are part of the kernel text. This kills entire classes 164 | of heap overflow exploits and similar kernel memory exposures. 165 | 166 | config HARDENED_USERCOPY_FALLBACK 167 | bool "Allow usercopy whitelist violations to fallback to object size" 168 | depends on HARDENED_USERCOPY 169 | default y 170 | help 171 | This is a temporary option that allows missing usercopy whitelists 172 | to be discovered via a WARN() to the kernel log, instead of 173 | rejecting the copy, falling back to non-whitelisted hardened 174 | usercopy that checks the slab allocation size instead of the 175 | whitelist size. This option will be removed once it seems like 176 | all missing usercopy whitelists have been identified and fixed. 177 | Booting with "slab_common.usercopy_fallback=Y/N" can change 178 | this setting. 179 | 180 | config HARDENED_USERCOPY_PAGESPAN 181 | bool "Refuse to copy allocations that span multiple pages" 182 | depends on HARDENED_USERCOPY 183 | depends on EXPERT 184 | help 185 | When a multi-page allocation is done without __GFP_COMP, 186 | hardened usercopy will reject attempts to copy it. There are, 187 | however, several cases of this in the kernel that have not all 188 | been removed. This config is intended to be used only while 189 | trying to find such users. 190 | 191 | config FORTIFY_SOURCE 192 | bool "Harden common str/mem functions against buffer overflows" 193 | depends on ARCH_HAS_FORTIFY_SOURCE 194 | help 195 | Detect overflows of buffers in common string and memory functions 196 | where the compiler can determine and validate the buffer sizes. 197 | 198 | config STATIC_USERMODEHELPER 199 | bool "Force all usermode helper calls through a single binary" 200 | help 201 | By default, the kernel can call many different userspace 202 | binary programs through the "usermode helper" kernel 203 | interface. Some of these binaries are statically defined 204 | either in the kernel code itself, or as a kernel configuration 205 | option. However, some of these are dynamically created at 206 | runtime, or can be modified after the kernel has started up. 207 | To provide an additional layer of security, route all of these 208 | calls through a single executable that can not have its name 209 | changed. 210 | 211 | Note, it is up to this single binary to then call the relevant 212 | "real" usermode helper binary, based on the first argument 213 | passed to it. If desired, this program can filter and pick 214 | and choose what real programs are called. 215 | 216 | If you wish for all usermode helper programs are to be 217 | disabled, choose this option and then set 218 | STATIC_USERMODEHELPER_PATH to an empty string. 219 | 220 | config STATIC_USERMODEHELPER_PATH 221 | string "Path to the static usermode helper binary" 222 | depends on STATIC_USERMODEHELPER 223 | default "/sbin/usermode-helper" 224 | help 225 | The binary called by the kernel when any usermode helper 226 | program is wish to be run. The "real" application's name will 227 | be in the first argument passed to this program on the command 228 | line. 229 | 230 | If you wish for all usermode helper programs to be disabled, 231 | specify an empty string here (i.e. ""). 232 | 233 | source "security/selinux/Kconfig" 234 | source "security/smack/Kconfig" 235 | source "security/tomoyo/Kconfig" 236 | source "security/apparmor/Kconfig" 237 | source "security/loadpin/Kconfig" 238 | source "security/yama/Kconfig" 239 | source "security/safesetid/Kconfig" 240 | source "security/lockdown/Kconfig" 241 | source "security/can-exec/Kconfig" 242 | source "security/hashcheck/Kconfig" 243 | source "security/whitelist/Kconfig" 244 | 245 | source "security/integrity/Kconfig" 246 | 247 | choice 248 | prompt "First legacy 'major LSM' to be initialized" 249 | default DEFAULT_SECURITY_SELINUX if SECURITY_SELINUX 250 | default DEFAULT_SECURITY_SMACK if SECURITY_SMACK 251 | default DEFAULT_SECURITY_TOMOYO if SECURITY_TOMOYO 252 | default DEFAULT_SECURITY_APPARMOR if SECURITY_APPARMOR 253 | default DEFAULT_SECURITY_CAN_EXEC if SECURITY_CAN_EXEC 254 | default DEFAULT_SECURITY_HASH_CHECK if SECURITY_HASH_CHECK 255 | default DEFAULT_SECURITY_WHITELIST if SECURITY_WHITELIST 256 | default DEFAULT_SECURITY_DAC 257 | 258 | help 259 | This choice is there only for converting CONFIG_DEFAULT_SECURITY 260 | in old kernel configs to CONFIG_LSM in new kernel configs. Don't 261 | change this choice unless you are creating a fresh kernel config, 262 | for this choice will be ignored after CONFIG_LSM has been set. 263 | 264 | Selects the legacy "major security module" that will be 265 | initialized first. Overridden by non-default CONFIG_LSM. 266 | 267 | config DEFAULT_SECURITY_SELINUX 268 | bool "SELinux" if SECURITY_SELINUX=y 269 | 270 | config DEFAULT_SECURITY_SMACK 271 | bool "Simplified Mandatory Access Control" if SECURITY_SMACK=y 272 | 273 | config DEFAULT_SECURITY_TOMOYO 274 | bool "TOMOYO" if SECURITY_TOMOYO=y 275 | 276 | config DEFAULT_SECURITY_APPARMOR 277 | bool "AppArmor" if SECURITY_APPARMOR=y 278 | 279 | 280 | config DEFAULT_SECURITY_CAN_EXEC 281 | bool "can-exec" if SECURITY_CAN_EXEC=y 282 | 283 | config DEFAULT_SECURITY_HASH_CHECK 284 | bool "hashcheck" if SECURITY_HASH_CHECK=y 285 | 286 | config DEFAULT_SECURITY_WHITELIST 287 | bool "whitelist" if SECURITY_WHITELIST=y 288 | 289 | config DEFAULT_SECURITY_DAC 290 | bool "Unix Discretionary Access Controls" 291 | 292 | endchoice 293 | 294 | config LSM 295 | string "Ordered list of enabled LSMs" 296 | default "lockdown,yama,loadpin,safesetid,integrity,smack,selinux,tomoyo,apparmor,bpf" if DEFAULT_SECURITY_SMACK 297 | default "lockdown,yama,loadpin,safesetid,integrity,apparmor,selinux,smack,tomoyo,bpf" if DEFAULT_SECURITY_APPARMOR 298 | default "lockdown,yama,loadpin,safesetid,integrity,tomoyo,bpf" if DEFAULT_SECURITY_TOMOYO 299 | default "lockdown,yama,loadpin,safesetid,integrity,bpf" if DEFAULT_SECURITY_DAC 300 | default "lockdown,yama,loadpin,safesetid,integrity,bpf,can-exec" if DEFAULT_SECURITY_CAN_EXEC 301 | default "lockdown,yama,loadpin,safesetid,integrity,bpf,hashcheck" if DEFAULT_SECURITY_HASH_CHECK 302 | default "localdown,yama,loadpin,safesetid,integrity,bpf,whitelist" if DEFAULT_SECURITY_WHITELIST 303 | default "lockdown,yama,loadpin,safesetid,integrity,selinux,smack,tomoyo,apparmor,bpf" 304 | help 305 | A comma-separated list of LSMs, in initialization order. 306 | Any LSMs left off this list will be ignored. This can be 307 | controlled at boot with the "lsm=" parameter. 308 | 309 | If unsure, leave this as the default. 310 | 311 | source "security/Kconfig.hardening" 312 | 313 | endmenu 314 | 315 | -------------------------------------------------------------------------------- /security/Makefile: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-2.0 2 | # 3 | # Makefile for the kernel security code 4 | # 5 | 6 | obj-$(CONFIG_KEYS) += keys/ 7 | subdir-$(CONFIG_SECURITY_SELINUX) += selinux 8 | subdir-$(CONFIG_SECURITY_SMACK) += smack 9 | subdir-$(CONFIG_SECURITY_TOMOYO) += tomoyo 10 | subdir-$(CONFIG_SECURITY_APPARMOR) += apparmor 11 | subdir-$(CONFIG_SECURITY_YAMA) += yama 12 | subdir-$(CONFIG_SECURITY_LOADPIN) += loadpin 13 | subdir-$(CONFIG_SECURITY_SAFESETID) += safesetid 14 | subdir-$(CONFIG_SECURITY_LOCKDOWN_LSM) += lockdown 15 | subdir-$(CONFIG_BPF_LSM) += bpf 16 | subdir-$(CONFIG_SECURITY_CAN_EXEC) += can-exec 17 | subdir-$(CONFIG_SECURITY_HASH_CHECK) += hashcheck 18 | subdir-$(CONFIG_SECURITY_WHITELIST) += whitelist 19 | 20 | 21 | # always enable default capabilities 22 | obj-y += commoncap.o 23 | obj-$(CONFIG_MMU) += min_addr.o 24 | 25 | # Object file lists 26 | obj-$(CONFIG_SECURITY) += security.o 27 | obj-$(CONFIG_SECURITYFS) += inode.o 28 | obj-$(CONFIG_SECURITY_SELINUX) += selinux/ 29 | obj-$(CONFIG_SECURITY_SMACK) += smack/ 30 | obj-$(CONFIG_SECURITY) += lsm_audit.o 31 | obj-$(CONFIG_SECURITY_TOMOYO) += tomoyo/ 32 | obj-$(CONFIG_SECURITY_APPARMOR) += apparmor/ 33 | obj-$(CONFIG_SECURITY_YAMA) += yama/ 34 | obj-$(CONFIG_SECURITY_LOADPIN) += loadpin/ 35 | obj-$(CONFIG_SECURITY_SAFESETID) += safesetid/ 36 | obj-$(CONFIG_SECURITY_LOCKDOWN_LSM) += lockdown/ 37 | obj-$(CONFIG_CGROUPS) += device_cgroup.o 38 | obj-$(CONFIG_BPF_LSM) += bpf/ 39 | obj-$(CONFIG_SECURITY_CAN_EXEC) += can-exec/ 40 | obj-$(CONFIG_SECURITY_HASH_CHECK) += hashcheck/ 41 | obj-$(CONFIG_SECURITY_WHITELIST) += whitelist/ 42 | 43 | 44 | # Object integrity file lists 45 | subdir-$(CONFIG_INTEGRITY) += integrity 46 | obj-$(CONFIG_INTEGRITY) += integrity/ 47 | -------------------------------------------------------------------------------- /security/can-exec/Kconfig: -------------------------------------------------------------------------------- 1 | config SECURITY_CAN_EXEC 2 | bool "can-exec Security Module" 3 | depends on SECURITY 4 | depends on NET 5 | select SECURITYFS 6 | select SECURITY_PATH 7 | select SECURITY_NETWORK 8 | select SRCU 9 | select BUILD_BIN2C 10 | default y 11 | help 12 | This selects an access control module which invokes userspace. 13 | Binaries will only be permitted if /sbin/can-exec returns 0. 14 | -------------------------------------------------------------------------------- /security/can-exec/Makefile: -------------------------------------------------------------------------------- 1 | obj-y = can_exec_lsm.o 2 | 3 | -------------------------------------------------------------------------------- /security/can-exec/README.md: -------------------------------------------------------------------------------- 1 | can-exec 2 | -------- 3 | 4 | This is a LSM in which the kernel calls a user-mode helper to decide if binaries should be executed. 5 | 6 | Every time a command is to be executed the kernel will invoke a user-space helper: 7 | 8 | ``` 9 | /sbin/can-exec $UID $COMMAND 10 | ``` 11 | 12 | The arguments supplied are the UID of the invoking user, and the command they're trying to execute. If the user-space binary exits with a return-code of zero the execution will be permitted, otherwise it will be denied. 13 | 14 | 15 | 16 | ## Installation & Configuration 17 | 18 | First of all you'll need to build the kernel with this module enabled. Since there have been changes to the Kernel recently, to allow LSM module-stacking, these are the settings I used for my own tests: 19 | 20 | ``` 21 | # Security options 22 | # 23 | CONFIG_KEYS=y 24 | # CONFIG_KEYS_REQUEST_CACHE is not set 25 | # CONFIG_PERSISTENT_KEYRINGS is not set 26 | # CONFIG_TRUSTED_KEYS is not set 27 | # CONFIG_ENCRYPTED_KEYS is not set 28 | # CONFIG_KEY_DH_OPERATIONS is not set 29 | CONFIG_SECURITY_DMESG_RESTRICT=y 30 | CONFIG_SECURITY=y 31 | CONFIG_SECURITYFS=y 32 | CONFIG_SECURITY_NETWORK=y 33 | CONFIG_PAGE_TABLE_ISOLATION=y 34 | CONFIG_SECURITY_NETWORK_XFRM=y 35 | CONFIG_SECURITY_PATH=y 36 | # CONFIG_INTEL_TXT is not set 37 | CONFIG_HAVE_HARDENED_USERCOPY_ALLOCATOR=y 38 | # CONFIG_HARDENED_USERCOPY is not set 39 | # CONFIG_FORTIFY_SOURCE is not set 40 | # CONFIG_STATIC_USERMODEHELPER is not set 41 | # CONFIG_SECURITY_SELINUX is not set 42 | # CONFIG_SECURITY_SMACK is not set 43 | # CONFIG_SECURITY_TOMOYO is not set 44 | # CONFIG_SECURITY_APPARMOR is not set 45 | # CONFIG_SECURITY_LOADPIN is not set 46 | # CONFIG_SECURITY_YAMA is not set 47 | # CONFIG_SECURITY_SAFESETID is not set 48 | # CONFIG_SECURITY_LOCKDOWN_LSM is not set 49 | CONFIG_SECURITY_CAN_EXEC=y 50 | # CONFIG_SECURITY_HASH_CHECK is not set 51 | # CONFIG_SECURITY_WHITELIST is not set 52 | # CONFIG_INTEGRITY is not set 53 | # CONFIG_IMA_SECURE_AND_OR_TRUSTED_BOOT is not set 54 | # CONFIG_DEFAULT_SECURITY_CAN_EXEC is not set 55 | CONFIG_DEFAULT_SECURITY_DAC=y 56 | CONFIG_LSM="yama,loadpin,safesetid,integrity,can-exec,selinux,smack,tomoyo,apparmor" 57 | ``` 58 | 59 | ## Kernel Testing 60 | 61 | Once you've rebooted into your new kernel you should be able to see that the module is compiled successfully and available by running: 62 | 63 | ``` 64 | $ echo $(cat /sys/kernel/security/lsm) 65 | capability,can_exec 66 | ``` 67 | 68 | If you see `can_exec` listed, and you get output from this command you're good: 69 | 70 | ``` 71 | $ dmesg | grep LSM 72 | [ 0.282365] LSM: Security Framework initializing 73 | [ 0.283323] LSM initialized: can_exec 74 | ``` 75 | 76 | Finally you should also see the file `/proc/sys/kernel/can-exec/enabled` exists, and will have the contents `0` - as the module is not yet enabled. 77 | 78 | 79 | ## Setup User-Space 80 | 81 | The goal of this module is that the kernel will invoke a user-space helper whenever a binary is executed. The next step is thus to make that binary available. 82 | 83 | * Install `/sbin/can-exec` from the [samples/](samples/) directory. 84 | * This will be invoked to decide if users can execute binaries. 85 | * The sample implementation will read configuration files beneath `/etc/can-exec`, but you could rewrite it to only allow execution of commands between specific times of the day, or something entirely different! 86 | 87 | I have two configuration-files setup upon my system, one for `redis`: 88 | 89 | ``` 90 | root@kernel:~# cat /etc/can-exec/redis.conf 91 | /usr/bin/redis-server 92 | ``` 93 | 94 | and one for the `nobody` user: 95 | 96 | ``` 97 | root@kernel:~# cat /etc/can-exec/nobody.conf 98 | /bin/sh 99 | /bin/dash 100 | /bin/bash 101 | /usr/bin/id 102 | /usr/bin/uptime 103 | ``` 104 | 105 | That means: 106 | 107 | * The `redis` user can execute __only__ the single binary `/usr/bin/redis-server`. 108 | * The `nobody` user can execute: 109 | * `/bin/sh` 110 | * `/bin/dash` 111 | * `/bin/bash` 112 | * `/usr/bin/id` 113 | * `/usr/bin/uptime` 114 | 115 | Once the user-space binary is in-place you can enable the enforcement by running the following command: 116 | 117 | ``` 118 | root@stretch:~# echo 1 > /proc/sys/kernel/can-exec/enabled 119 | ``` 120 | 121 | **NOTE**: As a result of [#11](https://github.com/skx/linux-security-modules/issues/11) you cannot disable the module, once enabled. 122 | 123 | 124 | ## Links 125 | 126 | There is some back-story in the following blog-post: 127 | 128 | * https://blog.steve.fi/yet_more_linux_security_module_craziness___.html 129 | -------------------------------------------------------------------------------- /security/can-exec/can_exec_lsm.c: -------------------------------------------------------------------------------- 1 | /* 2 | * can_exec_lsm.c - Steve Kemp 3 | * 4 | * This is a security module which is designed to allow a system 5 | * administrator to permit/deny the execution of arbitrary binaries 6 | * via the UID of the invoking user, and the path they're executing. 7 | * 8 | * Deploying 9 | * --------- 10 | * 11 | * Once compiled you'll find that your kernel has a new file: 12 | * 13 | * /proc/sys/kernel/can-exec/enabled 14 | * 15 | * Enable the support by writing `1` to that file, but note that you'll 16 | * need to have setup the binary `/sbin/can-exec` before you do that! 17 | * 18 | * The user-space binary will receive two command-line arguments: 19 | * 20 | * * The UID of the invoking user. 21 | * 22 | * * The complete path, but not arguments, to the binary the user is invoking 23 | * 24 | * The user-space helper should return an exit-code of `0` if the execution 25 | * should be permitted, otherwise it will be denied. 26 | * 27 | * Steve 28 | * -- 29 | * 30 | */ 31 | 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include 43 | 44 | 45 | // 46 | // Is this module enabled? 47 | // 48 | // Controlled via /proc/sys/kernel/can-exec/enabled 49 | // 50 | static int can_exec_enabled = 0; 51 | 52 | 53 | // 54 | // Attempt to get the fully-qualified path of the given file. 55 | // 56 | // I suspect this is fragile and won't cope with mount-points, etc. 57 | // 58 | char *get_path(struct file *file, char *buf, int buflen) 59 | { 60 | struct dentry *dentry = file->f_path.dentry; 61 | char *ret = dentry_path_raw(dentry, buf, buflen); 62 | return ret; 63 | } 64 | 65 | 66 | // 67 | // If this module is enabled then call our user-space helper, 68 | // `/sbin/can-exec` to decide if child-processes can be executed. 69 | // 70 | static int can_exec_bprm_check_security_usermode(struct linux_binprm *bprm) 71 | { 72 | struct subprocess_info *sub_info; 73 | int ret = 0; 74 | char *argv[4]; 75 | 76 | // 77 | // The current task & UID. 78 | // 79 | const struct task_struct *task = current; 80 | kuid_t uid = task->cred->uid; 81 | 82 | // 83 | // Environment for our user-space helper. 84 | // 85 | static char *envp[] = 86 | { 87 | "HOME=/", 88 | "TERM=linux", 89 | "PATH=/sbin:/bin:/usr/sbin:/usr/bin", NULL 90 | }; 91 | 92 | // 93 | // If this module is not enabled we allow all. 94 | // 95 | if (can_exec_enabled == 0) 96 | return 0; 97 | 98 | // 99 | // If we're trying to exec our helper - then allow it 100 | // 101 | if (strcmp(bprm->filename, "/sbin/can-exec") == 0) 102 | return 0; 103 | 104 | // 105 | // The command we'll be executing. 106 | // 107 | argv[0] = "/sbin/can-exec"; // helper 108 | argv[1] = (char *)kmalloc(10, GFP_KERNEL); // UID 109 | argv[2] = NULL; // CMD 110 | argv[3] = NULL; // Terminator 111 | 112 | // 113 | // Populate the UID. 114 | // 115 | sprintf(argv[1], "%d", uid.val); 116 | 117 | // 118 | // Get the fully-qualified path to the command the user is trying to run. 119 | // 120 | { 121 | char *path_buff = kmalloc(PAGE_SIZE, GFP_KERNEL); 122 | char *path = NULL; 123 | 124 | if (unlikely(!path_buff)) 125 | { 126 | printk(KERN_INFO "kmalloc failed for path_buff"); 127 | 128 | // avoid leaking 129 | kfree(argv[1]); 130 | 131 | return -ENOMEM; 132 | } 133 | 134 | memset(path_buff, 0, PAGE_SIZE); 135 | 136 | path = get_path(bprm->file, path_buff, PAGE_SIZE); 137 | 138 | if (path != NULL) 139 | { 140 | argv[2] = kstrdup(path, GFP_KERNEL); 141 | kfree(path_buff); 142 | } 143 | else 144 | { 145 | printk(KERN_INFO "calling get_path failed!"); 146 | 147 | // avoid leaking 148 | kfree(argv[1]); 149 | kfree(path_buff); 150 | return -EPERM; 151 | } 152 | } 153 | 154 | // 155 | // Prepare to execute the user-space helper. 156 | // 157 | sub_info = call_usermodehelper_setup(argv[0], argv, envp, GFP_KERNEL, 158 | NULL, NULL, NULL); 159 | 160 | if (sub_info == NULL) 161 | { 162 | printk(KERN_INFO "failed to call call_usermodehelper_setup\n"); 163 | 164 | // Avoid leaking 165 | kfree(argv[1]); 166 | kfree(argv[2]); 167 | return -ENOMEM; 168 | } 169 | 170 | 171 | // 172 | // Call the helper and get the return-code. 173 | // 174 | ret = call_usermodehelper_exec(sub_info, UMH_WAIT_PROC); 175 | ret = (ret >> 8) & 0xff; 176 | 177 | // 178 | // Prevent leaks 179 | // 180 | kfree(argv[1]); 181 | kfree(argv[2]); 182 | 183 | // 184 | // Show the result. 185 | // 186 | printk(KERN_INFO "Return code from user-space was %d\n", ret); 187 | 188 | if (ret == 0) 189 | return (ret); 190 | else 191 | return (-EPERM); 192 | 193 | } 194 | 195 | 196 | struct ctl_path can_exec_sysctl_path[] = 197 | { 198 | { .procname = "kernel", }, 199 | { .procname = "can-exec", }, 200 | { } 201 | }; 202 | 203 | static struct ctl_table can_exec_sysctl_table[] = 204 | { 205 | { 206 | .procname = "enabled", 207 | .data = &can_exec_enabled, 208 | .maxlen = sizeof(int), 209 | .mode = 0644, 210 | /* only handle a transition from default "0" to "1" */ 211 | .proc_handler = proc_dointvec_minmax, 212 | .extra1 = SYSCTL_ONE, 213 | .extra2 = SYSCTL_ONE, 214 | }, 215 | { } 216 | }; 217 | 218 | 219 | /* 220 | * The hooks we wish to be installed. 221 | */ 222 | static struct security_hook_list can_exec_hooks[] __lsm_ro_after_init = 223 | { 224 | LSM_HOOK_INIT(bprm_check_security, can_exec_bprm_check_security_usermode), 225 | }; 226 | 227 | /* 228 | * Initialize our module. 229 | */ 230 | static int __init can_exec_init(void) 231 | { 232 | /* register /proc/sys/can-exec/enabled */ 233 | if (!register_sysctl_paths(can_exec_sysctl_path, can_exec_sysctl_table)) 234 | panic("sysctl registration failed.\n"); 235 | 236 | /* register ourselves with the security framework */ 237 | security_add_hooks(can_exec_hooks, ARRAY_SIZE(can_exec_hooks), "can_exec"); 238 | printk(KERN_INFO "LSM initialized: can_exec\n"); 239 | return 0; 240 | } 241 | 242 | 243 | /* 244 | * Ensure the initialization code is called. 245 | */ 246 | DEFINE_LSM(can_exec_init) = { 247 | .init = can_exec_init, 248 | .name = "can-exec", 249 | }; 250 | -------------------------------------------------------------------------------- /security/can-exec/samples/Makefile: -------------------------------------------------------------------------------- 1 | 2 | can-exec: can-exec.c 3 | gcc -Wall -Werror -std=c99 -o can-exec can-exec.c 4 | 5 | install: can-exec 6 | install --mode=0755 --owner=root --group=root can-exec /sbin/can-exec 7 | 8 | clean: 9 | rm -f can-exec 10 | -------------------------------------------------------------------------------- /security/can-exec/samples/can-exec.c: -------------------------------------------------------------------------------- 1 | /* 2 | * User-space helper for the `can_exec` LSM. 3 | * 4 | * This binary is invoked to decide if execution should be permitted/denied, 5 | * by reading the file /etc/can-exec/$USERNAME.conf 6 | * 7 | * If a command is listed there it is allowed, otherwise denied. 8 | * 9 | * Steve 10 | * -- 11 | */ 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | 22 | 23 | // Log a message to STDOUT for testing, and to syslog for production use. 24 | void logger( const char* format, ...) { 25 | 26 | char buf[256]; 27 | va_list arg_ptr; 28 | 29 | // format 30 | va_start(arg_ptr, format); 31 | vsnprintf(buf, sizeof(buf)-1, format, arg_ptr); 32 | va_end(arg_ptr); 33 | 34 | // paranoia means we should ensure we're terminated. 35 | buf[sizeof(buf)] = '\0'; 36 | 37 | // console output 38 | fprintf(stderr, buf); 39 | fprintf(stderr, "\n"); 40 | 41 | // syslog 42 | syslog(LOG_NOTICE, buf); 43 | 44 | } 45 | 46 | int main(int argc, char *argv[]) 47 | { 48 | // 49 | // Ensure we have the correct number of arguments. 50 | // 51 | if (argc != 3) 52 | { 53 | logger("Invalid argument count."); 54 | exit(-1); 55 | } 56 | 57 | // 58 | // First argument should be 100% numeric. 59 | // 60 | for (int i = 0; i < strlen(argv[1]); i++) 61 | { 62 | if ((argv[1][i] < '0') || 63 | (argv[1][i] > '9')) 64 | { 65 | logger("Invalid initial argument."); 66 | return -1; 67 | } 68 | } 69 | 70 | 71 | // 72 | // Get the UID + program from the command-line arguments. 73 | // 74 | uid_t uid = atoi(argv[1]); 75 | char *prg = argv[2]; 76 | size_t prg_len = strlen(prg); 77 | 78 | 79 | // 80 | // Get the username 81 | // 82 | struct passwd *pwd = getpwuid(uid); 83 | 84 | if (pwd == NULL) 85 | { 86 | logger("Failed to convert UID %d to username", uid); 87 | return -1; 88 | } 89 | 90 | // 91 | // Log the UID, username, and command. 92 | // 93 | openlog("can-exec", LOG_CONS | LOG_PID | LOG_NDELAY, LOG_LOCAL1); 94 | logger("UID:%d USER:%s CMD:%s", uid, pwd->pw_name, prg); 95 | 96 | // 97 | // Root can execute everything. 98 | // 99 | if (uid == 0) 100 | { 101 | logger("root can execute everything"); 102 | return 0; 103 | } 104 | 105 | 106 | // 107 | // We'll read a per-user configuration file to see if the 108 | // execution should be permitted. 109 | // 110 | char filename[128] = {'\0'}; 111 | snprintf(filename, sizeof(filename) - 1, 112 | "/etc/can-exec/%s.conf", pwd->pw_name); 113 | 114 | // 115 | // Open the configuration-file. 116 | // 117 | FILE* fp = fopen(filename, "r"); 118 | 119 | if (! fp) 120 | { 121 | logger("Failed to open %s: denying execution.", filename); 122 | return -1; 123 | } 124 | 125 | // 126 | // Read each line and look for a match 127 | // 128 | char buffer[255]; 129 | 130 | while (fgets(buffer, sizeof(buffer) - 1, (FILE*) fp)) 131 | { 132 | // 133 | // Strip out newlines 134 | // 135 | for (int i = 0; i < strlen(buffer); i++) 136 | if (buffer[i] == '\r' || buffer[i] == '\n') 137 | buffer[i] = '\0'; 138 | 139 | // 140 | // Skip lines which are comments. 141 | // 142 | if (buffer[0] == '#') 143 | continue; 144 | 145 | // 146 | // Does the command the user is trying to execute 147 | // match this line? 148 | // 149 | // TODO 150 | // 151 | // - We could allow matching regular expressions. 152 | // - We could allow matching based on directory 153 | // - e.g. "allow /bin /sbin" 154 | // 155 | if (strncmp(prg, buffer, prg_len) == 0) 156 | { 157 | logger("allowing execution of command."); 158 | fclose(fp); 159 | return 0; 160 | } 161 | } 162 | 163 | // 164 | // If we reached here we have no match, so execution is denied. 165 | // 166 | logger("Denying execution of command - no match found."); 167 | fclose(fp); 168 | closelog(); 169 | return -1; 170 | } 171 | -------------------------------------------------------------------------------- /security/hashcheck/Kconfig: -------------------------------------------------------------------------------- 1 | config SECURITY_HASH_CHECK 2 | bool "Hash-Checking Security Module" 3 | depends on SECURITY 4 | depends on NET 5 | select SECURITYFS 6 | select SECURITY_PATH 7 | select SECURITY_NETWORK 8 | select SRCU 9 | select BUILD_BIN2C 10 | default n 11 | help 12 | This selects an attr-based access control. 13 | Binaries will only be permitted to be executed 14 | if there is a corresponding hash digest stored 15 | in the extended attribute. 16 | -------------------------------------------------------------------------------- /security/hashcheck/Makefile: -------------------------------------------------------------------------------- 1 | obj-y = hashcheck_lsm.o 2 | 3 | -------------------------------------------------------------------------------- /security/hashcheck/README.md: -------------------------------------------------------------------------------- 1 | # hashcheck 2 | 3 | This is a LSM in which the kernel denies the execution of binaries to non-root users, unless: 4 | 5 | * There is a `security.hash` extended-attribute upon the binary. 6 | * The contents of that label match the SHA1 hash of the binary contents. 7 | 8 | There is some back-story in the following blog-post: 9 | 10 | * https://blog.steve.fi/linux_security_modules__round_two_.html 11 | 12 | This builds upon the learning I made writing the [whitelist LSM](../whitelist/). 13 | -------------------------------------------------------------------------------- /security/hashcheck/hashcheck_lsm.c: -------------------------------------------------------------------------------- 1 | /* 2 | * hashcheck_lsm.c - Steve Kemp 3 | * 4 | * This is a security module which is designed to prevent 5 | * the execution of unknown or (maliciously) altered binaries. 6 | * 7 | * This is achieved by computing an SHA1 digest of every 8 | * binary before it is executed, then comparing that to the 9 | * (assumed) known-good value which is stored as an extended 10 | * attribute alongside the binary. 11 | * 12 | * The intention is thus that a malicious binary will either 13 | * have a missing hash, or a bogus hash. 14 | * 15 | * Potential flaws in this approach include: 16 | * 17 | * * The use of scripting languages which can do arbitrary "stuff". 18 | * 19 | * * An attacker updating the hashes after replacing the binarie(s). 20 | * 21 | * * The need to update the hashes when there are security updates. 22 | * 23 | * * It kills all ad-hoc shell-scripts. 24 | * 25 | * That said it's a reasonably simple approach which doesn't have any major 26 | * downside. 27 | * 28 | * 29 | * Deploying 30 | * --------- 31 | * 32 | * To add the appropriate hashes you could do something like this: 33 | * 34 | * for i in /bin/?* /sbin/?*; do 35 | * setfattr -n security.hash -v $(sha1sum $i | awk '{print $1}') $i 36 | * done 37 | * 38 | * Steve 39 | * -- 40 | * 41 | */ 42 | 43 | #include 44 | #include 45 | #include 46 | #include 47 | #include 48 | #include 49 | #include 50 | #include 51 | #include 52 | #include 53 | #include 54 | 55 | 56 | /* 57 | * Given a file and a blob of memory calculate the SHA1 hash 58 | * of the file contents, and store it in the memory. 59 | * 60 | * This is a hacky routine, but it does work :) 61 | * 62 | */ 63 | int calc_sha1_hash(struct file *file, u8 *digest) 64 | { 65 | struct crypto_shash *tfm; 66 | struct shash_desc *desc; 67 | loff_t i_size, offset = 0; 68 | char *rbuf; 69 | int rc = 0; 70 | 71 | // The target we're checking 72 | struct dentry *dentry = file->f_path.dentry; 73 | struct inode *inode = d_backing_inode(dentry); 74 | 75 | // Allocate the hashing-helper. 76 | tfm = crypto_alloc_shash("sha1", 0, 0); 77 | 78 | if (IS_ERR(tfm)) 79 | { 80 | int error = PTR_ERR(tfm); 81 | printk(KERN_INFO "failed to setup sha1 hasher\n"); 82 | return error; 83 | } 84 | 85 | // Allocate the description. 86 | desc = kmalloc(sizeof(*desc) + crypto_shash_descsize(tfm), GFP_KERNEL); 87 | 88 | if (!desc) 89 | { 90 | printk(KERN_INFO "Failed to kmalloc desc"); 91 | goto out; 92 | } 93 | 94 | // Setup the description 95 | desc->tfm = tfm; 96 | desc->flags = crypto_shash_get_flags(tfm); 97 | 98 | // Init the hash 99 | rc = crypto_shash_init(desc); 100 | 101 | if (rc) 102 | { 103 | printk(KERN_INFO "failed to crypto_shash_init"); 104 | goto out2; 105 | } 106 | 107 | // Allocate a buffer for reading the file contents 108 | rbuf = kzalloc(PAGE_SIZE, GFP_KERNEL); 109 | 110 | if (!rbuf) 111 | { 112 | printk(KERN_INFO "failed to kzalloc"); 113 | rc = -ENOMEM; 114 | goto out2; 115 | } 116 | 117 | // Find out how big the file is 118 | i_size = i_size_read(inode); 119 | 120 | // Read it, in page-sized chunks. 121 | while (offset < i_size) 122 | { 123 | 124 | int rbuf_len; 125 | rbuf_len = kernel_read(file, offset, rbuf, PAGE_SIZE); 126 | 127 | if (rbuf_len < 0) 128 | { 129 | rc = rbuf_len; 130 | break; 131 | } 132 | 133 | if (rbuf_len == 0) 134 | break; 135 | 136 | offset += rbuf_len; 137 | 138 | rc = crypto_shash_update(desc, rbuf, rbuf_len); 139 | 140 | if (rc) 141 | break; 142 | } 143 | 144 | // Free the buffer we used for holding the file-contents 145 | kfree(rbuf); 146 | 147 | // Finalise the SHA result. 148 | if (!rc) 149 | rc = crypto_shash_final(desc, digest); 150 | 151 | out2: 152 | kfree(desc); 153 | out: 154 | crypto_free_shash(tfm); 155 | return rc; 156 | } 157 | 158 | 159 | /* 160 | * Perform a check of a program execution/map. 161 | * 162 | * Return 0 if it should be allowed, -EPERM on block. 163 | */ 164 | static int hashcheck_bprm_check_security(struct linux_binprm *bprm) 165 | { 166 | u8 *digest; 167 | int i; 168 | char *hash = NULL; 169 | char *buffer = NULL; 170 | int rc = 0; 171 | 172 | // The current task & the UID it is running as. 173 | const struct task_struct *task = current; 174 | kuid_t uid = task->cred->uid; 175 | 176 | // The target we're checking 177 | struct dentry *dentry = bprm->file->f_path.dentry; 178 | struct inode *inode = d_backing_inode(dentry); 179 | int size = 0; 180 | 181 | // Root can access everything. 182 | if (uid.val == 0) 183 | return 0; 184 | 185 | // Allocate some RAM to hold the digest result 186 | digest = (u8*)kmalloc(SHA1_DIGEST_SIZE, GFP_KERNEL); 187 | 188 | if (!digest) 189 | { 190 | printk(KERN_INFO "failed to allocate storage for digest"); 191 | return 0; 192 | } 193 | 194 | // 195 | // We're now going to calculate the hash. 196 | // 197 | memset(digest, 0, SHA1_DIGEST_SIZE); 198 | rc = calc_sha1_hash(bprm->file, digest); 199 | 200 | // 201 | // Now allocate a second piece of RAM to store the human-readable hash. 202 | // 203 | hash = (char*)kmalloc(PAGE_SIZE, GFP_KERNEL); 204 | 205 | if (!hash) 206 | { 207 | printk(KERN_INFO "failed to allocate storage for digest-pretty"); 208 | rc = -ENOMEM; 209 | goto out; 210 | } 211 | 212 | // 213 | // Create the human-readable result. 214 | // 215 | memset(hash, 0, PAGE_SIZE); 216 | 217 | for (i = 0; i < SHA1_DIGEST_SIZE; i++) 218 | { 219 | snprintf(hash + (i * 2), 4, "%02x", digest[i]); 220 | } 221 | 222 | // 223 | // Allocate the buffer for reading the xattr value 224 | // 225 | buffer = kzalloc(PAGE_SIZE, GFP_KERNEL); 226 | 227 | if (buffer == NULL) 228 | { 229 | printk(KERN_INFO "failed to allocate buffer for xattr value\n"); 230 | goto out2; 231 | } 232 | 233 | // 234 | // Get the xattr value. 235 | // 236 | size = __vfs_getxattr(dentry, inode, "security.hash", buffer, PAGE_SIZE - 1); 237 | 238 | // 239 | // This is the return-result from this function. 240 | // 241 | rc = 0; 242 | 243 | // 244 | // No hash? Then the execution is denied. 245 | // 246 | if (size < 0) 247 | { 248 | printk(KERN_INFO "Missing `security.hash` value!\n"); 249 | rc = -EPERM; 250 | } 251 | else 252 | { 253 | // 254 | // Using a constant-time comparison see if we got a match. 255 | // 256 | if (crypto_memneq(buffer, hash, strlen(hash)) == 0) 257 | { 258 | printk(KERN_INFO "Hash of %s matched expected result %s - allowing execution\n", bprm->filename, hash); 259 | rc = 0; 260 | } 261 | else 262 | { 263 | printk(KERN_INFO "Hash mismatch for %s - denying execution [%s != %s]\n", bprm->filename, hash, buffer); 264 | rc = -EPERM; 265 | } 266 | } 267 | 268 | kfree(buffer); 269 | 270 | out2: 271 | kfree(hash); 272 | 273 | out: 274 | kfree(digest); 275 | 276 | return (rc); 277 | } 278 | 279 | /* 280 | * The hooks we wish to be installed. 281 | */ 282 | static struct security_hook_list hashcheck_hooks[] __lsm_ro_after_init = 283 | { 284 | LSM_HOOK_INIT(bprm_check_security, hashcheck_bprm_check_security), 285 | }; 286 | 287 | /* 288 | * Initialize our module. 289 | */ 290 | static int __init hashcheck_init(void) 291 | { 292 | /* register ourselves with the security framework */ 293 | security_add_hooks(hashcheck_hooks, ARRAY_SIZE(hashcheck_hooks), "hashcheck"); 294 | printk(KERN_INFO "LSM initialized: hashcheck\n"); 295 | return 0; 296 | } 297 | 298 | 299 | /* 300 | * Ensure the initialization code is called. 301 | */ 302 | DEFINE_LSM(hashcheck_init) = { 303 | .init = hashcheck_init, 304 | .name = "hashcheck", 305 | }; 306 | -------------------------------------------------------------------------------- /security/whitelist/Kconfig: -------------------------------------------------------------------------------- 1 | config SECURITY_WHITELIST 2 | bool "Whitelist Security Module" 3 | depends on SECURITY 4 | depends on NET 5 | select SECURITYFS 6 | select SECURITY_PATH 7 | select SECURITY_NETWORK 8 | select SRCU 9 | select BUILD_BIN2C 10 | default n 11 | help 12 | This selects an attr-based access control. 13 | Binaries with a particular xattr setting will be 14 | permitted to be executed by non-root users. 15 | 16 | -------------------------------------------------------------------------------- /security/whitelist/Makefile: -------------------------------------------------------------------------------- 1 | obj-y = whitelist_lsm.o 2 | 3 | -------------------------------------------------------------------------------- /security/whitelist/README.md: -------------------------------------------------------------------------------- 1 | # whitelist 2 | 3 | This is a LSM in which the kernel denies the execution of binaries 4 | to non-root users, unless there is an extended-attribute named 5 | `security.whitelisted` present upon the binary. 6 | 7 | **NOTE**: The content/value of that attribute doesn't matter, only the existance is tested 8 | 9 | There is some back-story in the following blog-post: 10 | 11 | * https://blog.steve.fi/so_i_accidentally_wrote_a_linux_security_module.html 12 | 13 | This module was enhanced in the [hashcheck LSM](../hashcheck/). 14 | -------------------------------------------------------------------------------- /security/whitelist/samples/whitelist.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Simple script to setup whitelisting attributes on files: 3 | * 4 | * whitelist --add /bin/bash /bin/sh /usr/bin/id /usr/bin/uptime [..] 5 | * 6 | * whitelist --del /bin/bash /bin/sh [..] 7 | * 8 | * whitelist --list [/sbin /usr/sbin] 9 | * 10 | * With no arguments it displays whitelisted binaries beneath the current directory, 11 | * recursively. If you prefer you can list the directories to search explicitly. 12 | * 13 | * Steve 14 | * -- 15 | */ 16 | 17 | /* We want POSIX.1-2008 + XSI, i.e. SuSv4, features */ 18 | #define _XOPEN_SOURCE 700 19 | 20 | 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | 32 | 33 | static int add_flag = 0; 34 | static int del_flag = 0; 35 | static int list_flag = 0; 36 | 37 | 38 | /* 39 | * Add the whitelist attribute to the given path. 40 | */ 41 | void add_whitelist(const char *path) 42 | { 43 | char value[2] = "1"; 44 | 45 | if (setxattr(path, "security.whitelisted", value, strlen(value), 0) == -1) 46 | perror("setxattr"); 47 | } 48 | 49 | /* 50 | * Remove the whitelist attribute from the given path. 51 | */ 52 | void del_whitelist(const char *path) 53 | { 54 | if (removexattr(path, "security.whitelisted") != 0) 55 | perror("removexattr"); 56 | } 57 | 58 | int print_entry(const char *filepath, const struct stat *info, 59 | const int typeflag, struct FTW *pathinfo) 60 | { 61 | 62 | /* 63 | * We only care about files. 64 | */ 65 | if (typeflag == FTW_F) 66 | { 67 | int length = getxattr(filepath, "security.whitelisted", NULL, 0); 68 | 69 | if (length > 0) 70 | printf("%s\n", filepath); 71 | } 72 | 73 | return 0; 74 | } 75 | /* 76 | * Look at all the files in the given directory, show those that are 77 | * whitelisted. 78 | */ 79 | void list_whitelist(const char *directory) 80 | { 81 | 82 | #ifndef USE_FDS 83 | #define USE_FDS 15 84 | #endif 85 | 86 | 87 | int result = nftw(directory, print_entry, USE_FDS, FTW_PHYS); 88 | 89 | if (result > 0) 90 | perror("nftw"); 91 | } 92 | 93 | 94 | /* 95 | * Entry-Point. 96 | */ 97 | int main(int argc, char **argv) 98 | { 99 | int c; 100 | 101 | while (1) 102 | { 103 | /* 104 | * Parse our command-line options. 105 | */ 106 | static struct option long_options[] = 107 | { 108 | {"add", no_argument, &add_flag, 1}, 109 | {"del", no_argument, &del_flag, 1}, 110 | {"list", no_argument, &list_flag, 1}, 111 | {0, 0, 0, 0} 112 | }; 113 | 114 | int option_index = 0; 115 | 116 | c = getopt_long(argc, argv, "", long_options, &option_index); 117 | 118 | if (c == -1) 119 | break; 120 | 121 | switch (c) 122 | { 123 | case 0: 124 | 125 | /* If this option set a flag, do nothing else now. */ 126 | if (long_options[option_index].flag != 0) 127 | break; 128 | 129 | case '?': 130 | /* getopt_long already printed an error message. */ 131 | break; 132 | 133 | default: 134 | abort(); 135 | } 136 | } 137 | 138 | /* No action? Then list. */ 139 | if ((add_flag == 0) && (del_flag == 0) && (list_flag == 0)) 140 | list_flag = 1; 141 | 142 | /* Adding whitelist to some files? */ 143 | if (add_flag) 144 | { 145 | while (optind < argc) 146 | add_whitelist(argv[optind++]) ; 147 | } 148 | 149 | /* Removing whitelist from some files? */ 150 | if (del_flag) 151 | { 152 | while (optind < argc) 153 | del_whitelist(argv[optind++]) ; 154 | } 155 | 156 | /* Listing files */ 157 | if (list_flag) 158 | { 159 | /* If we have arguments assume they're directories to list. */ 160 | if (optind < argc) 161 | { 162 | while (optind < argc) 163 | list_whitelist(argv[optind++]); 164 | } 165 | else 166 | { 167 | list_whitelist("."); 168 | } 169 | } 170 | 171 | /* All done */ 172 | exit(0); 173 | } 174 | -------------------------------------------------------------------------------- /security/whitelist/whitelist_lsm.c: -------------------------------------------------------------------------------- 1 | /* 2 | * whitelist_lsm.c 3 | * 4 | * Allow/deny execution of programs to non-root users, by looking for 5 | * a security attribute upon the file. 6 | * 7 | * To set a program as whitelisted you must add a label to the target, 8 | * for example: 9 | * 10 | * setfattr -n security.whitelisted -v 1 /bin/dash 11 | * 12 | * To confirm there is a label present you can use the dump option: 13 | * 14 | * ~# getfattr -d -m security /bin/dash 15 | * getfattr: Removing leading '/' from absolute path names 16 | * # file: bin/dash 17 | * security.whitelisted="1" 18 | * 19 | * Finally to revoke the label, and deny execution once more: 20 | * 21 | * ~# setfattr -x security.whitelisted /bin/dash 22 | * 23 | * There is a helper tool located in `samples/whitelist` which wraps 24 | * that for you, in a simple way. 25 | * 26 | * Steve 27 | * -- 28 | * 29 | */ 30 | 31 | 32 | #include 33 | #include 34 | #include 35 | #include 36 | 37 | 38 | /* 39 | * Perform a check of a program execution/map. 40 | * 41 | * Return 0 if it should be allowed, -EPERM on block. 42 | */ 43 | static int whitelist_bprm_check_security(struct linux_binprm *bprm) 44 | { 45 | // The current task & the UID it is running as. 46 | const struct task_struct *task = current; 47 | kuid_t uid = task->cred->uid; 48 | 49 | // The target we're checking 50 | struct dentry *dentry = bprm->file->f_path.dentry; 51 | struct inode *inode = d_backing_inode(dentry); 52 | 53 | // Size of the attribute, if any. 54 | int size = 0; 55 | 56 | // Root can access everything. 57 | if ( uid.val == 0 ) 58 | return 0; 59 | 60 | // Is there an attribute? If so allow the access 61 | size = __vfs_getxattr(dentry, inode, "security.whitelisted", NULL, 0); 62 | if ( size > 0 ) 63 | return 0; 64 | 65 | // Otherwise deny it. 66 | printk(KERN_INFO "whitelist LSM check of %s denying access for UID %d [ERRO:%d] \n", bprm->filename, uid.val, size ); 67 | return -EPERM; 68 | } 69 | 70 | /* 71 | * The hooks we wish to be installed. 72 | */ 73 | static struct security_hook_list whitelist_hooks[] __lsm_ro_after_init = { 74 | LSM_HOOK_INIT(bprm_check_security, whitelist_bprm_check_security), 75 | }; 76 | 77 | /* 78 | * Initialize our module. 79 | */ 80 | void __init whitelist_add_hooks(void) 81 | { 82 | security_add_hooks(whitelist_hooks, ARRAY_SIZE(whitelist_hooks), "whitelist"); 83 | printk(KERN_INFO "whitelist LSM initialized\n"); 84 | } 85 | --------------------------------------------------------------------------------