Probieren Sie den Linux-Kernel-Sperrmechanismus aus

Entschuldigung für die späte Veröffentlichung. Linux Adventskalender 2019 Dies ist der Artikel am 6. Tag. Heute werde ich den Kernel-Sperrmechanismus ausprobieren, der mit dem Linux-Kernel [5.4] geliefert wurde (https://lkml.org/lkml/2019/11/24/187).

Was ist die Kernel-Sperrfunktion?

Unter Linux (oder UNIX-basierten Betriebssystemen) ist es weniger wahrscheinlich, dass durch die Trennung der Berechtigung zwischen dem allgemeinen Benutzer und dem Root-Benutzer die Stabilität des Systems durch unachtsamen Betrieb durch den allgemeinen Benutzer beeinträchtigt wird. .. Wenn der Root-Benutzer jedoch versehentlich einen Vorgang ausführt oder wenn die Root-Berechtigung einem böswilligen Benutzer gestohlen wird, kann diese Berechtigungs-Trennungsmethode dies nicht verarbeiten.

Der Kernel-Sperrmechanismus ist eine Funktion, die "Operationen einschränkt, die Systemänderungen beinhalten, selbst wenn es sich um einen Root-Benutzer handelt", und wenn für den Kernel die Kernel-Sperrung festgelegt ist, selbst wenn es sich um einen Root-Benutzer handelt, / dev Der Zugriff auf / mem, / dev / kmen und CPU MSR (modellspezifisches Register) ist gesperrt, und außerdem ist es nicht möglich, den Kernel zu ändern (= Funktionen durch das Kernelmodul hinzufügen). Darüber hinaus unterliegt diese Funktion starken Einschränkungen für den Betrieb des Systems, und es besteht die Sorge, dass sie nicht funktioniert, wenn sie so wie sie ist auf ein vorhandenes System angewendet wird, sodass sie standardmäßig deaktiviert ist.

Probieren Sie die Kernel-Sperrfunktion aus

Nachdem Sie sich einen Überblick über die Funktionen verschafft haben, versuchen wir es mit der Kernel-Sperrfunktion. Erstellen Sie zunächst den Kernel. Dieses Mal werde ich versuchen, in die Umgebung von CentOS-7 zu bauen.

# cat /etc/redhat-release
CentOS Linux release 7.5.1804 (Core)

Holen Sie sich den Kernel-Quellcode

Ich werde den Linux-Kernel mit der neuesten Version der 5.x-Serie (Stand: 08. Dezember 2019) 5.4.2 testen. Führen Sie die folgenden Schritte aus, um den Kernel-Quellcode herunterzuladen und zu extrahieren. Nachfolgende Erstellungsprozeduren werden gemäß der zuvor geschriebenen Linux-Kernel-Erstellungsprozedur ausgeführt.

$ 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

Aktivieren Sie die Sperrfunktion in der Kernelkonfiguration

Nachdem der Kernel-Quellcode erweitert wurde, werfen wir einen Blick auf die Konfiguration für die Sperrfunktion. Die Konfiguration, die mit LOCK_DOWN_KERNEL_FORCE_ beginnt, scheint die Einstellung zu sein, die sich auf die Sperrfunktion bezieht.

# 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

Wenn man sich "LOCK_DOWN_KERNEL_FORCE_INTEGRITY" ansieht, scheint es, dass es im "Integritäts" -Modus ausgeführt wird (standardmäßig mit aktivierter Kernel-Sperre) und Änderungen am Kernel zur Laufzeit ungültig macht.

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.

Wenn Sie diese Einstellung aktivieren, verhält sie sich beim Laden des Kernelmoduls wie ein Fehler. Lassen Sie uns den Kernel erstellen und ausprobieren.

Führen Sie die folgenden Schritte aus, um die Linux-Kernelkonfiguration festzulegen.

$ cd linux-5.4.2
$ make defconfig
$ make menuconfig

Der Einstellungsort von LOCK_DOWN_KERNEL_FORCE_INTEGRITY scheint sich an dem folgenden Ort zu befinden.

-> Security options
 -> Basic module for enforcing kernel lockdown
  -> Kernel default lockdown mode

Der Unterschied zu make defconfig (Standardkonfiguration des Linux-Kernels) ist wie folgt. (In der Umgebung von CentOS-7 oder höher ist das Dateisystem XFS, daher muss XFS so eingestellt werden, dass es in den Kernel aufgenommen wird.)

# 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

Sie haben jetzt die erforderliche Kernelkonfiguration festgelegt. Führen Sie dann make aus, um den Eintrag für den erstellten Kernel zu GRUB hinzuzufügen.

# 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

Starten Sie neu, wählen Sie "Linux-5.4.2" aus GRUB und starten Sie und los geht's!

# 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

Das Laden des Kernelmoduls wird stapelweise erneut blockiert

Wenn ich versuche, ein geeignetes Kernelmodul zu laden, schlägt das Laden des Moduls fehl (= blockiert), selbst wenn es sich um den Root-Benutzer handelt.

# 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

Zu diesem Zeitpunkt wird die folgende Nachricht an die Konsole ausgegeben.

[  459.212341] Lockdown: insmod: unsigned module loading is restricted; see man kernel_lockdown.7

Aufgrund der Einstellung von LOCK_DOWN_KERNEL_FORCE_INTEGRITY wird kein Kernelmodul geladen.

# lsmod
Module                  Size  Used by
#

Wenn Sie sich den Inhalt von dmesg ansehen, wird die Meldung" Kernel ist von der Kernelkonfiguration gesperrt; "wird unmittelbar nach dem Starten von Linux ausgegeben, daher die Einstellung" LOCK_DOWN_KERNEL_FORCE_INTEGRITY ", die" Kerneländerungen nach dem Booten deaktiviert ". Das Verhalten stimmt mit überein.

# dmesg -T | head -n1 ; dmesg -T | egrep '(Kernel configuration|kernel_lockdown)'
[So 8. Dezember 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
[So 8. Dezember 00:38:20 2019] Kernel is locked down from Kernel configuration; see man kernel_lockdown.7
[So 8. Dezember 00:38:21 2019] Lockdown: swapper/0: hibernation is restricted; see man kernel_lockdown.7
[So 8. Dezember 00:38:55 2019] Lockdown: insmod: unsigned module loading is restricted; see man kernel_lockdown.7

Ich werde mir den Quellcode ansehen

Ich habe kurz die Kernel-Sperrfunktion ausprobiert. Ich habe das Verhalten irgendwie verstanden, aber es ist die Menschlichkeit (?), Die mich dazu bringt, den Quellcode tatsächlich zu betrachten. Werfen wir einen kurzen Blick auf den Quellcode.

Was ist der Blockierungsfluss beim Laden eines Kernelmoduls?

Lassen Sie uns zunächst herausfinden, wo der Fehler aufgetreten ist (Blockieren des Modulladens), als "insmod" ausgeführt wurde. Die folgende an die Konsole ausgegebene Nachricht kann ein Hinweis auf die Untersuchung sein.

[So 8. Dezember 00:38:55 2019] Lockdown: insmod: unsigned module loading is restricted; see man kernel_lockdown.7

Die obige Nachricht wird an den folgenden Stellen ausgegeben.

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         }

Der "nicht signierte Modul geladen" Teil der Nachricht ist die Zeichenfolge aus "lockdown_readsons [what]", auf die anscheinend durch die Konstante "LOCKDOWN_MODULE_SIGNATURE" verwiesen wird.

security/lockdown/lockdown.c


 19 static const char *const lockdown_reasons[LOCKDOWN_CONFIDENTIALITY_MAX+1] = {
 ...
 21         [LOCKDOWN_MODULE_SIGNATURE] = "unsigned module loading",

Die Substanz von LOCKDOWN_MODULE_SIGNATURE ist ein Aufzählungswert.

include/linux/security.h


 104 enum lockdown_reason {
 105         LOCKDOWN_NONE,
 106         LOCKDOWN_MODULE_SIGNATURE,
 ...

Es scheint, dass "LOCKDOWN_MODULE_SIGNATURE" nur an zwei Stellen referenziert wird: "security / lockdown / lockdown.c" (wo Sie gerade die Quelle verfolgen) und "kernel / module.c".

In kernel / module.c wird die Verarbeitung durch den Rückgabewert von mod_verify_sig () verzweigt, und die Struktur ist so, dasssecurity_locked_down ()mit LOCKDOWN_MODULE_SIGNATURE als Argument aufgerufen wird (Zeile 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 () ist in kernel / module.c definiert, von dem aus call_int_hook () aufgerufen wird.

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 () ist als Funktionsmakro in derselben C-Datei definiert und ruft security_hook_heads.FUNC () auf. Dies wird durch Makros erweitert, so dass "struct security_hook_hands-> Locked_down ()" aufgerufen wird.

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 ist als Mitgliedsvariable von struct security_hook_hands definiert, und für diese Mitgliedsvariable wird in security / lockdown / lockdown.c ein Funktionszeiger gesetzt.

include/linux/lsm_hooks.h


1823 struct security_hook_heads {
...
2062         struct hlist_head locked_down;
2063 } __randomize_layout;

Wenn der im Argument what in lockdown_is_locked_down () als Funktionszeiger festgelegte Indexwert als Arraylockdown_reasons []bezeichnet werden kann, ist das oben erwähnte Laden von" Lockdown: insmod: unsigned module "eingeschränkt; siehe man kernel_lockdown .7 "wird ausgegeben.

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 diesem Fall ist der Wert des Arguments "what" "LOCKDOWN_MODULE_SIGNATURE", das sich auf "vorzeichenloses Laden von Modulen" bezieht.

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 };

Die bedingte Kompilierung wird von LOCK_DOWN_KERNEL_FORCE_INTEGRITY durchgeführt, das als Kernelkonfiguration in lockdown_lsm_init () festgelegt ist, und die Sperrstufe wird auf LOCKDOWN_CONFIDENTIALITY_MAX festgelegt. Dies ist das Verhalten im obigen Array lockdown_reasons [], dass Elemente mit einem Wert größer als LOCKDOWN_MODULE_SIGNATURE und einem kleineren Wert (oder Array-Index) gültige Faktoren für die Kernel-Sperrung sind.

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 }

Obwohl es sich um eine grobe Erklärung handelt, konnte ich den Verhaltensfluss sehen, wenn LOCK_DOWN_KERNEL_FORCE_INTEGRITY in der Kernelkonfiguration aus dem Quellcode angegeben wurde.

Zusammenfassung

Ich habe den Kernel-Lockdown-Mechanismus von Linux eingeführt. Das Verhalten des Kernelmoduls in Bezug auf die Sicherheit ist an sich nicht vollständig, und die vom Modul festgelegten Werte und Funktionen werden aus anderen Quellen verwendet, sodass das Lesen des Quellcodes etwas schwierig ist. Es scheint gut, es mit "pr_notice ()" zu kombinieren und das Verhalten zu verstehen, während es tatsächlich verschoben wird.

Referenz-URL

Recommended Posts

Probieren Sie den Linux-Kernel-Sperrmechanismus aus
Versuchen wir zum ersten Mal Linux
Kompilieren des Linux-Kernels (Linux 5.x unter Ubuntu 20.04)
Hinweise zur Verwendung von KUnit, dem Unit-Test-Mechanismus des Linux-Kernels
Holen Sie sich die neueste Linux-Kernel-Version mit ArchLinux
Steuern Sie Linux-Trackpads
Informationen zu Linux-Kernelparametern
Linux Kernel Release 5.x (2/4)
Überprüfen Sie die Linux-Kernelversion
Linux Kernel Release 5.x (3/4)
Über den Prozess, den der Linux-Kernel mit x86-Mikrocode verarbeitet
Linux Kernel Release 5.x (4/4)
Ich habe versucht, den Linux-Kernel auf virtualbox + vagrant zu installieren
Linux Kernel Release 5.x (1/4)
Datenstrukturen vom Listentyp und ihre Operationen im Linux-Kernel
[Linux] [Kernelmodul] Geben Sie die Ausführungs-CPU von kthread an / begrenzen Sie sie
Der Kernel Address Sanitizer (KASAN) (2/2)
Selbst erstellter Linux-Kernel mit Clang
Versuchen Sie es mit der Twitter-API
[Linux] Aktualisieren Sie das Paket offline
Versuchen Sie es mit normaler Linux-Programmierung Teil 2
Installieren Sie JDK unter Linux
Versuchen Sie es mit normaler Linux-Programmierung Teil 3
Probieren Sie den Taxii-Server aus (1. Servereinstellungen)
Verstehen Sie das Linux-Audit-System Audit
Versuchen Sie es mit der Twitter-API
Die Linux Watchdog-Treiber-API
Versuchen Sie es mit normaler Linux-Programmierung Teil 4
Versuchen Sie es mit der PeeringDB 2.0-API
[Linux] Verzeichnis unter dem Stammverzeichnis
Versuchen Sie es mit normaler Linux-Programmierung Teil 6
Fügen Sie den Link unter Linux ein
Versuchen Sie, den PyODE-Schieberegler zu verdoppeln
Der Kernel Address Sanitizer (KASAN) (1/2)
[LINUX-Kernel neu erstellen] Upgrade (4.18.0 → 5.8.8)
Erklären Sie den Mechanismus von Linux, den Sie nicht unerwartet kennen