Essayez le mécanisme de verrouillage du noyau Linux

Désolé pour la publication tardive. Calendrier de l'Avent Linux 2019 Ceci est l'article du 6ème jour. Aujourd'hui, je vais essayer le mécanisme de verrouillage du noyau fourni avec le noyau Linux 5.4.

Quelle est la fonction de verrouillage du noyau?

Sous Linux (ou OS UNIX), en séparant l'autorité entre l'utilisateur général et l'utilisateur root, il est moins probable que la stabilité du système soit altérée en raison d'une utilisation imprudente par l'utilisateur général. .. Cependant, si l'utilisateur root effectue par inadvertance une opération ou si l'autorité racine est volée à un utilisateur malveillant, cette méthode de séparation d'autorité ne peut pas la gérer.

Le mécanisme de verrouillage du noyau est une fonction qui "restreint les opérations impliquant des modifications du système même s'il s'agit d'un utilisateur root", et si le noyau a un verrouillage du noyau défini, même s'il s'agit d'un utilisateur root, / dev L'accès à / mem, / dev / kmen et CPU MSR (registre spécifique au modèle) est bloqué, et en plus, il n'est pas possible de changer le noyau (= ajouter des fonctions par le module noyau). De plus, cette fonction impose de fortes restrictions sur le fonctionnement du système et on craint qu'elle ne fonctionne pas si elle est appliquée à un système existant tel quel, elle est donc désactivée par défaut.

Essayez la fonction de verrouillage du noyau

Maintenant que vous avez un aperçu des fonctionnalités, essayons la fonction de verrouillage du noyau. Tout d'abord, construisez le noyau. Cette fois, je vais essayer de construire dans l'environnement de CentOS-7.

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

Obtenir le code source du noyau

J'essaierai le noyau Linux avec la dernière version de la série 5.x (à partir du 08 décembre 2019) 5.4.2. Suivez les étapes ci-dessous pour télécharger et extraire le code source du noyau. Les procédures de construction suivantes sont exécutées conformément à la Procédure de construction du noyau Linux précédemment écrite.

$ 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

Activer la fonction de verrouillage dans la configuration du noyau

Maintenant que le code source du noyau a été développé, jetons un coup d'œil à la configuration liée à la fonction de verrouillage. La configuration commençant par LOCK_DOWN_KERNEL_FORCE_ semble être le paramètre lié à la fonction de verrouillage.

# 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

En regardant LOCK_DOWN_KERNEL_FORCE_INTEGRITY, il semble qu'il fonctionne en mode" intégrité "(par défaut avec le verrouillage du noyau activé) et invalide les modifications apportées au noyau lors de l'exécution.

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.

Cela signifie que si vous activez ce paramètre, il se comportera comme une erreur lors du chargement du module du noyau. Construisons le noyau et essayons-le.

Suivez les étapes ci-dessous pour définir la configuration du noyau Linux.

$ cd linux-5.4.2
$ make defconfig
$ make menuconfig

L'emplacement de réglage de LOCK_DOWN_KERNEL_FORCE_INTEGRITY semble se trouver à l'emplacement suivant.

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

La différence avec make defconfig (configuration par défaut du noyau Linux) est la suivante. (Dans l'environnement de CentOS-7 ou version ultérieure, le système de fichiers est XFS, il est donc nécessaire de définir XFS pour qu'il soit inclus dans le noyau.)

# 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

Vous avez maintenant défini la configuration de noyau requise. Ensuite, lancez make pour ajouter l'entrée du noyau construit à 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

Redémarrez, sélectionnez Linux-5.4.2 dans GRUB et démarrez et vous êtes prêt à partir!

# 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

Le chargement du module du noyau est rebloqué par lots

Lorsque j'essaye de charger un module de noyau approprié, le chargement du module échoue (= blocs) même s'il s'agit de l'utilisateur root.

# 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

À ce stade, le message suivant est envoyé à la console.

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

En raison du paramétrage de LOCK_DOWN_KERNEL_FORCE_INTEGRITY, aucun module du noyau n'est chargé.

# lsmod
Module                  Size  Used by
#

En regardant le contenu de dmesg, le message" Le noyau est verrouillé depuis la configuration du noyau; "est sorti immédiatement après le démarrage de Linux, donc le réglage de LOCK_DOWN_KERNEL_FORCE_INTEGRITY qui" désactive les changements du noyau après le démarrage " Le comportement est conforme à.

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

Je vais jeter un œil au code source

J'ai brièvement essayé la fonction de verrouillage du noyau. J'ai en quelque sorte compris le comportement, mais c'est l'humanité (?) Qui me donne envie de regarder le code source. Jetons un coup d'œil au code source.

Quel est le flux de blocage lors du chargement d'un module de noyau?

Tout d'abord, découvrons où l'erreur s'est produite (blocage du chargement du module) lors de l'exécution de ʻinsmod`. Le message suivant envoyé à la console peut être un indice pour l'enquête.

[Dim 8 décembre 00:38:55 2019] Lockdown: insmod: unsigned module loading is restricted; see man kernel_lockdown.7

Le message ci-dessus est émis aux endroits suivants.

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         }

La partie "module non signé chargé" du message est la chaîne extraite de lockdown_readsons [what], apparemment référencée par la constante 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",

La substance de LOCKDOWN_MODULE_SIGNATURE est une valeur d'énumération.

include/linux/security.h


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

Il semble que LOCKDOWN_MODULE_SIGNATURE ne soit référencé qu'à deux endroits, security / lockdown / lockdown.c (où vous êtes actuellement en train de rechercher la source) et kernel / module.c.

Dans kernel / module.c, le traitement est ramifié par la valeur de retour de mod_verify_sig (), et la structure est telle quesecurity_locked_down ()avec LOCKDOWN_MODULE_SIGNATURE comme argument est appelé (ligne 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 () est défini dans kernel / module.c, d'où il appelle call_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 () est défini comme une macro de fonction dans le même fichier C et appelle security_hook_heads.FUNC (). Ceci est macro-développé pour que struct security_hook_hands-> lock_down () soit appelé.

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 est défini comme une variable membre de struct security_hook_hands, et un pointeur de fonction est défini pour cette variable membre dans security / lockdown / lockdown.c.

include/linux/lsm_hooks.h


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

Si la valeur d'index passée dans l'argument what in lockdown_is_locked_down () définie comme un pointeur de fonction peut être appelée tableaulockdown_reasons [], le" Lockdown: insmod: le chargement du module non signé est restreint; voir man kernel_lockdown .7 "est en cours de sortie.

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

Dans ce cas, la valeur de l'argument what is LOCKDOWN_MODULE_SIGNATURE, qui fait référence au" chargement de module non signé ".

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

La compilation conditionnelle est effectuée par LOCK_DOWN_KERNEL_FORCE_INTEGRITY défini comme configuration du noyau dans lockdown_lsm_init (), et le niveau de verrouillage est réglé sur LOCKDOWN_CONFIDENTIALITY_MAX. C'est le comportement dans le tableau ci-dessus lockdown_reasons [] que les éléments avec une valeur supérieure à LOCKDOWN_MODULE_SIGNATURE et une valeur plus petite (ou index de tableau) sont des facteurs valides pour le verrouillage du noyau.

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 }

Bien que ce soit une explication approximative, j'ai pu voir le déroulement du comportement lorsque LOCK_DOWN_KERNEL_FORCE_INTEGRITY est spécifié dans la configuration du noyau à partir du code source.

Résumé

J'ai introduit le mécanisme de verrouillage du noyau de Linux. Le comportement du module du noyau autour de la sécurité n'est pas complet en lui-même, et les valeurs et fonctions définies par le module sont utilisées à partir d'autres sources, il semble donc un peu difficile de lire le code source. Il semble bon de le combiner avec pr_notice () et de comprendre le comportement tout en le déplaçant.

URL de référence

Recommended Posts

Essayez le mécanisme de verrouillage du noyau Linux
Essayons Linux pour la première fois
Compilation du noyau Linux (Linux 5.x sur Ubuntu 20.04)
Remarques sur l'utilisation de KUnit, le mécanisme de test unitaire du noyau Linux
Obtenez la dernière version du noyau Linux avec ArchLinux
Contrôler les trackpads Linux
À propos des paramètres du noyau Linux
Version du noyau Linux 5.x (2/4)
Vérifiez la version du noyau Linux
Version du noyau Linux 5.x (3/4)
À propos du processus que le noyau Linux gère le microcode x86
Version du noyau Linux 5.x (4/4)
J'ai essayé d'installer le noyau Linux sur virtualbox + vagrant
Version du noyau Linux 5.x (1/4)
Structures de données de type liste et leurs opérations dans le noyau Linux
[Linux] [module du noyau] Spécifier / limiter le CPU d'exécution de kthread
Le désinfectant d'adresses du noyau (KASAN) (2/2)
Noyau Linux auto-construit avec clang
Essayez d'utiliser l'API Twitter
[Linux] Mettez à jour le package hors ligne
Essayez la programmation Linux normale, partie 2
Installez JDK sur Linux
Essayez la programmation Linux normale, partie 3
Essayez le serveur Taxii (1. Paramètres du serveur)
Comprendre l'audit du système d'audit Linux
Essayez d'utiliser l'API Twitter
L'API du pilote Linux Watchdog
Essayez la programmation Linux normale, partie 4
Essayez d'utiliser l'API PeeringDB 2.0
[Linux] Répertoire sous la racine
Essayez la programmation Linux normale, partie 6
Collez le lien sous Linux
Essayez de doubler le curseur PyODE
Le désinfectant d'adresse de noyau (KASAN) (1/2)
[Reconstruction du noyau LINUX] Mise à jour (4.18.0 → 5.8.8)
Expliquer le mécanisme de Linux que vous ne connaissez pas de manière inattendue