Libro "BPF per il monitoraggio di Linux"

Libro "BPF per il monitoraggio di Linux"Ciao, residenti di Khabro! La macchina virtuale BPF è uno dei componenti più importanti del kernel Linux. Il suo corretto utilizzo consentirà agli ingegneri di sistema di individuare guasti e risolvere anche i problemi più complessi. Imparerai come scrivere programmi che monitorano e modificano il comportamento del kernel, come implementare in modo sicuro il codice per monitorare gli eventi nel kernel e molto altro ancora. David Calavera e Lorenzo Fontana ti aiuteranno a sbloccare il potere di BPF. Amplia le tue conoscenze sull'ottimizzazione delle prestazioni, sul networking e sulla sicurezza. - Utilizza BPF per monitorare e modificare il comportamento del kernel Linux. - Iniezione di codice per monitorare in modo sicuro gli eventi del kernel senza dover ricompilare il kernel o riavviare il sistema. — Utilizza pratici esempi di codice in C, Go o Python. - Assumi il controllo possedendo il ciclo di vita del programma BPF.

Sicurezza del kernel Linux, sue caratteristiche e Seccomp

BPF fornisce un modo potente per estendere il kernel senza sacrificare stabilità, sicurezza o velocità. Per questo motivo, gli sviluppatori del kernel hanno pensato che sarebbe stata una buona idea sfruttare la sua versatilità per migliorare l'isolamento dei processi in Seccomp implementando i filtri Seccomp supportati dai programmi BPF, noti anche come Seccomp BPF. In questo capitolo spiegheremo cos'è Seccomp e come si usa. Quindi imparerai come scrivere filtri Seccomp utilizzando i programmi BPF. Successivamente, esamineremo gli hook BPF integrati inclusi nel kernel per i moduli di sicurezza Linux.

I Linux Security Modules (LSM) sono un framework che fornisce un insieme di funzioni che possono essere utilizzate per implementare vari modelli di sicurezza in modo standardizzato. LSM può essere utilizzato direttamente nell'albero dei sorgenti del kernel, come Apparmor, SELinux e Tomoyo.

Cominciamo discutendo le capacità di Linux.

opportunità

L'essenza delle capacità di Linux è che è necessario concedere a un processo non privilegiato il permesso di eseguire una determinata attività, ma senza utilizzare suid per quello scopo, o altrimenti rendere il processo privilegiato, riducendo la possibilità di attacco e consentendo al processo di eseguire determinate attività. Ad esempio, se la tua applicazione deve aprire una porta privilegiata, diciamo 80, invece di eseguire il processo come root, puoi semplicemente assegnarle la funzionalità CAP_NET_BIND_SERVICE.

Considera un programma Go chiamato main.go:

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

Questo programma serve un server HTTP sulla porta 80 (questa è una porta privilegiata). Di solito lo eseguiamo immediatamente dopo la compilazione:

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

Tuttavia, poiché non garantiamo i privilegi di root, questo codice genererà un errore durante il collegamento della porta:

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

capsh (shell manager) è uno strumento che esegue una shell con un insieme specifico di funzionalità.

In questo caso, come già accennato, invece di garantire i diritti di root completi, puoi abilitare il collegamento della porta privilegiata fornendo la funzionalità cap_net_bind_service insieme a tutto il resto già presente nel programma. Per fare ciò, possiamo racchiudere il nostro programma in 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"

Capiamo un po' questa squadra.

  • capsh: usa capsh come shell.
  • —caps='cap_net_bind_service+eip cap_setpcap,cap_setuid,cap_setgid+ep' - poiché dobbiamo cambiare l'utente (non vogliamo eseguire come root), specificheremo cap_net_bind_service e la possibilità di cambiare effettivamente l'ID utente da root a nessuno, vale a dire cap_setuid e cap_setgid.
  • —keep=1 — vogliamo mantenere le funzionalità installate quando si passa dall'account root.
  • —user=“nobody” — l'utente finale che esegue il programma non sarà nessuno.
  • —addamb=cap_net_bind_service: imposta la cancellazione delle funzionalità correlate dopo il passaggio dalla modalità root.
  • - -c "./capabilities" - basta eseguire il programma.

Le capacità collegate sono un tipo speciale di capacità che vengono ereditate dai programmi figli quando il programma corrente le esegue utilizzando execve(). Solo le capacità che possono essere associate o, in altre parole, come capacità dell'ambiente, possono essere ereditate.

Probabilmente ti starai chiedendo cosa significa +eip dopo aver specificato la funzionalità nell'opzione --caps. Questi flag vengono utilizzati per determinare che la funzionalità:

-deve essere attivato (p);

- disponibile per l'uso (e);

-può essere ereditato dai processi figli (i).

Poiché vogliamo utilizzare cap_net_bind_service, dobbiamo farlo con il flag e. Quindi avvieremo la shell nel comando. Questo eseguirà il binario delle funzionalità e dobbiamo contrassegnarlo con il flag i. Infine, vogliamo che la funzionalità sia abilitata (lo abbiamo fatto senza modificare l'UID) con p. Sembra cap_net_bind_service+eip.

Puoi controllare il risultato usando ss. Accorciamo leggermente l'output per adattarlo alla pagina, ma mostrerà la porta associata e l'ID utente diverso da 0, in questo caso 65:

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

In questo esempio abbiamo usato capsh, ma puoi scrivere una shell usando libcap. Per ulteriori informazioni, vedere man 3 libcap.

Quando si scrivono programmi, molto spesso lo sviluppatore non conosce in anticipo tutte le funzionalità di cui il programma necessita in fase di esecuzione; Inoltre, queste funzionalità potrebbero cambiare nelle nuove versioni.

Per comprendere meglio le capacità del nostro programma, possiamo utilizzare lo strumento BCC, che imposta kprobe per la funzione del kernel 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

Possiamo ottenere la stessa cosa utilizzando bpftrace con un kprobe one-liner nella funzione del kernel 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

Il risultato sarà qualcosa di simile al seguente se le funzionalità del nostro programma sono abilitate dopo 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 quinta colonna rappresenta le capacità di cui il processo ha bisogno e poiché questo output include eventi non di controllo, vediamo tutti i controlli non di controllo e infine la capacità richiesta con il flag di controllo (ultimo nell'output) impostato su 1. Capacità. uno che ci interessa è CAP_NET_BIND_SERVICE, è definito come costante nel codice sorgente del kernel nel file include/uapi/linux/ability.h con identificatore 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">

Le funzionalità sono spesso abilitate in fase di esecuzione per contenitori come runC o Docker per consentirne l'esecuzione in modalità non privilegiata, ma sono consentite solo le funzionalità necessarie per eseguire la maggior parte delle applicazioni. Quando un'applicazione richiede determinate funzionalità, Docker può fornirle utilizzando --cap-add:

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

Questo comando conferirà al contenitore la funzionalità CAP_NET_ADMIN, consentendogli di configurare un collegamento di rete per aggiungere l'interfaccia dummy0.

La sezione successiva mostra come utilizzare funzionalità come il filtraggio, ma utilizzando una tecnica diversa che ci consente di implementare a livello di codice i nostri filtri.

Seccomp

Seccomp sta per Secure Computing ed è un livello di sicurezza implementato nel kernel Linux che consente agli sviluppatori di filtrare determinate chiamate di sistema. Sebbene Seccomp sia paragonabile in termini di funzionalità a Linux, la sua capacità di gestire determinate chiamate di sistema lo rende molto più flessibile rispetto a loro.

Le funzionalità di Seccomp e Linux non si escludono a vicenda e vengono spesso utilizzate insieme per trarre vantaggio da entrambi gli approcci. Ad esempio, potresti voler dare a un processo la capacità CAP_NET_ADMIN ma non consentirgli di accettare connessioni socket, bloccando le chiamate di sistema accetta e accetta4.

Il metodo di filtraggio Seccomp si basa sui filtri BPF che operano in modalità SECCOMP_MODE_FILTER e il filtraggio delle chiamate di sistema viene eseguito allo stesso modo dei pacchetti.

I filtri Seccomp vengono caricati utilizzando prctl tramite l'operazione PR_SET_SECCOMP. Questi filtri assumono la forma di un programma BPF che viene eseguito per ciascun pacchetto Seccomp rappresentato dalla struttura seccomp_data. Questa struttura contiene l'architettura di riferimento, un puntatore alle istruzioni del processore al momento della chiamata di sistema e un massimo di sei argomenti della chiamata di sistema, espressi come uint64.

Questo è l'aspetto della struttura seccomp_data dal codice sorgente del kernel nel file linux/seccomp.h:

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

Come puoi vedere da questa struttura, possiamo filtrare in base alla chiamata di sistema, ai suoi argomenti o ad una combinazione di entrambi.

Dopo aver ricevuto ciascun pacchetto Seccomp, il filtro deve eseguire l'elaborazione per prendere una decisione finale e dire al kernel cosa fare dopo. La decisione finale è espressa da uno dei valori restituiti (codici di stato).

- SECCOMP_RET_KILL_PROCESS - termina l'intero processo immediatamente dopo aver filtrato una chiamata di sistema che non viene eseguita per questo motivo.

- SECCOMP_RET_KILL_THREAD - termina il thread corrente immediatamente dopo aver filtrato una chiamata di sistema che non viene eseguita per questo motivo.

— SECCOMP_RET_KILL — alias per SECCOMP_RET_KILL_THREAD, lasciato per compatibilità con le versioni precedenti.

- SECCOMP_RET_TRAP - la chiamata di sistema è vietata e il segnale SIGSYS (Bad System Call) viene inviato all'attività che la chiama.

- SECCOMP_RET_ERRNO - La chiamata di sistema non viene eseguita e parte del valore restituito dal filtro SECCOMP_RET_DATA viene passato allo spazio utente come valore errno. A seconda della causa dell'errore vengono restituiti diversi valori errno. Nella sezione successiva viene fornito un elenco dei numeri di errore.

- SECCOMP_RET_TRACE - Utilizzato per notificare il tracciante ptrace utilizzando - PTRACE_O_TRACESECCOMP per intercettare quando viene eseguita una chiamata di sistema per vedere e controllare quel processo. Se un tracciante non è connesso, viene restituito un errore, errno viene impostato su -ENOSYS e la chiamata di sistema non viene eseguita.

- SECCOMP_RET_LOG - la chiamata di sistema viene risolta e registrata.

- SECCOMP_RET_ALLOW - la chiamata di sistema è semplicemente consentita.

ptrace è una chiamata di sistema per implementare meccanismi di tracciamento in un processo chiamato tracee, con la capacità di monitorare e controllare l'esecuzione del processo. Il programma tracee può influenzare efficacemente l'esecuzione e modificare i registri di memoria del tracee. Nel contesto Seccomp, ptrace viene utilizzato quando attivato dal codice di stato SECCOMP_RET_TRACE, in modo che il tracciante possa impedire l'esecuzione della chiamata di sistema e implementare la propria logica.

Errori di seccomp

Di tanto in tanto, mentre si lavora con Seccomp, si riscontrano vari errori, identificati da un valore restituito del tipo SECCOMP_RET_ERRNO. Per segnalare un errore, la chiamata di sistema seccomp restituirà -1 invece di 0.

Sono possibili i seguenti errori:

- EACCESS - Al chiamante non è consentito effettuare una chiamata di sistema. Questo di solito accade perché non ha i privilegi CAP_SYS_ADMIN o no_new_privs non è impostato usando prctl (ne parleremo più avanti);

— EFAULT — gli argomenti passati (args nella struttura seccomp_data) non hanno un indirizzo valido;

— EINVAL — le ragioni possono essere quattro:

-l'operazione richiesta è sconosciuta o non supportata dal kernel nella configurazione corrente;

-i flag specificati non sono validi per l'operazione richiesta;

-operazione include BPF_ABS, ma ci sono problemi con l'offset specificato, che potrebbe superare la dimensione della struttura seccomp_data;

-il numero di istruzioni passate al filtro supera il massimo;

— ENOMEM — memoria insufficiente per eseguire il programma;

- EOPNOTSUPP - l'operazione indicava che con SECCOMP_GET_ACTION_AVAIL l'azione era disponibile, ma il kernel non supporta i ritorni negli argomenti;

— ESRCH: si è verificato un problema durante la sincronizzazione di un altro flusso;

- ENOSYS: non è presente alcun tracciante collegato all'azione SECCOMP_RET_TRACE.

prctl è una chiamata di sistema che consente a un programma nello spazio utente di manipolare (impostare e ottenere) aspetti specifici di un processo, come endianness dei byte, nomi dei thread, modalità di calcolo sicura (Seccomp), privilegi, eventi Perf, ecc.

Seccomp può sembrarti una tecnologia sandbox, ma non lo è. Seccomp è un'utilità che consente agli utenti di sviluppare un meccanismo sandbox. Ora diamo un'occhiata a come vengono creati i programmi di interazione con l'utente utilizzando un filtro chiamato direttamente dalla chiamata di sistema Seccomp.

Esempio di filtro BPF Seccomp

Qui mostreremo come combinare le due azioni discusse in precedenza, ovvero:

— scriveremo un programma Seccomp BPF, che verrà utilizzato come filtro con codici di ritorno diversi a seconda delle decisioni prese;

- carica il filtro usando prctl.

Per prima cosa hai bisogno degli header della libreria standard e del kernel 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>

Prima di tentare questo esempio, dobbiamo assicurarci che il kernel sia compilato con CONFIG_SECCOMP e CONFIG_SECCOMP_FILTER impostato su y. Su una macchina funzionante puoi controllare questo in questo modo:

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

Il resto del codice è una funzione install_filter in due parti. La prima parte contiene il nostro elenco di istruzioni di filtraggio 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),
  };

Le istruzioni vengono impostate utilizzando le macro BPF_STMT e BPF_JUMP definite nel file linux/filter.h.
Esaminiamo le istruzioni.

- BPF_STMT(BPF_LD + BPF_W + BPF_ABS (offsetof(struct seccomp_data, arch))) - il sistema carica e accumula da BPF_LD sotto forma della parola BPF_W, i dati del pacchetto si trovano a un offset fisso BPF_ABS.

- BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, arch, 0, 3) - controlla utilizzando BPF_JEQ se il valore dell'architettura nella costante dell'accumulatore BPF_K è uguale ad arch. In tal caso, salta all'offset 0 all'istruzione successiva, altrimenti salta all'offset 3 (in questo caso) per generare un errore perché arch non corrisponde.

- BPF_STMT(BPF_LD + BPF_W + BPF_ABS (offsetof(struct seccomp_data, nr))) - Carica e accumula da BPF_LD sotto forma della parola BPF_W, che è il numero di chiamata di sistema contenuto nell'offset fisso di BPF_ABS.

— BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, nr, 0, 1) — confronta il numero di chiamata di sistema con il valore della variabile nr. Se sono uguali, passa all'istruzione successiva e disabilita la chiamata di sistema, altrimenti consente la chiamata di sistema con SECCOMP_RET_ALLOW.

- BPF_STMT(BPF_RET + BPF_K, SECCOMP_RET_ERRNO | (errore & SECCOMP_RET_DATA)) - termina il programma con BPF_RET e di conseguenza produce un errore SECCOMP_RET_ERRNO con il numero della variabile err.

- BPF_STMT(BPF_RET + BPF_K, SECCOMP_RET_ALLOW) - termina il programma con BPF_RET e consente l'esecuzione della chiamata di sistema utilizzando SECCOMP_RET_ALLOW.

SECCOMP È CBPF
Forse ti starai chiedendo perché viene utilizzato un elenco di istruzioni invece di un oggetto ELF compilato o un programma C compilato JIT.

Ci sono due ragioni per questo.

• Innanzitutto Seccomp utilizza cBPF (classic BPF) e non eBPF, il che significa: non ha registri, ma solo un accumulatore per memorizzare l'ultimo risultato di calcolo, come si vede nell'esempio.

• In secondo luogo, Seccomp accetta direttamente un puntatore a un array di istruzioni BPF e nient'altro. Le macro che abbiamo utilizzato aiutano semplicemente a specificare queste istruzioni in modo intuitivo per i programmatori.

Se hai bisogno di ulteriore aiuto per comprendere questo assembly, considera lo pseudocodice che fa la stessa cosa:

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

Dopo aver definito il codice del filtro nella struttura socket_filter, è necessario definire un sock_fprog contenente il codice e la lunghezza calcolata del filtro. Questa struttura dati è necessaria come argomento per dichiarare che il processo verrà eseguito in seguito:

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

C'è solo una cosa da fare nella funzione install_filter: caricare il programma stesso! Per fare ciò, utilizziamo prctl, prendendo PR_SET_SECCOMP come opzione per accedere alla modalità di elaborazione sicura. Quindi diciamo alla modalità di caricare il filtro utilizzando SECCOMP_MODE_FILTER, che è contenuto nella variabile prog di tipo sock_fprog:

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

Infine, possiamo usare la nostra funzione install_filter, ma prima dobbiamo usare prctl per impostare PR_SET_NO_NEW_PRIVS per l'esecuzione corrente e quindi evitare la situazione in cui i processi figli ricevono più privilegi dei loro genitori. Con questo, possiamo effettuare le seguenti chiamate prctl nella funzione install_filter senza avere i diritti di root.

Ora possiamo chiamare la funzione install_filter. Blocchiamo tutte le chiamate di sistema in scrittura relative all'architettura X86-64 e diamo semplicemente un permesso che blocchi tutti i tentativi. Dopo aver installato il filtro, continuiamo l'esecuzione utilizzando il primo argomento:

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

Iniziamo. Per compilare il nostro programma possiamo usare clang o gcc, in ogni caso si tratta semplicemente di compilare il file main.c senza opzioni speciali:

clang main.c -o filter-write

Come notato, abbiamo bloccato tutte le voci nel programma. Per testarlo è necessario un programma che generi qualcosa: ls sembra un buon candidato. Ecco come si comporta solitamente:

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

Meraviglioso! Ecco come funziona il nostro programma wrapper: passiamo semplicemente il programma che vogliamo testare come primo argomento:

./filter-write "ls -la"

Una volta eseguito, questo programma produce un output completamente vuoto. Tuttavia, possiamo usare strace per vedere cosa sta succedendo:

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

Il risultato del lavoro è notevolmente ridotto, ma la parte corrispondente mostra che i record sono bloccati con l'errore EPERM, lo stesso che abbiamo configurato. Ciò significa che il programma non emette nulla perché non può accedere alla chiamata di sistema write:

[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)

Ora capisci come funziona Seccomp BPF e hai una buona idea di cosa puoi farci. Ma non ti piacerebbe ottenere lo stesso risultato con eBPF invece che con cBPF per sfruttarne tutta la potenza?

Quando si pensa ai programmi eBPF, la maggior parte delle persone pensa di scriverli semplicemente e caricarli con privilegi di amministratore. Sebbene questa affermazione sia generalmente vera, il kernel implementa una serie di meccanismi per proteggere gli oggetti eBPF a vari livelli. Questi meccanismi sono chiamati trappole BPF LSM.

Trappole BPF LSM

Per fornire un monitoraggio degli eventi di sistema indipendente dall'architettura, LSM implementa il concetto di trap. Una hook call è tecnicamente simile a una system call, ma è indipendente dal sistema e integrata con l'infrastruttura. LSM fornisce un nuovo concetto in cui un livello di astrazione può aiutare a evitare i problemi incontrati quando si ha a che fare con chiamate di sistema su diverse architetture.

Al momento in cui scrivo, il kernel ha sette hook associati ai programmi BPF e SELinux è l'unico LSM integrato che li implementa.

Il codice sorgente per le trappole si trova nell'albero del kernel nel file 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);

Ciascuno di essi sarà chiamato in diverse fasi di esecuzione:

— security_bpf — esegue un controllo iniziale delle chiamate di sistema BPF eseguite;

- security_bpf_map - controlla quando il kernel restituisce un descrittore di file per la mappa;

- security_bpf_prog - controlla quando il kernel restituisce un descrittore di file per il programma eBPF;

— security_bpf_map_alloc — controlla se il campo di sicurezza all'interno delle mappe BPF è inizializzato;

- security_bpf_map_free - controlla se il campo sicurezza è cancellato all'interno delle mappe BPF;

— security_bpf_prog_alloc — controlla se il campo di sicurezza è inizializzato all'interno dei programmi BPF;

- security_bpf_prog_free - controlla se il campo sicurezza è cancellato all'interno dei programmi BPF.

Ora, vedendo tutto questo, capiamo: l’idea alla base degli intercettori LSM BPF è che possano fornire protezione a ogni oggetto eBPF, garantendo che solo chi ha i privilegi adeguati possa eseguire operazioni su carte e programmi.

Riassunto

La sicurezza non è qualcosa che puoi implementare in modo unico per tutto ciò che desideri proteggere. È importante essere in grado di proteggere i sistemi a diversi livelli e in modi diversi. Che ci crediate o no, il modo migliore per proteggere un sistema è organizzare diversi livelli di protezione da diverse posizioni, in modo che la riduzione della sicurezza di un livello non consenta l'accesso all'intero sistema. Gli sviluppatori principali hanno fatto un ottimo lavoro fornendoci una serie di livelli e punti di contatto diversi. Ci auguriamo di averti fornito una buona comprensione di cosa sono i livelli e di come utilizzare i programmi BPF per lavorare con essi.

A proposito di autori

Davide Calavera è il CTO di Netlify. Ha lavorato nel supporto Docker e ha contribuito allo sviluppo degli strumenti Runc, Go e BCC, nonché ad altri progetti open source. Conosciuto per il suo lavoro su progetti Docker e per lo sviluppo dell'ecosistema di plugin Docker. David è molto appassionato dei grafici delle fiamme ed è sempre alla ricerca di ottimizzare le prestazioni.

Lorenzo Fontana lavora nel team open source di Sysdig, dove si concentra principalmente su Falco, un progetto Cloud Native Computing Foundation che fornisce sicurezza di runtime dei contenitori e rilevamento di anomalie tramite un modulo kernel ed eBPF. È appassionato di sistemi distribuiti, reti definite dal software, kernel Linux e analisi delle prestazioni.

» Per ulteriori informazioni sul libro, visitare il sito sito web dell'editore
» Sommario
» Estratto

Per Khabrozhiteli sconto del 25% sul coupon - Linux

Dopo il pagamento della versione cartacea del libro, viene inviato un e-book all'indirizzo di posta elettronica.

Fonte: habr.com

Aggiungi un commento