Përshëndetje, banorë të Khabro! Makina virtuale BPF është një nga komponentët më të rëndësishëm të kernelit Linux. Përdorimi i duhur i tij do t'i lejojë inxhinierët e sistemit të gjejnë gabime dhe të zgjidhin edhe problemet më komplekse. Do të mësoni se si të shkruani programe që monitorojnë dhe modifikojnë sjelljen e kernelit, si të zbatoni në mënyrë të sigurt kodin për të monitoruar ngjarjet në kernel dhe shumë më tepër. David Calavera dhe Lorenzo Fontana do t'ju ndihmojnë të zhbllokoni fuqinë e BPF. Zgjeroni njohuritë tuaja për optimizimin e performancës, rrjetëzimin, sigurinë. - Përdorni BPF për të monitoruar dhe modifikuar sjelljen e kernelit Linux. - Injektoni kodin për të monitoruar në mënyrë të sigurt ngjarjet e kernelit pa pasur nevojë të ripërpiloni kernelin ose të rindizni sistemin. — Përdorni shembuj të përshtatshëm kodesh në C, Go ose Python. - Merrni kontrollin duke zotëruar ciklin jetësor të programit BPF.
Siguria e kernelit Linux, veçoritë e tij dhe Seccomp
BPF ofron një mënyrë të fuqishme për të zgjeruar kernelin pa sakrifikuar stabilitetin, sigurinë ose shpejtësinë. Për këtë arsye, zhvilluesit e kernelit menduan se do të ishte një ide e mirë të përdornin shkathtësinë e tij për të përmirësuar izolimin e procesit në Seccomp duke zbatuar filtra Seccomp të mbështetur nga programet BPF, të njohura gjithashtu si Seccomp BPF. Në këtë kapitull do të shpjegojmë se çfarë është Seccomp dhe si përdoret. Më pas do të mësoni se si të shkruani filtrat Seccomp duke përdorur programet BPF. Pas kësaj, ne do të shikojmë grepa të integruara BPF që përfshihen në kernel për modulet e sigurisë Linux.
Modulet e Sigurisë Linux (LSM) janë një kornizë që ofron një grup funksionesh që mund të përdoren për të zbatuar modele të ndryshme sigurie në një mënyrë të standardizuar. LSM mund të përdoret direkt në pemën e burimit të kernelit, si Apparmor, SELinux dhe Tomoyo.
Le të fillojmë duke diskutuar aftësitë e Linux.
Aftësitë
Thelbi i aftësive të Linux-it është se ju duhet t'i jepni një procesi të paprivilegjuar leje për të kryer një detyrë të caktuar, por pa përdorur suid për këtë qëllim, ose përndryshe ta bëni procesin të privilegjuar, duke zvogëluar mundësinë e sulmit dhe duke e lejuar procesin të kryejë detyra të caktuara. Për shembull, nëse aplikacioni juaj duhet të hapë një port të privilegjuar, le të themi 80, në vend që ta ekzekutojë procesin si rrënjë, thjesht mund t'i jepni atij aftësinë CAP_NET_BIND_SERVICE.
Konsideroni një program Go të quajtur 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 i privilegjuar). Zakonisht ne e ekzekutojmë atë menjëherë pas përpilimit:
$ go build -o capabilities main.go
$ ./capabilitiesSidoqoftë, meqenëse nuk po japim privilegje rrënjësore, ky kod do të shkaktojë një gabim kur lidh portin:
2019/04/25 23:17:06 listen tcp :80: bind: permission denied
exit status 1capsh (menaxher i guaskës) është një mjet që drejton një guaskë me një grup të caktuar aftësish.
Në këtë rast, siç është përmendur tashmë, në vend që të jepni të drejta të plota rrënjësore, mund të aktivizoni lidhjen e privilegjuar të portit duke ofruar aftësinë cap_net_bind_service së bashku me gjithçka tjetër që është tashmë në program. Për ta bërë këtë, ne mund ta mbyllim 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 ta kuptojmë pak këtë ekip.
- capsh - përdorni capsh si një guaskë.
- —caps='cap_net_bind_service+eip cap_setpcap,cap_setuid,cap_setgid+ep' - meqenëse duhet të ndryshojmë përdoruesin (nuk duam të ekzekutojmë si rrënjë), do të specifikojmë cap_net_bind_service dhe aftësinë për të ndryshuar në fakt ID-në e përdoruesit nga root për askush, përkatësisht cap_setuid dhe cap_setgid.
- —keep=1 — duam të ruajmë aftësitë e instaluara kur kalojmë nga llogaria rrënjë.
- —user=“askush” — përdoruesi përfundimtar që drejton programin do të jetë askush.
- —addamb=cap_net_bind_service — vendosni pastrimin e aftësive të lidhura pas kalimit nga modaliteti rrënjë.
- - -c "./kapabilities" - thjesht ekzekutoni programin.
Aftësitë e lidhura janë një lloj i veçantë i aftësive që trashëgohen nga programet fëmijë kur programi aktual i ekzekuton ato duke përdorur execve(). Mund të trashëgohen vetëm aftësitë që lejohen të shoqërohen, ose me fjalë të tjera, si aftësi mjedisore.
Ju ndoshta po pyesni se çfarë do të thotë +eip pasi të keni specifikuar aftësinë në opsionin --caps. Këta flamuj përdoren për të përcaktuar se aftësia:
-duhet të aktivizohet (p);
- në dispozicion për përdorim (e);
-mund të trashëgohet nga proceset e fëmijës (i).
Meqenëse duam të përdorim cap_net_bind_service, duhet ta bëjmë këtë me flamurin e. Pastaj ne do të lëshojmë guaskën në komandë. Kjo do të ekzekutojë aftësitë binare dhe ne duhet ta shënojmë atë me flamurin i. Së fundi, ne duam që veçoria të aktivizohet (e bëmë këtë pa ndryshuar UID) me p. Duket si cap_net_bind_service+eip.
Ju mund ta kontrolloni rezultatin duke përdorur ss. Le ta shkurtojmë pak daljen për t'u përshtatur në faqe, por do të tregojë portin e lidhur dhe ID-në e përdoruesit përveç 0, në këtë rast 65:
# 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 kemi përdorur capsh, por ju mund të shkruani një guaskë duke përdorur libcap. Për më shumë informacion, shihni man 3 libcap.
Kur shkruani programe, shpesh zhvilluesi nuk i njeh paraprakisht të gjitha veçoritë që i nevojiten programit në kohën e ekzekutimit; Për më tepër, këto veçori mund të ndryshojnë në versionet e reja.
Për të kuptuar më mirë aftësitë e programit tonë, mund të marrim mjetin e aftë BCC, i cili vendos kprobe për funksionin e kernelit 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 1Ne mund të arrijmë të njëjtën gjë duke përdorur bpftrace me një kprobe me një rresht në funksionin e kernelit 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ë nxjerrë diçka si më poshtë nëse aftësitë e programit tonë aktivizohen 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ë është aftësitë që i nevojiten procesit, dhe duke qenë se ky rezultat përfshin ngjarje jo audituese, ne shohim të gjitha kontrollet jo audituese dhe në fund aftësinë e kërkuar me flamurin e auditimit (i fundit në dalje) të vendosur në 1. Aftësia. njëra për të cilën ne jemi të interesuar është CAP_NET_BIND_SERVICE, është përcaktuar si një konstante në kodin burimor të kernelit 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">Aftësitë shpesh aktivizohen në kohën e ekzekutimit për kontejnerë të tillë si runC ose Docker për t'i lejuar ata të funksionojnë në modalitetin e paprivilegjuar, por atyre u lejohen vetëm aftësitë e nevojshme për të ekzekutuar shumicën e aplikacioneve. Kur një aplikacion kërkon aftësi të caktuara, Docker mund t'i sigurojë ato duke përdorur --cap-add:
docker run -it --rm --cap-add=NET_ADMIN ubuntu ip link add dummy0 type dummyKjo komandë do t'i japë kontejnerit aftësinë CAP_NET_ADMIN, duke e lejuar atë të konfigurojë një lidhje rrjeti për të shtuar ndërfaqen dummy0.
Seksioni tjetër tregon se si të përdorim veçori të tilla si filtrimi, por duke përdorur një teknikë të ndryshme që na lejon të implementojmë në mënyrë programore filtrat tanë.
Seccomp
Seccomp do të thotë Secure Computing dhe është një shtresë sigurie e zbatuar në kernelin Linux që lejon zhvilluesit të filtrojnë thirrje të caktuara të sistemit. Megjithëse Seccomp është i krahasueshëm në aftësi me Linux, aftësia e tij për të menaxhuar thirrje të caktuara të sistemit e bën atë shumë më fleksibël në krahasim me to.
Karakteristikat e Seccomp dhe Linux nuk janë reciprokisht ekskluzive dhe shpesh përdoren së bashku për të përfituar nga të dyja qasjet. Për shembull, mund të dëshironi t'i jepni një procesi aftësinë CAP_NET_ADMIN, por të mos e lejoni atë të pranojë lidhjet e prizës, duke bllokuar thirrjet e sistemit pranoni dhe pranoni4.
Metoda e filtrimit Seccomp bazohet në filtrat BPF që funksionojnë në modalitetin SECCOMP_MODE_FILTER dhe filtrimi i thirrjeve të sistemit kryhet në të njëjtën mënyrë si për paketat.
Filtrat Seccomp ngarkohen duke përdorur prctl nëpërmjet operacionit PR_SET_SECCOMP. Këta filtra marrin formën e një programi BPF që ekzekutohet për çdo paketë Seccomp të përfaqësuar nga struktura seccomp_data. Kjo strukturë përmban arkitekturën e referencës, një tregues për udhëzimet e procesorit në kohën e thirrjes së sistemit dhe një maksimum prej gjashtë argumenteve të thirrjes së sistemit, të shprehura si uint64.
Kjo është se si duket struktura seccomp_data nga kodi burimor i kernelit në skedarin linux/seccomp.h:
struct seccomp_data {
int nr;
__u32 arch;
__u64 instruction_pointer;
__u64 args[6];
};Siç mund ta shihni nga kjo strukturë, ne mund të filtrojmë nga thirrja e sistemit, argumentet e tij ose një kombinim i të dyjave.
Pas marrjes së çdo pakete Seccomp, filtri duhet të kryejë përpunim për të marrë një vendim përfundimtar dhe për t'i treguar kernelit se çfarë të bëjë më pas. Vendimi përfundimtar shprehet me një nga vlerat e kthimit (kodet e statusit).
- SECCOMP_RET_KILL_PROCESS - vret të gjithë procesin menjëherë pas filtrimit të një thirrjeje sistemi që nuk ekzekutohet për shkak të kësaj.
- SECCOMP_RET_KILL_THREAD - përfundon thread-in aktual menjëherë pas filtrimit të një thirrjeje sistemi që nuk është ekzekutuar për shkak të kësaj.
— SECCOMP_RET_KILL — pseudonimi për SECCOMP_RET_KILL_THREAD, i lënë për përputhshmëri të prapambetur.
- SECCOMP_RET_TRAP - thirrja e sistemit është e ndaluar dhe sinjali SIGSYS (Thirrje e keqe e sistemit) i dërgohet detyrës që e thërret.
- SECCOMP_RET_ERRNO - Thirrja e sistemit nuk ekzekutohet dhe një pjesë e vlerës së kthimit të filtrit SECCOMP_RET_DATA i kalohet hapësirës së përdoruesit si vlerë e gabuar. Në varësi të shkakut të gabimit, kthehen vlera të ndryshme gabimi. Një listë e numrave të gabimeve jepet në seksionin tjetër.
- SECCOMP_RET_TRACE - Përdoret për të njoftuar gjurmuesin ptrace duke përdorur - PTRACE_O_TRACESECCOMP për të përgjuar kur një thirrje sistemi ekzekutohet për të parë dhe kontrolluar atë proces. Nëse një gjurmues nuk është i lidhur, kthehet një gabim, errno vendoset në -ENOSYS dhe thirrja e sistemit nuk ekzekutohet.
- SECCOMP_RET_LOG - thirrja e sistemit zgjidhet dhe regjistrohet.
- SECCOMP_RET_ALLOW - thirrja e sistemit thjesht lejohet.
ptrace është një thirrje sistemi për të zbatuar mekanizmat gjurmues në një proces të quajtur tracee, me aftësinë për të monitoruar dhe kontrolluar ekzekutimin e procesit. Programi i gjurmimit mund të ndikojë efektivisht në ekzekutimin dhe të modifikojë regjistrat e memories së gjurmës. Në kontekstin 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 vet.
Gabimet Seccomp
Herë pas here, gjatë punës me Seccomp, do të hasni gabime të ndryshme, të cilat identifikohen nga një vlerë kthyese e tipit 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:
- AKSES - Telefonuesi nuk lejohet të kryejë një telefonatë sistemore. Kjo zakonisht ndodh sepse nuk ka privilegje CAP_SYS_ADMIN ose no_new_privs nuk është vendosur duke përdorur prctl (për këtë do të flasim më vonë);
— EFAULT — argumentet e kaluar (args në strukturën seccomp_data) nuk kanë një adresë të vlefshme;
— EINVAL — mund të ketë katër arsye këtu:
-operacioni i kërkuar është i panjohur ose nuk mbështetet nga kerneli në konfigurimin aktual;
-flamujt e specifikuar nuk janë të vlefshëm për operacionin e kërkuar;
-operacioni përfshin BPF_ABS, por ka probleme me kompensimin e specifikuar, i cili mund të tejkalojë madhësinë e strukturës seccomp_data;
-numri i instruksioneve të kaluara në filtër tejkalon maksimumin;
— ENOMEM — memorie e pamjaftueshme për të ekzekutuar programin;
- EOPNOTSUPP - operacioni tregoi se me SECCOMP_GET_ACTION_AVAIL veprimi ishte i disponueshëm, por kerneli nuk i mbështet kthimet në argumente;
— ESRCH — ka ndodhur një problem gjatë sinkronizimit të një transmetimi tjetër;
- ENOSYS - Nuk ka asnjë gjurmues të bashkangjitur me veprimin SECCOMP_RET_TRACE.
prctl është një thirrje sistemi që lejon një program të hapësirës së përdoruesit të manipulojë (caktojë dhe marrë) aspekte specifike të një procesi, të tilla si endianiteti i bajtit, emrat e temave, mënyra e sigurt e llogaritjes (Seccomp), privilegjet, ngjarjet Perf, etj.
Seccomp mund t'ju duket si një teknologji sandbox, por nuk është ashtu. Seccomp është një mjet që lejon përdoruesit të zhvillojnë një mekanizëm sandbox. Tani le të shohim se si krijohen programet e ndërveprimit me përdoruesit duke përdorur një filtër të thirrur drejtpërdrejt nga thirrja e sistemit Seccomp.
Shembull i filtrit BPF Seccomp
Këtu do të tregojmë se si të kombinojmë dy veprimet e diskutuara më parë, domethënë:
— do të shkruajmë një program Seccomp BPF, i cili do të përdoret si filtër me kode të ndryshme kthimi në varësi të vendimeve të marra;
— ngarkoni filtrin duke përdorur prctl.
Së pari ju nevojiten titujt nga biblioteka standarde dhe kerneli 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>Përpara se të provojmë këtë shembull, duhet të sigurohemi që kerneli është kompiluar me CONFIG_SECCOMP dhe CONFIG_SECCOMP_FILTER të vendosur në y. Në një makinë pune mund ta kontrolloni këtë si kjo:
cat /proc/config.gz| zcat | grep -i CONFIG_SECCOMP
Pjesa tjetër e kodit është një funksion install_filter me dy pjesë. Pjesa e parë përmban listën tonë të udhëzimeve të filtrimit të 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),
}; Udhëzimet vendosen duke përdorur makrot BPF_STMT dhe BPF_JUMP të përcaktuara në skedarin linux/filter.h.
Le të kalojmë nëpër udhëzimet.
- BPF_STMT(BPF_LD + BPF_W + BPF_ABS (offsetof(struct seccomp_data, arch))) - sistemi ngarkon dhe grumbullohet nga BPF_LD në formën e fjalës BPF_W, të dhënat e paketës janë të vendosura në një kompensim fiks BPF_ABS.
- BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, hark, 0, 3) - kontrollon duke përdorur BPF_JEQ nëse vlera e arkitekturës në konstantën e akumuluesit BPF_K është e barabartë me harkun. Nëse po, hidhet në offset 0 në udhëzimin tjetër, përndryshe hidhet në offset 3 (në këtë rast) për të hedhur një gabim sepse harku nuk përputhet.
- BPF_STMT(BPF_LD + BPF_W + BPF_ABS (offsetof(struct seccomp_data, nr))) - Ngarkohet dhe grumbullohet nga BPF_LD në formën e fjalës BPF_W, që është numri i thirrjes së sistemit që gjendet në kompensimin fiks të BPF_ABS.
— BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, nr, 0, 1) — krahason numrin e thirrjes së sistemit me vlerën e ndryshores nr. Nëse janë të barabarta, kalon te udhëzimi tjetër dhe çaktivizon thirrjen e sistemit, përndryshe lejon thirrjen e sistemit me SECCOMP_RET_ALLOW.
- BPF_STMT(BPF_RET + BPF_K, SECCOMP_RET_ERRNO | (gabim & SECCOMP_RET_DATA)) - përfundon programin me BPF_RET dhe si rezultat prodhon një gabim SECCOMP_RET_ERRNO me numrin nga ndryshorja err.
- BPF_STMT(BPF_RET + BPF_K, SECCOMP_RET_ALLOW) - përfundon programin me BPF_RET dhe lejon që thirrja e sistemit të ekzekutohet duke përdorur SECCOMP_RET_ALLOW.
SECCOMP ËSHTË CBPF
Ju mund të pyesni veten pse përdoret një listë udhëzimesh në vend të një objekti ELF të përpiluar ose një programi C të përpiluar nga JIT.Ka dy arsye për këtë.
• Së pari, Seccomp përdor cBPF (BPF klasik) dhe jo eBPF, që do të thotë: nuk ka regjistra, por vetëm një akumulator për të ruajtur rezultatin e fundit të llogaritjes, siç mund të shihet në shembull.
• Së dyti, Seccomp pranon një tregues drejt një grupi udhëzimesh BPF drejtpërdrejt dhe asgjë tjetër. Makrot që kemi përdorur thjesht ndihmojnë në specifikimin e këtyre udhëzimeve në një mënyrë të përshtatshme për programuesit.
Nëse keni nevojë për më shumë ndihmë për të kuptuar këtë asamble, merrni parasysh 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, duhet të përcaktoni një sock_fprog që përmban kodin dhe gjatësinë e llogaritur të filtrit. Kjo strukturë e të dhënave është e nevojshme si një argument për të deklaruar që procesi të ekzekutohet më vonë:
struct sock_fprog prog = {
.len = (unsigned short)(sizeof(filter) / sizeof(filter[0])),
.filter = filter,
};Mbetet vetëm një gjë për të bërë në funksionin install_filter - ngarkoni vetë programin! Për ta bërë këtë, ne përdorim prctl, duke marrë PR_SET_SECCOMP si një opsion për të hyrë në modalitetin e sigurt të llogaritjes. Pastaj i themi modalitetit që të ngarkojë filtrin duke përdorur SECCOMP_MODE_FILTER, i cili gjendet në variablin prog të llojit sock_fprog:
if (prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog)) {
perror("prctl(PR_SET_SECCOMP)");
return 1;
}
return 0;
}Së fundi, ne mund të përdorim funksionin tonë install_filter, por para kësaj duhet të përdorim prctl për të vendosur PR_SET_NO_NEW_PRIVS për ekzekutimin aktual dhe në këtë mënyrë të shmangim situatën kur proceset e fëmijëve marrin më shumë privilegje se prindërit e tyre. Me këtë, ne mund të bëjmë thirrjet e mëposhtme prctl në funksionin install_filter pa pasur të drejta rrënjësore.
Tani mund të thërrasim funksionin install_filter. Le të bllokojmë të gjitha thirrjet e sistemit të shkrimit që lidhen me arkitekturën X86-64 dhe thjesht të japim një leje që bllokon të gjitha përpjekjet. Pas instalimit të filtrit, ne 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ë përpiluar programin tonë, ne mund të përdorim ose clang ose gcc, sido që të jetë, është thjesht përpilimi i skedarit main.c pa opsione të veçanta:
clang main.c -o filter-writeSiç u përmend, ne kemi bllokuar të gjitha hyrjet në program. Për ta testuar këtë ju duhet një program që nxjerr diçka - ls duket si një kandidat i mirë. Kështu sillet ajo 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
E mrekullueshme! Ja se si duket përdorimi i programit tonë mbështjellës: Ne thjesht kalojmë programin që duam të testojmë si argumentin e parë:
./filter-write "ls -la"Kur ekzekutohet, ky program prodhon dalje krejtësisht bosh. Megjithatë, ne mund të përdorim strace për të parë se çfarë po ndodh:
strace -f ./filter-write "ls -la"Rezultati i punës është shkurtuar shumë, por pjesa përkatëse e tij tregon se të dhënat janë të bllokuara me gabimin EPERM - i njëjti që konfiguruam. Kjo do të thotë që programi nuk nxjerr asgjë sepse nuk mund të hyjë në thirrjen e sistemit të shkrimit:
[pid 25099] write(2, "ls: ", 4) = -1 EPERM (Operation not permitted)
[pid 25099] write(2, "write error", 11) = -1 EPERM (Operation not permitted)
[pid 25099] write(2, "n", 1) = -1 EPERM (Operation not permitted)Tani e kuptoni se si funksionon Seccomp BPF dhe keni një ide të mirë se çfarë mund të bëni me të. Por a nuk do të dëshironit të arrinit të njëjtën gjë me eBPF në vend të cBPF për të shfrytëzuar fuqinë e saj të plotë?
Kur mendojnë për programet eBPF, shumica e njerëzve mendojnë se thjesht i shkruajnë ato dhe i ngarkojnë me privilegje administratori. Ndërsa kjo deklaratë është përgjithësisht e vërtetë, kerneli zbaton një sërë mekanizmash për të mbrojtur objektet eBPF në nivele të ndryshme. Këta mekanizma quhen kurthe BPF LSM.
Kurthe BPF LSM
Për të ofruar monitorim të pavarur nga arkitektura e ngjarjeve të sistemit, LSM zbaton konceptin e kurtheve. Një telefonatë hook është teknikisht e ngjashme me një thirrje sistemi, por është e pavarur nga sistemi dhe e integruar me infrastrukturën. LSM ofron një koncept të ri në të cilin një shtresë abstraksioni mund të ndihmojë në shmangien e problemeve që hasen kur kemi të bëjmë me thirrjet e sistemit në arkitektura të ndryshme.
Në kohën e shkrimit, kerneli ka shtatë grepa të lidhur me programet BPF dhe SELinux është i vetmi LSM i integruar që i zbaton ato.
Kodi burimor për kurthe ndodhet në pemën e kernelit 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);Secila prej tyre do të thirret në faza të ndryshme të ekzekutimit:
— Security_bpf — kryen një kontroll fillestar të thirrjeve të ekzekutuara të sistemit BPF;
- security_bpf_map - kontrollon kur kerneli kthen një përshkrues skedari për hartën;
- security_bpf_prog - kontrollon kur kerneli kthen një përshkrues skedari për programin eBPF;
— security_bpf_map_alloc — kontrollon nëse fusha e sigurisë brenda hartave BPF është inicializuar;
- security_bpf_map_free - kontrollon nëse fusha e sigurisë është pastruar brenda hartave BPF;
— security_bpf_prog_alloc — kontrollon nëse fusha e sigurisë është inicializuar brenda programeve BPF;
- security_bpf_prog_free - kontrollon nëse fusha e sigurisë është pastruar brenda programeve BPF.
Tani, duke parë të gjitha këto, kuptojmë: ideja prapa përgjuesve LSM BPF është se ata mund të ofrojnë mbrojtje për çdo objekt eBPF, duke siguruar që vetëm ata me privilegjet e duhura mund të kryejnë operacione në karta dhe programe.
Përmbledhje
Siguria nuk është diçka që mund ta zbatoni në një mënyrë të vetme për gjithçka që dëshironi të mbroni. Është e rëndësishme të jeni në gjendje të mbroni sistemet në nivele të ndryshme dhe në mënyra të ndryshme. Besoni apo jo, mënyra më e mirë për të siguruar një sistem është organizimi i niveleve të ndryshme të mbrojtjes nga pozicione të ndryshme, në mënyrë që ulja e sigurisë së një niveli të mos lejojë akses në të gjithë sistemin. Zhvilluesit kryesorë kanë bërë një punë të shkëlqyeshme për të na dhënë një grup shtresash dhe pikash kontakti të ndryshme. Shpresojmë t'ju kemi dhënë një kuptim të mirë se çfarë janë shtresat dhe si të përdorni programet BPF për të punuar me to.
Rreth autorëve
David Calavera është CTO në Netlify. Ai punoi në mbështetjen e Docker dhe kontribuoi në zhvillimin e mjeteve Runc, Go dhe BCC, si dhe në projekte të tjera me burim të hapur. I njohur për punën e tij në projektet Docker dhe zhvillimin e ekosistemit të shtojcave Docker. David është shumë i apasionuar pas grafikëve të flakës dhe gjithmonë kërkon të optimizojë performancën.
Lorenzo Fontana punon në ekipin me burim të hapur në Sysdig, ku ai është i fokusuar kryesisht në Falco, një projekt i Cloud Native Computing Foundation që ofron sigurinë e kohës së funksionimit të kontejnerëve dhe zbulimin e anomalive përmes një moduli kernel dhe eBPF. Ai është i apasionuar pas sistemeve të shpërndara, rrjeteve të përcaktuara me softuer, kernelit Linux dhe analizës së performancës.
» Më shumë detaje rreth librit mund të gjenden në
»
»
Për Khabrozhiteley 25% zbritje duke përdorur kupon - Linux
Pas pagesës së versionit në letër të librit, një libër elektronik do të dërgohet me e-mail.
Burimi: www.habr.com
