Üdvözlet, Habr-lakók! A BPF virtuális gép a Linux kernel egyik legfontosabb összetevője. Megfelelő alkalmazásával a rendszertechnikák képesek lesznek a hibák azonosítására és a legbonyolultabb problémák megoldására. Megtanulja, hogyan készíthet olyan programokat, amelyek nyomon követik és módosítják a kernel viselkedését, biztonságosan telepítheti a kódot az események figyelésére a kernelben, és még sok mást. David Calavera és Lorenzo Fontana segítenek Önnek felfedezni a BPF lehetőségeit. Bővítse tudását a teljesítményoptimalizálás, a hálózatok és a biztonság terén. — Használja a BPF-t a Linux kernel viselkedésének nyomon követésére és módosítására. — Telepítse a kódot az események biztonságos figyelésére a kernelben — kernel újrakonfigurálása vagy a rendszer újraindítása nélkül. — Használja a C, Go vagy Python nyelvű kényelmes kódmintákat. — Uralkodjon a helyzeten a BPF program életciklusának ismeretével.
A Linux kernel biztonsága, lehetőségei és Seccomp
A BPF hatékony módot kínál a kernel bővítésére anélkül, hogy veszélyeztetné a stabilitást, a biztonságot és a sebességet. Ezért a kernel fejlesztői úgy gondolták, hogy érdemes lenne hasznosítani annak sokoldalúságát a folyamatok izolációjának javítására a Seccomp segítségével, Seccomp BPF néven ismert BPF programok által támogatott Seccomp szűrők megvalósításával. Ebben a fejezetben elmondjuk, mi az a Seccomp és hogyan alkalmazható. Aztán megtanulja, hogyan írhat Seccomp szűrőket BPF programok segítségével. Ezt követően megvizsgáljuk a BPF beépített csapdáit, amelyek a Linux biztonsági modulok számára vannak a kernelben.
A Linux biztonsági moduljai (LSM) egy platform, amely egy sor funkciót biztosít a különböző biztonsági modellek szabványosított megvalósításához. Az LSM közvetlenül felhasználható a kernel forráskód fájljában, mint például az Apparmor, SELinux és Tomoyo.
Kezdjük a Linux lehetőségeinek megvitatásával.
Lehetőségek
A Linux lehetőségeinek lényege, hogy engedélyt kell adni egy nem privilegizált folyamatnak egy adott feladat végrehajtására anélkül, hogy ezt suid használatával tennénk, vagy más módon privilegizálnánk a folyamatot, csökkentve ezzel a támadások lehetőségét, és lehetővé téve a folyamat számára a bizonyos feladatok végrehajtását. Például, ha az alkalmazásának meg kell nyitnia egy privilegizált portot, mondjuk a 80-asat, akkor ahelyett, hogy a folyamatot root néven indítaná, egyszerűen engedélyezheti a CAP_NET_BIND_SERVICE lehetőséget.
Nézzük a main.go nevű Go programot:
package main
import (
"net/http"
"log"
)
func main() {
log.Fatalf("%v", http.ListenAndServe(":80", nil))
}Ez a program HTTP szervert üzemel az 80-as porton (ez privilegizált port). Általában közvetlenül a fordítás után indítjuk el:
$ go build -o capabilities main.go
$ ./capabilitiesAzonban mivel nem adunk root jogosultságokat, ez a kód hibát ad a port lekötésekor:
2019/04/25 23:17:06 listen tcp :80: bind: permission denied
exit status 1A capsh (uborkázó shell) egy olyan eszköz, amely egy adott lehetőségek halmazával indít el egy shellt.
Ebben az esetben, mint már említettük, ahelyett, hogy teljes root jogosultságot adnánk, lehetőséget adhatunk a privilegizált portok lekötésére a cap_net_bind_service engedélyezésével, a programban már meglévő összes többi mellett. Ehhez bezárhatjuk a programunkat a capsh parancsba:
# 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"Nézzük meg ezt a parancsot részletesebben.
- capsh — használjuk a capsh-t shellként.
- —caps='cap_net_bind_service+eip cap_setpcap,cap_setuid,cap_setgid+ep' — mivel felhasználót kell váltanunk (nem akarjuk root jogosultságokkal futtatni), megadjuk a cap_net_bind_service-t és a felhasználó azonosítójának tényleges megváltoztatásához szükséges cap_setuid és cap_setgid lehetőségeket.
- —keep=1 — meg akarjuk tartani a beállított lehetőségeket, amikor átváltunk a root fiókról.
- —user="nobody" — az alkalmazást futtató végfelhasználó nobody lesz.
- —addamb=cap_net_bind_service — beállítjuk az összes kapcsolódó lehetőség tisztítását a root módból való áttérést követően.
- — -c "./capabilities" — egyszerűen futtatjuk a programot.
A kapcsolódó lehetőségek speciális típusú lehetőségek, amelyeket a gyermekprogramok öröklődnek, amikor a jelenlegi program végrehajtja őket az execve() segítségével. Csak a kapcsolódóként engedélyezett lehetőségek öröklődhetnek, más szavakkal, mint környezeti lehetőségek.
Valószínűleg kíváncsi arra, mit jelent a +eip a -caps opcióban megadott képesség után. Ezek a flag-ek a képesség meghatározására szolgálnak:
-aktiválva kell lennie (p);
-alkalmazható (e);
-örökölhető a gyerekfolyamatok által (i).
Mivel a cap_net_bind_service képességet szeretnénk használni, ezt az e flag-gel kell végrehajtanunk. Ezután shell-t indítunk a parancsban. Ennek eredményeként elindul egy bináris fájl, amelyet i flag-gel kell megjelölnünk. Végül szeretnénk, ha a képesség aktiválva lenne (ezt UID megváltoztatása nélkül tettük) p segítségével. Ez így néz ki: cap_net_bind_service+eip.
Ellenőrizheti az eredményt az ss segítségével. Egy kicsit csökkentjük a kimenetet, hogy elférjen az oldalon, de meg fogja mutatni a kapcsolt portot és a felhasználói azonosítót, amely eltér a 0-tól, ebben az esetben 65 534:
# ss -tulpn -e -H | cut -d' ' -f17-
128 *:80 *:*
users:(("capabilities",pid=30040,fd=3)) uid:65534 ino:11311579 sk:2c v6only:0Ebben a példában a capsh-t használtuk, de a libcap segítségével is írhat shell-t. További információkért lásd a man 3 libcap dokumentációt.
A programok írása során a fejlesztő gyakran nem tudja előre, hogy a programnak futásidőben mely képességekre lesz szüksége; sőt, az új verziókban ezek a képességek megváltozhatnak.
A programunk képességeinek jobb megértéséhez használhatjuk a BCC capable eszközt, amely kprobe-ot állít be a cap_capable kernel függvényhez:
/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 1Ugyanezt elérhetjük a bpftrace segítségével egy egysoros kprobe-val a cap_capable kernel függvényben:
bpftrace -e
'kprobe:cap_capable {
time("%H:%M:%S ");
printf("%-6d %-6d %-16s %-4d %dn", uid, pid, comm, arg2, arg3);
}'
| grep -i capabilitiesEz körülbelül a következőt fogja kiírni, ha a programunk képességei aktiválva vannak kprobe után:
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 1Az ötödik oszlop a folyamat által igényelt képességeket tartalmazza. Mivel ezek a kimenetek nem audit eseményeket is tartalmaznak, látjuk az összes nem audit ellenőrzést, és végül a szükséges képességet, amelynek audit flag-je (az utolsó a kimenetben) 1-re van állítva. A minket érdeklő képesség a CAP_NET_BIND_SERVICE, amelyet állandóként definiálnak a kernel forráskódjában az include/uapi/linux/ability.h fájlban az azonosítóval 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">A lehetőségek gyakran felhasználásra kerülnek konténerek végrehajtása során, mint például a runC vagy a Docker, hogy azok nem privilegizált módban működjenek, miközben csak a legtöbb alkalmazás futtatásához szükséges lehetőségekhez férnek hozzá. Amikor egy alkalmazásnak bizonyos lehetőségekre van szüksége, azt a Dockerben —cap-add segítségével lehet biztosítani:
docker run -it --rm --cap-add=NET_ADMIN ubuntu ip link add dummy0 type dummyEz a parancs lehetővé teszi a konténer számára a CAP_NET_ADMIN jogosultságot, ami lehetővé teszi számára, hogy beállítson egy hálózati hivatkozást a dummy0 interfész hozzáadásához.
A következő szakaszban a lehetőségek, mint például a szűrés, egy másik módszerrel kerülnek bemutatásra, amely lehetővé teszi számunkra, hogy programozottan valósítsuk meg a saját szűrőinket.
Seccomp
A Seccomp a Secure Computing rövidítése, ami egy biztonsági szint, amelyet a Linux kernel valósít meg, és lehetővé teszi a fejlesztők számára, hogy szűrjék a bizonyos rendszerhívásokat. Bár a Seccomp összehasonlítható a Linux lehetőségeivel, a bizonyos rendszerhívások irányítására való képessége sokkal rugalmasabbá teszi őt azoknál.
A Seccomp és a Linux lehetőségek nem zárják ki egymást, gyakran együtt használják őket, hogy mindkét megközelítés előnyeit élvezhessük. Például előfordulhat, hogy szeretnénk egy folyamatnak lehetővé tenni a CAP_NET_ADMIN jogosultságot, de nem akarjuk engedélyezni számára a kapcsolatok fogadását egy socketen, blokkolva az accept és accept4 rendszerhívásokat.
A Seccomp szűrési módja a BPF szűrőkön alapul, amelyek SECCOMP_MODE_FILTER üzemmódban működnek, a rendszerhívások szűrése pedig ugyanúgy történik, mint a csomagok esetében.
A Seccomp szűrők prctl használatával kerülnek betöltésre a PR_SET_SECCOMP művelettel. Ezek a szűrők BPF program formájában vannak, amely minden egyes Seccomp csomag során fut, a seccomp_data struktúra segítségével. Ez a struktúra tartalmaz egy referencia architektúrát, a processzor utasításainak mutatóját a rendszerhívás idején és legfeljebb hat rendszerhívás argumentumot, amelyek uint64 formában vannak kifejezve.
Íme, hogyan néz ki a seccomp_data struktúra a kernel forráskódjában a linux/seccomp.h fájlban:
struct seccomp_data {
int nr;
__u32 arch;
__u64 instruction_pointer;
__u64 args[6];
};Ahogy ebből a struktúrából látható, szűrhetünk a rendszerhívás, annak argumentumai vagy azok kombinációja alapján.
A Seccomp filter must process each received packet to make a final decision and inform the kernel of the next steps. The final decision is expressed through one of the returned values (status codes).
— SECCOMP_RET_KILL_PROCESS — terminating the entire process immediately after filtering the system call, which is thus not executed.
— SECCOMP_RET_KILL_THREAD — terminating the current thread immediately after filtering the system call, which is thus not executed.
— SECCOMP_RET_KILL — an alias for SECCOMP_RET_KILL_THREAD, preserved for backward compatibility.
— SECCOMP_RET_TRAP — the system call is prohibited, and the SIGSYS (Bad System Call) signal is sent to the executing task.
— SECCOMP_RET_ERRNO — the system call is not executed, and part of the return value of the SECCOMP_RET_DATA filter is passed to user space as the errno value. Different errno values are returned depending on the error cause. A list of error numbers is provided in the next section.
— SECCOMP_RET_TRACE — used to notify the ptrace tracer with — PTRACE_O_TRACESECCOMP to intercept when a system call is executed to see and control this process. If the tracer is not attached, an error is returned, errno is set to -ENOSYS, and the system call is not executed.
— SECCOMP_RET_LOG — the system call is allowed and logged.
— SECCOMP_RET_ALLOW — the system call is simply permitted.
ptrace is a system call for implementing tracing mechanisms in a process called tracee, allowing observation and control over the execution of the process. The tracing program can effectively influence execution and modify the tracee's memory registers. In the context of Seccomp, ptrace is used when code state SECCOMP_RET_TRACE is triggered, thus the tracer can prevent the execution of the system call and implement its own logic.
Seccomp Errors
Occasionally, while working with Seccomp, you will encounter various errors identified by the return value of the type SECCOMP_RET_ERRNO. To report an error, the seccomp system call will return -1 instead of 0.
The following errors may occur:
— EACCESS — a hívó fél számára tilos a rendszerhívás végrehajtása. Ez általában azért történik, mert nincs CAP_SYS_ADMIN jogosultsága, vagy a no_new_privs nincs beállítva prctl segítségével (erről később beszélünk);
— EFAULT — a megadott argumentumok (args a seccomp_data struktúrában) nem rendelkeznek érvényes címmel;
— EINVAL — itt négy ok lehet:
- a kért művelet ismeretlen vagy a kernel jelenlegi konfigurációjában nem támogatott;
- a megadott flag-ek érvénytelenek a kért művelethez;
- a művelet tartalmazza a BPF_ABS-t, de probléma van a megadott eltolással, amely meghaladhatja a seccomp_data struktúra méretét;
- a szűrőbe átadott utasítások száma meghaladja a maximumot;
— ENOMEM — nincs elég memória a program végrehajtásához;
— EOPNOTSUPP — a művelet jelezte, hogy a SECCOMP_GET_ACTION_AVAIL akció elérhető volt, de a kernel nem támogatja az argumentumok visszatérését;
— ESRCH — probléma merült fel egy másik szál szinkronizálásakor;
— ENOSYS — nincs nyomkövető csatolva a SECCOMP_RET_TRACE akcióhoz.
A prctl egy rendszerhívás, amely lehetővé teszi a felhasználói térbeli program számára, hogy kezelje (beállítsa és lekérdezze) a folyamat egyes aspektusait, például a bájtok sorrendjét, a szálak nevét, a védett számítási módot (Seccomp), a jogosultságokat, a Perf eseményeket stb.
A Seccomp technológia homokozónak tűnhet, de nem az. A Seccomp egy olyan segédprogram, amely lehetővé teszi a felhasználók számára, hogy fejlesszenek egy homokozó mechanizmust. Most nézzük meg, hogyan készülnek a felhasználói interakciókat megvalósító programok közvetlenül a Seccomp rendszerhíváson keresztül elérhető szűrő használatával.
Seccomp BPF szűrő példa
Itt megmutatjuk, hogyan kapcsoljuk össze a korábban tárgyalt két akciót, nevezetesen:
— írunk egy Seccomp BPF programot, amely szűrőként lesz alkalmazva a különböző visszatérő kódokkal a meghozott döntések függvényében;
— feltöltjük a szűrőt a prctl használatával.
Először a Linux standard könyvtárából és kerneléből származó fejlécekre van szükségünk:
#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>Mielőtt megpróbálnánk végrehajtani ezt a példát, meg kell győződnünk arról, hogy a kernel CONFIG_SECCOMP és CONFIG_SECCOMP_FILTER beállításokkal van lefordítva y értékre. A munkahelyi gépen ezt így ellenőrizhetjük:
cat /proc/config.gz| zcat | grep -i CONFIG_SECCOMP
A kód többi része az install_filter függvényt tartalmazza, amely két részből áll. Az első rész tartalmazza a BPF szűrési utasításainkat:
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),
}; Az utasításokat a linux/filter.h fájlban definiált BPF_STMT és BPF_JUMP makrók segítségével állítjuk be.
Nézzük át az utasításokat.
— BPF_STMT(BPF_LD + BPF_W + BPF_ABS (offsetof(struct seccomp_data, arch))) — a rendszer BPF_LD segítségével betölti és felhalmozza a BPF_W formájában egy szót, amelynek az adatai egy rögzített eltolású BPF_ABS.
— BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, arch, 0, 3) — ellenőrzi a BPF_JEQ alkalmazásával, hogy a processzor architektúrája az akkumulátor BPF_K állandójában megegyezik-e az arch értékével. Ha igen, 0 eltolással a következő utasításra ugrik, különben hiba üzenetet ad, 3 eltolással ugorva (ebben az esetben), mivel az arch nem egyezik.
— BPF_STMT(BPF_LD + BPF_W + BPF_ABS (offsetof(struct seccomp_data, nr))) — a rendszer betölti és felhalmozza a BPF_LD segítségével egy szót, amely a rendszerhívás számát tartalmazza, egy rögzített eltolású BPF_ABS.
— BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, nr, 0, 1) — összehasonlítja a rendszerhívás számát az nr változó értékével. Ha megegyeznek, a következő utasításra ugrik, és megakadályozza a rendszerhívást, egyébként engedélyezi a rendszerhívást SECCOMP_RET_ALLOW.
— BPF_STMT(BPF_RET + BPF_K, SECCOMP_RET_ERRNO | (error & SECCOMP_RET_DATA)) — a programot a BPF_RET segítségével befejezi, és az eredményül kapott SECCOMP_RET_ERRNO hibát ad meg az err változóból származó számmal.
— BPF_STMT(BPF_RET + BPF_K, SECCOMP_RET_ALLOW) — a programot a BPF_RET segítségével befejezi, és engedélyezi a rendszerhívás végrehajtását SECCOMP_RET_ALLOW alkalmazásával.
A SECCOMP – CBPF
Valószínűleg kíváncsi arra, hogy miért nem egy ELF objektum vagy egy C program, amely JIT-tel van lefordítva, hanem egy utasításlistát használunk.Ennek két oka van.
• Először is, a Seccomp a cBPF-t (klasszikus BPF) alkalmazza, nem pedig az eBPF-t, ami azt jelenti: nincs regiszter, csak egy akkumulátor a legutolsó számítási eredmény tárolására, amit a példában is meg lehet figyelni.
• Másodszor, a Seccomp közvetlenül egy BPF utasításokat tartalmazó tömbre mutat, és semmi mást. Az általam használt makrók csupán segítenek a programozók számára kényelmes formában megadni ezeket az utasításokat.
Ha további segítségre van szüksége ennek a buildnek a megértéséhez, tekintse meg a pseudo-kódot, amely ugyanezt csinálja:
if (arch != AUDIT_ARCH_X86_64) {
return SECCOMP_RET_ALLOW;
}
if (nr == __NR_write) {
return SECCOMP_RET_ERRNO;
}
return SECCOMP_RET_ALLOW;A socket_filter struktúrában a szűrő kódjának meghatározása után definiálni kell a sock_fprog-ot, amely tartalmazza a kódot és a számított szűrőhosszt. Ez a struktúra szükséges, mint argumentum a folyamat későbbi bejelentéséhez:
struct sock_fprog prog = {
.len = (unsigned short)(sizeof(filter) / sizeof(filter[0])),
.filter = filter,
};Csak egy dolgot kell még megtenni az install_filter függvényben: betölteni magát a programot! Ehhez a prctl-t használjuk, beállítva a PR_SET_SECCOMP-ot opcióként, hogy belépjünk a védett számítási módba. Ezután megadjuk a szűrő betöltésének módját az SECCOMP_MODE_FILTER segítségével, amely a prog típusú sock_fprog változóban található:
if (prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog)) {
perror("prctl(PR_SET_SECCOMP)");
return 1;
}
return 0;
}Végül használhatjuk az install_filter függvényünket, de előtte aktiválnunk kell a prctl-t, hogy beállítsuk a PR_SET_NO_NEW_PRIVS-t a jelenlegi végrehajtásra, ezáltal elkerülve, hogy a gyermek folyamatok szélesebb jogosultságokat kapjanak, mint a szülők. Közben a következő prctl hívásokat tehetjük az install_filter függvényben, root jogok nélkül.
Most felhívhatjuk az install_filter függvényt. Blokkoljuk az összes write rendszerhívást, amelyek az X86-64 architektúrára vonatkoznak, és egyszerűen engedélyezünk egy blokkoló engedélyt, amely megakadályozza az összes kísérletet. A szűrő beállítása után folytatjuk a végrehajtást az első argumentummal:
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]);
}Kezdjük. A programunk fordításához használhatunk clang-ot vagy gcc-t, mindkét esetben ez egyszerűen a main.c fájl fordítása speciális opciók nélkül:
clang main.c -o filter-writeAhogy megjegyeztük, blokkoltuk az összes írást a programban. Ennek ellenőrzésére szükség van egy programra, amely valamit kiír, – az ls jó jelöltnek tűnik. Így viselkedik általában:
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
Kiváló! Íme, hogy néz ki a programunk parancssori használata: egyszerűen átadjuk a tesztelni kívánt programot első argumentumként:
./filter-write "ls -la"A program végrehajtása után teljesen üres kimenetet ad. Ennek ellenére alkalmazhatunk strace-t, hogy lássuk, mi történik:
strace -f ./filter-write "ls -la"A kimenet szignifikánsan rövidített, de a releváns rész azt mutatja, hogy a bejegyzések blokkolva vannak EPERM hibával — pontosan azzal, amit beállítottunk. Ez azt jelenti, hogy a program semmilyen kimenetet nem ad, mivel nem tud hozzáférni a write rendszerhíváshoz:
[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)Most már érted, hogyan működik a Seccomp BPF, és jól tudod, mit lehet vele kezdeni. De vajon nem lenne jobb ugyanezt elérni eBPF használatával a cBPF helyett, hogy kihasználhassuk annak teljes erejét?
Az eBPF programok kapcsán a legtöbben azt gondolják, hogy egyszerűen csak megírják őket és rendszergazdai jogosultságokkal töltik fel. Bár ez az állítás alapvetően igaz, a kernel egy sor mechanizmust valósít meg az eBPF objektumok védelmére különböző szinteken. Ezeket a mechanizmusokat BPF LSM horgonyoknak nevezik.
BPF LSM horgonyok
A rendszeresemények architektúrától független ellenőrzésének biztosítása érdekében az LSM a horgonyok koncepcióját valósítja meg. Technikai szempontból a horgonyhívás hasonló a rendszerhíváshoz, bármilyen rendszertől független, és integrálva van az infrastruktúrába. Az LSM egy új koncepciót kínál, amely lehetővé teszi az ablak szintje közötti problémák elkerülését a különböző architektúrák rendszerhívásainak kezelése során.
A könyv írásának időpontjában a kernelnek hét horgonya volt a BPF programokhoz, és a SELinux volt az egyetlen beépített LSM, amely megvalósította őket.
A horgonyok forráskódja a kernel fájlstruktúrájában található az include/linux/security.h fájlban:
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);Mindkettő a végrehajtás különböző szakaszaiban lesz aktiválva:
— security_bpf — végzi el a BPF-végrehajtott rendszerhívások kezdeti ellenőrzését;
— security_bpf_map — ellenőrzi, amikor a kernel fájl-azonosítót ad vissza a térképre;
— security_bpf_prog — ellenőrzi, amikor a kernel fájl-azonosítót ad vissza az eBPF programhoz;
— security_bpf_map_alloc — ellenőrzi, hogy a biztonsági mező inicializálva van-e a BPF térképekben;
— security_bpf_map_free — ellenőrzi, hogy a biztonsági mező törlése végrehajtásra kerül-e a BPF térképekben;
— security_bpf_prog_alloc — ellenőrzi, hogy a biztonsági mező inicializálva van-e a BPF programokban;
— security_bpf_prog_free — ellenőrzi, hogy a biztonsági mező törlése végrehajtásra kerül-e a BPF programokban.
Most, hogy mindezt látjuk, megértjük: az LSM BPF interceptoraik lényege, hogy képesek védelmet nyújtani az eBPF objektumok számára, biztosítva, hogy csak azok végezhetnek műveleteket a térképeken és programokon, akik rendelkeznek a megfelelő jogosultságokkal.
Összefoglaló
A biztonságot nem lehet univerzális módon bevezetni mindenféle védelemhez, amit meg akarsz védeni. Fontos, hogy képes legyél a rendszereket különböző szinteken és eltérő módon megvédeni. Akárhogy is nézed, a legjobb módja a rendszer biztonságának garantálása, ha különböző védelmi szinteket szervezel eltérő pozíciókból, hogy az egyik szint biztonságának csökkentése ne biztosítson hozzáférést az egész rendszerhez. A kernel fejlesztői nagy munkát végeztek, hogy különböző réteget és interakciós pontokat biztosítsanak számunkra. Reméljük, hogy adtunk egy jó képet arról, mik a rétegek és hogyan használhatók a BPF programok a velük való munkához.
A szerzőkről
David Calavera a Netlify műszaki igazgatója. Dolgozott a Docker támogatási szolgáltatásában, és részt vett a Runc, Go és BCC eszközök, valamint más nyílt forráskódú projektek fejlesztésében. Ismert a Docker projektekhez és a Docker plugin ökoszisztéma fejlesztéséhez való hozzájárulásáról. David nagyon szereti a flame grafikonokat és mindig a teljesítményoptimalizálásra törekszik.
Lorenzo Fontana A Sysdig szoftverfejlesztő csapatában dolgozik, ahol főleg a Falco projekttel foglalkozik - a Cloud Native Computing Foundation projektje, amely a konténeres környezetek biztonságát és az anomáliák észlelését biztosítja a kernel modulen és az eBPF segítségével. Érdeklik a disztribúciós rendszerek, a szoftver által vezérelt hálózatok, a Linux kernel és a teljesítményelemzés.
» Részletesebben a könyvről a
»
»
A Habr lakói számára 25% kedvezmény a kuponnal — Linux
A papíralapú könyv kifizetése után elektronikus könyv kerül kiküldésre e-mailben.
Forrás: habr.com
