PĂ«rshĂ«ndetje, HabraĂ«r! Virtual Machine BPF Ă«shtĂ« njĂ« nga komponentĂ«t mĂ« tĂ« rĂ«ndĂ«sishĂ«m tĂ« bĂ«rthamĂ«s Linux. PĂ«rdorimi i tij i saktĂ« do t'i ndihmojĂ« inxhinierĂ«t sistemikĂ« tĂ« gjejnĂ« gabime dhe tĂ« zgjidhin madje edhe problemet mĂ« tĂ« komplikuara. Ju do tĂ« mĂ«soni si tĂ« krijoni progarama qĂ« monitorojnĂ« dhe modifikojnĂ« sjelljen e bĂ«rthamĂ«s, do tĂ« jeni nĂ« gjendje tĂ« vendosni sigurt kodo pĂ«r monitorimin e ngjarjeve nĂ« bĂ«rthamĂ« dhe shumĂ« mĂ« tepĂ«r. David Calavera dhe Lorenzo Fontana do t'ju ndihmojnĂ« tĂ« zbuloni mundĂ«sitĂ« e BPF. Zgjeroni njohuritĂ« tuaja nĂ« optimizimin e performancĂ«s, rrjetave, sigurisĂ«. â PĂ«rdorni BPF pĂ«r tĂ« ndjekur dhe modifikuar sjelljen e bĂ«rthamĂ«s Linux. â Vendosni kod pĂ«r monitorimin e sigurt tĂ« ngjarjeve nĂ« bĂ«rthamĂ« â pa nevojĂ«n pĂ«r tĂ« rikonfiguroni bĂ«rthamĂ«n ose riçelĂ«si sistemit. â ShfrytĂ«zoni shembujt e lehtĂ« tĂ« kodit nĂ« C, Go ose Python. â Menaxhoni situatĂ«n duke pasur kontroll mbi 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 kompromentuar qëndrueshmërinë, sigurinë dhe shpejtësinë. Për këtë arsye, zhvilluesit e bërthamës menduan se do të ishte mirë të përdorej shumëllojshmëria e tij 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 ne do të tregojmë çfarë është Seccomp dhe si përdoret. Më pas do të mësoni se si të shkruani filtra Seccomp duke përdorur programet BPF. Pas kësaj, do të shqyrtojmë kurthe të integruara BPF që ekzistojnë në bërthamë për modulat e sigurisë në Linux.
Modulat e sigurisë në Linux (LSM) janë një platformë që ofron një grup funksionesh që mund të aplikohen për një implementim të standardizuar të modeleve të ndryshme të sigurisë. LSM mund të përdoret direkt në pemën e kodit burimor të bërthamës, për shembull Apparmor, SELinux dhe Tomoyo.
Le të fillojmë me diskutimin e mundësive të Linux.
Mundësitë
Thelbi i mundĂ«sive tĂ« Linux Ă«shtĂ« se ju duhet tĂ« ofroni njĂ« procesi tĂ« paprivilegjuar lejen pĂ«r tĂ« kryer njĂ« detyrĂ« tĂ« caktuar, por pa pĂ«rdorimin e suid pĂ«r kĂ«tĂ« qĂ«llim, ose pĂ«r ndonjĂ« mĂ«nyrĂ« tjetĂ«r tĂ« bĂ«ni procesin tĂ« priviligjuar, duke ulur mundĂ«sitĂ« e sulmeve dhe duke i dhĂ«nĂ« procesit mundĂ«sinĂ« pĂ«r tĂ« kryer detyra tĂ« caktuara. PĂ«r shembull, nĂ«se aplikacioni juaj ka nevojĂ« tĂ« hapĂ« njĂ« port privilegjuar, le t'i themi, 80, nĂ« vend qĂ« tĂ« drejtoni procesin nĂ«n emrin e root, mund tâi ofroni atij lejen CAP_NET_BIND_SERVICE.
Le të shqyrtojmë një program Go me emrin main.go:
package main
import (
"net/http"
"log"
)
func main() {
log.Fatalf("%v", http.ListenAndServe(":80", nil))
}Ky program shërben një server HTTP në portin 80 (ky është një port privilegjuar). Zakonisht, ne e ekzekutojmë atë menjëherë pas kompaktimit:
$ go build -o capabilities main.go
$ ./capabilitiesMegjithatë, pasi nuk i ofrojmë privilegjet e root, ky kod do të japë një gabim kur përpiqet të lidhet me portin:
2019/04/25 23:17:06 listen tcp :80: bind: permission denied
exit status 1capsh (mjeti i menaxhimit të shell) është një mjet që ekzekuton një shell me një grup të caktuar mundësish.
Në këtë rast, siç u tha më parë, në vend që të ofrojmë të drejtat e plota të root, mund të lejojmë lidhjen e porteve privilegjuar duke ofruar lejen cap_net_bind_service së bashku me të gjitha të tjerat që janë tashmë në program. Për këtë, ne mund ta mbështesim programin tonë 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"Le të shqyrtojmë pak këtë komandë.
- capsh â pĂ«rdorim capsh si shell.
- âcaps='cap_net_bind_service+eip cap_setpcap,cap_setuid,cap_setgid+ep' â pasi kemi nevojĂ« tĂ« ndryshojmĂ« pĂ«rdoruesin (nuk duam tĂ« ekzekutojmĂ« me privilegjet e root), do tĂ« specifikojmĂ« cap_net_bind_service dhe mundĂ«sinĂ« pĂ«r tĂ« ndryshuar identitetin e pĂ«rdoruesit nga root nĂ« nobody, pĂ«rkatĂ«sisht cap_setuid dhe cap_setgid.
- âkeep=1 â duam tĂ« mbajmĂ« mundĂ«sitĂ« e vendosura kur ndodhi kalimi nga llogaria root.
- âuser="nobody" â pĂ«rdoruesi pĂ«rfundimtar qĂ« ekzekuton programin do tĂ« jetĂ« nobody.
- âaddamb=cap_net_bind_service â vendosim pastrimin e mundĂ«sive tĂ« lidhura pas kalimit nga reja root.
- â -c "./capabilities" â thjesht ekzekutojmĂ« programin.
Mundësitë e lidhura janë një lloj i veçantë i mundësive që trashëgohen nga programet fëmijë kur programi aktual i ekzekuton ato përmes execve(). Mundësitë mund të trashëgohen vetëm nëse janë lejuar si të lidhura, ose, me fjalë të tjera, si mundësi ambienti.
Probabilisht jeni tĂ« interesuar çfarĂ« do tĂ« thotĂ« +eip pas specifikimit tĂ« mundĂ«sisĂ« nĂ« opsionin âcaps. KĂ«to flamuj pĂ«rdoren pĂ«r tĂ« pĂ«rcaktuar se si mundĂ«sia:
-duhet të activehet (p);
-janë të disponueshme për zbatim (e);
-mund të trashëgohet nga proceset fëmijë (i).
Pasi ne duam të përdorim cap_net_bind_service, duhet ta bëjmë këtë me flag e. Pastaj do të ekzekutojmë shell-in në komandë. Si rezultat, do të ekzekutohet skedari binar capabilities dhe na nevojitet ta shënojmë me flag i. Në fund, dëshirojmë që mundësia të aktivizohet (këtë e bëmë pa ndryshuar UID) përmes p. Kjo duket si cap_net_bind_service+eip.
Mund ta kontrolloni rezultatin me ss. Do ta shkurtëzojmë pak daljen që të bjerë në faqe, por do të tregojë portin e lidhur dhe identifikuesin e përdoruesit që është ndryshe nga 0, në këtë rast 65 534:
# ss -tulpn -e -H | cut -d' ' -f17-
128 *:80 *:*
users:(("capabilities",pid=30040,fd=3)) uid:65534 ino:11311579 sk:2c v6only:0Në këtë shembull, ne përdorëm capsh, por ju mund të shkruani një shell të bazuar në libcap. Për më shumë informacion, konsultohuni me man 3 libcap.
Gjatë shkrimit të programeve, zhvilluesi shpesh nuk e di paraprakisht të gjitha mundësitë që kërkon programi gjatë ekzekutimit; për më tepër, në versionet e reja, këto mundësi mund të ndryshojnë.
Për të kuptuar më mirë mundësitë e programit tonë, mund të marrim mjetin BCC capable, i cili vendos kprobe për funksionin e bërthamës 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 1Të njëjtën gjë e arrijmë duke përdorur bpftrace me një kprobe në një linjë në funksionin e bërthamës cap_capable:
bpftrace -e
'kprobe:cap_capable {
time("%H:%M:%S ");
printf("%-6d %-6d %-16s %-4d %dn", uid, pid, comm, arg2, arg3);
}'
| grep -i capabilitiesKjo do të tregojë diçka si më poshtë, nëse mundësitë e programit tonë janë aktivizuar pas 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 1Kolona e pestë përfaqëson mundësitë që nevojiten nga procesi dhe, pasi këto të dhëna përfshijnë gjithashtu ngjarje jo-audituese, ne shohim të gjitha verifikimet jo-audituese dhe, në fund, mundësinë e kërkuar me flag auditi (të fundit në dalje), e vendosur në 1. Mundësia që na intereson është CAP_NET_BIND_SERVICE, e cila definohet si konstantë në kodin burimor të bërthamës në skedarin include/uapi/linux/ability.h me identifikuesin 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 aktivizohen gjatĂ« ekzekutimit tĂ« kontejnerĂ«ve, si runC ose Docker, pĂ«r t'i lejuar ata tĂ« funksionojnĂ« nĂ« modin e papĂ«rshtatur, 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 disa mundĂ«si specifike, nĂ« Docker mund tĂ« sigurohen ato pĂ«rmes âcap-add:
docker run -it --rm --cap-add=NET_ADMIN ubuntu ip link add dummy0 type dummyKjo komandë do t'i ofrojë kontejnerit mundësinë CAP_NET_ADMIN, duke i lejuar asaj të konfigurojë lidhjen rrjet për të shtuar ndërfaqen dummy0.
Në seksionin në vijim tregohet përdorimi i mundësive si filtrimi, por me një metodë tjetër që do të na lejojë të implementojmë filtrat tanë në mënyrë programore.
Seccomp
Seccomp do të thotë Computim të Sigurt, është një nivel sigurie i implementuar në bërthamën Linux, i cili lejon zhvilluesit të filtrojnë thirrjet specifike të sistemit. Ndërsa Seccomp është në përputhje me mundësitë Linux, aftësia e tij për të menaxhuar thirrjet specifike të sistemit e bën atë shumë më fleksibël në krahasim me to.
Seccomp dhe mundësitë Linux nuk përjashtojnë njëra-tjetrën, ato përdoren shpesh së bashku, për të përfituar nga të dyja qasjet. Për shembull, mund të dëshironi t'i jepni procesit mundësinë CAP_NET_ADMIN, por mos t'i lejoni atij të pranojë lidhje përmes socket-it, duke bllokuar thirrjet e sistemit accept dhe accept4.
Metoda e filtrimit të Seccomp bazohet në filtrat BPF që operojnë në modin SECCOMP_MODE_FILTER, dhe filtrimi i thirrjeve të sistemit realizohet ashtu si për paketat.
Filtrat Seccomp ngarkohen duke përdorur prctl nëpërmjet operacionit PR_SET_SECCOMP. Këto filtra kanë formën e një programi BPF, i cili ekzekutohet për çdo paketë Seccomp, e paraqitur përmes strukturës seccomp_data. Kjo strukturë përmban arkitekturën referuese, treguesin e instrukcioneve të procesorit gjatë thirrjes së sistemit dhe maksimumi gjashtë argumente të thirrjes së sistemit, të shprehura si uint64.
Ja si 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ç shihet nga kjo strukturë, ne mund të filtrojmë sipas thirrjes së sistemit, argumenteve të saj ose kombinimeve të tyre.
Pas marrjes së secilës paketë Seccomp, filtri është i detyruar të kryejë përpunim për të marrë një vendim përfundimtar dhe për të njoftuar bërthamën se çfarë të bëjë më pas. Vendimi përfundimtar shprehet si një nga vlerat e kthimit (kodet e gjendjes).
â SECCOMP_RET_KILL_PROCESS â pĂ«rfundimi i gjithĂ« procesit menjĂ«herĂ« pas filtrimit tĂ« thirrjes sĂ« sistemit, e cila pĂ«r shkak tĂ« kĂ«saj nuk ekzekutohet.
â SECCOMP_RET_KILL_THREAD â pĂ«rfundimi i thread-it aktual menjĂ«herĂ« pas filtrimit tĂ« thirrjes sĂ« sistemit, e cila pĂ«r shkak tĂ« kĂ«saj nuk ekzekutohet.
â SECCOMP_RET_KILL â aliasi pĂ«r SECCOMP_RET_KILL_THREAD, lĂ«nĂ« pĂ«r qĂ«llime tĂ« prapavijĂ«s.
â SECCOMP_RET_TRAP â thirrja sistemore Ă«shtĂ« e ndaluar, dhe sinjali SIGSYS (Thirrje Sistemi e Keqe) dĂ«rgohet procesit qĂ« e thirri atĂ«.
â SECCOMP_RET_ERRNO â thirrja sistemore nuk ekzekutohet, dhe njĂ« pjesĂ« e vlerĂ«s kthyer nga filtri SECCOMP_RET_DATA kalon nĂ« hapĂ«sirĂ«n e pĂ«rdoruesit si vlera errno. Vlera tĂ« ndryshme errno kthehen nĂ« varĂ«si tĂ« arsyes sĂ« gabimit. Lista e numrave tĂ« gabimeve jepet nĂ« seksionin nĂ« vijim.
â SECCOMP_RET_TRACE â pĂ«rdoret pĂ«r tĂ« njoftuar gjurmuesin ptrace me â PTRACE_O_TRACESECCOMP pĂ«r tĂ« kapur, kur ekzekutohet thirrja sistemore, 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 sistemore nuk ekzekutohet.
â SECCOMP_RET_LOG â thirrja sistemore Ă«shtĂ« e lejuar dhe regjistrohet nĂ« log.
â SECCOMP_RET_ALLOW â thirrja sistemore lejohet thjesht.
ptrace është një thirrje sistemore për të zbatuar mekanizmat e gjurmimit në një proces, i quajtur tracee, me mundësi vëzhgimi dhe kontrolli mbi ekzekutimin e procesit. Programi i gjurmimit mund të ndikojë në mënyrë efikase në ekzekutimin dhe të ndryshojë regjistrat e memorizimit të tracee. Në kontekstin e Seccomp, ptrace përdoret kur nis kodin e gjendjes SECCOMP_RET_TRACE, prandaj gjurmuesi mund të parandalojë ekzekutimin e thirrjes sistemore dhe të implementojë logjikën e vet.
Gabimet Seccomp
Nga koha në kohë, kur punoni me Seccomp, do të hasni në gabime të ndryshme, të cilat identifikohen nga vlera e kthyer e tipit SECCOMP_RET_ERRNO. Për të raportuar një gabim, thirrja sistemore seccomp do të kthejë -1 në vend të 0.
Gabime të mundshme:
â EACCESS â palĂ«s qĂ« thĂ«rret nuk i lejohet tĂ« bĂ«jĂ« thirrjen sistemore. Kjo ndodh zakonisht sepse ajo nuk ka privilegjin CAP_SYS_ADMIN ose nuk Ă«shtĂ« vendosur no_new_privs me prctl (pĂ«r kĂ«tĂ« do tĂ« flasim mĂ« vonĂ«);
â EFAULT â argumentet e dĂ«rguara (args nĂ« strukturĂ«n seccomp_data) nuk kanĂ« njĂ« adresĂ« tĂ« vlefshme;
â EINVAL â kĂ«tu mund tĂ« jenĂ« katĂ«r arsyet:
- operacioni i kërkuar është i panjohur ose nuk mbështetet nga bërthama në konfigurimin aktual;
- flasët e caktuara janë të pavlefshme për operacionin e kërkuar;
- operacioni përfshin BPF_ABS, por ka probleme me offset-in e caktuar, i cili mund të kalojë 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 me SECCOMP_GET_ACTION_AVAIL veprimi ishte i disponueshĂ«m, megjithatĂ« bĂ«rthama nuk mbĂ«shtet kthimin nĂ« argumentet;
â ESRCH â kishte njĂ« problem me sinkronizimin e njĂ« thread-i tjetĂ«r;
â ENOSYS â nuk ka gjurmues tĂ« lidhur me veprimin SECCOMP_RET_TRACE.
prctl është një thirrje sistemore që i lejon programit të hapësirës së përdoruesit të menaxhojë (të vendosë dhe të marrë) aspekte specifike të procesit, si rendin e bajtëve, emrat e thread-eve, modalitetin e llogarive të mbrojtura (Seccomp), privilegjet, ngjarjet Perf etj.
Seccomp mund t'ju duket si një teknologji kori, por nuk është e tillë. Seccomp është një utilitar që lejon përdoruesit të krijojnë mekanizma kori. Tani le të shohim se si krijohen programet e ndërveprimit të përdoruesit duke përdorur filtrin, i thirrur drejtpërdrejt nga thirrja sistemore Seccomp.
Shembulli i filtrit BPF Seccomp
Këtu do të tregojmë se si të lidhim dy veprime të shqyrtuara më parë, dmth:
â do tĂ« shkruajmĂ« njĂ« program Seccomp BPF, qĂ« do tĂ« pĂ«rdoret si filtĂ«r me kode tĂ« ndryshme kthimi nĂ« varĂ«si tĂ« vendimeve tĂ« marra;
â do tĂ« ngarkohet filtri, duke pĂ«rdorur prctl.
Për të filluar duhen përfshirjet 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ë kompiluar me CONFIG_SECCOMP dhe CONFIG_SECCOMP_FILTER të vendosura në y. Në makinën tuaj të punës mund ta kontrolloni kështu:
cat /proc/config.gz| zcat | grep -i CONFIG_SECCOMP
Pjesa tjetër e kodit është një funksion install_filter, e përbërë nga dy pjesë. Pjesa e parë përmban listën tonë të instrukcioneve të filtrit 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 duke përdorur makros BPF_STMT dhe BPF_JUMP, të përcaktuara në skedarin linux/filter.h.
Të kalojmë përmes instruksioneve.
â 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 paketĂ«s vendosen me njĂ« offset tĂ« fiksur BPF_ABS.
â BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, arch, 0, 3) â kontrollon me 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 njĂ« offset 0 nĂ« instrukcionin e ardhshĂ«m, nĂ« tĂ« kundĂ«rt jep njĂ« gabim dhe kalon me offset 3 (nĂ« kĂ«tĂ« rast), sepse arch nuk pĂ«rputhet.
â BPF_STMT(BPF_LD + BPF_W + BPF_ABS (offsetof(struct seccomp_data, nr))) â ngarkon dhe akumulon me BPF_LD nĂ« formĂ«n e fjalĂ«s BPF_W, e cila Ă«shtĂ« numri i thirrjes sĂ« sistemit, e cila ndodhet nĂ« 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 variables nr. NĂ«se ato janĂ« tĂ« barabarta, kalon nĂ« instrukcionin e ardhshĂ«m 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 rezulton me njĂ« gabim SECCOMP_RET_ERRNO me numrin nga variabla err.
â BPF_STMT(BPF_RET + BPF_K, SECCOMP_RET_ALLOW) â pĂ«rfundon programin me BPF_RET dhe lejon ekzekutimin e thirrjes sĂ« sistemit me SECCOMP_RET_ALLOW.
SECCOMP â ĂSHTĂ CBPF
Ndoshta ju intereson pse përdoret një listë instrukcionesh në vend të një objekti ELF të kompiluar ose një programi C të kompiluar me JIT.Ka dy arsye për këtë.
⹠E para, Seccomp aplikon cBPF (BPF klasik), jo eBPF, që do të thotë: nuk ka regjistra, vetëm një akumulator për të ruajtur rezultatin e fundit të llogaritjeve, siç mund të shihet në shembullin.
⹠E dyta, Seccomp pranon një pointer në një array instrukcionesh BPF direkt dhe asgjë tjetër. Makros që ne përdorëm, thjesht ndihmojnë në specificimin e këtyre instrukcioneve në një formë më të përshtatshme për programuesit.
Nëse ju nevojitet ndihmë shtesë për të kuptuar këtë mbledhje, 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;Pas përcaktimit të kodit të filtrit në strukturën socket_filter, është e nevojshme të përcaktojmë sock_fprog, e cila përmban kodin dhe gjatësinë e llogaritur të filtrit. Kjo strukturë të dhënash është e nevojshme si argument për shpalljen e funksionit të procesit në vazhdim:
struct sock_fprog prog = {
.len = (unsigned short)(sizeof(filter) / sizeof(filter[0])),
.filter = filter,
};I mbetet vetĂ«m njĂ« gjĂ« tĂ« bĂ«jmĂ« nĂ« funksionin install_filter, â tĂ« ngarkojmĂ« vetĂ« programin! PĂ«r kĂ«tĂ« 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Ă« tĂ« ngarkohet filtri me SECCOMP_MODE_FILTER, i cili gjendet 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;
}Në fund, mund të përdorim funksionin tonë install_filter, por para kësaj duhet të aktivizojmë 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. Në këtë rast, mund të bëjmë thirrjet e mëposhtme prctl në funksionin install_filter pa pasur të drejta root.
Tani mund të thërrasim funksionin install_filter. Do të bllokojmë të gjitha thirrjet e sistemit write që i përkasin arkitekturës X86-64, dhe thjesht do të japim leje, duke bllokuar 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ë kompilim i skedarit main.c pa opsione të veçanta:
clang main.c -o filter-writeSiç u pĂ«rmend, ne bllokuam tĂ« gjitha shkrimet nĂ« program. PĂ«r ta kontrolluar kĂ«tĂ«, na nevojitet njĂ« program qĂ« prodhon diçka, â ls duket njĂ« kandidat i mirĂ«. Ja se si zakonisht sillet ai:
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
Mrekulli! Ja se si duket përdorimi i programit tonë të kapsulës: thjesht kalojmë programin që duam të testojmë si argumentin e parë:
./filter-write "ls -la"Pas ekzekutimit, ky program jep një dalje tërësisht bosh. Megjithatë, mund të aplikojmë strace për të parë se çfarë ndodh:
strace -f ./filter-write "ls -la"Rezultati Ă«shtĂ« shumĂ« i pĂ«rshkurtuar, por pjesa pĂ«rkatĂ«se tregon se shkrimet bllokohen me gabimin EPERM â tĂ« njĂ«jtin, qĂ« ne konfiguram. Kjo do tĂ« thotĂ« se programma nuk nxjerr asgjĂ«, sepse nuk mund tĂ« aksesojĂ« thirrjen e sistemit 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 tani e kuptoni se si funksionon Seccomp BPF, dhe keni një pamje të qartë se çfarë mund të bëni me të. Por a nuk do të donit të arrinit të njëjtin rezultat me eBPF në vend të cBPF, për të shfrytëzuar të gjitha fuqitë e tij?
Kur mendojnë për programet e eBPF, shumica e njerëzve besojnë se ata thjesht i shkruajnë dhe i ngarkojnë me privilegje administratorësh. Megjithatë, kjo është e vërtetë, bërthama zbatuan një grup mekanizmash për të mbrojtur objektet e eBPF në nivele të ndryshme. Këto mekanizma quhen kapakë BPF LSM.
Kapakët BPF LSM
Për të siguruar kontrollin e ngjarjeve sistemore të pavarur nga arhitektura, LSM implementon konceptin e kapakëve. Teknikisht, thirrja e kapakut është e ngjashme me një thirrje sistemore, por është e pavarur nga sistemi dhe integruar me infrastrukturën. LSM ofron një koncept të ri, ku niveli i abstraksionit mund të ndihmojë në shmangien e problemeve që ndodhin gjatë punës me thirrjet sistemore në arkitektura të ndryshme.
Në momentin e shkruajtjes së librit, bërthama kishte shtatë kapakë të lidhur me programet BPF, dhe SELinux ishte i vetmi LSM i integruar që i implementonte ata.
Kodi burimor i kapakëve është i 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Ă« faza tĂ« ndryshme tĂ« ekzekutimit:
â security_bpf â bĂ«n kontrollin fillestar tĂ« thirrjeve sistemore tĂ« BPF;
â security_bpf_map â kontrollon kur bĂ«rthama kthen njĂ« deshifrues skedari pĂ«r hartĂ«n;
â security_bpf_prog â kontrollon kur bĂ«rthama kthen njĂ« deshifrues skedari pĂ«r programin e eBPF;
â security_bpf_map_alloc â kontrollon nĂ«se fusha e sigurisĂ« brenda hartave tĂ« BPF Ă«shtĂ« inicializuar;
â security_bpf_map_free â kontrollon nĂ«se pastrohet fusha e sigurisĂ« brenda hartave tĂ« BPF;
â security_bpf_prog_alloc â kontrollon nĂ«se fusha e sigurisĂ« brenda programeve tĂ« BPF Ă«shtĂ« inicializuar;
â security_bpf_prog_free â kontrollon nĂ«se pastrohet fusha e sigurisĂ« brenda programeve tĂ« BPF.
Tani, duke parë të gjitha këto, ne kuptojmë: ideja e kapakëve LSM BPF qëndron se ata mund të ofrojnë mbrojtje për çdo objekt eBPF, duke garantuar që vetëm ata që kanë privilegjet e duhura të mund të kryejnë operacione mbi harta dhe programe.
Curriculum Vitae
Siguria nuk Ă«shtĂ« diçka qĂ« mund ta implementoni nĂ« njĂ« mĂ«nyrĂ« universale pĂ«r gjithçka qĂ« dĂ«shironi tĂ« mbrojni. ĂshtĂ« e rĂ«ndĂ«sishme tĂ« keni mundĂ«sinĂ« tĂ« mbroni sistemet nĂ« nivele tĂ« ndryshme dhe nĂ« mĂ«nyra tĂ« ndryshme. DĂ«shironi tĂ« besoni, dĂ«shironi jo, mĂ«nyra mĂ« e mirĂ« pĂ«r tĂ« mbrojtur njĂ« sistem Ă«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Ă« aksesin nĂ« tĂ« gjithĂ« sistemin. Zhvilluesit e bĂ«rthamĂ«s kanĂ« bĂ«rĂ« njĂ« punĂ« tĂ« madhe duke na ofruar njĂ« grup tĂ« ndryshĂ«m shtresash dhe pikash ndĂ«rveprimi. ShpresojmĂ« qĂ« ju kemi dhĂ«nĂ« njĂ« ide tĂ« mirĂ« se çfarĂ« janĂ« shtresat dhe si tĂ« pĂ«rdorni programet BPF pĂ«r tĂ« punuar me to.
Për autorët
David Calavera është drejtor teknik në Netlify. Ai ka punuar në shërbimin e mbështetjes për Docker dhe ka kontribuar në zhvillimin e mjeteve Runc, Go dhe BCC, si dhe projekte të tjera me kod të hapur. Ai njihet për punën e tij në projektet Docker dhe zhvillimin e ekosistemit të plug-in-ve Docker. David është i apasionuar pas grafikëve të flakës dhe gjithmonë përpiqet për optimizimin e performancës.
Lorenzo Fontana punon nĂ« ekipin e zhvilluesve tĂ« softuerit me kod tĂ« hapur nĂ« Sysdig, ku kryesisht merret me Falco â njĂ« projekt i Cloud Native Computing Foundation qĂ« siguron sigurinĂ« e mjediseve tĂ« ekzekutimit tĂ« kontejnerĂ«ve dhe zbulimin e anomalive pĂ«rmes modulit tĂ« bĂ«rthamĂ«s dhe eBPF. Ai Ă«shtĂ« i apasionuar pas sistemeve tĂ« shpĂ«rndara, rrjeteve tĂ« pĂ«rcaktuara me program, bĂ«rthamĂ«s Linux dhe analizĂ«s sĂ« performancĂ«s.
» Më shumë detaje për librin mund të gjenden në
»
»
PĂ«r pĂ«rdoruesit e Habr, zbritje 25% me kuponin â OpenVPN
Pas pagesës për versionin në paper të librit, një libër elektronik do të dërgohet në e-mail.
Burimi: habr.com
