
Rex é um framework de extensão de kernel seguro e utilizável que permite carregar e executar programas de extensão de kernel em Rust no lugar do eBPF.
____ _______ __ _____ _ _
| _ \| ____\ \/ / | ____|_ _| |_ ___ _ __ ___(_) ___ _ __ ___
| |_) | _| \ / | _| \ \/ / __/ _ \ '_ \/ __| |/ _ \| '_ \/ __|
| _ <| |___ / \ | |___ > <| || __/ | | \__ \ | (_) | | | \__ \
|_| \_\_____/_/\_\ |_____/_/\_\\__\___|_| |_|___/_|\___/|_| |_|___|
Rex é um framework de extensão de kernel seguro e utilizável que permite carregar e executar programas de extensão de kernel em Rust no lugar do eBPF. Diferente de frameworks baseados em eBPF como Aya, os programas Rex não passam pelo verificador interno do kernel; em vez disso, os programas são implementados no subconjunto seguro do Rust, no qual o compilador Rust realiza as verificações de segurança necessárias e gera código nativo diretamente. Esta abordagem evita os requisitos de verificação excessivamente restritivos (por exemplo, restrições de complexidade do programa) e os consequentes erros de verificação obscuros, ao mesmo tempo que potencialmente oferece uma melhor oportunidade de otimização no backend do compilador nativo (isto é, LLVM) do que a abordagem eBPF backend + JIT no kernel.
Rex atualmente suporta os seguintes recursos:
kprobe, perf_event, tracepoint, xdp, e tc.O exemplo a seguir implementa um programa kprobe que se anexa a uma chamada de sistema selecionada e injeta um erro (especificado por errno) na chamada de sistema em um processo (especificado pelo seu pid). O exemplo completo, incluindo o programa carregador, pode ser encontrado em 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)
}
Mais programas de exemplo podem ser encontrados em samples.
Você pode encontrar o guia detalhado aqui.
Documentações adicionais de design podem ser encontradas em docs.
A extensão eBPF existente depende do verificador eBPF no kernel para fornecer garantias de segurança. Isso infelizmente leva a problemas de usabilidade onde programas seguros são rejeitados pelo verificador, incluindo mas não se limitando a:
Rex visa resolver esses problemas aproveitando diretamente a garantia de segurança do safe Rust. Desenvolvedores podem implementar seus programas de qualquer maneira que possa ser escrita em Rust seguro com poucas restrições, e não precisam mais se preocupar com complexidade do programa, o gerador de código, ou encontrar a (muitas vezes contra-intuitiva) maneira de expressar a mesma lógica para agradar o verificador.
Demonstramos isso com a implementação do BPF Memcached Cache (BMC), um programa de extensão de ponta para aceleração do Memcached. Como um programa eBPF complexo, o BMC é forçado a ser dividido em vários componentes conectados por tail-calls BPF e usar implementações de loops/ramificações estranhas para agradar o verificador, que são totalmente desnecessárias em sua implementação Rex.
Por exemplo, mostramos o código na lógica de invalidação de cache do BPF-BMC que procura por um comando SET no payload do pacote:
// 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;
}
...
}
O código não só introduz uma restrição extra no loop (off < BMC_MAX_PACKET_LENGTH) apenas para passar pelo verificador, mas também emprega código boilerplate repetido para verificar os finais do pacote (data_end) e lógica complicada para corresponder à string "set" no pacote.
Nenhum desses encargos é necessário com o poder do Rust seguro no Rex, que não tem limites de complexidade e oferece mais liberdade na implementação:
let set_iter = payload.windows(4).enumerate().filter_map(|(i, v)| {
if v == b"set " {
Some(i)
} else {
None
}
});
A implementação completa do BMC no Rex pode ser encontrada em samples/bmc.
Rex é licenciado sob a licença GPLv2. Os submódulos (Linux, Rust, LLVM) neste repositório são licenciados sob seus próprios termos. Por favor, veja os arquivos de licença correspondentes para mais detalhes. Além disso, o benchmark memcached é licenciado sob a licença MIT.