
Rex è un framework sicuro e utilizzabile per estensioni del kernel che consente di caricare ed eseguire programmi di estensione del kernel in Rust al posto di eBPF.
____ _______ __ _____ _ _
| _ \| ____\ \/ / | ____|_ _| |_ ___ _ __ ___(_) ___ _ __ ___
| |_) | _| \ / | _| \ \/ / __/ _ \ '_ \/ __| |/ _ \| '_ \/ __|
| _ <| |___ / \ | |___ > <| || __/ | | \__ \ | (_) | | | \__ \
|_| \_\_____/_/\_\ |_____/_/\_\\__\___|_| |_|___/_|\___/|_| |_|___|
Rex è un framework di estensione del kernel sicuro e utilizzabile che permette di caricare ed eseguire programmi di estensione del kernel scritti in Rust al posto di eBPF. A differenza dei framework basati su eBPF come Aya, i programmi Rex non passano attraverso il verificatore interno al kernel; invece, i programmi sono implementati nel sottoinsieme sicuro di Rust, su cui il compilatore Rust esegue i controlli di sicurezza necessari e genera codice nativo direttamente. Questo approccio evita i requisiti di verifica eccessivamente restrittivi (ad esempio, vincoli di complessità del programma) e i conseguenti errori di verifica oscuri, mentre allo stesso tempo potenzialmente offre una migliore opportunità di ottimizzazione nel backend del compilatore nativo (cioè LLVM) rispetto all'approccio backend eBPF + JIT nel kernel.
Rex attualmente supporta le seguenti funzionalità:
kprobe, perf_event, tracepoint, xdp e tc.Il seguente esempio implementa un programma kprobe che si attacca a una chiamata di sistema selezionata e inietta un errore (specificato da errno) nella chiamata di sistema su un processo (specificato dal suo pid). L'esempio completo, incluso il programma loader, si trova in samples/error_injector.
#![no_std]
#![no_main]
use rex::kprobe::kprobe;
use rex::map::RexHashMap;
use rex::pt_regs::PtRegs;
use rex::rex_kprobe;
use rex::rex_map;
use rex::Result;
#[allow(non_upper_case_globals)]
#[rex_map]
static pid_to_errno: RexHashMap<i32, u64> = RexHashMap::new(1, 0);
#[rex_kprobe]
pub fn err_injector(obj: &kprobe, ctx: &mut PtRegs) -> Result {
obj.bpf_get_current_task()
.map(|t| t.get_pid())
.and_then(|p| obj.bpf_map_lookup_elem(&pid_to_errno, &p).cloned())
.map(|e| obj.bpf_override_return(ctx, e))
.ok_or(0)
}
Altri programmi di esempio si trovano in samples.
Puoi trovare la guida dettagliata qui.
Documentazione aggiuntiva di progettazione si trova in docs.
L'attuale estensione eBPF si basa sul verificatore eBPF interno al kernel per fornire garanzie di sicurezza. Ciò purtroppo porta a problemi di usabilità in cui programmi sicuri vengono rifiutati dal verificatore, inclusi ma non limitati a:
Rex mira a risolvere questi problemi sfruttando direttamente la garanzia di sicurezza del Rust sicuro. Gli sviluppatori possono implementare i loro programmi in qualsiasi modo che possa essere scritto in Rust sicuro con poche restrizioni, e non devono più preoccuparsi della complessità del programma, del generatore di codice o di trovare il modo (spesso controintuitivo) di esprimere la stessa logica per soddisfare il verificatore.
Lo dimostriamo con l'implementazione della BPF Memcached Cache (BMC), un programma di estensione all'avanguardia per l'accelerazione di Memcached. Come programma eBPF complesso, BMC è costretto a essere suddiviso in diversi componenti collegati da tail-call BPF e a utilizzare implementazioni di loop/branch scomode per soddisfare il verificatore, che non sono affatto necessarie nella sua implementazione Rex.
Ad esempio, mostriamo il codice nella logica di invalidazione della cache della BPF-BMC che cerca un comando SET nel payload del pacchetto:
// Searches for SET command in payload
for (unsigned int off = 0;
off < BMC_MAX_PACKET_LENGTH && payload + off + 1 <= data_end;
off++) {
if (set_found == 0 && payload[off] == 's' &&
payload + off + 3 <= data_end && payload[off + 1] == 'e' &&
payload[off + 2] == 't') {
off += 3;
set_found = 1;
}
...
}
Il codice non solo introduce un vincolo aggiuntivo nel ciclo (off < BMC_MAX_PACKET_LENGTH) esclusivamente per superare il verificatore, ma impiega anche codice boilerplate ripetuto per controllare le estremità del pacchetto (data_end) e una logica macchinosa per trovare la stringa "set" nel pacchetto.
Nessuno di questi oneri è necessario con la potenza del Rust sicuro in Rex, che non ha limiti di complessità e offre maggiore libertà nell'implementazione:
let set_iter = payload.windows(4).enumerate().filter_map(|(i, v)| {
if v == b"set " {
Some(i)
} else {
None
}
});
L'implementazione completa di BMC in Rex si trova in samples/bmc.
Rex è concesso in licenza sotto la licenza GPLv2. I sottomoduli (Linux, Rust, LLVM) in questo repository sono concessi in licenza secondo i propri termini. Si prega di consultare i rispettivi file di licenza per maggiori dettagli. Inoltre, il benchmark memcached è concesso in licenza sotto la licenza MIT.