BPF: il Berkeley Packet Filter
Il Berkeley Packet Filter (BPF) o Berkeley Filter interessa tutti i sistemi operativi Unix, come Linux. Il compito principale della “macchina virtuale per compiti speciali” (in inglese "Special Purpose Virtual Machine"), sviluppata nel 1992, è quello di filtrare i pacchetti di dati dalle reti e di incorporarli nel kernel del sistema operativo. Il BPF rappresenta un’interfaccia con i livelli di sicurezza di unità di dati o programmi. I livelli di sicurezza hanno il compito di garantire la trasmissione affidabile dei pacchetti di dati e di regolamentarne l’accesso.
Quando un tale pacchetto di dati raggiunge il destinatario, il BPF legge i dati dei livelli di sicurezza dal pacchetto e cerca, ad esempio, gli errori. In questo modo il destinatario può correggere e confrontare i dati con le definizioni dei filtri e quindi accettare o rifiutare un pacchetto che non è considerato pertinente. In questo modo si può risparmiare molta capacità di calcolo.
Come funziona il Berkeley Packet Filter?
Per svolgere le sue funzioni, il Berkeley Packet Filter è stato integrato come interprete in linguaggio macchina all’interno di una macchina virtuale. Di conseguenza, il BPF esegue un formato predefinito di istruzioni. In qualità di interprete, il Berkeley Filter legge i file sorgente, li analizza ed esegue un’istruzione dopo l’altra. Poi traduce le istruzioni in codice macchina per l’esecuzione diretta.
Grazie alle SysCalls, che sono chiamate di funzioni di sistema speciali e pronte all’uso, il Berkeley Filter invia richieste al centro del sistema operativo, chiamato anche kernel. Quest’ultimo controlla i permessi di accesso prima di accettare o rifiutare la richiesta. Tra le circa 330 SysCalls di Linux vi sono ad esempio:
- read – permesso di lettura, con il quale si può leggere un file
- write – permesso di scrittura, che consente la scrittura di un file
- open – consente di aprire i file o i dispositivi
- close – consente di chiudere i file o i dispositivi
- stat – consente di recuperare lo stato di un file
Grazie a un continuo e costante sviluppo, oggi il BPF funziona come una macchina virtuale universale direttamente nel kernel del sistema operativo, dove si svolge l’intero processo e l’organizzazione dei dati. Con l’aggiunta di nuove caratteristiche, il filtro è noto come Extended BPF, abbreviato eBPF. Questo è in grado di eseguire qualsiasi codice intermedio (byte code) utilizzato in modo sicuro e durante il runtime (compilazione just-in-time) direttamente nel kernel operativo. L’Extended BPF funziona nel kernel operativo all’interno di un ambiente isolato ed è per tale motivo protetto. Il modello di ambiente noto come Sandbox minimizza il rischio che il sistema abbia un effetto negativo sulla logica del kernel operativo.
Il Berkeley Filter può funzionare sia in modalità kernel (massimo accesso alle risorse della macchina), sia in modalità utente (accesso limitato alle risorse di calcolo).
Vantaggi del Berkeley Filter
Con l’eBPF è possibile filtrare i pacchetti di dati e quindi evitare che dati irrilevanti rallentino le prestazioni del PC. I record di dati inutilizzabili o non corretti possono quindi essere rifiutati o riparati fin dall’inizio. Inoltre, l’Extended BPF fornisce una maggiore sicurezza grazie alle SysCalls, chiamate di sistema che consentono di misurare o tracciare facilmente le prestazioni del vostro sistema operativo.
Già nel 2007 l’implementazione del BPF è stata estesa con le “Zero copy buffer extensions”. Grazie a queste estensioni, i driver dei dispositivi possono salvare i pacchetti di dati catturati direttamente nel programma, senza dover prima copiare i dati.
Programmazione dei filtri con BPF
In modalità utente, è possibile definire in qualsiasi momento i singoli filtri per l’interfaccia del Berkeley Filter. In passato i codici corrispondenti venivano scritti manualmente e tradotti in un byte code BPF. Oggi, grazie al compilatore LLVM Clang Compiler, è possibile tradurre direttamente i byte code.
Le librerie del kernel operativo contengono anche programmi di esempio che semplificano la definizione dei programmi eBPF. Diverse funzioni di guida semplificano il vostro lavoro.
I verificatori eBPF per la sicurezza
L’esecuzione di chiamate di sistema nel kernel è sempre associata ad alcuni rischi per la sicurezza e la stabilità. Prima che una eBPF SysCall si carichi, deve superare una serie di controlli:
- In primo luogo viene verificato che la chiamata di sistema sia completa e non contenga loop, cosa che potrebbe portare a un crash del kernel. Il grafico del flusso di controllo del programma viene esaminato per rintracciare le istruzioni non raggiungibili che non vengono caricate successivamente.
- Prima e dopo l’esecuzione di un’istruzione viene controllato lo stato della chiamata di sistema eBPF. Questo per garantire che l’Extended BPF operi solo negli ambiti consentiti e non acceda ai dati al di fuori della Sandbox. Non è necessario controllare ogni percorso singolarmente, in quanto di solito è sufficiente un sottoinsieme.
- Infine, viene impostato anche il tipo di SysCall. Questo passaggio è importante per definire quali funzioni del kernel possono essere richiamate dalla SysCall e quali strutture dati possono essere accessibili. Ad esempio, è possibile utilizzare chiamate di sistema che accedono direttamente ai pacchetti di dati della rete.
I tipi di SysCall esaminano approssimativamente quattro funzioni: dove il programma può essere allegato, quali funzioni di guida del kernel possono essere richiamate, se i pacchetti di dati di rete sono accessibili direttamente o meno, e quale tipo di oggetto viene trasmesso con priorità in una chiamata di sistema.
Attualmente sono supportati dal kernel i seguenti tipi di eBPF SysCall:
- BPF_PROG_TYPE_SOCKET_FILTER
- BPF_PROG_TYPE_KPROBE
- BPF_PROG_TYPE_SCHED_CLS
- BPF_PROG_TYPE_SCHED_ACT
- BPF_PROG_TYPE_TRACEPOINT
- BPF_PROG_TYPE_XDP
- BPF_PROG_TYPE_PERF_EVENT
- BPF_PROG_TYPE_CGROUP_SKB
- BPF_PROG_TYPE_CGROUP_SOCK
- BPF_PROG_TYPE_LWT_ *
- BPF_PROG_TYPE_SOCK_OPS
- BPF_PROG_TYPE_SK_SKB
- BPF_PROG_CGROUP_DEVICE