Livre "BPF pour la surveillance Linux"

Livre "BPF pour la surveillance Linux"Bonjour, résidents de Khabro ! La machine virtuelle BPF est l'un des composants les plus importants du noyau Linux. Son utilisation appropriée permettra aux ingénieurs système de détecter les défauts et de résoudre même les problèmes les plus complexes. Vous apprendrez à écrire des programmes qui surveillent et modifient le comportement du noyau, à implémenter en toute sécurité du code pour surveiller les événements dans le noyau, et bien plus encore. David Calavera et Lorenzo Fontana vous aideront à libérer la puissance du BPF. Développez vos connaissances en optimisation des performances, en réseau et en sécurité. - Utilisez BPF pour surveiller et modifier le comportement du noyau Linux. - Injectez du code pour surveiller en toute sécurité les événements du noyau sans avoir à recompiler le noyau ou à redémarrer le système. — Utilisez des exemples de code pratiques en C, Go ou Python. - Prenez le contrôle en maîtrisant le cycle de vie du programme BPF.

Sécurité du noyau Linux, ses fonctionnalités et Seccomp

BPF fournit un moyen puissant d'étendre le noyau sans sacrifier la stabilité, la sécurité ou la vitesse. Pour cette raison, les développeurs du noyau ont pensé que ce serait une bonne idée d'utiliser sa polyvalence pour améliorer l'isolation des processus dans Seccomp en implémentant des filtres Seccomp pris en charge par les programmes BPF, également connus sous le nom de Seccomp BPF. Dans ce chapitre, nous expliquerons ce qu'est Seccomp et comment il est utilisé. Vous apprendrez ensuite à écrire des filtres Seccomp à l'aide de programmes BPF. Après cela, nous examinerons les hooks BPF intégrés inclus dans le noyau pour les modules de sécurité Linux.

Les modules de sécurité Linux (LSM) sont un cadre qui fournit un ensemble de fonctions pouvant être utilisées pour implémenter divers modèles de sécurité de manière standardisée. LSM peut être utilisé directement dans l'arborescence des sources du noyau, comme Apparmor, SELinux et Tomoyo.

Commençons par discuter des capacités de Linux.

opportunités

L'essence des capacités de Linux est que vous devez accorder à un processus non privilégié une autorisation pour effectuer une certaine tâche, mais sans utiliser suid à cette fin, ou autrement rendre le processus privilégié, réduisant ainsi le risque d'attaque et permettant au processus d'effectuer certaines tâches. Par exemple, si votre application doit ouvrir un port privilégié, par exemple 80, au lieu d'exécuter le processus en tant que root, vous pouvez simplement lui attribuer la fonctionnalité CAP_NET_BIND_SERVICE.

Considérons un programme Go nommé main.go :

package main
import (
            "net/http"
            "log"
)
func main() {
     log.Fatalf("%v", http.ListenAndServe(":80", nil))
}

Ce programme dessert un serveur HTTP sur le port 80 (c'est un port privilégié). Habituellement, nous l'exécutons immédiatement après la compilation :

$ go build -o capabilities main.go
$ ./capabilities

Cependant, comme nous n'accordons pas les privilèges root, ce code générera une erreur lors de la liaison du port :

2019/04/25 23:17:06 listen tcp :80: bind: permission denied
exit status 1

capsh (shell manager) est un outil qui exécute un shell avec un ensemble spécifique de fonctionnalités.

Dans ce cas, comme déjà mentionné, au lieu d'accorder les droits root complets, vous pouvez activer la liaison de port privilégié en fournissant la fonctionnalité cap_net_bind_service ainsi que tout ce qui est déjà dans le programme. Pour ce faire, nous pouvons mettre notre programme en capsh :

# capsh --caps='cap_net_bind_service+eip cap_setpcap,cap_setuid,cap_setgid+ep' 
   --keep=1 --user="nobody" 
   --addamb=cap_net_bind_service -- -c "./capabilities"

Comprenons un peu cette équipe.

  • capsh - utilisez capsh comme shell.
  • —caps='cap_net_bind_service+eip cap_setpcap,cap_setuid,cap_setgid+ep' - puisque nous devons changer d'utilisateur (nous ne voulons pas exécuter en tant que root), nous spécifierons cap_net_bind_service et la possibilité de modifier réellement l'ID utilisateur de root à personne, à savoir cap_setuid et cap_setgid.
  • —keep=1 — nous souhaitons conserver les fonctionnalités installées lors du passage du compte root.
  • —user="nobody" — l'utilisateur final qui exécute le programme ne sera personne.
  • —addamb=cap_net_bind_service — définit la suppression des fonctionnalités associées après le passage du mode root.
  • - -c "./capabilities" - exécutez simplement le programme.

Les capacités liées sont un type particulier de capacités héritées par les programmes enfants lorsque le programme actuel les exécute à l'aide de execve(). Seules les capacités dont l'association est autorisée, ou en d'autres termes, en tant que capacités d'environnement, peuvent être héritées.

Vous vous demandez probablement ce que signifie +eip après avoir spécifié la capacité dans l'option --caps. Ces indicateurs sont utilisés pour déterminer que la capacité :

-doit être activé (p);

- disponible pour utilisation (e);

-peut être hérité par les processus enfants (i).

Puisque nous voulons utiliser cap_net_bind_service, nous devons le faire avec le drapeau e. Ensuite, nous démarrerons le shell dans la commande. Cela exécutera le binaire des capacités et nous devons le marquer avec le drapeau i. Enfin, nous voulons que la fonctionnalité soit activée (nous l'avons fait sans changer l'UID) avec p. Cela ressemble à cap_net_bind_service+eip.

Vous pouvez vérifier le résultat en utilisant ss. Raccourcissons un peu la sortie pour qu'elle tienne sur la page, mais elle affichera le port associé et l'ID utilisateur autre que 0, dans ce cas 65 :

# ss -tulpn -e -H | cut -d' ' -f17-
128 *:80 *:*
users:(("capabilities",pid=30040,fd=3)) uid:65534 ino:11311579 sk:2c v6only:0

Dans cet exemple, nous avons utilisé capsh, mais vous pouvez écrire un shell en utilisant libcap. Pour plus d'informations, consultez man 3 libcap.

Lors de l'écriture de programmes, il arrive souvent que le développeur ne connaisse pas à l'avance toutes les fonctionnalités dont le programme a besoin au moment de son exécution ; De plus, ces fonctionnalités peuvent changer dans les nouvelles versions.

Pour mieux comprendre les capacités de notre programme, nous pouvons utiliser l'outil compatible BCC, qui définit le kprobe pour la fonction noyau cap_capable :

/usr/share/bcc/tools/capable
TIME      UID  PID   TID   COMM               CAP    NAME           AUDIT
10:12:53 0 424     424     systemd-udevd 12 CAP_NET_ADMIN         1
10:12:57 0 1103   1101   timesync        25 CAP_SYS_TIME         1
10:12:57 0 19545 19545 capabilities       10 CAP_NET_BIND_SERVICE 1

Nous pouvons obtenir la même chose en utilisant bpftrace avec un kprobe à une seule ligne dans la fonction noyau cap_capable :

bpftrace -e 
   'kprobe:cap_capable {
      time("%H:%M:%S ");
      printf("%-6d %-6d %-16s %-4d %dn", uid, pid, comm, arg2, arg3);
    }' 
    | grep -i capabilities

Cela produira quelque chose comme ce qui suit si les capacités de notre programme sont activées après kprobe :

12:01:56 1000 13524 capabilities 21 0
12:01:56 1000 13524 capabilities 21 0
12:01:56 1000 13524 capabilities 21 0
12:01:56 1000 13524 capabilities 12 0
12:01:56 1000 13524 capabilities 12 0
12:01:56 1000 13524 capabilities 12 0
12:01:56 1000 13524 capabilities 12 0
12:01:56 1000 13524 capabilities 10 1

La cinquième colonne représente les capacités dont le processus a besoin, et comme cette sortie inclut des événements non liés à l'audit, nous voyons tous les contrôles non liés à l'audit et enfin la capacité requise avec l'indicateur d'audit (dernier dans la sortie) défini sur 1. Capacité. celui qui nous intéresse est CAP_NET_BIND_SERVICE, il est défini comme une constante dans le code source du noyau dans le fichier include/uapi/linux/ability.h avec l'identifiant 10 :

/* Allows binding to TCP/UDP sockets below 1024 */
/* Allows binding to ATM VCIs below 32 */
#define CAP_NET_BIND_SERVICE 10<source lang="go">

Les fonctionnalités sont souvent activées au moment de l'exécution pour les conteneurs tels que runC ou Docker afin de leur permettre de s'exécuter en mode non privilégié, mais elles ne disposent que des fonctionnalités nécessaires à l'exécution de la plupart des applications. Lorsqu'une application nécessite certaines fonctionnalités, Docker peut les fournir en utilisant --cap-add :

docker run -it --rm --cap-add=NET_ADMIN ubuntu ip link add dummy0 type dummy

Cette commande donnera au conteneur la capacité CAP_NET_ADMIN, lui permettant de configurer un lien réseau pour ajouter l'interface dummy0.

La section suivante montre comment utiliser des fonctionnalités telles que le filtrage, mais en utilisant une technique différente qui nous permet d'implémenter par programme nos propres filtres.

Seccomp

Seccomp signifie Secure Computing et est une couche de sécurité implémentée dans le noyau Linux qui permet aux développeurs de filtrer certains appels système. Bien que Seccomp soit comparable en capacités à Linux, sa capacité à gérer certains appels système le rend beaucoup plus flexible qu'eux.

Les fonctionnalités Seccomp et Linux ne s'excluent pas mutuellement et sont souvent utilisées ensemble pour bénéficier des deux approches. Par exemple, vous souhaiterez peut-être donner à un processus la capacité CAP_NET_ADMIN mais ne pas lui permettre d'accepter les connexions socket, bloquant ainsi les appels système accept et accept4.

La méthode de filtrage Seccomp est basée sur des filtres BPF fonctionnant en mode SECCOMP_MODE_FILTER, et le filtrage des appels système s'effectue de la même manière que pour les paquets.

Les filtres Seccomp sont chargés à l'aide de prctl via l'opération PR_SET_SECCOMP. Ces filtres prennent la forme d'un programme BPF qui est exécuté pour chaque paquet Seccomp représenté par la structure seccomp_data. Cette structure contient l'architecture de référence, un pointeur vers les instructions du processeur au moment de l'appel système et un maximum de six arguments d'appel système, exprimés sous la forme uint64.

Voici à quoi ressemble la structure seccomp_data d'après le code source du noyau dans le fichier linux/seccomp.h :

struct seccomp_data {
int nr;
      __u32 arch;
      __u64 instruction_pointer;
      __u64 args[6];
};

Comme vous pouvez le voir sur cette structure, nous pouvons filtrer par l'appel système, ses arguments ou une combinaison des deux.

Après avoir reçu chaque paquet Seccomp, le filtre doit effectuer un traitement pour prendre une décision finale et indiquer au noyau quoi faire ensuite. La décision finale est exprimée par l'une des valeurs de retour (codes d'état).

- SECCOMP_RET_KILL_PROCESS - tue l'ensemble du processus immédiatement après avoir filtré un appel système qui n'est pas exécuté pour cette raison.

- SECCOMP_RET_KILL_THREAD - termine le thread en cours immédiatement après avoir filtré un appel système qui n'est pas exécuté pour cette raison.

— SECCOMP_RET_KILL — alias pour SECCOMP_RET_KILL_THREAD, laissé pour compatibilité ascendante.

- SECCOMP_RET_TRAP - l'appel système est interdit, et le signal SIGSYS (Bad System Call) est envoyé à la tâche qui l'appelle.

- SECCOMP_RET_ERRNO - L'appel système n'est pas exécuté et une partie de la valeur de retour du filtre SECCOMP_RET_DATA est transmise à l'espace utilisateur en tant que valeur errno. Selon la cause de l'erreur, différentes valeurs d'errno sont renvoyées. Une liste des numéros d’erreur est fournie dans la section suivante.

- SECCOMP_RET_TRACE - Utilisé pour notifier le traceur ptrace à l'aide de - PTRACE_O_TRACESECCOMP pour intercepter lorsqu'un appel système est exécuté pour voir et contrôler ce processus. Si aucun traceur n'est connecté, une erreur est renvoyée, errno est défini sur -ENOSYS et l'appel système n'est pas exécuté.

- SECCOMP_RET_LOG - l'appel système est résolu et enregistré.

- SECCOMP_RET_ALLOW - l'appel système est simplement autorisé.

ptrace est un appel système pour implémenter des mécanismes de traçage dans un processus appelé suivi, avec la capacité de surveiller et de contrôler l'exécution du processus. Le programme trace peut influencer efficacement l'exécution et modifier les registres mémoire de l'observé. Dans le contexte Seccomp, ptrace est utilisé lorsqu'il est déclenché par le code d'état SECCOMP_RET_TRACE, afin que le traceur puisse empêcher l'exécution de l'appel système et implémenter sa propre logique.

Erreurs Seccomp

De temps en temps, en travaillant avec Seccomp, vous rencontrerez diverses erreurs, identifiées par une valeur de retour de type SECCOMP_RET_ERRNO. Pour signaler une erreur, l'appel système seccomp renverra -1 au lieu de 0.

Les erreurs suivantes sont possibles :

- EACCESS - L'appelant n'est pas autorisé à passer un appel système. Cela se produit généralement parce qu'il ne dispose pas des privilèges CAP_SYS_ADMIN ou que no_new_privs n'est pas défini à l'aide de prctl (nous en reparlerons plus tard) ;

— EFAULT — les arguments passés (arguments dans la structure seccomp_data) n'ont pas d'adresse valide ;

— EINVAL — il peut y avoir ici quatre raisons :

-l'opération demandée est inconnue ou n'est pas supportée par le noyau dans la configuration actuelle ;

-les drapeaux spécifiés ne sont pas valides pour l'opération demandée ;

-l'opération inclut BPF_ABS, mais il y a des problèmes avec le décalage spécifié, qui peut dépasser la taille de la structure seccomp_data ;

-le nombre d'instructions passées au filtre dépasse le maximum ;

— ENOMEM — pas assez de mémoire pour exécuter le programme ;

- EOPNOTSUPP - l'opération a indiqué qu'avec SECCOMP_GET_ACTION_AVAIL l'action était disponible, mais le noyau ne prend pas en charge les retours en arguments ;

— ESRCH — un problème est survenu lors de la synchronisation d'un autre flux ;

- ENOSYS - Il n'y a aucun traceur attaché à l'action SECCOMP_RET_TRACE.

prctl est un appel système qui permet à un programme de l'espace utilisateur de manipuler (définir et obtenir) des aspects spécifiques d'un processus, tels que le boutisme des octets, les noms de threads, le mode de calcul sécurisé (Seccomp), les privilèges, les événements Perf, etc.

Seccomp peut vous sembler être une technologie sandbox, mais ce n'est pas le cas. Seccomp est un utilitaire qui permet aux utilisateurs de développer un mécanisme sandbox. Voyons maintenant comment les programmes d'interaction utilisateur sont créés à l'aide d'un filtre appelé directement par l'appel système Seccomp.

Exemple de filtre BPF Seccomp

Nous allons montrer ici comment combiner les deux actions évoquées précédemment, à savoir :

— nous écrirons un programme Seccomp BPF, qui servira de filtre avec différents codes retour en fonction des décisions prises ;

— chargez le filtre en utilisant prctl.

Vous avez d'abord besoin des en-têtes de la bibliothèque standard et du noyau Linux :

#include <errno.h>
#include <linux/audit.h>
#include <linux/bpf.h>
#include <linux/filter.h>
#include <linux/seccomp.h>
#include <linux/unistd.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/prctl.h>
#include <unistd.h>

Avant de tenter cet exemple, nous devons nous assurer que le noyau est compilé avec CONFIG_SECCOMP et CONFIG_SECCOMP_FILTER définis sur y. Sur une machine en état de marche, vous pouvez vérifier ceci comme ceci :

cat /proc/config.gz| zcat | grep -i CONFIG_SECCOMP

Le reste du code est une fonction install_filter en deux parties. La première partie contient notre liste d'instructions de filtrage BPF :

static int install_filter(int nr, int arch, int error) {
  struct sock_filter filter[] = {
    BPF_STMT(BPF_LD + BPF_W + BPF_ABS, (offsetof(struct seccomp_data, arch))),
    BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, arch, 0, 3),
    BPF_STMT(BPF_LD + BPF_W + BPF_ABS, (offsetof(struct seccomp_data, nr))),
    BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, nr, 0, 1),
    BPF_STMT(BPF_RET + BPF_K, SECCOMP_RET_ERRNO | (error & SECCOMP_RET_DATA)),
    BPF_STMT(BPF_RET + BPF_K, SECCOMP_RET_ALLOW),
  };

Les instructions sont définies à l'aide des macros BPF_STMT et BPF_JUMP définies dans le fichier linux/filter.h.
Passons en revue les instructions.

- BPF_STMT(BPF_LD + BPF_W + BPF_ABS (offsetof(struct seccomp_data, arch))) - le système se charge et s'accumule à partir de BPF_LD sous la forme du mot BPF_W, les données des paquets sont situées à un décalage fixe BPF_ABS.

- BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, arch, 0, 3) - vérifie à l'aide de BPF_JEQ si la valeur d'architecture dans la constante de l'accumulateur BPF_K est égale à arch. Si c'est le cas, saute au décalage 0 à l'instruction suivante, sinon saute au décalage 3 (dans ce cas) pour générer une erreur car arch ne correspond pas.

- BPF_STMT(BPF_LD + BPF_W + BPF_ABS (offsetof(struct seccomp_data, nr))) - Charge et accumule depuis BPF_LD sous la forme du mot BPF_W, qui est le numéro d'appel système contenu dans le décalage fixe de BPF_ABS.

— BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, nr, 0, 1) — compare le numéro d'appel système avec la valeur de la variable nr. S'ils sont égaux, passe à l'instruction suivante et désactive l'appel système, sinon autorise l'appel système avec SECCOMP_RET_ALLOW.

- BPF_STMT(BPF_RET + BPF_K, SECCOMP_RET_ERRNO | (error & SECCOMP_RET_DATA)) - termine le programme avec BPF_RET et produit par conséquent une erreur SECCOMP_RET_ERRNO avec le numéro de la variable err.

- BPF_STMT(BPF_RET + BPF_K, SECCOMP_RET_ALLOW) - termine le programme avec BPF_RET et permet d'exécuter l'appel système à l'aide de SECCOMP_RET_ALLOW.

SECCOMP EST CBPF
Vous vous demandez peut-être pourquoi une liste d'instructions est utilisée à la place d'un objet ELF compilé ou d'un programme C compilé JIT.

Il y a deux raisons à cela.

• Tout d'abord, Seccomp utilise cBPF (BPF classique) et non eBPF, ce qui signifie : il n'a pas de registres, mais seulement un accumulateur pour stocker le dernier résultat de calcul, comme on peut le voir dans l'exemple.

• Deuxièmement, Seccomp accepte directement un pointeur vers un tableau d'instructions BPF et rien d'autre. Les macros que nous avons utilisées aident simplement à spécifier ces instructions d'une manière conviviale pour les programmeurs.

Si vous avez besoin d'aide supplémentaire pour comprendre cet assembly, considérez le pseudocode qui fait la même chose :

if (arch != AUDIT_ARCH_X86_64) {
    return SECCOMP_RET_ALLOW;
}
if (nr == __NR_write) {
    return SECCOMP_RET_ERRNO;
}
return SECCOMP_RET_ALLOW;

Après avoir défini le code du filtre dans la structure socket_filter, vous devez définir un sock_fprog contenant le code et la longueur calculée du filtre. Cette structure de données est nécessaire comme argument pour déclarer que le processus s'exécutera ultérieurement :

struct sock_fprog prog = {
   .len = (unsigned short)(sizeof(filter) / sizeof(filter[0])),
   .filter = filter,
};

Il ne reste plus qu'une chose à faire dans la fonction install_filter : charger le programme lui-même ! Pour ce faire, nous utilisons prctl, en prenant PR_SET_SECCOMP comme option pour passer en mode informatique sécurisé. Ensuite, nous indiquons au mode de charger le filtre en utilisant SECCOMP_MODE_FILTER, qui est contenu dans la variable prog de type sock_fprog :

  if (prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog)) {
    perror("prctl(PR_SET_SECCOMP)");
    return 1;
  }
  return 0;
}

Enfin, nous pouvons utiliser notre fonction install_filter, mais avant cela, nous devons utiliser prctl pour définir PR_SET_NO_NEW_PRIVS pour l'exécution en cours et ainsi éviter la situation où les processus enfants reçoivent plus de privilèges que leurs parents. Avec cela, nous pouvons effectuer les appels prctl suivants dans la fonction install_filter sans avoir les droits root.

Nous pouvons maintenant appeler la fonction install_filter. Bloqueons tous les appels système d'écriture liés à l'architecture X86-64 et donnons simplement une autorisation qui bloque toutes les tentatives. Après avoir installé le filtre, nous continuons l'exécution en utilisant le premier argument :

int main(int argc, char const *argv[]) {
  if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) {
   perror("prctl(NO_NEW_PRIVS)");
   return 1;
  }
   install_filter(__NR_write, AUDIT_ARCH_X86_64, EPERM);
  return system(argv[1]);
 }

Commençons. Pour compiler notre programme, nous pouvons utiliser soit clang, soit gcc, dans les deux cas, il s'agit simplement de compiler le fichier main.c sans options spéciales :

clang main.c -o filter-write

Comme indiqué, nous avons bloqué toutes les entrées du programme. Pour tester cela, vous avez besoin d'un programme qui génère quelque chose - ls semble être un bon candidat. Voici comment elle se comporte habituellement :

ls -la
total 36
drwxr-xr-x 2 fntlnz users 4096 Apr 28 21:09 .
drwxr-xr-x 4 fntlnz users 4096 Apr 26 13:01 ..
-rwxr-xr-x 1 fntlnz users 16800 Apr 28 21:09 filter-write
-rw-r--r-- 1 fntlnz users 19 Apr 28 21:09 .gitignore
-rw-r--r-- 1 fntlnz users 1282 Apr 28 21:08 main.c

Merveilleux! Voici à quoi ressemble l'utilisation de notre programme wrapper : Nous transmettons simplement le programme que nous voulons tester comme premier argument :

./filter-write "ls -la"

Une fois exécuté, ce programme produit une sortie complètement vide. Cependant, nous pouvons utiliser strace pour voir ce qui se passe :

strace -f ./filter-write "ls -la"

Le résultat du travail est considérablement raccourci, mais la partie correspondante montre que les enregistrements sont bloqués avec l'erreur EPERM - la même que celle que nous avons configurée. Cela signifie que le programme ne génère rien car il ne peut pas accéder à l'appel système d'écriture :

[pid 25099] write(2, "ls: ", 4) = -1 EPERM (Operation not permitted)
[pid 25099] write(2, "write error", 11) = -1 EPERM (Operation not permitted)
[pid 25099] write(2, "n", 1) = -1 EPERM (Operation not permitted)

Vous comprenez désormais le fonctionnement de Seccomp BPF et avez une bonne idée de ce que vous pouvez en faire. Mais n'aimeriez-vous pas obtenir la même chose avec l'eBPF au lieu du cBPF pour exploiter toute sa puissance ?

Lorsqu'on pense aux programmes eBPF, la plupart des gens pensent qu'ils les écrivent simplement et les chargent avec des privilèges d'administrateur. Bien que cette affirmation soit généralement vraie, le noyau implémente un ensemble de mécanismes pour protéger les objets eBPF à différents niveaux. Ces mécanismes sont appelés pièges BPF LSM.

Pièges BPF LSM

Pour fournir une surveillance indépendante de l'architecture des événements système, LSM implémente le concept de pièges. Un appel hook est techniquement similaire à un appel système, mais il est indépendant du système et intégré à l’infrastructure. LSM fournit un nouveau concept dans lequel une couche d'abstraction peut aider à éviter les problèmes rencontrés lors du traitement des appels système sur différentes architectures.

Au moment de la rédaction de cet article, le noyau possède sept hooks associés aux programmes BPF, et SELinux est le seul LSM intégré qui les implémente.

Le code source des traps se trouve dans l'arborescence du noyau dans le fichier include/linux/security.h :

extern int security_bpf(int cmd, union bpf_attr *attr, unsigned int size);
extern int security_bpf_map(struct bpf_map *map, fmode_t fmode);
extern int security_bpf_prog(struct bpf_prog *prog);
extern int security_bpf_map_alloc(struct bpf_map *map);
extern void security_bpf_map_free(struct bpf_map *map);
extern int security_bpf_prog_alloc(struct bpf_prog_aux *aux);
extern void security_bpf_prog_free(struct bpf_prog_aux *aux);

Chacun d’eux sera appelé à différentes étapes d’exécution :

— security_bpf — effectue une vérification initiale des appels système BPF exécutés ;

- security_bpf_map - vérifie quand le noyau renvoie un descripteur de fichier pour la carte ;

- security_bpf_prog - vérifie quand le noyau renvoie un descripteur de fichier pour le programme eBPF ;

— security_bpf_map_alloc — vérifie si le champ de sécurité à l'intérieur des cartes BPF est initialisé ;

- security_bpf_map_free - vérifie si le champ de sécurité est effacé dans les cartes BPF ;

— security_bpf_prog_alloc — vérifie si le champ de sécurité est initialisé dans les programmes BPF ;

- security_bpf_prog_free - vérifie si le champ de sécurité est effacé dans les programmes BPF.

Maintenant, voyant tout cela, nous comprenons : l'idée derrière les intercepteurs LSM BPF est qu'ils peuvent fournir une protection à chaque objet eBPF, garantissant que seuls ceux disposant des privilèges appropriés peuvent effectuer des opérations sur les cartes et les programmes.

Résumé

La sécurité n’est pas quelque chose que vous pouvez mettre en œuvre de manière universelle pour tout ce que vous souhaitez protéger. Il est important de pouvoir protéger les systèmes à différents niveaux et de différentes manières. Croyez-le ou non, la meilleure façon de sécuriser un système est d'organiser différents niveaux de protection à partir de différentes positions, de sorte que réduire la sécurité d'un niveau ne permette pas l'accès à l'ensemble du système. Les développeurs principaux ont fait un excellent travail en nous proposant un ensemble de différentes couches et points de contact. Nous espérons vous avoir permis de bien comprendre ce que sont les couches et comment utiliser les programmes BPF pour travailler avec elles.

À propos des auteurs

David Calavera est le CTO chez Netlify. Il a travaillé dans le support Docker et a contribué au développement des outils Runc, Go et BCC, ainsi que d'autres projets open source. Connu pour son travail sur les projets Docker et le développement de l'écosystème de plugins Docker. David est très passionné par les graphiques de flammes et cherche toujours à optimiser les performances.

Lorenzo Fontana travaille au sein de l'équipe open source de Sysdig, où il se concentre principalement sur Falco, un projet de la Cloud Native Computing Foundation qui assure la sécurité d'exécution des conteneurs et la détection des anomalies via un module de noyau et eBPF. Il est passionné par les systèmes distribués, les réseaux définis par logiciel, le noyau Linux et l'analyse des performances.

» Plus de détails sur le livre peuvent être trouvés sur site de l'éditeur
» table des matières
» Extrait

Pour Khabrozhiteley, 25 % de réduction en utilisant le coupon - Linux/Unix

Dès paiement de la version papier du livre, un livre électronique sera envoyé par e-mail.

Source: habr.com

Ajouter un commentaire