Sorry for the late posting. Linux Advent Calendar 2019 This is the article on the 6th day. Today I'm going to try the kernel lockdown mechanism introduced from 5.4 of the Linux kernel.
In Linux (or UNIX-like OS), by separating the authority between the general user and the root user, it is less likely that the system stability will be impaired due to careless operation by the general user. .. However, if the root user inadvertently performs an operation, or if the malicious user has been deprived of root privileges, this privilege separation method cannot handle it.
The kernel lockdown mechanism is a function that "restricts operations that involve system changes even if you are the root user", and if the kernel has kernel lockdown set, even if you are the root user, / dev Access to / mem
, / dev / kmen
and CPU MSR (model-specific register) is blocked, and in addition, kernel changes (= addition of functions by the kernel module) cannot be performed. In addition, since this function imposes strong restrictions on the operation of the system, there is a concern that it will not work if it is applied to the existing system as it is, so it is disabled by default.
Now that you have an overview of the features, let's try the kernel lockdown feature. First, build the kernel. This time I will build it in the environment of CentOS-7.
# cat /etc/redhat-release
CentOS Linux release 7.5.1804 (Core)
I will try the Linux kernel with 5.4.2
, which is the latest version of the 5.x series (as of December 08, 2019). Follow the steps below to download and extract the kernel source code. The following build procedure is performed according to Building procedure of Linux kernel written before.
$ curl -O https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.4.2.tar.xz
$ cd /usr/src
$ sudo bash
# tar Jxf /home/centos/work/linux_build/linux-5.4.2.tar.xz
Now that the kernel source code has been expanded, let's take a look at the config related to the lockdown function.
The config starting with LOCK_DOWN_KERNEL_FORCE_
seems to be the setting related to the lockdown function.
# find . -type f | grep Kconfig | xargs grep LOCK_DOWN
./security/lockdown/Kconfig: default LOCK_DOWN_KERNEL_FORCE_NONE
./security/lockdown/Kconfig:config LOCK_DOWN_KERNEL_FORCE_NONE
./security/lockdown/Kconfig:config LOCK_DOWN_KERNEL_FORCE_INTEGRITY
./security/lockdown/Kconfig:config LOCK_DOWN_KERNEL_FORCE_CONFIDENTIALITY
Looking at LOCK_DOWN_KERNEL_FORCE_INTEGRITY
, it seems that it runs in" integrity "mode (by default when kernel lockdown is enabled) and invalidates changes to the kernel at runtime.
config LOCK_DOWN_KERNEL_FORCE_INTEGRITY
bool "Integrity"
help
The kernel runs in integrity mode by default. Features that allow
the kernel to be modified at runtime are disabled.
This means that if you enable this setting, it will behave like an error when loading the kernel module. Let's build the kernel and give it a try.
Follow the steps below to set the Linux kernel config.
$ cd linux-5.4.2
$ make defconfig
$ make menuconfig
The setting location of LOCK_DOWN_KERNEL_FORCE_INTEGRITY
seems to be in the following location.
-> Security options
-> Basic module for enforcing kernel lockdown
-> Kernel default lockdown mode
The difference from make defconfig
(default config of Linux kernel) is as follows.
(In the environment of CentOS-7 or later, the file system is XFS, so it is necessary to set XFS to be included in the kernel)
# diff -ur .config.ORIG .config | grep -v '^ '
--- .config.ORIG 2019-12-07 16:45:20.264339000 +0900
+++ .config 2019-12-07 23:17:48.150270000 +0900
@@ -725,13 +725,22 @@
+CONFIG_MODULE_SIG_FORMAT=y
-# CONFIG_MODULE_SIG is not set
+CONFIG_MODULE_SIG=y
+# CONFIG_MODULE_SIG_FORCE is not set
+CONFIG_MODULE_SIG_ALL=y
+CONFIG_MODULE_SIG_SHA1=y
+# CONFIG_MODULE_SIG_SHA224 is not set
+# CONFIG_MODULE_SIG_SHA256 is not set
+# CONFIG_MODULE_SIG_SHA384 is not set
+# CONFIG_MODULE_SIG_SHA512 is not set
+CONFIG_MODULE_SIG_HASH="sha1"
@@ -3890,7 +3899,13 @@
-# CONFIG_XFS_FS is not set
+CONFIG_XFS_FS=y
+# CONFIG_XFS_QUOTA is not set
+# CONFIG_XFS_POSIX_ACL is not set
+# CONFIG_XFS_RT is not set
+# CONFIG_XFS_ONLINE_SCRUB is not set
+# CONFIG_XFS_WARN is not set
+# CONFIG_XFS_DEBUG is not set
@@ -4109,7 +4124,11 @@
-# CONFIG_SECURITY_LOCKDOWN_LSM is not set
+CONFIG_SECURITY_LOCKDOWN_LSM=y
+# CONFIG_SECURITY_LOCKDOWN_LSM_EARLY is not set
+# CONFIG_LOCK_DOWN_KERNEL_FORCE_NONE is not set
+CONFIG_LOCK_DOWN_KERNEL_FORCE_INTEGRITY=y
+# CONFIG_LOCK_DOWN_KERNEL_FORCE_CONFIDENTIALITY is not set
@@ -4332,6 +4351,7 @@
+CONFIG_MODULE_SIG_KEY="certs/signing_key.pem"
@@ -4369,7 +4389,7 @@
-# CONFIG_LIBCRC32C is not set
+CONFIG_LIBCRC32C=y
You have now set the required kernel config. Then run make
to add the kernel entry you built to GRUB.
# make
# make modules_install \
&& cp -f arch/x86_64/boot/bzImage /boot/vmlinuz-5.4.2.x86_64 \
&& mkinitrd --force /boot/initramfs-5.4.2.x86_64.img 5.4.2 \
&& grub2-mkconfig -o /boot/grub2/grub.cfg
Reboot, select Linux-5.4.2
from GRUB and boot and you're ready to go!
# uname -a
Linux linuxadvcal 5.4.2 #7 SMP Sat Dec 7 22:04:12 JST 2019 x86_64 x86_64 x86_64 GNU/Linux
When I try to load a suitable kernel module, the module load fails (= blocks) even if it is the root user.
# insmod /usr/lib/modules/5.4.2/build/net/netfilter/xt_nat.ko
insmod: ERROR: could not insert module /usr/lib/modules/5.4.2/build/net/netfilter/xt_nat.ko: Operation not permitted
At this time, the following message is output to the console.
[ 459.212341] Lockdown: insmod: unsigned module loading is restricted; see man kernel_lockdown.7
Due to the setting of LOCK_DOWN_KERNEL_FORCE_INTEGRITY
, no kernel module is loaded.
# lsmod
Module Size Used by
#
Looking at the contents of dmesg
, the message" Kernel is locked down from Kernel configuration; "is output immediately after starting Linux, so the setting of LOCK_DOWN_KERNEL_FORCE_INTEGRITY
that "kernel changes after booting are invalidated" The behavior is in line with.
# dmesg -T | head -n1 ; dmesg -T | egrep '(Kernel configuration|kernel_lockdown)'
[Sun December 8 00:38:21 2019] Linux version 5.4.2 (root@linuxadvcal) (gcc version 4.8.5 20150623 (Red Hat 4.8.5-28) (GCC)) #7 SMP Sat Dec 7 22:04:12 JST 2019
[Sun December 8 00:38:20 2019] Kernel is locked down from Kernel configuration; see man kernel_lockdown.7
[Sun December 8 00:38:21 2019] Lockdown: swapper/0: hibernation is restricted; see man kernel_lockdown.7
[Sun December 8 00:38:55 2019] Lockdown: insmod: unsigned module loading is restricted; see man kernel_lockdown.7
I briefly tried the kernel lockdown feature. I somehow understood the behavior, but it is humanity (?) That makes me want to actually look at the source code. Let's take a quick look at the source code.
First, let's find the part that gives an error (blocking module loading) when ʻinsmod` is executed. The following message output to the console may be a clue to the investigation.
[Sun December 8 00:38:55 2019] Lockdown: insmod: unsigned module loading is restricted; see man kernel_lockdown.7
The above message is output in the following places.
security/lockdown/lockdown.c
79 /**
80 * lockdown_is_locked_down - Find out if the kernel is locked down
81 * @what: Tag to use in notice generated if lockdown is in effect
82 */
83 static int lockdown_is_locked_down(enum lockdown_reason what)
84 {
...
89 if (kernel_locked_down >= what) {
90 if (lockdown_reasons[what])
91 pr_notice("Lockdown: %s: %s is restricted; see man kernel_lockdown.7\n",
92 current->comm, lockdown_reasons[what]);
93 return -EPERM;
94 }
The "unsigned module loaded" part of the message is the string taken from lockdown_readsons [what]
, apparently referenced by the constant LOCKDOWN_MODULE_SIGNATURE
.
security/lockdown/lockdown.c
19 static const char *const lockdown_reasons[LOCKDOWN_CONFIDENTIALITY_MAX+1] = {
...
21 [LOCKDOWN_MODULE_SIGNATURE] = "unsigned module loading",
The substance of LOCKDOWN_MODULE_SIGNATURE
is an enum value.
include/linux/security.h
104 enum lockdown_reason {
105 LOCKDOWN_NONE,
106 LOCKDOWN_MODULE_SIGNATURE,
...
It seems that LOCKDOWN_MODULE_SIGNATURE
is referenced only in two places, security / lockdown / lockdown.c
(where you are currently chasing the source) and kernel / module.c
.
In kernel / module.c
, the processing is branched by the return value ofmod_verify_sig ()
, and the structure is such thatsecurity_locked_down ()
with LOCKDOWN_MODULE_SIGNATURE
as an argument is called (line 2882).
kernel/module.c
2839 #ifdef CONFIG_MODULE_SIG
2840 static int module_sig_check(struct load_info *info, int flags)
2841 {
...
2851 if (flags == 0 &&
2852 info->len > markerlen &&
2853 memcmp(mod + info->len - markerlen, MODULE_SIG_STRING, markerlen) == 0) {
2854 /* We truncate the module to discard the signature */
2855 info->len -= markerlen;
2856 err = mod_verify_sig(mod, info);
2857 }
2858
2859 switch (err) {
...
2871 case -ENOPKG:
2872 reason = "Loading of module with unsupported crypto";
2873 goto decide;
...
2876 decide:
2877 if (is_module_sig_enforced()) {
2878 pr_notice("%s is rejected\n", reason);
2879 return -EKEYREJECTED;
2880 }
2881
2882 return security_locked_down(LOCKDOWN_MODULE_SIGNATURE);
2883
2884 /* All other errors are fatal, including nomem, unparseable
2885 * signatures and signature check failures - even if signatures
2886 * aren't required.
2887 */
2888 default:
2889 return err;
2890 }
security_locked_down ()
is defined in kernel / module.c
, from which it callscall_int_hook ()
.
security/security.c
2402 int security_locked_down(enum lockdown_reason what)
2403 {
2404 return call_int_hook(locked_down, 0, what);
2405 }
2406 EXPORT_SYMBOL(security_locked_down);
call_int_hook ()
is defined as a function macro in the same C file and calls security_hook_heads.FUNC ()
. This is macro expanded so that struct security_hook_hands-> locked_down ()
is called.
security/security.c
38 struct security_hook_heads security_hook_heads __lsm_ro_after_init;
...
657 #define call_int_hook(FUNC, IRC, ...) ({ \
658 int RC = IRC; \
659 do { \
660 struct security_hook_list *P; \
661 \
662 hlist_for_each_entry(P, &security_hook_heads.FUNC, list) { \
663 RC = P->hook.FUNC(__VA_ARGS__); \
664 if (RC != 0) \
665 break; \
666 } \
667 } while (0); \
668 RC; \
669 })
Locked_down
is defined as a member variable of struct security_hook_hands
, and a function pointer is set to this member variable in security / lockdown / lockdown.c
.
include/linux/lsm_hooks.h
1823 struct security_hook_heads {
...
2062 struct hlist_head locked_down;
2063 } __randomize_layout;
If the index value passed in the argument what
inlockdown_is_locked_down ()
set as a function pointer can be referenced as an arraylockdown_reasons []
, the above-mentioned" Lockdown: insmod: unsigned module loading is restricted; see man kernel_lockdown It is outputting .7 ".
security/lockdown/lockdown.c
83 static int lockdown_is_locked_down(enum lockdown_reason what)
84 {
85 if (WARN(what >= LOCKDOWN_CONFIDENTIALITY_MAX,
86 "Invalid lockdown reason"))
87 return -EPERM;
88
89 if (kernel_locked_down >= what) {
90 if (lockdown_reasons[what])
91 pr_notice("Lockdown: %s: %s is restricted; see man kernel_lockdown.7\n",
92 current->comm, lockdown_reasons[what]);
93 return -EPERM;
94 }
95
96 return 0;
97 }
98
99 static struct security_hook_list lockdown_hooks[] __lsm_ro_after_init = {
100 LSM_HOOK_INIT(locked_down, lockdown_is_locked_down),
101 };
In this case, the value of the argument what
is LOCKDOWN_MODULE_SIGNATURE
, which refers to "unsigned module loading".
security/lockdown/lockdown.c
19 static const char *const lockdown_reasons[LOCKDOWN_CONFIDENTIALITY_MAX+1] = {
20 [LOCKDOWN_NONE] = "none",
21 [LOCKDOWN_MODULE_SIGNATURE] = "unsigned module loading",
...
41 [LOCKDOWN_CONFIDENTIALITY_MAX] = "confidentiality",
42 };
Conditional compilation is performed by LOCK_DOWN_KERNEL_FORCE_INTEGRITY
set as the kernel config inlockdown_lsm_init ()
, and the lockdown level is set to LOCKDOWN_CONFIDENTIALITY_MAX
. This is the behavior in the above array lockdown_reasons []
that items with a value greater than LOCKDOWN_MODULE_SIGNATURE
and a smaller value (or array index) are valid factors for kernel lockdown.
security/lockdown/lockdown.c
51 static int lock_kernel_down(const char *where, enum lockdown_reason level)
52 {
53 if (kernel_locked_down >= level)
54 return -EPERM;
55
56 kernel_locked_down = level;
57 pr_notice("Kernel is locked down from %s; see man kernel_lockdown.7\n",
58 where);
59 return 0;
60 }
...
103 static int __init lockdown_lsm_init(void)
104 {
105 #if defined(CONFIG_LOCK_DOWN_KERNEL_FORCE_INTEGRITY)
106 lock_kernel_down("Kernel configuration", LOCKDOWN_INTEGRITY_MAX);
107 #elif defined(CONFIG_LOCK_DOWN_KERNEL_FORCE_CONFIDENTIALITY)
108 lock_kernel_down("Kernel configuration", LOCKDOWN_CONFIDENTIALITY_MAX);
109 #endif
110 security_add_hooks(lockdown_hooks, ARRAY_SIZE(lockdown_hooks),
111 "lockdown");
112 return 0;
113 }
Although it is a rough explanation, I was able to see the flow of behavior when LOCK_DOWN_KERNEL_FORCE_INTEGRITY
is specified in the kernel config from the source code.
I introduced the kernel lockdown mechanism of Linux. The behavior of the kernel module around security is not complete by itself, and the values and functions set by the module are used from other sources, so it feels a little difficult to read the source code. In combination with pr_notice ()
, it seems good to read the behavior while actually moving it.
Recommended Posts