Livro "BPF para Monitoramento do Linux"

Livro "BPF para Monitoramento do Linux"Olá, hobristas! A máquina virtual BPF é um dos componentes mais importantes do núcleo Linux. Seu uso adequado permitirá que os engenheiros de sistemas encontrem falhas e resolvam até os problemas mais complexos. Você aprenderá a criar programas que monitoram e modificam o comportamento do núcleo, poderá incorporar código para supervisionar eventos no núcleo de forma segura e muito mais. David Calavera e Lorenzo Fontana o ajudarão a explorar as capacidades do BPF. Amplie seus conhecimentos sobre otimização de desempenho, redes e segurança. — Use o BPF para rastrear e modificar o comportamento do núcleo Linux. — Implemente código para monitoramento seguro de eventos no núcleo — sem a necessidade de recompilar o núcleo ou reiniciar o sistema. — Utilize exemplos de código em C, Go ou Python. — Controle a situação dominando o ciclo de vida do programa BPF.

Segurança do núcleo Linux, suas capacidades e Seccomp

O BPF fornece uma maneira poderosa de estender o núcleo sem comprometer a estabilidade, segurança e velocidade. Por essa razão, os desenvolvedores do núcleo acharam que seria interessante usar sua versatilidade para melhorar o isolamento de processos no Seccomp, implementando filtros Seccomp suportados por programas BPF, conhecidos como Seccomp BPF. Neste capítulo, falaremos sobre o que é o Seccomp e como ele é aplicado. Em seguida, você aprenderá a escrever filtros Seccomp usando programas BPF. Por fim, examinaremos as armadilhas BPF integradas que existem no núcleo para módulos de segurança Linux.

Os Módulos de Segurança do Linux (LSM) são uma plataforma que fornece um conjunto de funções que podem ser aplicadas para a implementação padronizada de vários modelos de segurança. O LSM pode ser usado diretamente na árvore de código-fonte do núcleo, como Apparmor, SELinux e Tomoyo.

Comecemos discutindo as capacidades do Linux.

Recursos

A essência das funcionalidades do Linux é que você precisa conceder a um processo não privilegiado permissão para executar uma determinada tarefa, mas sem o uso do suid para esse fim, ou de outra forma tornar o processo privilegiado, reduzindo a possibilidade de ataques e garantindo ao processo a capacidade de executar certas tarefas. Por exemplo, se seu aplicativo precisa abrir uma porta privilegiada, digamos, 80, em vez de executar o processo como root, você pode simplesmente conceder a ele a permissão CAP_NET_BIND_SERVICE.

Vamos considerar um programa Go chamado main.go:

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

Este programa serve um servidor HTTP na porta 80 (uma porta privilegiada). Geralmente, o executamos logo após a compilação:

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

No entanto, como não estamos concedendo privilégios de root, esse código gerará um erro ao tentar vincular a porta:

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

capsh (ferramenta de gerenciamento de shell) é uma ferramenta que executa um shell com um conjunto específico de capacidades.

Neste caso, como já mencionado, em vez de conceder permissões completas de root, podemos permitir a vinculação de portas privilegiadas, concedendo a permissão cap_net_bind_service juntamente com todas as outras que já estão no programa. Para isso, podemos encapsular nosso programa em 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"

Vamos analisar um pouco esse comando.

  • capsh — usamos capsh como shell.
  • —caps='cap_net_bind_service+eip cap_setpcap,cap_setuid,cap_setgid+ep' — como precisamos mudar o usuário (não queremos ser executados com privilégios de root), especificamos cap_net_bind_service e a capacidade de efetivamente mudar o identificador de usuário de root para nobody, ou seja, cap_setuid e cap_setgid.
  • —keep=1 — queremos manter as capacidades já definidas ao alternar da conta root.
  • —user="nobody" — o usuário final que executa o programa será nobody.
  • —addamb=cap_net_bind_service — configuramos a limpeza das capacidades associadas após a alternância do modo root.
  • — -c "./capabilities" — simplesmente executamos o programa.

Capacidades associadas são um tipo especial de capacidades que são herdadas por programas filhos quando o programa atual as executa com execve(). Somente as capacidades permitidas como associadas podem ser herdadas, ou, em outras palavras, capacidades de ambiente.

Provavelmente, você está curioso sobre o que significa +eip após a especificação de capacidade na opção —caps. Essas flags são usadas para determinar se a capacidade:

-deve estar ativada (p);

-está disponível para aplicação (e);

-pode ser herdada por processos filhos (i).

Como queremos usar cap_net_bind_service, precisamos fazer isso com a flag e. Então, vamos iniciar o shell no comando. Como resultado, um arquivo binário de capabilities será iniciado e precisamos marcá-lo com a flag i. Finalmente, queremos que a capacidade esteja ativada (fizemos isso sem alterar o UID) usando p. Isso se parece com cap_net_bind_service+eip.

Você pode verificar o resultado usando ss. Vamos diminuir um pouco a saída para que ela caiba na página, mas ela mostrará a porta associada e o identificador de usuário diferente de 0, neste 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

Neste exemplo, usamos capsh, mas você pode escrever um shell usando libcap. Para mais informações, consulte man 3 libcap.

Ao escrever programas, os desenvolvedores frequentemente não sabem de antemão todas as capacidades necessárias para o programa em tempo de execução; além disso, em novas versões, essas capacidades podem mudar.

Para entender melhor as capacidades do nosso programa, podemos usar a ferramenta BCC capable, que estabelece um kprobe para a função do 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

Podemos alcançar o mesmo resultado usando bpftrace com um kprobe de linha única na função do 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

Isso irá produzir algo como o seguinte, se as capacidades do nosso programa forem ativadas após o 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

A quinta coluna representa as capacidades exigidas pelo processo e, uma vez que esses dados incluem eventos não auditáveis, vemos todas as verificações não auditáveis e, finalmente, a capacidade exigida com a flag de auditoria (a última na saída), definida como 1. A capacidade que nos interessa é CAP_NET_BIND_SERVICE, que é definida como uma constante no código fonte do kernel no arquivo include/uapi/linux/ability.h com o identificador 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">

As funcionalidades são frequentemente utilizadas durante a execução de contêineres, como runC ou Docker, para que funcionem em modo não privilegiado, mas apenas as permissões necessárias para executar a maioria das aplicações são concedidas. Quando uma aplicação requer permissões específicas, elas podem ser fornecidas no Docker através do —cap-add:

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

Este comando concederá ao contêiner a permissão CAP_NET_ADMIN, permitindo que ele configure a ligação de rede para adicionar a interface dummy0.

Na próxima seção, mostramos como usar essas permissões, como filtragem, mas com um método diferente que nos permitirá implementar nossos próprios filtros programaticamente.

Seccomp

Seccomp significa Secure Computing, é um nível de segurança implementado no núcleo do Linux que permite que os desenvolvedores filtrem chamadas de sistema específicas. Embora o Seccomp seja comparável às permissões do Linux, sua capacidade de gerenciar chamadas de sistema específicas o torna muito mais flexível em comparação com elas.

Seccomp e as permissões do Linux não se excluem; muitas vezes são usados juntos para obter benefícios de ambas as abordagens. Por exemplo, você pode querer conceder ao processo a permissão CAP_NET_ADMIN, mas não permitir que ele estabeleça conexões através de um socket, bloqueando as chamadas de sistema accept e accept4.

O método de filtragem do Seccomp é baseado em filtros BPF, funcionando no modo SECCOMP_MODE_FILTER, e a filtragem das chamadas de sistema é realizada da mesma forma que para pacotes.

Os filtros do Seccomp são carregados usando prctl através da operação PR_SET_SECCOMP. Esses filtros têm a forma de um programa BPF, que é executado para cada pacote Seccomp apresentado através da estrutura seccomp_data. Essa estrutura contém a arquitetura de referência, um ponteiro para as instruções do processador durante a chamada de sistema e até seis argumentos da chamada de sistema, expressos como uint64.

Aqui está como a estrutura seccomp_data se apresenta no código-fonte do núcleo no arquivo linux/seccomp.h:

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

Como pode ser visto nesta estrutura, podemos filtrar com base na chamada de sistema, em seus argumentos ou em suas combinações.

Após receber cada pacote, o filtro Seccomp deve executar um processamento para tomar uma decisão final e informar ao núcleo o que fazer a seguir. A decisão final é expressa por um dos valores retornados (códigos de estado).

— SECCOMP_RET_KILL_PROCESS — finaliza todo o processo imediatamente após a filtragem da chamada de sistema, que, devido a isso, não é executada.

— SECCOMP_RET_KILL_THREAD — finaliza a thread atual imediatamente após a filtragem da chamada de sistema, que, devido a isso, não é executada.

— SECCOMP_RET_KILL — um alias para SECCOMP_RET_KILL_THREAD, mantido por compatibilidade.

— SECCOMP_RET_TRAP — a chamada de sistema é proibida, e o sinal SIGSYS (Bad System Call) é enviado para a tarefa que a chamou.

— SECCOMP_RET_ERRNO — a chamada de sistema não é executada, e parte do valor retornado do filtro SECCOMP_RET_DATA é passada para o espaço do usuário como valor de errno. Dependendo da razão do erro, diferentes valores de errno são retornados. A lista de números de erro é apresentada na seção seguinte.

— SECCOMP_RET_TRACE — utilizado para notificar o depurador ptrace com — PTRACE_O_TRACESECCOMP para interceptar quando a chamada de sistema é executada, permitindo ver e controlar esse processo. Se o depurador não estiver conectado, é retornado um erro e errno é definido como -ENOSYS, e a chamada de sistema não é executada.

— SECCOMP_RET_LOG — a chamada de sistema é permitida e registrada no log.

— SECCOMP_RET_ALLOW — a chamada de sistema é simplesmente permitida.

ptrace é uma chamada de sistema para implementar mecanismos de rastreamento em um processo chamado tracee, com a capacidade de observar e controlar a execução do processo. O programa de rastreamento pode afetar efetivamente a execução e modificar os registradores de memória do tracee. No contexto do Seccomp, ptrace é utilizado quando é acionado pelo estado SECCOMP_RET_TRACE, permitindo que o depurador impeça a execução da chamada de sistema e implemente sua própria lógica.

Erros do Seccomp

Periodicamente, ao trabalhar com o Seccomp, você encontrará diversos erros que são identificados pelo valor retornado do tipo SECCOMP_RET_ERRNO. Para relatar um erro, a chamada de sistema seccomp retornará -1 em vez de 0.

Os seguintes erros são possíveis:

— EACCESS — a parte chamadora não tem permissão para fazer chamadas de sistema. Normalmente, isso ocorre porque ela não possui privilégios CAP_SYS_ADMIN ou porque o no_new_privs não está configurado com prctl (falaremos mais sobre isso depois);

— EFAULT — os argumentos fornecidos (args na estrutura seccomp_data) não têm um endereço válido;

— EINVAL — aqui podem haver quatro razões:

- a operação solicitada é desconhecida ou não é suportada pelo kernel na configuração atual;

- os flags especificados são inválidos para a operação solicitada;

- a operação inclui BPF_ABS, mas há problemas com o deslocamento especificado, que pode exceder o tamanho da estrutura seccomp_data;

- o número de instruções passadas para o filtro excede o máximo permitido;

— ENOMEM — memória insuficiente para executar o programa;

— EOPNOTSUPP — a operação indicou que a SECCOMP_GET_ACTION_AVAIL estava disponível, entretanto o kernel não suporta o retorno nos argumentos;

— ESRCH — houve um problema ao sincronizar outro thread;

— ENOSYS — não há um rastreador anexado à ação SECCOMP_RET_TRACE.

prctl é uma chamada de sistema que permite que um programa de espaço do usuário gerencie (defina e recupere) aspectos específicos do processo, como o número de sequência de bytes, nomes de threads, modo de computação protegida (Seccomp), privilégios, eventos Perf, etc.

Seccomp pode parecer uma tecnologia de sandbox, mas não é. Seccomp é uma ferramenta que permite que os usuários desenvolvam um mecanismo de sandbox. Agora, vamos ver como são criados programas de interação do usuário usando um filtro chamado diretamente pela chamada de sistema Seccomp.

Exemplo de filtro BPF Seccomp

Aqui vamos mostrar como combinar duas ações discutidas anteriormente, a saber:

— escreveremos um programa Seccomp BPF que será aplicado como um filtro com códigos de retorno diferentes, dependendo das decisões tomadas;

— carregaremos o filtro usando prctl.

Para começar, precisamos das cabeçalhos da biblioteca padrão e do 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>

Antes de tentar executar este exemplo, devemos garantir que o kernel foi compilado com CONFIG_SECCOMP e CONFIG_SECCOMP_FILTER definidos como y. Em uma máquina de trabalho, isso pode ser verificado assim:

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

O restante do código consiste na função install_filter, que é composta por duas partes. A primeira parte contém nossa lista de instruções de filtragem 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),
  };

As instruções são definidas usando os macros BPF_STMT e BPF_JUMP, que estão contidos no arquivo linux/filter.h.
Vamos analisar as instruções.

— BPF_STMT(BPF_LD + BPF_W + BPF_ABS (offsetof(struct seccomp_data, arch))) — o sistema carrega e acumula com BPF_LD na forma de palavra BPF_W, os dados do pacote estão localizados com um deslocamento fixo BPF_ABS.

— BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, arch, 0, 3) — verifica usando BPF_JEQ se o valor da arquitetura na constante acumuladora BPF_K é igual a arch. Se for, avança com o deslocamento 0 para a próxima instrução, caso contrário, pula com o deslocamento 3 (neste caso) para gerar um erro, pois arch não corresponde.

— BPF_STMT(BPF_LD + BPF_W + BPF_ABS (offsetof(struct seccomp_data, nr))) — carrega e acumula com BPF_LD na forma de palavra BPF_W, que é o número da chamada do sistema armazenado em um deslocamento fixo BPF_ABS.

— BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, nr, 0, 1) — compara o número da chamada do sistema com o valor da variável nr. Se forem iguais, avança para a próxima instrução e proíbe a chamada do sistema, caso contrário, permite a chamada do sistema com SECCOMP_RET_ALLOW.

— BPF_STMT(BPF_RET + BPF_K, SECCOMP_RET_ERRNO | (error & SECCOMP_RET_DATA)) — finaliza o programa com BPF_RET e retorna um erro SECCOMP_RET_ERRNO com o número da variável err.

— BPF_STMT(BPF_RET + BPF_K, SECCOMP_RET_ALLOW) — finaliza o programa com BPF_RET e permite a execução da chamada do sistema com SECCOMP_RET_ALLOW.

SECCOMP — É CBPF
Talvez você esteja se perguntando por que é utilizada uma lista de instruções em vez de um objeto ELF compilado ou um programa em C compilado com JIT.

Há duas razões para isso.

• Primeiro, o Seccomp aplica cBPF (BPF clássico) em vez de eBPF, o que significa: ele não possui registradores, apenas um acumulador para armazenar o último resultado dos cálculos, como pode ser visto no exemplo.

• Em segundo lugar, o Seccomp recebe um ponteiro para um array de instruções BPF diretamente e nada mais. Os macros que usamos ajudam apenas a indicar essas instruções de uma forma que seja conveniente para os programadores.

Se você precisar de ajuda adicional para entender essa construção, considere o pseudocódigo que faz o mesmo:

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

Após definir o código do filtro na estrutura socket_filter, é preciso definir sock_fprog, que contém o código e o comprimento calculado do filtro. Essa estrutura de dados é necessária como argumento para a declaração de operação do processo posteriormente:

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

Agora, só falta uma coisa a fazer na função install_filter: carregar o próprio programa! Para isso, usamos prctl, passando PR_SET_SECCOMP como opção, para entrar no modo de computação protegido. Em seguida, especificamos que o modo deve carregar o filtro usando SECCOMP_MODE_FILTER, que está na variável prog do tipo sock_fprog:

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

Finalmente, podemos utilizar nossa função install_filter, mas antes precisamos usar prctl para definir PR_SET_NO_NEW_PRIVS para a execução atual, evitando assim que processos filhos obtenham privilégios mais amplos que o pai. Com isso, podemos realizar as seguintes chamadas de prctl na função install_filter, sem precisar de permissões root.

Agora podemos chamar a função install_filter. Iremos bloquear todas as chamadas de sistema write relacionadas à arquitetura X86-64 e simplesmente permitir que bloqueie todas as tentativas. Após definir o filtro, continuamos a execução, utilizando o primeiro argumento:

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

Vamos lá. Para compilar nosso programa, podemos usar tanto clang quanto gcc, de qualquer forma, é apenas a compilação do arquivo main.c sem opções especiais:

clang main.c -o filter-write

Como mencionado, bloqueamos todas as gravações no programa. Para verificar isso, precisamos de um programa que produza alguma saída; ls parece ser um bom candidato. Veja como ele normalmente se comporta:

ls -la
total 36
drwxr-xr-x 2 fntlnz users 4096 Abr 28 21:09 .
drwxr-xr-x 4 fntlnz users 4096 Abr 26 13:01 ..
-rwxr-xr-x 1 fntlnz users 16800 Abr 28 21:09 filter-write
-rw-r--r-- 1 fntlnz users 19 Abr 28 21:09 .gitignore
-rw-r--r-- 1 fntlnz users 1282 Abr 28 21:08 main.c

Ótimo! Veja como é usada nossa interface de programa: simplesmente passamos o programa que queremos testar como o primeiro argumento:

./filter-write "ls -la"

Após a execução, este programa produz uma saída completamente vazia. No entanto, podemos usar o strace para ver o que está acontecendo:

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

A saída do resultado está bastante reduzida, mas a parte correspondente mostra que as gravações estão sendo bloqueadas com o erro EPERM — aquele que configuramos. Isso significa que o programa não produz saída porque não consegue acessar a chamada do sistema write:

[pid 25099] write(2, "ls: ", 4) = -1 EPERM (Operação não permitida)
[pid 25099] write(2, "erro de gravação", 14) = -1 EPERM (Operação não permitida)
[pid 25099] write(2, "n", 1) = -1 EPERM (Operação não permitida)

Agora você entende como o Seccomp BPF funciona e tem uma boa ideia do que pode ser feito com ele. Mas não seria interessante conseguir o mesmo com o eBPF em vez do cBPF, para aproveitar toda a sua potência?

Quando se pensa em programas eBPF, a maioria das pessoas acredita que apenas os escreveu e os carregou com permissões de administrador. Embora essa afirmação seja válida de modo geral, o núcleo implementa um conjunto de mecanismos para proteger objetos eBPF em vários níveis. Esses mecanismos são chamados de ganchos BPF LSM.

Ganchos BPF LSM

Para garantir o controle de eventos do sistema independente da arquitetura, o LSM implementa o conceito de ganchos. Tecnicamente, uma chamada de gancho é semelhante a uma chamada de sistema, no entanto, é independente do sistema e integra-se à infraestrutura. O LSM fornece um novo conceito, onde o nível de abstração é capaz de ajudar a evitar problemas que surgem ao trabalhar com chamadas de sistema em diferentes arquiteturas.

No momento da redação deste livro, o núcleo tinha sete ganchos relacionados a programas BPF e o SELinux era o único LSM embutido que os implementava.

O código-fonte dos ganchos está localizado na árvore do núcleo no arquivo 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);

Cada um deles será chamado em diferentes estágios de execução:

— security_bpf — realiza a verificação inicial das chamadas de sistema executadas pelo BPF;

— security_bpf_map — verifica quando o núcleo retorna um descritor de arquivo para o mapa;

— security_bpf_prog — verifica quando o núcleo retorna um descritor de arquivo para o programa eBPF;

— security_bpf_map_alloc — verifica se o campo de segurança dentro dos mapas BPF foi inicializado;

— security_bpf_map_free — verifica se a limpeza do campo de segurança dentro dos mapas BPF está sendo realizada;

— security_bpf_prog_alloc — verifica se o campo de segurança dentro dos programas BPF está sendo inicializado;

— security_bpf_prog_free — verifica se o campo de segurança dentro dos programas BPF está sendo limpo.

Agora, vendo tudo isso, entendemos: a ideia dos interceptores LSM BPF é que eles podem garantir a proteção de cada objeto eBPF, assegurando que apenas aqueles com as devidas permissões possam realizar operações em mapas e programas.

Resumo

Segurança não é algo que você pode implementar de maneira universal para tudo o que deseja proteger. É importante conseguir proteger os sistemas em diferentes níveis e de diferentes formas. Acredite ou não, o melhor jeito de assegurar um sistema é organizar diferentes níveis de proteção a partir de várias posições, para que a redução da segurança em um nível não permita o acesso a todo o sistema. Os desenvolvedores do núcleo fizeram um ótimo trabalho em nos fornecer um conjunto de camadas diferentes e pontos de interação. Esperamos que tenhamos dado uma boa ideia do que são as camadas e como usar programas BPF para interagir com elas.

Sobre os autores

David Calavera é diretor técnico na Netlify. Ele trabalhou no suporte do Docker e participou do desenvolvimento de ferramentas como Runc, Go e BCC, além de outros projetos de código aberto. É conhecido por seu trabalho em projetos Docker e no desenvolvimento do ecossistema de plugins do Docker. David é apaixonado por gráficos de chama e sempre busca otimização de desempenho.

Lorenzo Fontana Trabalha na equipe de desenvolvimento de software de código aberto na Sysdig, onde se concentra principalmente no Falco — um projeto da Cloud Native Computing Foundation que garante a segurança do ambiente de execução de containers e a detecção de anomalias através de módulos do kernel e eBPF. Ele é apaixonado por sistemas distribuídos, redes definidas por software, kernel Linux e análise de desempenho.

» Você pode se aprofundar no livro em site da editora
» Índice
» Trecho

Para os moradores do Habr, desconto de 25% com o cupom — Linux

Após o pagamento da versão impressa do livro, o e-book será enviado por e-mail.

Fonte: habr.com

Compre hospedagem confiável para sites com proteção contra DDoS, servidores VPS VDS 🔥 Compre hospedagem confiável para sites com proteção contra DDoS, servidores VPS VDS | ProHoster