Il libro "BPF per il monitoraggio di Linux"

Il libro "BPF per il monitoraggio di Linux"Ciao, Habroditi! La macchina virtuale BPF è uno dei componenti fondamentali del kernel Linux. Il suo utilizzo appropriato consente agli ingegneri di sistema di rilevare guasti e risolvere i problemi più complessi. Imparerai a creare programmi che monitorano e modificano il comportamento del kernel, potrai implementare in sicurezza codice per la registrazione degli eventi nel kernel e molto altro. David Calavera e Lorenzo Fontana ti guideranno nell'esplorazione delle potenzialità di BPF. Espandi le tue conoscenze su ottimizzazione delle prestazioni, reti e sicurezza. — Utilizza BPF per monitorare e modificare il comportamento del kernel Linux. — Implementa codice per un monitoraggio sicuro degli eventi nel kernel — senza necessità di ricompilare il kernel o riavviare il sistema. — Approfitta di utili esempi di codice in C, Go o Python. — Gestisci la situazione padroneggiando il ciclo di vita del programma BPF.

Sicurezza del kernel Linux, le sue potenzialità e Seccomp

BPF offre un modo potente per estendere il kernel senza compromettere stabilità, sicurezza e velocità. Per questo motivo, gli sviluppatori del kernel hanno ritenuto che sarebbe stato utile sfruttarne la versatilità per migliorare l'isolamento dei processi in Seccomp implementando filtri Seccomp supportati dai programmi BPF, noti anche come Seccomp BPF. In questo capitolo spiegheremo cos'è Seccomp e come viene utilizzato. Poi imparerai a scrivere filtri Seccomp utilizzando programmi BPF. Successivamente, esamineremo le trappole BPF incorporate nel kernel per i moduli di sicurezza Linux.

I moduli di sicurezza Linux (LSM) sono una piattaforma che fornisce un insieme di funzioni che possono essere applicate per implementare in modo standardizzato diversi modelli di sicurezza. LSM può essere utilizzato direttamente nell'albero del codice sorgente del kernel, ad esempio Apparmor, SELinux e Tomoyo.

Iniziamo discutendo delle potenzialità di Linux.

Capacità

Il concetto delle potenzialità di Linux è che è necessario fornire a un processo non privilegiato il permesso di eseguire una specifica attività, ma senza utilizzare suid per questo scopo, o altrimenti rendere il processo privilegiato, riducendo così la possibilità di attacchi 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 dargli il permesso CAP_NET_BIND_SERVICE.

Esaminiamo un programma Go chiamato main.go:

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

Questo programma gestisce un server HTTP sulla porta 80 (che è una porta privilegiata). Solitamente lo eseguiamo subito dopo la compilazione:

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

Tuttavia, poiché non forniamo privilegi di root, questo codice genererà un errore durante il tentativo di legare la porta:

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

capsh (utility di gestione della shell) è uno strumento che avvia una shell con un particolare insieme di potenzialità.

In questo caso, come già detto, invece di concedere diritti completi di root, possiamo consentire la legatura delle porte privilegiate, concedendo la potenzialità cap_net_bind_service insieme a tutte le altre già previste nel programma. Per farlo 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"

Esaminiamo questa linea di comando.

  • capsh — utilizziamo capsh come shell.
  • —caps=’cap_net_bind_service+eip cap_setpcap,cap_setuid,cap_setgid+ep’ — poiché è necessario cambiare utente (non vogliamo essere eseguiti con i privilegi di root), specifichiamo cap_net_bind_service e le potenzialità che ci consentono di cambiare effettivamente l'identità dell'utente da root a nobody, ovvero cap_setuid e cap_setgid.
  • —keep=1 — desideriamo mantenere le potenzialità impostate quando avviene il passaggio dall'account root.
  • —user=«nobody» — l'utente finale che eseguirà il programma sarà nobody.
  • —addamb=cap_net_bind_service — impostiamo la pulizia delle potenzialità associate dopo il passaggio dal modo root.
  • — -c "./capabilities" — semplicemente eseguiamo il programma.

Le potenzialità associate sono un tipo speciale di potenzialità che vengono ereditate dai programmi figlio quando il programma corrente le esegue tramite execve(). Possono essere ereditate solo potenzialità autorizzate come associate, in altre parole, come potenzialità di ambiente.

Probabilmente ti starai chiedendo cosa significhi +eip dopo aver specificato la potenzialità nell'opzione —caps. Questi flag sono utilizzati per determinare che la potenzialità:

- deve essere attivata (p);

- è disponibile per l'applicazione (e);

- può essere ereditata dai processi figlio (i).

Poiché vogliamo utilizzare cap_net_bind_service, dobbiamo farlo con il flag e. Poi eseguiremo una shell nel comando. Di conseguenza verrà eseguito un file binario capabilities e dobbiamo contrassegnarlo con il flag i. Infine, vogliamo che la capacità sia attivata (lo abbiamo fatto senza cambiare UID) tramite p. Questo appare come cap_net_bind_service+eip.

Puoi controllare il risultato utilizzando ss. Accorceremo un po' l'output per farlo stare nella pagina, ma mostrerà la porta associata e l'identificativo utente, diversi da 0, in questo caso 65 534:

# 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 utilizzato capsh, ma puoi scrivere una shell utilizzando libcap. Per ulteriori informazioni, consulta il man 3 libcap.

Quando scrivono programmi, gli sviluppatori spesso non sanno in anticipo tutte le capacità necessarie al programma durante l'esecuzione; inoltre, in versioni successive queste capacità potrebbero cambiare.

Per comprendere meglio le capacità del nostro programma, possiamo utilizzare lo strumento BCC capable, che imposta un kprobe per la funzione 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 lo stesso risultato utilizzando bpftrace con un kprobe in linea per la funzione 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

Questo stamperà qualcosa di simile al seguente, se le capacità del nostro programma sono state attivate dopo il 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 sono le capacità richieste dal processo e, poiché questi output includono anche eventi non auditati, vediamo tutti i controlli non auditati e, infine, la capacità richiesta con il flag di audit (l'ultima nell'output), impostato a 1. La capacità che ci interessa è CAP_NET_BIND_SERVICE, che è definita come costante nel codice sorgente del kernel nel file include/uapi/linux/ability.h con identificativo 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 capacità sono spesso utilizzate durante l'esecuzione di container, come runC o Docker, per funzionare in modalità non privilegiata, ma solo quelle capacità necessarie per eseguire la maggior parte delle applicazioni sono consentite. Quando un'applicazione richiede determinate capacità, in Docker può essere fornita tramite —cap-add:

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

Questo comando fornirà al container la capacità CAP_NET_ADMIN, consentendogli di configurare il link di rete per aggiungere l'interfaccia dummy0.

Nella sezione successiva viene mostrato l'uso di tali capacità, come il filtraggio, ma con un metodo diverso che ci permetterà di implementare i nostri filtri in modo programmatico.

Seccomp

Seccomp significa Secure Computing, è un livello di sicurezza implementato nel kernel Linux che consente agli sviluppatori di filtrare determinate chiamate di sistema. Sebbene Seccomp sia paragonabile alle capacità di Linux, la sua capacità di gestire specifiche chiamate di sistema lo rende molto più flessibile rispetto a esse.

Seccomp e le capacità di Linux non si escludono a vicenda, vengono spesso utilizzati insieme per ottenere vantaggi da entrambi gli approcci. Ad esempio, potresti voler fornire al processo la capacità CAP_NET_ADMIN, ma non consentire di accettare connessioni tramite socket, bloccando le chiamate di sistema accept e accept4.

Il metodo di filtraggio di Seccomp si basa su filtri BPF, che operano in modalità SECCOMP_MODE_FILTER, e il filtraggio delle chiamate di sistema avviene come per i pacchetti.

I filtri Seccomp vengono caricati utilizzando prctl tramite l'operazione PR_SET_SECCOMP. Questi filtri hanno la forma di un programma BPF che viene eseguito per ogni pacchetto Seccomp presentato tramite la struttura seccomp_data. Questa struttura contiene l'architettura di riferimento, un puntatore delle istruzioni del processore durante la chiamata di sistema e fino a sei argomenti della chiamata di sistema, espressi come uint64.

Ecco come appare la 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 si può vedere da questa struttura, possiamo filtrare in base alla chiamata di sistema, ai suoi argomenti o alla loro combinazione.

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

— SECCOMP_RET_KILL_PROCESS — termina immediatamente tutto il processo dopo il filtraggio della chiamata di sistema, che di conseguenza non viene eseguita.

— SECCOMP_RET_KILL_THREAD — termina immediatamente il thread corrente dopo il filtraggio della chiamata di sistema, che di conseguenza non viene eseguita.

— SECCOMP_RET_KILL — alias per SECCOMP_RET_KILL_THREAD, rimasto per retrocompatibilità.

— SECCOMP_RET_TRAP — la chiamata di sistema è vietata, e il segnale SIGSYS (Bad System Call) viene inviato al processo che l'ha chiamata.

— SECCOMP_RET_ERRNO — la chiamata di sistema non viene eseguita, e parte del ritorno del filtro SECCOMP_RET_DATA viene passato allo spazio utente come valore errno. A seconda della ragione dell'errore, vengono restituiti diversi valori errno. Un elenco dei numeri di errore è fornito nella sezione seguente.

— SECCOMP_RET_TRACE — utilizzato per informare il tracciatore ptrace con — PTRACE_O_TRACESECCOMP per intercettare l'esecuzione della chiamata di sistema, in modo da vedere e controllare questo processo. Se non è collegato alcun tracciatore, viene restituito un errore, errno è impostato su -ENOSYS, e la chiamata di sistema non viene eseguita.

— SECCOMP_RET_LOG — la chiamata di sistema è consentita e registrata nel log.

— 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 possibilità di osservare e controllare l'esecuzione del processo. Un programma di tracciamento può influenzare efficacemente l'esecuzione e modificare i registri di memoria del tracee. Nel contesto di Seccomp, ptrace è utilizzato quando il codice dello stato SECCOMP_RET_TRACE viene eseguito, quindi il tracciatore può prevenire l'esecuzione della chiamata di sistema e implementare la propria logica.

Errori di Seccomp

Di tanto in tanto, lavorando con Seccomp, ti troverai a fronteggiare vari errori, identificati dal valore di ritorno di tipo SECCOMP_RET_ERRNO. Per segnalare un errore, la chiamata di sistema seccomp restituirà -1 invece di 0.

Possono verificarsi i seguenti errori:

— EACCESS — non è consentito alla parte chiamante di effettuare la chiamata di sistema. Questo succede di solito perché non ha i privilegi CAP_SYS_ADMIN o non è impostato no_new_privs tramite prctl (di cui parleremo più avanti);

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

— EINVAL — ci possono essere quattro motivi:

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

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

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

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

— ENOMEM — non c'è abbastanza memoria per eseguire il programma;

— EOPNOTSUPP — l'operazione ha indicato che con SECCOMP_GET_ACTION_AVAIL l'azione era disponibile, tuttavia il kernel non supporta il ritorno negli argomenti;

— ESRCH — c'è stato un problema nella sincronizzazione di un altro thread;

— ENOSYS — non c'è un tracciatore collegato all'azione SECCOMP_RET_TRACE.

prctl è una chiamata di sistema che consente a un programma nello spazio utente di gestire (impostare e ricevere) aspetti specifici del processo, come il numero di byte, i nomi dei thread, la modalità di calcolo protetta (Seccomp), privilegi, eventi di performance, ecc.

Seccomp potrebbe sembrarti una tecnologia sandbox, ma non è così. Seccomp è un'utilità che permette agli utenti di progettare meccanismi di sandboxing. Ora esaminiamo come vengono create le interazioni delle applicazioni utente utilizzando un filtro invocato direttamente dalla chiamata di sistema Seccomp.

Esempio di filtro BPF Seccomp

Qui mostreremo come combinare due azioni esaminate in precedenza, vale a dire:

— scriveremo un programma Seccomp BPF che sarà applicato come filtro con codici di ritorno differenti a seconda delle decisioni prese;

— caricheremo il filtro utilizzando prctl.

Per iniziare, servono le intestazioni dalla libreria standard e dal 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 provare a eseguire questo esempio, dobbiamo assicurarci che il kernel sia compilato con CONFIG_SECCOMP e CONFIG_SECCOMP_FILTER impostati su y. Su un machine di lavoro, questo può essere verificato così:

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

Il resto del codice consiste nella funzione install_filter, composta da due parti. La prima parte contiene la nostra lista 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 i macro BPF_STMT e BPF_JUMP definiti nel file linux/filter.h.
Passiamo in rassegna le istruzioni.

— BPF_STMT(BPF_LD + BPF_W + BPF_ABS (offsetof(struct seccomp_data, arch))) — il sistema carica e accumula con BPF_LD in forma di parola BPF_W, i dati del pacchetto sono localizzati con uno spostamento fisso BPF_ABS.

— BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, arch, 0, 3) — verifica se il valore dell'architettura nella costante dell'accumulatore BPF_K è uguale a arch utilizzando BPF_JEQ. Se è così, passa con uno spostamento di 0 alla prossima istruzione, altrimenti salta con uno spostamento di 3 (in questo caso), poiché arch non corrisponde.

— BPF_STMT(BPF_LD + BPF_W + BPF_ABS (offsetof(struct seccomp_data, nr))) — carica e accumula con BPF_LD in forma di parola BPF_W, che rappresenta il numero della chiamata di sistema contenuto in uno spostamento fisso BPF_ABS.

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

— BPF_STMT(BPF_RET + BPF_K, SECCOMP_RET_ERRNO | (error & SECCOMP_RET_DATA)) — termina il programma con BPF_RET e restituisce un errore SECCOMP_RET_ERRNO con il numero da 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 con SECCOMP_RET_ALLOW.

SECCOMP — È cBPF
Potresti chiederti perché si utilizzi un elenco di istruzioni invece di un oggetto ELF compilato o di un programma C compilato con JIT.

Ci sono due motivi.

• In primo luogo, Seccomp applica cBPF (classic BPF) e non eBPF, il che significa: non ha registri, ma solo un accumulatore per memorizzare l'ultimo risultato dei calcoli, come si può vedere nell'esempio.

• In secondo luogo, Seccomp accetta un puntatore a un array di istruzioni BPF direttamente e nient'altro. I macro che abbiamo utilizzato servono solo a specificare queste istruzioni in una forma conveniente per i programmatori.

Se hai bisogno di ulteriore aiuto per comprendere questa costruzione, considera il 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 sock_fprog, contenente il codice e la lunghezza calcolata del filtro. Questa struttura dati è necessaria come argomento per dichiarare successivamente il processo:

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

Rimane solo una cosa da fare nella funzione install_filter: caricare il programma stesso! Per farlo, utilizziamo prctl, impostando PR_SET_SECCOMP come opzione per entrare in modalità di calcolo protetta. Poi indichiamo al sistema di caricare il filtro utilizzando SECCOMP_MODE_FILTER, che si trova 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 utilizzare la nostra funzione install_filter, ma prima dobbiamo attivare prctl per impostare PR_SET_NO_NEW_PRIVS per l'esecuzione attuale e quindi evitare che i processi figli ricevano privilegi più ampi rispetto ai processi genitori. A questo punto possiamo effettuare le seguenti chiamate prctl nella funzione install_filter senza avere diritti di root.

Ora possiamo chiamare la funzione install_filter. Bloccheremo tutte le chiamate di sistema write relative all'architettura X86-64 e semplicemente daremo il permesso di bloccare tutti i tentativi. Dopo aver impostato 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 utilizzare sia clang che 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 scritture nel programma. Per verificare questo, è necessaria un programma che produca output — 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

Ottimo! Ecco come appare l'uso del nostro programma wrapper: semplicemente passiamo il programma che vogliamo testare come primo argomento:

./filter-write "ls -la"

Dopo l'esecuzione, questo programma produce un output completamente vuoto. Tuttavia, possiamo utilizzare strace per vedere cosa succede:

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

Il risultato è notevolmente accorciato, ma la parte pertinente mostra che le scritture vengono bloccate con l'errore EPERM — quello stesso errore che abbiamo configurato. Questo significa che il programma non produce output perché non può accedere alla chiamata di sistema write:

[pid 25099] write(2, "ls: ", 4) = -1 EPERM (Operazione non consentita)
[pid 25099] write(2, "write error", 11) = -1 EPERM (Operazione non consentita)
[pid 25099] write(2, "n", 1) = -1 EPERM (Operazione non consentita)

Ora capite come funziona Seccomp BPF e avete una buona idea di cosa è possibile fare con esso. Ma non sarebbe interessante ottenere lo stesso risultato con eBPF invece di cBPF, per sfruttare tutta la sua potenza?

Riflettendo sui programmi eBPF, la maggior parte delle persone pensa che siano semplicemente scritti e caricati con privilegi di amministratore. Sebbene questa affermazione sia generalmente vera, il kernel implementa un insieme di meccanismi per proteggere gli oggetti eBPF a vari livelli. Questi meccanismi sono chiamati BPF LSM hooks.

BPF LSM hooks

Per garantire un controllo degli eventi di sistema indipendente dall'architettura, LSM implementa il concetto di hooks. Tecnica mente, una chiamata ad un hook è simile a una chiamata di sistema, tuttavia è indipendente dal sistema e integrata con l'infrastruttura. LSM offre un nuovo concetto, in cui il livello di astrazione è in grado di aiutare ad evitare problemi che sorgono nel lavorare con le chiamate di sistema su diverse architetture.

Al momento della scrittura di questo libro, il kernel aveva sette hooks legati ai programmi BPF, e SELinux è l'unico LSM integrato che li implementa.

Il codice sorgente degli hooks 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);

Ognuna di esse verrà chiamata in fasi diverse dell'esecuzione:

— security_bpf — esegue un controllo iniziale sulle chiamate di sistema BPF già eseguite;

— security_bpf_map — verifica quando il kernel restituisce un descrittore di file per una mappa;

— security_bpf_prog — verifica quando il kernel restituisce un descrittore di file per un programma eBPF;

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

— security_bpf_map_free — controlla se il campo di sicurezza all'interno delle mappe BPF viene pulito;

— security_bpf_prog_alloc — verifica se il campo di sicurezza all'interno dei programmi BPF viene inizializzato;

— security_bpf_prog_free — verifica se il campo di sicurezza all'interno dei programmi BPF viene pulito.

Ora, vedendo tutto questo, comprendiamo: l'idea degli hook LSM BPF è che possono fornire protezione a ciascun oggetto eBPF, garantendo che solo quelli con i privilegi appropriati possano eseguire operazioni su mappe e programmi.

Riepilogo

La sicurezza non è qualcosa che puoi implementare in modo universale per tutto ciò che vuoi proteggere. È importante essere in grado di proteggere i sistemi a diversi livelli e in modi diversi. Che tu ci creda o no, il modo migliore per garantire la sicurezza di un sistema è organizzare vari livelli di protezione da diverse posizioni, in modo che la diminuzione della sicurezza di un livello non consenta l'accesso all'intero sistema. Gli sviluppatori del kernel hanno fatto un ottimo lavoro fornendoci un insieme di diversi strati e punti di interazione. Speriamo di avervi dato una buona idea di cosa siano gli strati e come utilizzare i programmi BPF per lavorarci.

Chi siamo

David Calavera è Chief Technology Officer di Netlify. Ha lavorato nel supporto di Docker ed è stato coinvolto nello sviluppo di strumenti come Runc, Go, BCC e altri progetti open source. È conosciuto per il suo lavoro su progetti Docker e per lo sviluppo dell'ecosistema dei plugin Docker. David ama molto i flame graph e cerca sempre di ottimizzare le prestazioni.

Lorenzo Fontana lavora nel team di sviluppo software open source di Sysdig, dove si occupa principalmente di Falco — un progetto della Cloud Native Computing Foundation che fornisce sicurezza per l'ambiente di esecuzione dei container e rilevamento di anomalie tramite modulo del kernel e eBPF. È appassionato di sistemi distribuiti, reti definite dal software, kernel Linux e analisi delle prestazioni.

» Puoi approfondire con il libro su sito dell'editore
» Indice
» Estratto

Per gli utenti di Habr, uno sconto del 25% con il coupon — Linux

Dopo il pagamento della versione cartacea del libro, verrà inviato un libro elettronico via email.

Fonte: habr.com

Acquista hosting affidabile per siti web con protezione DDoS, server VPS VDS 🔥 Acquista hosting affidabile per siti web con protezione DDoS, server VPS VDS | ProHoster