Cartea „BPF pentru monitorizarea Linux”

Cartea „BPF pentru monitorizarea Linux”Salut, Habraji! Mașina virtuală BPF este unul dintre cele mai importante componente ale nucleului Linux. Utilizarea sa corectă va permite inginerilor de sistem să detecteze defecțiuni și să rezolve probleme chiar și cele mai complexe. Vei învăța să creezi programe care monitorizează și modifică comportamentul nucleului, poți să încorporezi în siguranță cod pentru a observa evenimente în nucleu și multe altele. David Calavera și Lorenzo Fontana te vor ajuta să descoperi capacitățile BPF. Extinde-ți cunoștințele despre optimizarea performanței, rețelistică, securitate. — Folosește BPF pentru a urmări și modifica comportamentul nucleului Linux. — Încorporează cod pentru monitorizarea sigură a evenimentelor în nucleu — fără a fi nevoie să recompili nucleul sau să repornești sistemul. — Folosește exemple de cod utile în C, Go sau Python. — Gestionează situația, stăpânind ciclul de viață al programului BPF.

Securitatea nucleului Linux, capacitățile sale și Seccomp

BPF oferă o modalitate puternică de a extinde nucleul fără a compromite stabilitatea, securitatea și viteza. De aceea, dezvoltatorii nucleului au considerat că ar fi bine să folosească versatilitatea sa pentru a îmbunătăți izolarea proceselor în Seccomp prin implementarea filtrilor Seccomp, suportate de programele BPF, cunoscute și sub denumirea de Seccomp BPF. În acest capitol, vom explica ce este Seccomp și cum este aplicat. Apoi, vei învăța cum să scrii filtre Seccomp cu ajutorul programelor BPF. După aceea, vom examina capcanele integrate BPF care există în nucleu pentru modulele de securitate Linux.

Modulele de securitate Linux (LSM) sunt o platformă care oferă un set de funcții ce pot fi aplicate pentru implementarea standardizată a diferitelor modele de securitate. LSM poate fi utilizat direct în arborele sursă al nucleului, de exemplu Apparmor, SELinux și Tomoyo.

Să începem cu discutarea capacităților Linux.

Funcționalități

Esanța capacităților Linux constă în faptul că trebuie să oferiți unui proces fără privilegii permisiunea de a executa o anumită sarcină, dar fără a folosi suid în acest scop, sau altfel să faceți procesul privilegiate, reducând posibilitatea atacurilor și oferind procesului capacitatea de a executa anumite sarcini. De exemplu, dacă aplicația dvs. trebuie să deschidă un port privilegiat, să zicem, 80, în loc să rulați procesul sub numele root, puteți pur și simplu să îi oferiți capacitatea CAP_NET_BIND_SERVICE.

Să luăm în considerare programul Go numit main.go:

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

Acest program servește un server HTTP pe portul 80 (este un port privilegiat). De obicei, îl rulăm imediat după compilare:

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

Cu toate acestea, deoarece nu oferim privilegii root, acest cod va da o eroare la conectarea portului:

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

capsh (un instrument de gestionare a shell-ului) este un instrument care pornește un shell cu un anumit set de capacități.

În acest caz, după cum s-a menționat, în loc să oferim drepturi complete root, putem permite conectarea porturilor privilegiate, oferind capacitatea cap_net_bind_service împreună cu toate celelalte care sunt deja în program. Pentru a face acest lucru putem încadra programul nostru în 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"

Să ne ocupăm puțin de această comandă.

  • capsh — folosim capsh ca shell.
  • —caps='cap_net_bind_service+eip cap_setpcap,cap_setuid,cap_setgid+ep' — deoarece trebuie să schimbăm utilizatorul (nu dorim să ne rulăm cu drepturi de root), vom specifica cap_net_bind_service și capacitățile care ne permit efectiv să schimbăm ID-ul utilizatorului de la root la nobody, respectiv cap_setuid și cap_setgid.
  • —keep=1 — dorim să păstrăm capacitățile stabilite, atunci când se face trecerea de la contul root.
  • —user="nobody" — utilizatorul final care va rula programul va fi nobody.
  • —addamb=cap_net_bind_service — stabilim curățarea capacităților asociate după trecerea din modul root.
  • — -c "./capabilities" — pur și simplu rulăm programul.

Capacitățile asociate sunt un tip special de capacități, care sunt moștenite de programele fiică, atunci când programul curent le execută prin execve(). Numai capacitățile permise ca asociate pot fi moștenite, sau, în alte cuvinte, ca capacitățile de mediu.

Probabil că te întrebi ce înseamnă +eip după menționarea capabilității în opțiunea —caps. Aceste steaguri sunt folosite pentru a determina dacă capabilitatea:

-trebuie să fie activată (p);

-este disponibilă pentru a fi aplicată (e);

-poate fi moștenită de procesele copil (i).

Dat fiind că dorim să folosim cap_net_bind_service, trebuie să facem acest lucru cu steagul e. Apoi, vom rula un shell în comandă. Ca rezultat, se va lansa fișierul binar capabilities, iar noi trebuie să-l marcăm cu steagul i. În final, dorim ca capabilitatea să fie activată (am făcut asta fără a schimba UID) prin p. Asta arată ca cap_net_bind_service+eip.

Poți verifica rezultatul folosind ss. Vom scurta puțin ieșirea pentru a se încadra pe pagină, dar va arăta portul asociat și identificatorul de utilizator, diferit de 0, în acest caz 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

În acest exemplu, am folosit capsh, dar poți scrie un shell folosind libcap. Pentru informații suplimentare, consultă man 3 libcap.

Atunci când scriu programe, dezvoltatorii nu știu adesea dinainte toate capabilitățile necesare programului în timpul execuției; mai mult, în versiunile noi, aceste capabilități pot să se schimbe.

Pentru a înțelege mai bine capabilitățile programului nostru, putem lua instrumentul BCC capable, care stabilește kprobe pentru funcția nucleului 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

Putem obține același lucru folosind bpftrace cu un kprobe pe o linie în funcția nucleului 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

Aceasta va afișa ceva de genul următor, dacă capabilitățile programului nostru sunt activate după 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

Cea de-a cincea coloană reprezintă capabilitățile de care are nevoie procesul, iar deoarece aceste ieșiri includ, de asemenea, evenimente non-audit, vedem toate verificările non-audit și, în final, capabilitatea necesară cu steagul de audit (ultimul în ieșire), setat la 1. Capabilitatea care ne interesează este CAP_NET_BIND_SERVICE, definită ca o constantă în codul sursă al nucleului în fișierul include/uapi/linux/ability.h cu identificatorul 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">

Funcțiile sunt adesea utilizate în timpul rulării containerelor, cum ar fi runC sau Docker, pentru a funcționa într-un mod neprivilegiat, dar cu permisiunea de a avea doar acele funcții necesare pentru a rula majoritatea aplicațiilor. Când o aplicație necesită funcții specifice, acestea pot fi furnizate în Docker folosind —cap-add:

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

Această comandă va oferi containerului capacitatea CAP_NET_ADMIN, permițându-i să configureze un link de rețea pentru a adăuga interfața dummy0.

Secțiunea următoare arată utilizarea acestor capacități, cum ar fi filtrarea, dar printr-o metodă diferită, care ne va permite să implementăm propriile filtre în mod programatic.

Seccomp

Seccomp înseamnă Secure Computing, un nivel de securitate implementat în nucleul Linux, care permite dezvoltatorilor să filtreze anumite apeluri de sistem. Deși Seccomp este comparabil cu funcțiile Linux, capacitatea sa de a gestiona anumite apeluri de sistem îl face mult mai flexibil în comparație cu acestea.

Seccomp și funcțiile Linux nu se exclud reciproc; ele sunt adesea utilizate împreună pentru a beneficia de ambele abordări. De exemplu, s-ar putea să doriți să oferiți procesului capacitatea CAP_NET_ADMIN, dar să nu îi permiteți să primească conexiuni prin socket, blocând apelurile de sistem accept și accept4.

Metoda de filtrare Seccomp se bazează pe filtre BPF care funcționează în modul SECCOMP_MODE_FILTER, iar filtrarea apelurilor de sistem se realizează la fel ca pentru pachete.

Filtrele Seccomp sunt încărcate folosind prctl prin operațiunea PR_SET_SECCOMP. Aceste filtre au forma unui program BPF, care este executat pentru fiecare pachet Seccomp prezentat folosind structura seccomp_data. Această structură conține arhitectura de referință, un pointer la instrucțiunile procesorului în timpul apelului de sistem și maximum șase argumente ale apelului de sistem, exprimate ca uint64.

Iată cum arată structura seccomp_data din codul sursă al nucleului în fișierul linux/seccomp.h:

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

Așa cum se poate observa din această structură, putem să filtrăm în funcție de apelul de sistem, argumentele sale sau combinația acestora.

După primirea fiecărui pachet, filtrul Seccomp trebuie să efectueze o procesare pentru a lua o decizie finală și a informa nucleul despre ce să facă mai departe. Decizia finală este exprimată prin unul dintre codurile de stare returnate.

— SECCOMP_RET_KILL_PROCESS — terminarea întregului proces imediat după filtrarea apelului de sistem, care din această cauză nu se va executa.

— SECCOMP_RET_KILL_THREAD — terminarea firului de execuție curent imediat după filtrarea apelului de sistem, care din această cauză nu se va executa.

— SECCOMP_RET_KILL — alias pentru SECCOMP_RET_KILL_THREAD, lăsat pentru compatibilitate inversă.

— SECCOMP_RET_TRAP — apelul de sistem este interzis și semnalul SIGSYS (Apel de sistem greșit) este trimis sarcinii care l-a invocat.

— SECCOMP_RET_ERRNO — apelul de sistem nu se execută, iar o parte a valorii returnate de filtrul SECCOMP_RET_DATA este transmisă spațiului utilizator ca valoare errno. În funcție de motivul erorii, sunt returnate diferite valori errno. Lista codurilor de eroare este prezentată în secțiunea următoare.

— SECCOMP_RET_TRACE — folosit pentru a notifica debuggerele ptrace cu — PTRACE_O_TRACESECCOMP pentru a intercepta atunci când apelul de sistem este executat, pentru a vedea și controla acest proces. Dacă debuggerul nu este conectat, se returnează o eroare, errno fiind setat la -ENOSYS, iar apelul de sistem nu se execută.

— SECCOMP_RET_LOG — apelul de sistem este permis și înregistrat în jurnal.

— SECCOMP_RET_ALLOW — apelul de sistem este pur și simplu permis.

ptrace este un apel de sistem pentru implementarea mecanismelor de depanare într-un proces numit tracee, cu capacitatea de a observa și controla execuția procesului. Programul de depanare poate influența efectiv execuția și schimba registrele de memorie ale tracee-ului. În contextul Seccomp, ptrace este folosit atunci când se rulează codul de stare SECCOMP_RET_TRACE, astfel debuggerul poate preveni executarea apelului de sistem și implementa propria logică.

Erori Seccomp

Din când în când, când lucrați cu Seccomp, veți întâlni diverse erori, care sunt identificate prin valoarea returnată de tip SECCOMP_RET_ERRNO. Pentru a semnala o eroare, apelul de sistem seccomp va returna -1 în loc de 0.

Următoarele erori sunt posibile:

— EACCESS — apelul de sistem nu este permis pentru partea care îl face. De obicei, acest lucru se întâmplă deoarece nu are privilegii CAP_SYS_ADMIN sau no_new_privs nu este setat prin prctl (despre aceasta vom vorbi mai târziu);

— EFAULT — argumentele transmise (args în structura seccomp_data) nu au o adresă validă;

— EINVAL — aici pot fi patru motive:

- operația solicitată este necunoscută sau nu este acceptată de kernel în configurația actuală;

- flagurile specificate sunt invalide pentru operația solicitată;

- operația include BPF_ABS, dar există probleme cu offset-ul specificat, care poate depăși dimensiunea structurii seccomp_data;

- numărul de instrucțiuni transmise filtrului depășește maximul;

— ENOMEM — nu există suficientă memorie pentru a executa programul;

— EOPNOTSUPP — operația a indicat că SECCOMP_GET_ACTION_AVAIL era disponibil, cu toate acestea, kernelul nu suportă revenirea în argumente;

— ESRCH — a apărut o problemă în sincronizarea unei alte thread-uri;

— ENOSYS — nu există un debugger atașat acțiunii SECCOMP_RET_TRACE.

prctl este un apel de sistem care permite programului din spațiul utilizatorului să gestioneze (să seteze și să obțină) aspecte specifice ale procesului, cum ar fi ordinea baitului, numele thread-urilor, modul de calcul protejat (Seccomp), privilegii, evenimente Perf etc.

Seccomp poate părea o tehnologie de sandbox, dar nu este. Seccomp este un utilitar care permite utilizatorilor să dezvolte un mecanism de sandbox. Acum să examinăm cum se creează programele de interacțiune cu utilizatorii folosind filtrul apelat direct prin apelul de sistem Seccomp.

Exemplu de filtru BPF Seccomp

Aici vom arăta cum să combinăm cele două acțiuni discutate anterior, și anume:

— vom scrie un program Seccomp BPF care va fi aplicat ca filtru cu coduri de întoarcere diferite în funcție de deciziile luate;

— vom încărca filtrul folosind prctl.

Pentru început, avem nevoie de headere din biblioteca standard și kernelul 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>

Înainte de a încerca să executăm acest exemplu, trebuie să ne asigurăm că kernelul a fost compilat cu CONFIG_SECCOMP și CONFIG_SECCOMP_FILTER setate pe y. Pe un sistem de lucru, acest lucru se poate verifica astfel:

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

Restul codului reprezintă o funcție install_filter, formată din două părți. Prima parte conține lista noastră de instrucțiuni pentru filtrarea 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),
  };

Instrucțiunile sunt stabilite folosind macro-urile BPF_STMT și BPF_JUMP, definite în fișierul linux/filter.h.
Să parcurgem instrucțiunile.

— BPF_STMT(BPF_LD + BPF_W + BPF_ABS (offsetof(struct seccomp_data, arch))) — sistemul încarcă și acumulează cu BPF_LD sub formă de cuvânt BPF_W, datele pachetului fiind plasate la un offset fix BPF_ABS.

— BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, arch, 0, 3) — verifică, folosind BPF_JEQ, dacă valoarea arhitecturii din acumulatorul constant BPF_K este egală cu arch. Dacă este adevărat, se saltă cu un offset de 0 la următoarea instrucțiune, altfel, pentru a genera o eroare, va sări cu un offset de 3 (în acest caz), deoarece arch nu coincide.

— BPF_STMT(BPF_LD + BPF_W + BPF_ABS (offsetof(struct seccomp_data, nr))) — încarcă și acumulează cu BPF_LD sub formă de cuvânt BPF_W, care este numărul apelului de sistem, aflat la un offset fix BPF_ABS.

— BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, nr, 0, 1) — compară numărul apelului de sistem cu valoarea variabilei nr. Dacă sunt egale, se saltă la următoarea instrucțiune și interzice apelul de sistem, altfel permite apelul de sistem cu SECCOMP_RET_ALLOW.

— BPF_STMT(BPF_RET + BPF_K, SECCOMP_RET_ERRNO | (error & SECCOMP_RET_DATA)) — încheie programul cu BPF_RET și în consecință returnează o eroare SECCOMP_RET_ERRNO cu numărul din variabila err.

— BPF_STMT(BPF_RET + BPF_K, SECCOMP_RET_ALLOW) — încheie programul cu BPF_RET și permite execuția apelului de sistem folosind SECCOMP_RET_ALLOW.

SECCOMP — ACESTA ESTE CBPF
Poate că te întrebi de ce se folosește o listă de instrucțiuni în loc de un obiect ELF compilat sau un program C compilat cu JIT.

Există două motive pentru aceasta.

• În primul rând, Seccomp aplică cBPF (BPF clasic), nu eBPF, ceea ce înseamnă: nu are registre, ci doar un acumulator pentru a stoca ultimul rezultat al calculelor, așa cum se poate observa în exemplu.

• În al doilea rând, Seccomp primește un pointer la un array de instrucțiuni BPF direct și nimic mai mult. Macros-urile pe care le-am folosit ajută doar la specificarea acestor instrucțiuni într-o formă convenabilă pentru programatori.

Dacă aveți nevoie de ajutor suplimentar pentru a înțelege această construcție, luați în considerare pseudocodul care face același lucru:

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

După definirea codului filtrului în structura socket_filter, trebuie să definim sock_fprog, care conține codul și lungimea calculată a filtrului. Această structură de date este necesară ca argument pentru anunțarea funcționării procesului ulterior:

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

Rămâne doar un lucru de făcut în funcția install_filter: să încărcăm programul propriu-zis! Pentru aceasta, folosim prctl, luând PR_SET_SECCOMP ca opțiune pentru a intra în modul de calcul protejat. Apoi, vom specifica modul să încarce filtrul folosind SECCOMP_MODE_FILTER, care este conținut în variabila prog de tip sock_fprog:

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

În cele din urmă, putem folosi funcția noastră install_filter, dar înainte trebuie să apelăm prctl pentru a stabili PR_SET_NO_NEW_PRIVS pentru execuția curentă și astfel evităm situația în care procesele fiu primesc privilegii mai largi decât cele ale părintelui. În acest timp, putem efectua următoarele apeluri prctl în funcția install_filter, fără a avea drepturi root.

Acum putem apela funcția install_filter. Vom bloca toate apelurile sistemului write legate de arhitectura X86-64 și pur și simplu vom da permisiunea care blochează toate încercările. După instalarea filtrului, continuăm execuția, folosind primul argument:

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

Hai să începem. Pentru a compila programul nostru, putem folosi fie clang, fie gcc; oricum, este doar o compilare a fișierului main.c fără opțiuni speciale:

clang main.c -o filter-write

După cum s-a menționat, am blocat toate scrierile în program. Pentru a verifica acest lucru, avem nevoie de un program care să genereze ieșire; ls pare un candidat bun. Iată cum se comportă de obicei:

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

Perfect! Iată cum arată utilizarea programului nostru shell: pur și simplu transmitem programul pe care dorim să-l testăm ca prim argument:

./filter-write "ls -la"

După execuție, acest program produce o ieșire complet goală. Cu toate acestea, putem aplica strace pentru a vedea ce se întâmplă:

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

Rezultatul execuției este semnificativ scurtat, dar partea relevantă arată că înregistrările sunt blocate cu eroarea EPERM — exact aceea pe care am configurat-o. Aceasta înseamnă că programul nu produce nicio ieșire deoarece nu poate accesa apelul de sistem write:

[pid 25099] write(2, "ls: ", 4) = -1 EPERM (Operațiune nepermisă)
[pid 25099] write(2, "eroare de scriere", 11) = -1 EPERM (Operațiune nepermisă)
[pid 25099] write(2, "n", 1) = -1 EPERM (Operațiune nepermisă)

Acum înțelegeți cum funcționează Seccomp BPF și aveți o idee bună despre ce se poate realiza cu ajutorul său. Dar nu ar fi interesant să obținem același lucru folosind eBPF în loc de cBPF, pentru a folosi toată puterea sa?

Când se gândesc la programele eBPF, majoritatea oamenilor cred că pur și simplu le scriu și le încarcă cu privilegii de administrator. Deși această afirmație este, în general, adevărată, nucleul implementează un set de mecanisme pentru a proteja obiectele eBPF la diferite niveluri. Aceste mecanisme se numesc capcanele BPF LSM.

Capcanele BPF LSM

Pentru a asigura un control al evenimentelor sistemice independent de arhitectură, LSM implementează conceptul de capcane. Tehnic, apelul capcanei este similar cu un apel de sistem, dar este independent de sistem și integrat cu infrastructura. LSM oferă un nou concept, în care nivelul de abstracție poate ajuta la evitarea problemelor care apar în timpul lucrului cu apelurile de sistem pe diferite arhitecturi.

La momentul scrierii cărții, nucleul avea șapte capcane legate de programele BPF și SELinux era singurul LSM încorporat care le implementa.

Codul sursă al capcanelor este plasat în arborele nucleului în fișierul 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);

Fiecare dintre ele va fi apelată în diferite etape de execuție:

— security_bpf — efectuează verificarea inițială a apelurilor sistemice BPF efectuate;

— security_bpf_map — verifică atunci când kernelul returnează un descriptor de fișier pentru hartă;

— security_bpf_prog — verifică atunci când kernelul returnează un descriptor de fișier pentru programul eBPF;

— security_bpf_map_alloc — verifică dacă câmpul de securitate este inițializat în interiorul hărților BPF;

— security_bpf_map_free — verifică dacă curățarea câmpului de securitate în interiorul hărților BPF este realizată;

— security_bpf_prog_alloc — verifică dacă câmpul de securitate este inițializat în interiorul programelor BPF;

— security_bpf_prog_free — verifică dacă câmpul de securitate este curățat în interiorul programelor BPF.

Acum, văzând toate acestea, înțelegem: ideea interceptoarelor LSM BPF este că acestea pot asigura protecția fiecărui obiect eBPF, garantând că doar aceia care au privilegii corespunzătoare pot efectua operații asupra hărților și programelor.

Rezumat

Securitatea nu este ceva ce poți implementa într-un mod universal pentru tot ceea ce dorești să protejezi. Este important să poți proteja sistemele la diferite niveluri și în moduri diferite. Vrei să crezi sau nu, cea mai bună metodă de a securiza un sistem este să organizezi diferite niveluri de protecție din perspective diferite, astfel încât scăderea securității unui nivel să nu permită accesul la întregul sistem. Dezvoltatorii kernelului au realizat o muncă importantă, oferindu-ne un set de straturi și puncte de interacțiune diferite. Sperăm că v-am oferit o idee bună despre ce sunt aceste straturi și cum să folosiți programele BPF pentru a lucra cu ele.

Despre autori

David Calavera este director tehnic la Netlify. A lucrat în suportul Docker și a participat la dezvoltarea instrumentelor Runc, Go și BCC, precum și a altor proiecte open-source. Este cunoscut pentru lucrările sale la proiectele Docker și pentru dezvoltarea ecosistemului pluginurilor Docker. David iubește grafurile de flacără și aspiră mereu către optimizarea performanței.

Lorenzo Fontana lucrează ca parte a echipei de dezvoltare software cu sursă deschisă la Sysdig, unde se concentrează în principal pe Falco — un proiect al Cloud Native Computing Foundation, care asigură securitatea mediului de execuție a containerelor și detectarea anomaliilor prin intermediul modulului kernel și eBPF. Este pasionat de sistemele distribuite, rețelele definite prin software, kernelul Linux și analiza performanței.

» Puteți găsi mai multe detalii despre carte pe site-ul editurii
» Cuprins
» Fragment

Pentru utilizatorii Habr, discount de 25% cu codul — Linux

După plata versiunii tipărite a cărții, se va trimite o carte electronică pe e-mail.

Sursa: habr.com

Cumpără un hosting fiabil pentru site-uri cu protecție DDoS, servere VPS VDS 🔥 Cumpără un hosting fiabil pentru site-uri cu protecție DDoS, servere VPS VDS | ProHoster