├── .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 |
--------------------------------------------------------------------------------