Könyv "BPF Linux Monitoring"

Könyv "BPF Linux Monitoring"Sziasztok Khabro lakosai! A BPF virtuális gép a Linux kernel egyik legfontosabb összetevője. Megfelelő használata lehetővé teszi a rendszermérnökök számára, hogy megtalálják a hibákat és megoldják a legbonyolultabb problémákat is. Megtanulhatja, hogyan kell olyan programokat írni, amelyek figyelik és módosítják a kernel viselkedését, hogyan lehet biztonságosan implementálni kódot a kernel eseményeinek megfigyelésére és még sok mást. David Calavera és Lorenzo Fontana segítenek felszabadítani a BPF erejét. Bővítse tudását a teljesítményoptimalizálás, a hálózatépítés és a biztonság terén. - Használja a BPF-et a Linux kernel viselkedésének figyelésére és módosítására. - Kód beszúrása a kernelesemények biztonságos figyeléséhez anélkül, hogy újra kellene fordítani a rendszermagot vagy újra kellene indítani a rendszert. — Használjon kényelmes kódpéldákat C, Go vagy Python nyelven. - Vegye át az irányítást a BPF program életciklusának birtokában.

Linux Kernel Security, szolgáltatásai és a Seccomp

A BPF hatékony módot biztosít a kernel kiterjesztésére a stabilitás, a biztonság vagy a sebesség feláldozása nélkül. Emiatt a kernel fejlesztői úgy gondolták, hogy jó ötlet lenne felhasználni a sokoldalúságát a folyamatok elkülönítésének javítására a Seccomp-ban a BPF programok által támogatott Seccomp szűrők, más néven Seccomp BPF megvalósításával. Ebben a fejezetben elmagyarázzuk, mi az a Seccomp, és hogyan használják. Ezután megtanulja, hogyan kell Seccomp-szűrőket írni BPF-programokkal. Ezt követően megvizsgáljuk a Linux biztonsági modulok kernelében található beépített BPF hookokat.

A Linux Security Modules (LSM) egy olyan keretrendszer, amely olyan funkciókat biztosít, amelyek segítségével különböző biztonsági modelleket lehet szabványosított módon megvalósítani. Az LSM közvetlenül használható a kernel forrásfájában, mint például az Apparmor, a SELinux és a Tomoyo.

Kezdjük a Linux képességeinek megvitatásával.

Képességek

A Linux képességeinek lényege, hogy egy privilegizálatlan folyamatnak engedélyt kell adni egy bizonyos feladat elvégzésére, de anélkül, hogy erre a célra suid-ot használnánk, vagy más módon privilegizálttá kell tenni a folyamatot, csökkentve ezzel a támadás lehetőségét, és lehetővé kell tenni a folyamat számára bizonyos feladatok elvégzését. Például, ha az alkalmazásnak meg kell nyitnia egy privilegizált portot, mondjuk a 80-ast, ahelyett, hogy rootként futtatná a folyamatot, egyszerűen megadhatja neki a CAP_NET_BIND_SERVICE képességet.

Tekintsünk egy main.go nevű Go programot:

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

Ez a program egy HTTP-kiszolgálót szolgál ki a 80-as porton (ez egy privilegizált port). Általában a fordítás után azonnal lefuttatjuk:

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

Mivel azonban nem adunk root jogosultságokat, ez a kód hibát jelez a port kötésekor:

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

A capsh (shell manager) egy olyan eszköz, amely meghatározott képességekkel rendelkező parancsértelmezőt futtat.

Ebben az esetben, mint már említettük, a teljes root jogok megadása helyett engedélyezheti a privilegizált port-összerendelést a cap_net_bind_service képesség és minden más, ami már a programban van, megadásával. Ehhez a programunkat capsh-ban csatolhatjuk:

# 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"

Értsük meg egy kicsit ezt a csapatot.

  • capsh - használja a capsh-t shellként.
  • —caps='cap_net_bind_service+eip cap_setpcap,cap_setuid,cap_setgid+ep' - mivel meg kell változtatnunk a felhasználót (nem akarunk rootként futni), megadjuk a cap_net_bind_service paramétert és a felhasználói azonosító módosításának lehetőségét root senkinek, nevezetesen cap_setuid és cap_setgid.
  • —keep=1 — meg akarjuk tartani a telepített képességeket, amikor a root fiókról váltunk.
  • —user=“senki” — a programot futtató végfelhasználó senki lesz.
  • —addamb=cap_net_bind_service — a kapcsolódó képességek törlésének beállítása a gyökér módról való váltás után.
  • - -c "./capabilities" - csak futtassa a programot.

A kapcsolt képességek olyan speciális képességek, amelyeket a gyermekprogramok örökölnek, amikor az aktuális program végrehajtja őket az execve() segítségével. Csak azok a képességek örökölhetők, amelyek társíthatók, vagy más szóval, mint környezeti képességek.

Valószínűleg kíváncsi vagy, mit jelent az +eip, miután megadtad a képességet a --caps opcióban. Ezeket a jelzőket használják annak meghatározására, hogy a képesség:

-aktiválni kell (p);

- használható (e);

-örökölhetik a gyermekfolyamatok (i).

Mivel a cap_net_bind_service-t szeretnénk használni, ezt az e jelzővel kell megtennünk. Ezután elindítjuk a parancsértelmezőt. Ez binárisan futtatja a képességeket, és meg kell jelölnünk az i jelzővel. Végül azt akarjuk, hogy a funkció engedélyezve legyen (ezt az UID megváltoztatása nélkül tettük) a p. Úgy néz ki, mint cap_net_bind_service+eip.

Az eredményt az ss segítségével ellenőrizheti. Rövidítsük le egy kicsit a kimenetet, hogy elférjen az oldalon, de a hozzá tartozó port és felhasználói azonosító nem 0, jelen esetben 65 jelenik meg:

# ss -tulpn -e -H | cut -d' ' -f17-
128 *:80 *:*
users:(("capabilities",pid=30040,fd=3)) uid:65534 ino:11311579 sk:2c v6only:0

Ebben a példában capsh-t használtunk, de írhat shellt a libcap használatával. További információkért lásd man 3 libcap.

A programok írásakor a fejlesztő gyakran nem ismeri előre az összes funkciót, amelyre a programnak futás közben szüksége van; Ezenkívül ezek a funkciók az új verziókban módosulhatnak.

A programunk képességeinek jobb megértéséhez használhatjuk a BCC-képes eszközt, amely beállítja a cap_capable kernelfüggvény kprobe-ját:

/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

Ugyanezt érhetjük el a bpftrace egysoros kprobe használatával a cap_capable kernelfüggvényben:

bpftrace -e 
   'kprobe:cap_capable {
      time("%H:%M:%S ");
      printf("%-6d %-6d %-16s %-4d %dn", uid, pid, comm, arg2, arg3);
    }' 
    | grep -i capabilities

Ez valami ilyesmit ad ki, ha programunk képességei a kprobe után engedélyezve vannak:

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

Az ötödik oszlop azokat a képességeket tartalmazza, amelyekre a folyamatnak szüksége van, és mivel ez a kimenet nem audit eseményeket is tartalmaz, az összes nem auditálási ellenőrzést, és végül a szükséges képességet látjuk, ha az audit flag (utolsó a kimenetben) értéke 1. Képesség. ami érdekel minket, a CAP_NET_BIND_SERVICE, konstansként van definiálva a kernel forráskódjában az include/uapi/linux/ability.h fájlban 10-es azonosítóval:

/* 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 képességek gyakran futásidőben engedélyezve vannak az olyan tárolóknál, mint a runC vagy a Docker, hogy lehetővé tegyék számukra a privilegizált módban való futtatást, de csak a legtöbb alkalmazás futtatásához szükséges képességek engedélyezettek. Ha egy alkalmazás bizonyos képességeket igényel, a Docker ezeket a --cap-add paranccsal tudja biztosítani:

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

Ez a parancs megadja a tárolónak a CAP_NET_ADMIN képességet, amely lehetővé teszi egy hálózati kapcsolat konfigurálását a dummy0 interfész hozzáadásához.

A következő rész bemutatja, hogyan használhatunk olyan funkciókat, mint a szűrés, de más technikával, amely lehetővé teszi saját szűrőink programozását.

Seccomp

A Seccomp a Secure Computing rövidítése, és a Linux kernelben megvalósított biztonsági réteg, amely lehetővé teszi a fejlesztők számára bizonyos rendszerhívások szűrését. Bár a Seccomp képességeit tekintve a Linuxhoz hasonlítható, bizonyos rendszerhívások kezelésére való képessége sokkal rugalmasabbá teszi azokhoz képest.

A Seccomp és a Linux szolgáltatásai nem zárják ki egymást, és gyakran együtt használják őket mindkét megközelítés előnyeinek kihasználása érdekében. Előfordulhat például, hogy egy folyamatnak megadja a CAP_NET_ADMIN képességet, de nem engedheti meg, hogy fogadja a socket kapcsolatokat, blokkolva ezzel az elfogadás és elfogadás4 rendszerhívásokat.

A Seccomp szűrési módszer a SECCOMP_MODE_FILTER módban működő BPF szűrőkön alapul, és a rendszerhívás szűrése ugyanúgy történik, mint a csomagoknál.

A Seccomp szűrők betöltése a prctl használatával történik a PR_SET_SECCOMP műveleten keresztül. Ezek a szűrők egy BPF-program formáját öltik, amely a seccomp_data struktúra által képviselt minden Seccomp csomagra lefut. Ez a struktúra tartalmazza a referencia architektúrát, egy mutatót a processzor utasításaira a rendszerhívás idején, és legfeljebb hat rendszerhívási argumentumot, uint64-ként kifejezve.

Így néz ki a seccomp_data szerkezet a kernel forráskódjából a linux/seccomp.h fájlban:

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

Amint ebből a struktúrából látható, szűrhetünk a rendszerhívás, annak argumentumai vagy a kettő kombinációja alapján.

Az egyes Seccomp-csomagok fogadása után a szűrőnek feldolgozást kell végrehajtania, hogy meghozza a végső döntést, és megmondja a kernelnek, hogy mit kell tennie. A végső döntést az egyik visszatérési érték (állapotkód) fejezi ki.

- SECCOMP_RET_KILL_PROCESS - az emiatt nem végrehajtott rendszerhívás szűrése után azonnal leállítja a teljes folyamatot.

- SECCOMP_RET_KILL_THREAD - azonnal leállítja az aktuális szálat az emiatt nem végrehajtott rendszerhívás szűrése után.

— SECCOMP_RET_KILL — SECCOMP_RET_KILL_THREAD álneve, balra a visszafelé kompatibilitás miatt.

- SECCOMP_RET_TRAP - a rendszerhívás tiltva van, és a SIGSYS (Bad System Call) jelet küldi az azt hívó feladatnak.

- SECCOMP_RET_ERRNO - A rendszerhívás nem kerül végrehajtásra, és a SECCOMP_RET_DATA szűrő visszatérési értékének egy része errno értékként kerül átadásra a felhasználói térbe. A hiba okától függően a rendszer különböző errno értékeket ad vissza. A hibaszámok listája a következő részben található.

- SECCOMP_RET_TRACE - A ptrace nyomkövető értesítésére szolgál a - PTRACE_O_TRACESECCOMP használatával, hogy elfogja, ha rendszerhívást hajtanak végre a folyamat megtekintéséhez és vezérléséhez. Ha a nyomkövető nincs csatlakoztatva, hibaüzenet jelenik meg, az errno értéke -ENOSYS, és a rendszerhívás nem kerül végrehajtásra.

- SECCOMP_RET_LOG - a rendszerhívás feloldva és naplózásra kerül.

- SECCOMP_RET_ALLOW - a rendszerhívás egyszerűen engedélyezett.

A ptrace egy olyan rendszerhívás, amely nyomkövetési mechanizmusokat valósít meg a tracee nevű folyamatban, és lehetővé teszi a folyamat végrehajtásának figyelését és vezérlését. A nyomkövetési program hatékonyan befolyásolhatja a végrehajtást és módosíthatja a nyomkövetési regisztereket. A Seccomp kontextusban a ptrace akkor használatos, amikor a SECCOMP_RET_TRACE állapotkód váltja ki, így a nyomkövető megakadályozhatja a rendszerhívás végrehajtását, és megvalósíthatja a saját logikáját.

Seccomp hibák

A Seccomp használata közben időről időre különféle hibákkal találkozhat, amelyeket a SECCOMP_RET_ERRNO típusú visszatérési érték azonosít. Hiba bejelentéséhez a seccomp rendszerhívás 1 helyett -0-et ad vissza.

A következő hibák lehetségesek:

- EACCESS - A hívó fél nem kezdeményezhet rendszerhívást. Ez általában azért történik, mert nem rendelkezik CAP_SYS_ADMIN jogosultságokkal, vagy a no_new_privs nincs beállítva a prctl használatával (erről később beszélünk);

— EFAULT — az átadott argumentumok (a seccomp_data struktúrában lévő argumentumok) nem rendelkeznek érvényes címmel;

— EINVAL — ennek négy oka lehet:

-a kért művelet ismeretlen, vagy a kernel nem támogatja az aktuális konfigurációban;

-a megadott jelzők nem érvényesek a kért műveletre;

-művelet tartalmazza a BPF_ABS-t, de problémák vannak a megadott offsettel, ami meghaladhatja a seccomp_data struktúra méretét;

-a szűrőnek á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 művelettel elérhető volt a művelet, de a kernel nem támogatja az argumentumokban való visszatérést;

— ESRCH — probléma lépett fel egy másik adatfolyam szinkronizálása közben;

- ENOSYS - Nincs nyomkövető csatolva a SECCOMP_RET_TRACE művelethez.

A prctl egy olyan rendszerhívás, amely lehetővé teszi a felhasználói térprogram számára a folyamat bizonyos aspektusainak manipulálását (beállítását és lekérését), például a bájtvégződést, a szálneveket, a biztonságos számítási módot (Seccomp), a jogosultságokat, a Perf eseményeket stb.

A Seccomp homokozó technológiának tűnhet számodra, de nem az. A Seccomp egy olyan segédprogram, amely lehetővé teszi a felhasználók számára, hogy homokozó mechanizmust fejlesszenek ki. Most nézzük meg, hogyan jönnek létre a felhasználói interakciós programok a Seccomp rendszerhívása által közvetlenül meghívott szűrő használatával.

Példa BPF Seccomp szűrőre

Itt megmutatjuk, hogyan lehet kombinálni a korábban tárgyalt két műveletet, nevezetesen:

— írunk egy Seccomp BPF programot, amely a meghozott döntések függvényében különböző visszatérési kódokkal szűrőként fog működni;

— töltse be a szűrőt a prctl segítségével.

Először fejlécekre van szüksége a szabványos könyvtárból és a Linux kernelből:

#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 ezt a példát, meg kell győződnünk arról, hogy a kernel úgy van lefordítva, hogy a CONFIG_SECCOMP és a CONFIG_SECCOMP_FILTER y-ra van állítva. Egy működő gépen ezt így ellenőrizheti:

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

A kód többi része egy kétrészes install_filter függvény. Az első rész tartalmazza a BPF szűrési utasításaink listáját:

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ások beállítása a linux/filter.h fájlban definiált BPF_STMT és BPF_JUMP makrók használatával történik.
Menjünk végig az utasításokon.

- BPF_STMT(BPF_LD + BPF_W + BPF_ABS (offsetof(struct seccomp_data, arch))) - a rendszer a BPF_LD-ből tölti be és halmozódik fel a BPF_W szó formájában, a csomagadatok fix eltolásban BPF_ABS találhatók.

- BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, arch, 0, 3) - a BPF_JEQ segítségével ellenőrzi, hogy a BPF_K akkumulátor állandó architektúra értéke megegyezik-e az arch értékkel. Ha igen, a 0 eltolásnál ugrik a következő utasításra, ellenkező esetben a 3. eltolásnál ugrik (ebben az esetben), hogy hibát dobjon, mert az arch nem egyezik.

- BPF_STMT(BPF_LD + BPF_W + BPF_ABS (offsetof(struct seccomp_data, nr))) - Betölti és felhalmozódik a BPF_LD-ből a BPF_W szó formájában, amely a BPF_ABS rögzített eltolásában található rendszerhívási szám.

— 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 egyenlők, továbblép a következő utasításra, és letiltja a rendszerhívást, ellenkező esetben engedélyezi a rendszerhívást a SECCOMP_RET_ALLOW-val.

- BPF_STMT(BPF_RET + BPF_K, SECCOMP_RET_ERRNO | (hiba & SECCOMP_RET_DATA)) - leállítja a programot a BPF_RET paraméterrel, és ennek eredményeként SECCOMP_RET_ERRNO hibát ad az err változó számával.

- BPF_STMT(BPF_RET + BPF_K, SECCOMP_RET_ALLOW) - leállítja a programot a BPF_RET paraméterrel, és lehetővé teszi a rendszerhívás végrehajtását a SECCOMP_RET_ALLOW használatával.

A SECCOMP CBPF
Elgondolkodhat azon, hogy miért egy utasításlistát használunk a lefordított ELF objektum vagy a JIT által lefordított C program helyett.

Ennek két oka van.

• Először is, a Seccomp cBPF-et (klasszikus BPF) használ, és nem eBPF-et, ami azt jelenti, hogy nincsenek regiszterei, hanem csak egy akkumulátora az utolsó számítási eredmény tárolására, amint az a példában is látható.

• Másodszor, a Seccomp közvetlenül fogadja el a BPF utasítások tömbjének mutatóját, semmi mást. Az általunk használt makrók egyszerűen segítik ezeket az utasításokat programozóbarát módon megadni.

Ha további segítségre van szüksége ennek az összeállításnak a megértéséhez, vegye figyelembe a pszeudokódot, amely ugyanezt teszi:

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

Miután megadta a szűrőkódot a socket_filter struktúrában, meg kell határoznia egy sock_fprog-ot, amely tartalmazza a kódot és a szűrő számított hosszát. Ez az adatstruktúra argumentumként szükséges a folyamat későbbi futtatásának deklarálásához:

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

Már csak egy dolgot kell tenni az install_filter függvényben – töltse be magát a programot! Ehhez a prctl-t használjuk, és a PR_SET_SECCOMP lehetőséget választjuk a biztonságos számítási módba való belépéshez. Ezután azt mondjuk a módnak, hogy töltse be a szűrőt a SECCOMP_MODE_FILTER használatával, amely a sock_fprog típusú prog 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 a prctl használatával be kell állítanunk a PR_SET_NO_NEW_PRIVS-t az aktuális végrehajtáshoz, és ezzel elkerülhetjük azt a helyzetet, hogy a gyermekfolyamatok több jogosultságot kapjanak, mint a szüleik. Ezzel az install_filter függvényben root jogok nélkül hajthatjuk végre a következő prctl hívásokat.

Most már meghívhatjuk az install_filter függvényt. Letiltjuk az X86-64 architektúrához kapcsolódó összes írási rendszerhívást, és egyszerűen adjunk engedélyt, amely blokkol minden próbálkozást. A szűrő telepítése után az első argumentum használatával folytatjuk a végrehajtást:

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 el. A programunk lefordításához használhatjuk a clang vagy a gcc parancsot, akárcsak a main.c fájl fordítását, speciális beállítások nélkül:

clang main.c -o filter-write

Mint már említettük, a programban minden bejegyzést blokkoltunk. Ennek teszteléséhez szükség van egy programra, amely kiad valamit – ls jó jelöltnek tűnik. Általában így viselkedik:

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

Csodálatos! Így néz ki a wrapper programunk használata: Egyszerűen átadjuk a tesztelni kívánt programot első argumentumként:

./filter-write "ls -la"

A program végrehajtásakor teljesen üres kimenetet ad. Használhatjuk azonban a strace-t, hogy megnézzük, mi történik:

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

A munka eredménye jelentősen lerövidül, de a megfelelő része azt mutatja, hogy a rekordok blokkolva vannak az EPERM hibával - ugyanaz, amelyet konfiguráltunk. Ez azt jelenti, hogy a program nem ad ki semmit, mert nem tud hozzáférni az írási 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 megértette, hogyan működik a Seccomp BPF, és van egy jó ötlete, hogy mit tehet vele. De nem szeretné elérni ugyanezt az eBPF-el a cBPF helyett, hogy kihasználhassa annak teljes erejét?

Amikor az eBPF programokra gondol, a legtöbb ember azt gondolja, hogy egyszerűen megírja és rendszergazdai jogosultságokkal tölti be. Bár ez az állítás általában igaz, a kernel egy sor mechanizmust valósít meg az eBPF objektumok különböző szintű védelmére. Ezeket a mechanizmusokat BPF LSM csapdáknak nevezik.

BPF LSM csapdák

A rendszeresemények architektúrától független felügyelete érdekében az LSM megvalósítja a csapdák koncepcióját. A hookhívás technikailag hasonló a rendszerhíváshoz, de rendszerfüggetlen és integrált az infrastruktúrával. Az LSM egy új koncepciót biztosít, amelyben az absztrakciós réteg segíthet elkerülni a különböző architektúrákon történő rendszerhívások kezelése során felmerülő problémákat.

A cikk írásakor a kernel hét hook-ot tartalmaz a BPF programokhoz, és a SELinux az egyetlen beépített LSM, amely ezeket megvalósítja.

A csapdák forráskódja az include/linux/security.h fájl kernelfájában található:

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

Mindegyiket a végrehajtás különböző szakaszaiban hívják meg:

— security_bpf — elvégzi a végrehajtott BPF rendszerhívások kezdeti ellenőrzését;

- security_bpf_map - ellenőrzi, hogy a kernel mikor ad vissza fájlleírót a térképhez;

- security_bpf_prog - ellenőrzi, hogy a kernel mikor ad vissza fájlleírót az eBPF programhoz;

— security_bpf_map_alloc — ellenőrzi, hogy a BPF-leképezéseken belüli biztonsági mező inicializálva van-e;

- security_bpf_map_free - ellenőrzi, hogy a biztonsági mező törlődik-e a BPF-térképeken belül;

— security_bpf_prog_alloc — ellenőrzi, hogy a biztonsági mező inicializálva van-e a BPF programokon belül;

- security_bpf_prog_free - ellenőrzi, hogy a biztonsági mezőt törölték-e a BPF programokon belül.

Most mindezt látva megértjük: az LSM BPF elfogók mögött az az elképzelés áll, hogy védelmet nyújthatnak minden eBPF objektumnak, biztosítva, hogy csak a megfelelő jogosultságokkal rendelkezők végezhessenek műveleteket kártyákon és programokon.

Összegzés

A biztonság nem olyasmi, amit egyformán alkalmazhat mindenre, amit védeni szeretne. Fontos, hogy a rendszereket különböző szinteken és módon védeni lehessen. Akár hiszi, akár nem, egy rendszer biztonságossá tételének legjobb módja, ha különböző pozíciókból különböző szintű védelmet szervezünk, így egy szint biztonságának csökkentése nem teszi lehetővé a teljes rendszer elérését. A fő fejlesztők nagyszerű munkát végeztek, hogy különböző rétegeket és érintkezési pontokat adtak nekünk. Reméljük, hogy sikerült megértenünk, hogy mik azok a rétegek, és hogyan használhatjuk a BPF programokat a velük való együttműködéshez.

A szerzőkről

David Calavera a Netlify műszaki igazgatója. Dolgozott a Docker támogatásában, és hozzájárult a Runc, Go és BCC eszközök fejlesztéséhez, valamint más nyílt forráskódú projektekhez. A Docker-projekteken végzett munkájáról és a Docker-bővítmények ökoszisztémájának fejlesztéséről ismert. David nagyon szenvedélyes a lánggrafikonok iránt, és mindig a teljesítmény optimalizálására törekszik.

Lorenzo Fontana a Sysdig nyílt forráskódú csapatában dolgozik, ahol elsősorban a Falcóra összpontosít, egy Cloud Native Computing Foundation projektre, amely konténer futásidejű biztonságát és anomáliák észlelését biztosítja kernelmodulon és eBPF-en keresztül. Szenvedélye az elosztott rendszerek, a szoftver által definiált hálózatépítés, a Linux kernel és a teljesítményelemzés.

» A könyvvel kapcsolatos további információkért látogasson el ide a kiadó honlapján
» tartalomjegyzék
» Részlet

Khabrozhiteli esetében 25% kedvezmény a kuponból - Linux

A könyv papíralapú változatának befizetése után e-könyvet küldünk az e-mail címre.

Forrás: will.com

Hozzászólás