Try the Linux kernel lockdown mechanism

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.

What is the kernel lockdown function?

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.

Try the kernel lockdown feature

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)

Get kernel source code

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

Enable lockdown feature in kernel config

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

Kernel module loading is batch reblocked

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'll take a look at the source code

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.

What is the flow of blocking when loading a kernel module?

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.

Summary

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.

Reference URL

Recommended Posts

Try the Linux kernel lockdown mechanism
What is the Linux kernel?
A quick overview of the Linux kernel
Let's try Linux for the first time
Compiling the Linux kernel (Linux 5.x on Ubuntu 20.04)
A memo for utilizing the unit test mechanism KUnit of the Linux kernel
Get the latest Linux kernel version with Arch Linux
Control the Linux trackpad
About Linux kernel parameters
Linux kernel release 5.x (2/4)
Check Linux kernel version
Linux kernel release 5.x (3/4)
Linux kernel build time
About the process that the Linux kernel handles x86 microcode
Linux kernel release 5.x (4/4)
Try NeosVR on Linux
I tried installing the Linux kernel on virtualbox + vagrant
Linux kernel release 5.x (1/4)
Listed data structures in the Linux kernel and their operations
[Linux] [kernel module] Specify / limit the execution CPU of kthread
The Kernel Address Sanitizer (KASAN) (2/2)
Self-build linux kernel with clang
Try using the Twitter API
[Linux] Update the package offline
Try normal Linux programming Part 2
Install the JDK on Linux
Try normal Linux programming Part 3
Try the Taxii server (1. Server settings)
Understand the Linux audit system Audit
Try using the Twitter API
The Linux Watchdog driver API
Try normal Linux programming Part 4
Try using the PeeringDB 2.0 API
[Linux] Directory under the root
Try normal Linux programming Part 6
Paste the link on linux
Try doubling the PyODE slider
The Kernel Address Sanitizer (KASAN) (1/2)
[LINUX kernel rebuild] Version upgrade (4.18.0 → 5.8.8)
Explaining the mechanism of Linux that you do not know unexpectedly