Libri «BPF për monitorimin e Linux»

Libri «BPF pĂ«r monitorimin e Linux»PĂ«rshĂ«ndetje, HabrozhitorĂ«! Virtualizimi BPF Ă«shtĂ« njĂ« nga komponentĂ«t mĂ« tĂ« rĂ«ndĂ«sishĂ«m tĂ« bĂ«rthamĂ«s Linux. PĂ«rdorimi i tij i duhur do t'u mundĂ«sojĂ« inxhinierĂ«ve sistemikĂ« tĂ« zbulojnĂ« defektet dhe tĂ« zgjidhin edhe problemet mĂ« tĂ« komplikuara. Ju do tĂ« mĂ«soni si tĂ« krijoni programe qĂ« ndjekin dhe modifikojnĂ« sjelljen e bĂ«rthamĂ«s, si dhe si tĂ« implementoni kod pĂ«r monitorimin e ngjarjeve nĂ« bĂ«rthamĂ« nĂ« mĂ«nyrĂ« tĂ« sigurt dhe shumĂ« mĂ« tepĂ«r. David Calavera dhe Lorenzo Fontana do t'ju ndihmojnĂ« tĂ« shfrytĂ«zoni mundĂ«sitĂ« e BPF. Zgjeroni njohuritĂ« tuaja mbi optimizimin e performancĂ«s, rrjetet, sigurinĂ«. — PĂ«rdorni BPF pĂ«r tĂ« ndjekur dhe modifikuar sjelljen e bĂ«rthamĂ«s Linux. — Implementoni kod pĂ«r monitorimin e sigurt tĂ« ngjarjeve nĂ« bĂ«rthamĂ« — pa nevojĂ«n e rikompilimit tĂ« bĂ«rthamĂ«s ose rinisjes sĂ« sistemit. — ShfrytĂ«zoni shembujt e lehtĂ« tĂ« kodit nĂ« C, Go ose Python. — Menaxhoni situatĂ«n duke zotĂ«ruar ciklin e jetĂ«s sĂ« programit BPF.

Siguria e bërthamës Linux, mundësitë e saj dhe Seccomp

BPF ofron një mënyrë të fuqishme për të zgjeruar bërthamën pa komprometuar stabilitetin, sigurinë dhe shpejtësinë. Për këtë arsye, zhvilluesit e bërthamës menduan se do të ishte mirë të shfrytëzonin universin e saj për të përmirësuar izolimin e proceseve në Seccomp përmes implementimit të filtrave Seccomp, të mbështetur nga programet BPF, të njohura gjithashtu si Seccomp BPF. Në këtë kapitull do të flasim për çfarë është Seccomp dhe si aplikohet. Më pas do të mësoni si të shkruani filtra Seccomp me ndihmën e programeve BPF. Pas kësaj, do të shqyrtojmë grackat e ndërtuara BPF që janë në bërthamë për modulet e sigurisë Linux.

Modulet e sigurisë Linux (LSM) janë një platformë që ofron një grup funksionesh që mund të aplikohen për implementimin standardizuar të modeleve të ndryshme të sigurisë. LSM mund të përdoret drejtpërdrejt në pemën e kodit burimor të bërthamës, siç është Apparmor, SELinux dhe Tomoyo.

Le të fillojmë me diskutimin e mundësive të Linux.

Mundësitë

The essence of Linux capabilities is that you need to give a non-privileged process permission to perform a specific task, but without using suid for this purpose, or otherwise elevate the process to privileged status, thereby reducing the potential for attacks while allowing the process to execute certain tasks. For example, if your application needs to open a privileged port, say, 80, instead of running the process as root, you can simply grant it the CAP_NET_BIND_SERVICE capability.

Let's consider a Go program named main.go:

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

This program serves an HTTP server on port 80 (a privileged port). Typically, we run it right after compilation:

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

However, since we are not granting root privileges, this code will produce an error when binding the port:

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

capsh (capability shell) is a tool that launches a shell with a specific set of capabilities.

In this case, as mentioned, instead of granting full root privileges, we can permit binding to privileged ports by providing the cap_net_bind_service capability along with all the others that already exist in the program. To do this, we can wrap our program in 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"

Let's break down this command a bit.

  • capsh — we use capsh as the shell.
  • —caps='cap_net_bind_service+eip cap_setpcap,cap_setuid,cap_setgid+ep' — since we need to switch users (we don't want to run with root privileges), we specify cap_net_bind_service and the ability to actually change the user ID from root to nobody, specifically cap_setuid and cap_setgid.
  • —keep=1 — we want to retain the set capabilities when switching from the root account.
  • —user="nobody" — the end user running the program will be nobody.
  • —addamb=cap_net_bind_service — we set the cleanup of related capabilities after switching from root mode.
  • — -c "./capabilities" — we simply run the program.

Related capabilities are a special kind of capabilities that are inherited by child programs when the current program executes them using execve(). Only capabilities permitted as related, or in other words, as environment capabilities, can be inherited.

Probablemente te estĂ©s preguntando quĂ© significa +eip despuĂ©s de indicar una capacidad en la opciĂłn —caps. Estos flags se utilizan para determinar que la capacidad:

-debe ser activada (p);

-estĂĄ disponible para ser aplicada (e);

-puede ser heredada por procesos secundarios (i).

Dado que queremos usar cap_net_bind_service, debemos hacerlo con el flag e. Luego lanzaremos una shell en el comando. Como resultado, se ejecutarå el archivo binario capabilities, y necesitamos marcarlo con el flag i. Finalmente, queremos que la capacidad esté activada (lo hicimos sin cambiar el UID) con p. Esto se ve como cap_net_bind_service+eip.

Puedes verificar el resultado con ss. Acortaremos un poco la salida para que quepa en la pĂĄgina, pero mostrarĂĄ el puerto relacionado y el identificador de usuario, diferentes de 0, en este 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

En este ejemplo usamos capsh, pero puedes escribir una shell usando libcap. Para mĂĄs informaciĂłn, consulta man 3 libcap.

Al escribir programas, los desarrolladores a menudo no saben de antemano todas las capacidades que necesitarĂĄ el programa durante su ejecuciĂłn; ademĂĄs, en nuevas versiones, estas capacidades pueden cambiar.

Para comprender mejor las capacidades de nuestro programa, podemos tomar la herramienta BCC capable, que establece un kprobe para la funciĂłn del nĂșcleo 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 lograr lo mismo usando bpftrace con un kprobe en una sola lĂ­nea en la funciĂłn del nĂșcleo 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

Esto generarå algo como lo siguiente, si las capacidades de nuestro programa estån activadas después del 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

La quinta columna son las capacidades que necesita el proceso, y dado que esta salida incluye eventos no auditados, vemos todas las verificaciones no auditadas y, finalmente, la capacidad requerida con el flag de auditorĂ­a (el Ășltimo en la salida), establecido en 1. La capacidad que nos interesa es CAP_NET_BIND_SERVICE, que se define como una constante en el cĂłdigo fuente del nĂșcleo en el archivo include/uapi/linux/ability.h con el 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">

MundĂ«sitĂ« shpesh pĂ«rdoren gjatĂ« ekzekutimit tĂ« kontejnerĂ«ve, si runC ose Docker, qĂ« tĂ« funksionojnĂ« nĂ« njĂ« mod tĂ« pa privilegjuar, por u janĂ« lejuar vetĂ«m ato mundĂ«si qĂ« janĂ« tĂ« nevojshme pĂ«r tĂ« ekzekutuar shumicĂ«n e aplikacioneve. Kur njĂ« aplikacion ka nevojĂ« pĂ«r mundĂ«si tĂ« caktuara, ato mund tĂ« sigurohen nĂ« Docker me —cap-add:

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

Kjo komandë do të ofrojë kontejnerit mundësinë CAP_NET_ADMIN, e cila do t'i lejojë të rregullojë lidhjen rrjetësore për të shtuar ndërfaqen dummy0.

Në seksionin e ardhshëm, do të tregojmë përdorimin e mundësive të tilla si filtrimi, por me një metodë tjetër që do të na lejojë të implementojmë filtrat e tanishëm programatikisht.

Seccomp

Seccomp do të thotë Siguria e Kompjuterave, është një nivel sigurie i zbatuar në bërthamën Linux, i cili lejon zhvilluesit të filtrojnë thirrjet e caktuara të sistemit. Edhe pse Seccomp është i krahasueshëm me mundësitë e Linux, aftësia e tij për të menaxhuar thirrje të caktuara të sistemit e bën atë shumë më të përshtatshëm në krahasim me to.

Seccomp dhe mundësitë e Linux nuk përjashtohen nga njëra-tjetra, ato shpesh përdoren së bashku për të përfituar nga të dy qasjet. Për shembull, mund të dëshironit t'i ofroni procesit mundësinë CAP_NET_ADMIN, por pa e lejuar atë të pranojë lidhje përmes soketeve, duke bllokuar thirrjet e sistemit accept dhe accept4.

Metoda e filtrimit Seccomp bazohen në filtrat BPF, që funksionojnë në mënyrën SECCOMP_MODE_FILTER, dhe filtrimi i thirrjeve të sistemit kryhet ashtu si për paketat.

Filtrat Seccomp ngarkohen duke përdorur prctl përmes operacionit PR_SET_SECCOMP. Këto filtra kanë formën e një programi BPF, i cili ekzekutohet për çdo paketë Seccomp, e paraqitur me strukturën seccomp_data. Kjo strukturë përmban arkitekturën e referencës, treguesit e instruksioneve të procesorit gjatë thirrjes së sistemit dhe maksimumi gjashtë argumente të thirrjes së sistemit, të shprehur si uint64.

Kështu duket struktura seccomp_data nga kodi burimor i bërthamës në skedarin linux/seccomp.h:

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

Siç duket nga kjo strukturë, ne mund të filtrojmë sipas thirrjes së sistemit, argumenteve të saj ose për kombinimin e tyre.

Pas pas marrjes së çdo pakete, filtri Seccomp duhet të kryejë përpunimin për të marrë një vendim përfundimtar dhe për t'i komunikuar bërthamës se çfarë të bëjë më pas. Vendimi përfundimtar shprehet me një nga vlerat e kthyer (kodet e statusit).

— SECCOMP_RET_KILL_PROCESS — mbyllja e tĂ« gjithĂ« procesit menjĂ«herĂ« pas filtrimit tĂ« thirrjes sĂ« sistemit, e cila pĂ«r kĂ«tĂ« arsye nuk ekzekutohet.

— SECCOMP_RET_KILL_THREAD — mbyllja e thread-it aktual menjĂ«herĂ« pas filtrimit tĂ« thirrjes sĂ« sistemit, e cila pĂ«r kĂ«tĂ« arsye nuk ekzekutohet.

— SECCOMP_RET_KILL — alias pĂ«r SECCOMP_RET_KILL_THREAD, e lĂ«nĂ« pĂ«r pĂ«rputhshmĂ«ri me versionet e mĂ«parshme.

— SECCOMP_RET_TRAP — thirrja e sistemit Ă«shtĂ« e ndaluar, dhe sinjali SIGSYS (Thirrje e Keqe e Sistem) dĂ«rgohet procesit qĂ« e thĂ«rret atĂ«.

— SECCOMP_RET_ERRNO — thirrja e sistemit nuk ekzekutohet, dhe njĂ« pjesĂ« e vlerĂ«s sĂ« kthyer nga filtri SECCOMP_RET_DATA dĂ«rgohet nĂ« hapĂ«sirĂ«n e pĂ«rdoruesit si vlerĂ« errno. NĂ« varĂ«si tĂ« shkakut tĂ« gabimit, kthehen vlera tĂ« ndryshme errno. Lista e numrave tĂ« gabimeve jepet nĂ« seksionin nĂ« vazhdim.

— SECCOMP_RET_TRACE — pĂ«rdoret pĂ«r tĂ« njoftuar gjurmuesin ptrace me — PTRACE_O_TRACESECCOMP pĂ«r tĂ« kapur kur ekzekutohet thirrja e sistemit, pĂ«r tĂ« parĂ« dhe kontrolluar kĂ«tĂ« proces. NĂ«se gjurmuesi nuk Ă«shtĂ« i lidhur, kthehet njĂ« gabim, errno vendoset nĂ« -ENOSYS, dhe thirrja e sistemit nuk ekzekutohet.

— SECCOMP_RET_LOG — thirrja e sistemit Ă«shtĂ« e lejuar dhe regjistrohet nĂ« log.

— SECCOMP_RET_ALLOW — thirrja e sistemit thjesht lejohet.

ptrace është një thirrje sistemi për të realizuar mekanizmat e gjurmimit në një proces, i quajtur tracee, me mundësinë për të vëzhguar dhe kontrolluar ekzekutimin e procesit. Programi i gjurmimit mund të ndikojë efektivisht në ekzekutimin dhe të ndryshojë regjistrat e memories të tracee. Në kontekstin e Seccomp, ptrace përdoret kur aktivizohet nga kodi i statusit SECCOMP_RET_TRACE, kështu që gjurmuesi mund të parandalojë ekzekutimin e thirrjes së sistemit dhe të zbatojë logjikën e tij.

Gabimet Seccomp

Herë pas here, ndërsa punoni me Seccomp, do të përballeni me gabime të ndryshme që identifikohen nga vlera e kthyer tip SECCOMP_RET_ERRNO. Për të raportuar një gabim, thirrja e sistemit seccomp do të kthejë -1 në vend të 0.

Gabimet e mëposhtme janë të mundshme:

— EACCESS — palĂ«s sĂ« thirrjes nuk i Ă«shtĂ« lejuar tĂ« bĂ«jĂ« thirrje nĂ« sistem. Kjo zakonisht ndodh pĂ«r shkak se nuk ka privilegje CAP_SYS_ADMIN ose qĂ« no_new_privs nuk Ă«shtĂ« vendosur me prctl (do tĂ« flasim pĂ«r kĂ«tĂ« mĂ« vonĂ«);

— EFAULT — argumentet e dĂ«rguara (args nĂ« strukturĂ«n seccomp_data) nuk kanĂ« njĂ« adresĂ« tĂ« vlefshme;

— EINVAL — kĂ«tu mund tĂ« ketĂ« katĂ«r arsye:

- operacioni i kërkuar është i panjohur ose nuk mbështetet nga bërthama në konfigurimin aktual;

- flamujt e specifikuar janë të pavlefshëm për operacionin e kërkuar;

- operacioni përfshin BPF_ABS, por ka probleme me offset-in e specifikuar, i cili mund të tejkalojë madhësinë e strukturës seccomp_data;

- numri i instrukcioneve të dërguara në filtrin tejkalon maksimumin;

— ENOMEM — nuk ka mjaft memorie pĂ«r tĂ« ekzekutuar programin;

— EOPNOTSUPP — operacioni tregoi se SECCOMP_GET_ACTION_AVAIL veprimi ishte nĂ« dispozicion, megjithatĂ« bĂ«rthama nuk mbĂ«shtet kthimin nĂ« argumente;

— ESRCH — ndodhi njĂ« problem gjatĂ« sinkronizimit tĂ« njĂ« tĂ« ftuari tjetĂ«r;

— ENOSYS — nuk ka njĂ« gjurmues tĂ« bashkangjitur me veprimin SECCOMP_RET_TRACE.

prctl është një thirrje në sistem që lejon programin e hapur të menaxhojë (vendosë dhe marrë) aspekte specifike të procesit, siç janë numri i rendit të bajtëve, emrat e flluskave, moda e llogaritjeve të mbrojtura (Seccomp), privilegjet, ngjarjet e Perf etj.

Seccomp mund të duket si një teknologji e kutisë së rërës, por nuk është kështu. Seccomp është një utilitar që i lejon përdoruesit të zhvillojnë një mekanizëm të kutisë së rërës. Tani le të shohim se si krijohen programet e ndërveprimit të përdoruesit duke përdorur një filtrues të thirrur drejtpërdrejt nga thirrja e sistemit Seccomp.

shembulli i filtrit BPF Seccomp

Këtu do të tregojmë se si të lidhim dy veprime të shqyrtuara më parë, domethënë:

— do tĂ« shkruajmĂ« njĂ« program Seccomp BPF qĂ« do tĂ« pĂ«rdoret si njĂ« filtrues me kode tĂ« ndryshme kthimi nĂ« pĂ«rputhje me vendimet e marrĂ«;

— do tĂ« ngarkojmĂ« filtrin duke pĂ«rdorur prctl.

Për të filluar, na duhen përkufizimet nga biblioteka standarde dhe bërthama 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>

Para se të përpiqemi të ekzekutojmë këtë shembull, duhet të sigurohemi që bërthama është kompozuar me CONFIG_SECCOMP dhe CONFIG_SECCOMP_FILTER të vendosura në y. Në një makinë pune, mund ta verifikojmë kështu:

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

Pjesa tjetër e kodit përbëhet nga funksioni install_filter, i ndarë në dy pjesë. Pjesa e parë përmban listën tonë të instrukcioneve për filtrimin 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),
  };

Instruksionet vendosen pĂ«rmes makroseve BPF_STMT dhe BPF_JUMP, tĂ« pĂ«rcaktuara nĂ« 파음in linux/filter.h.
Le të shkojmë përmes instrukcioneve.

— BPF_STMT(BPF_LD + BPF_W + BPF_ABS (offsetof(struct seccomp_data, arch))) — sistemi ngarkon dhe akumulohet me BPF_LD nĂ« formĂ«n e fjalĂ«s BPF_W, tĂ« dhĂ«nat e paketave ndodhen me njĂ« offset tĂ« fiksuar BPF_ABS.

— BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, arch, 0, 3) — verifikon duke pĂ«rdorur BPF_JEQ, nĂ«se vlera e arkitekturĂ«s nĂ« konstantĂ«n e akumulatorit BPF_K Ă«shtĂ« e barabartĂ« me arch. NĂ«se po, kalon me offset 0 nĂ« instrukcionin tjetĂ«r, pĂ«rndryshe, pĂ«r tĂ« shpallur njĂ« gabim, do tĂ« kalojĂ« me offset 3 (nĂ« kĂ«tĂ« rast), sepse arch nuk Ă«shtĂ« e njĂ«jtĂ«.

— BPF_STMT(BPF_LD + BPF_W + BPF_ABS (offsetof(struct seccomp_data, nr))) — ngarkon dhe akumulohet me BPF_LD nĂ« formĂ«n e fjalĂ«s BPF_W, e cila Ă«shtĂ« numri i thirrjes sĂ« sistemit, e cila ndodhet me njĂ« offset tĂ« fiksuar BPF_ABS.

— BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, nr, 0, 1) — krahaso numrin e thirrjes sĂ« sistemit me vlerĂ«n e variablit nr. NĂ«se ato janĂ« tĂ« barabartĂ«, kalon nĂ« instrukcionin tjetĂ«r dhe ndalon thirrjen e sistemit, pĂ«rndryshe lejon thirrjen e sistemit me SECCOMP_RET_ALLOW.

— BPF_STMT(BPF_RET + BPF_K, SECCOMP_RET_ERRNO | (error & SECCOMP_RET_DATA)) — pĂ«rfundon programin me BPF_RET dhe si rezultat jep gabimin SECCOMP_RET_ERRNO me numrin nga variabli err.

— BPF_STMT(BPF_RET + BPF_K, SECCOMP_RET_ALLOW) — pĂ«rfundon programin me BPF_RET dhe lejon ekzekutimin e thirrjes sĂ« sistemit me ndihmĂ«n e SECCOMP_RET_ALLOW.

SECCOMP — ËSHTË CBPF
Mund të jeni të interesuar pse, në vend të një objekti ELF të kompiluar ose një programi në C, të kompiluar me JIT, përdoret një listë instrukcionesh.

Ka dy arsye për këtë.

‱ SĂ« pari, Seccomp aplikon cBPF (BPF klasik), dhe jo eBPF, qĂ« do tĂ« thotĂ«: nuk ka regjistra, por vetĂ«m akumulator pĂ«r tĂ« ruajtur rezultatin e fundit tĂ« llogaritjeve, siç mund ta shihni nĂ« shembull.

‱ NĂ« radhĂ« tĂ« dytĂ«, Seccomp merr njĂ« tregues pĂ«r njĂ« masiv instrukcionesh BPF direkt dhe asgjĂ« tjetĂ«r. Makros qĂ« kemi pĂ«rdorur thjesht ndihmojnĂ« nĂ« specifikimin e kĂ«tyre instrukcioneve nĂ« njĂ« formĂ« mĂ« tĂ« rehatshme pĂ«r programuesit.

Nëse keni nevojë për ndihmë shtesë për të kuptuar këtë ndërtim, shqyrtoni pseudokodin që bën të njëjtën gjë:

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

Pasi të përcaktohet kodi i filtrit në strukturën socket_filter, duhet të përcaktoni sock_fprog, e cila përmban kodin dhe gjatësi e kalkuluar e filtrit. Kjo strukturë të dhënash është e nevojshme si një argument për shpalljen e punës së procesit në vazhdim:

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

Ngeli vetëm një gjë që duhet të bëjmë në funksionin install_filter, - të ngarkohet vetë programi! Për këtë ne përdorim prctl, duke marrë PR_SET_SECCOMP si opsion për të hyrë në modin e llogaritjeve të mbrojtura. Pastaj do të specifikojmë që modit të ngarkohet filtri me SECCOMP_MODE_FILTER, i cili ndodhet në variablën prog të tipit sock_fprog:

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

Më në fund, mund të përfitojmë nga funksioni ynë install_filter, por para kësaj duhet të angazhojmë prctl për të vendosur PR_SET_NO_NEW_PRIVS për ekzekutimin aktual dhe kështu të shmangim situatën kur proceset fëmijë marrin privilegje më të gjera se ato të prindërve. Kështu, ne mund të bëjmë thirrjet e mëposhtme prctl në funksionin install_filter, pa pasur nevojë për të drejtat root.

Tani mund të thërrasim funksionin install_filter. Do të bllokojmë të gjitha thirrjet sistemike write që i përkasin arkitekturës X86-64, dhe thjesht do të japim leje që bllokon të gjitha përpjekjet. Pas vendosjes së filtrit vazhdojmë ekzekutimin duke përdorur argumentin e parë:

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

Le të fillojmë. Për të kompaktuar programin tonë mund të përdorim ose clang ose gcc, në çdo rast kjo është thjesht një kompaktim i skedarit main.c pa opsione të veçanta:

clang main.c -o filter-write

Siç u pĂ«rmend, kemi bllokuar tĂ« gjitha shkrimet nĂ« program. PĂ«r ta verifikuar kĂ«tĂ«, na nevojitet njĂ« program qĂ« sheh diçka – ls duket tĂ« jetĂ« njĂ« kandidat i mirĂ«. Ja si vepron zakonisht:

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

Super! Ky është si duket përdorimi i programit tonë të shell: thjesht e kalojmë programin që duam të testojmë si argumentin e parë:

./filter-write "ls -la"

Pas ekzekutimit, ky program jep një dalje të plotë bosh. Megjithatë, mund të aplikojmë strace për të parë se çfarë po ndodh:

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

Rezultati i ekzekutimit Ă«shtĂ« shumĂ« i shkurtuar, por pjesa pĂ«rkatĂ«se tregon se shkrimet po bllokohen me gabimin EPERM — atĂ« tĂ« cilin e kemi konfiguruar. Kjo do tĂ« thotĂ« se programi nuk jep asnjĂ« dalje, sepse nuk mund tĂ« qaset nĂ« thirrjen sistemore write:

[pid 25099] write(2, "ls: ", 4) = -1 EPERM (Operacioni nuk lejohet)
[pid 25099] write(2, "gabim shkrimi", 11) = -1 EPERM (Operacioni nuk lejohet)
[pid 25099] write(2, "n", 1) = -1 EPERM (Operacioni nuk lejohet)

Tani e kuptoni se si funksionon Seccomp BPF, dhe keni një ide të mirë se çfarë mund të bëhet me të. Por a nuk do të ishte më mirë të arrinim të njëjtën gjë me eBPF në vend të cBPF, për të shfrytëzuar gjithë fuqinë e tij?

Kur mendojnë për programet e eBPF, shumica e njerëzve besojnë se thjesht i shkruajnë ato dhe i ngarkojnë me privilegje administrator. Megjithëse kjo përshtatje në përgjithësi është e saktë, bërthama zbaton një grup mekanizmash për të mbrojtur objektet e eBPF në nivele të ndryshme. Këta mekanizma quhen kapacitetet BPF LSM.

Kapacitetet BPF LSM

Për të ofruar një kontroll të pavarur nga arkitektura mbi ngjarjet sistemore, LSM zbaton konceptin e kapacitetit. Teknikisht, thirrja e kapacitetit është e ngjashme me thirrjen sistemore, megjithatë është e pavarur nga sistemi dhe e integruar me infrastrukturën. LSM ofron një koncept të ri, ku niveli i abstraksionit është i aftë të ndihmojë në shmangien e problemeve që dalin gjatë punës me thirrjet sistemore në arkitektura të ndryshme.

Në momentin e shkruarjes së librit, bërthama kishte shtatë kapacitete të lidhura me programet BPF, dhe SELinux ishte një LSM i vetëm i integruar që i zbatoi ato.

Kodi burimor i kapaciteteve është vendosur në pemën e bërthamës në skedarin 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);

Çdo njĂ«ra prej tyre do tĂ« thirret nĂ« etapa tĂ« ndryshme tĂ« ekzekutimit:

— security_bpf — bĂ«n verifikimin fillestar tĂ« thirrjeve sistemore tĂ« kryera BPF;

— security_bpf_map — kontrollon kur bĂ«rthamja kthen njĂ« descriptor skedari pĂ«r hartĂ«n;

— security_bpf_prog — kontrollon kur bĂ«rthamja kthen njĂ« descriptor skedari pĂ«r programin eBPF;

— security_bpf_map_alloc — kontrollon nĂ«se fusha e sigurisĂ« Ă«shtĂ« inicializuar brenda hartave BPF;

— security_bpf_map_free — kontrollon nĂ«se po kryhet pastrimi i fushĂ«s sĂ« sigurisĂ« brenda hartave BPF;

— security_bpf_prog_alloc — kontrollon nĂ«se fusha e sigurisĂ« po inicializohet brenda programeve BPF;

— security_bpf_prog_free — kontrollon nĂ«se fusha e sigurisĂ« po pastrohet brenda programeve BPF.

Tani, duke parë të gjitha këto, ne kuptojmë: ideja e kapësve LSM BPF është se ata mund të sigurojnë mbrojtjen e çdo objekti eBPF, duke garantuar që vetëm ata që kanë privilegje përkatëse mund të kryejnë operacione mbi hartat dhe programet.

CV

Siguria nuk Ă«shtĂ« diçka qĂ« mund ta implementoni nĂ« njĂ« mĂ«nyrĂ« universale pĂ«r çdo gjĂ« qĂ« dĂ«shironi tĂ« mbrojtni. ËshtĂ« e rĂ«ndĂ«sishme tĂ« keni mundĂ«sinĂ« pĂ«r tĂ« mbrojtur sistemet nĂ« nivele tĂ« ndryshme dhe nĂ« mĂ«nyra tĂ« ndryshme. Besoni apo jo, mĂ«nyra mĂ« e mirĂ« pĂ«r tĂ« siguruar sistemin Ă«shtĂ« tĂ« organizoni nivele tĂ« ndryshme mbrojtjeje nga pozita tĂ« ndryshme, nĂ« mĂ«nyrĂ« qĂ« zvogĂ«limi i sigurisĂ« nĂ« njĂ« nivel tĂ« mos lejojĂ« qasje nĂ« tĂ« gjithĂ« sistemin. Zhvilluesit e bĂ«rthamĂ«s kanĂ« bĂ«rĂ« njĂ« punĂ« tĂ« madhe duke na ofruar njĂ« grup tĂ« ndryshĂ«m nivelesh dhe pikash ndĂ«rveprimi. Ne shpresojmĂ« se ju kemi dhĂ«nĂ« njĂ« ide tĂ« mirĂ« se çfarĂ« janĂ« kĂ«to nivele dhe si t'i pĂ«rdorni programet BPF pĂ«r tĂ« punuar me to.

Për autorët

David Calavera është drejtori teknik në Netlify. Ai ka punuar në shërbimin e mbështetjes së Docker dhe ka marrë pjesë në zhvillimin e mjeteve Runc, Go dhe BCC, si dhe projekteve të tjera me burim të hapur. Ai është i njohur për punën e tij në projektet Docker dhe zhvillimin e ekosistemit të plugineve Docker. David e do shumë grafët e flakës dhe gjithmonë synon të optimizojë performancën.

Lorenzo Fontana Punon nĂ« njĂ« ekip zhvilluesish tĂ« softuerit me kod tĂ« hapur nĂ« Sysdig, ku kryesisht merret me Falco – njĂ« projekt i Fondacionit Cloud Native Computing, i cili siguron sigurinĂ« e mjedisit tĂ« ekzekutimit tĂ« kontejnerĂ«ve dhe zb discovery anomalia pĂ«rmes modulit tĂ« bĂ«rthamĂ«s dhe eBPF. Ai Ă«shtĂ« i apasionuar pas sistemeve tĂ« shpĂ«rndara, rrjeteve tĂ« pĂ«rcaktuara nga programi, bĂ«rthamĂ«s Linux dhe analizĂ«s sĂ« performancĂ«s.

» Më shumë informacion mbi librin mund të gjendet në faqen e botuesit
» Përmbajtja
» Pjesa

PĂ«r anĂ«tarĂ«t e Habra ka njĂ« zbritje prej 25% me kodin — Linux

Pas pagesës për versionin në letër të librit, libri elektronik dërgohet në e-mail.

Burimi: habr.com

Blini hosting tĂ« besueshĂ«m pĂ«r faqe interneti me mbrojtje nga DDoS, serverĂ« VPS VDS đŸ”„ Blini hosting tĂ« besueshĂ«m pĂ«r faqe interneti me mbrojtje nga DDoS, serverĂ« VPS VDS | ProHoster