BPF pienimmille, osa nolla: klassinen BPF

Berkeley Packet Filters (BPF) on Linux-ydintekniikka, joka on ollut englanninkielisten teknisten julkaisujen etusivuilla jo useita vuosia. Konferenssit ovat täynnä raportteja BPF:n käytöstä ja kehityksestä. David Miller, Linux-verkkoalijärjestelmän ylläpitäjä, kutsuu puheensa Linux Plumbers 2018 -tapahtumassa "Tämä keskustelu ei koske XDP:tä" (XDP on yksi BPF:n käyttötapaus). Brendan Gregg pitää puheen otsikolla Linuxin BPF-supervoimat. Toke Høiland-Jørgensen nauraaettä ydin on nyt mikroydin. Thomas Graf edistää ajatusta BPF on javascript ytimelle.

Habrén BPF:stä ei vieläkään ole systemaattista kuvausta, ja siksi yritän artikkelisarjassa puhua tekniikan historiasta, kuvailla arkkitehtuuria ja kehitystyökaluja sekä hahmotella BPF:n käyttöalueita ja käytäntöjä. Tämä artikkeli, nolla, sarjassa, kertoo klassisen BPF:n historiasta ja arkkitehtuurista sekä paljastaa sen toimintaperiaatteiden salaisuudet. tcpdump, seccomp, strace, ja paljon enemmän.

BPF:n kehitystä ohjaa Linux-verkkoyhteisö, BPF:n tärkeimmät olemassa olevat sovellukset liittyvät verkkoihin ja siksi luvalla @eukariot, kutsuin sarjaa "BPF pienimmille" suuren sarjan kunniaksi "Verkostot pienimmille".

Lyhyt kurssi BPF:n historiasta(c)

Nykyaikainen BPF-tekniikka on parannettu ja laajennettu versio vanhasta tekniikasta, jolla on sama nimi, jota kutsutaan nykyään klassiseksi BPF:ksi sekaannusten välttämiseksi. Klassiseen BPF:ään perustuen luotiin tunnettu apuohjelma tcpdump, mekanismi seccomp, sekä vähemmän tunnettuja moduuleja xt_bpf varten iptables ja luokitin cls_bpf. Nykyaikaisessa Linuxissa klassiset BPF-ohjelmat käännetään automaattisesti uuteen muotoon, mutta käyttäjän näkökulmasta API on pysynyt paikallaan ja klassiselle BPF:lle löytyy edelleen uusia käyttötapoja, kuten tässä artikkelissa näemme. Tästä syystä ja myös siksi, että seurataan klassisen BPF:n kehityshistoriaa Linuxissa, tulee entistä selvemmäksi, miten ja miksi se kehittyi nykyaikaiseen muotoonsa, päätin aloittaa artikkelilla klassisesta BPF:stä.

Viime vuosisadan XNUMX-luvun lopulla kuuluisan Lawrence Berkeley -laboratorion insinöörit kiinnostuivat kysymyksestä, kuinka verkkopaketteja suodatetaan oikein viime vuosisadan XNUMX-luvun lopulla moderneilla laitteilla. Alun perin CSPF (CMU/Stanford Packet Filter) -teknologiaan toteutetun suodatuksen perusideana oli suodattaa tarpeettomat paketit mahdollisimman varhaisessa vaiheessa, ts. ydintilassa, koska näin vältetään tarpeettoman tiedon kopioiminen käyttäjätilaan. Ajonaikaisen suojauksen tarjoamiseksi käyttäjäkoodin suorittamiselle ydintilassa käytettiin hiekkalaatikko-virtuaalikonetta.

Nykyisten suodattimien virtuaalikoneet on kuitenkin suunniteltu toimimaan pinopohjaisissa koneissa, eivätkä ne toimineet yhtä tehokkaasti uudemmissa RISC-koneissa. Tuloksena Berkeley Labsin insinöörien ponnistelujen avulla kehitettiin uusi BPF (Berkeley Packet Filters) -tekniikka, jonka virtuaalikoneen arkkitehtuuri on suunniteltu Motorola 6502 -prosessorin pohjalta, joka on tunnettujen tuotteiden, kuten esim. Apple II tai NES. Uusi virtuaalikone paransi suodattimen suorituskykyä kymmeniä kertoja verrattuna olemassa oleviin ratkaisuihin.

BPF-konearkkitehtuuri

Tutustumme arkkitehtuuriin toimivalla tavalla esimerkkejä analysoiden. Oletetaan kuitenkin aluksi, että koneessa oli kaksi 32-bittistä rekisteriä, jotka ovat käyttäjän käytettävissä, akku A ja hakemistorekisteri X, 64 tavua muistia (16 sanaa), joka on käytettävissä kirjoittamista ja myöhempää lukemista varten, ja pieni komentojärjestelmä näiden objektien kanssa työskentelemiseen. Ohjelmissa oli myös hyppyohjeita ehdollisten lausekkeiden toteuttamiseen, mutta ohjelman oikea-aikaisen valmistumisen takaamiseksi hyppyjä sai tehdä vain eteenpäin, eli erityisesti silmukoiden luominen oli kiellettyä.

Yleinen kaavio koneen käynnistämiseksi on seuraava. Käyttäjä luo ohjelman BPF-arkkitehtuurille ja käyttämällä jotkut ydinmekanismi (kuten järjestelmäkutsu), lataa ohjelman ja yhdistää siihen jollekin ytimen tapahtumageneraattoriin (esimerkiksi tapahtuma on seuraavan paketin saapuminen verkkokortille). Kun tapahtuma tapahtuu, ydin suorittaa ohjelman (esimerkiksi tulkissa) ja koneen muisti vastaa jollekin ytimen muistialue (esimerkiksi saapuvan paketin tiedot).

Yllä oleva riittää, jotta voimme alkaa tarkastella esimerkkejä: tutustumme järjestelmään ja komentomuotoon tarpeen mukaan. Jos haluat välittömästi tutkia virtuaalikoneen komentojärjestelmää ja oppia kaikista sen ominaisuuksista, voit lukea alkuperäisen artikkelin BSD-pakettisuodatin ja/tai tiedoston ensimmäinen puolisko Documentation/Networking/filter.txt ytimen dokumentaatiosta. Lisäksi voit tutkia esitystä libpcap: Pakettikaappauksen arkkitehtuuri- ja optimointimenetelmä, jossa McCanne, yksi BPF:n kirjoittajista, puhuu luomisen historiasta libpcap.

Siirrymme tarkastelemaan kaikkia merkittäviä esimerkkejä klassisen BPF:n käytöstä Linuxissa: tcpdump (libpcap), seccomp, xt_bpf, cls_bpf.

tcpdump

BPF:n kehitys tehtiin rinnakkain pakettisuodatuksen käyttöliittymän kehittämisen kanssa - hyvin tunnettu apuohjelma tcpdump. Ja koska tämä on vanhin ja tunnetuin esimerkki klassisen BPF:n käytöstä, joka on saatavilla moniin käyttöjärjestelmiin, aloitamme tekniikan tutkimuksen sillä.

(Käytin kaikki tämän artikkelin esimerkit Linuxissa 5.6.0-rc6. Joidenkin komentojen tulosta on muokattu luettavuuden parantamiseksi.)

Esimerkki: IPv6-pakettien tarkkailu

Kuvitellaan, että haluamme tarkastella kaikkia IPv6-paketteja rajapinnassa eth0. Tätä varten voimme suorittaa ohjelman tcpdump yksinkertaisella suodattimella ip6:

$ sudo tcpdump -i eth0 ip6

Tässä tapauksessa tcpdump kokoaa suodattimen ip6 BPF-arkkitehtuurin tavukoodiin ja lähetä se ytimeen (katso yksityiskohdat kohdasta Tcpdump: latautuu). Ladattu suodatin suoritetaan jokaiselle rajapinnan läpi kulkevalle paketille eth0. Jos suodatin palauttaa nollasta poikkeavan arvon n, sitten asti n tavua paketista kopioidaan käyttäjätilaan ja näemme sen lähdössä tcpdump.

BPF pienimmille, osa nolla: klassinen BPF

Osoittautuu, että voimme helposti selvittää, mikä tavukoodi lähetettiin ytimeen tcpdump avulla tcpdump, jos käytämme sitä vaihtoehdolla -d:

$ sudo tcpdump -i eth0 -d ip6
(000) ldh      [12]
(001) jeq      #0x86dd          jt 2    jf 3
(002) ret      #262144
(003) ret      #0

Rivillä nolla suoritamme komennon ldh [12], joka tarkoittaa "load into register A puoli sanaa (16 bittiä), joka sijaitsee osoitteessa 12" ja ainoa kysymys on, millaista muistia käsittelemme? Vastaus on, että klo x alkaa (x+1)analysoidun verkkopaketin tavu. Luimme paketteja Ethernet-liitännästä eth0ja tämä välineetettä paketti näyttää tältä (yksinkertaisuuden vuoksi oletetaan, että paketissa ei ole VLAN-tunnisteita):

       6              6          2
|Destination MAC|Source MAC|Ether Type|...|

Siis komennon suorittamisen jälkeen ldh [12] rekisterissä A tulee kenttä Ether Type — tässä Ethernet-kehyksessä lähetetyn paketin tyyppi. Rivillä 1 vertaamme rekisterin sisältöä A (pakettityyppi) c 0x86ddja tämä ja on Tyyppi, josta olemme kiinnostuneita, on IPv6. Rivillä 1 on vertailukomennon lisäksi kaksi muuta saraketta - jt 2 и jf 3 - arvosanat, joihin sinun on mentävä, jos vertailu on onnistunut (A == 0x86dd) ja epäonnistunut. Joten onnistuneessa tapauksessa (IPv6) mennään riville 2 ja epäonnistuneessa tapauksessa riville 3. Rivillä 3 ohjelma päättyy koodiin 0 (älä kopioi pakettia), rivillä 2 ohjelma päättyy koodiin 262144 (kopioi minulle enintään 256 kilotavun paketti).

Monimutkaisempi esimerkki: tarkastelemme TCP-paketteja kohdeportin mukaan

Katsotaan miltä näyttää suodatin, joka kopioi kaikki TCP-paketit kohdeportilla 666. Tarkastellaan IPv4-tapausta, koska IPv6-tapaus on yksinkertaisempi. Kun olet tutkinut tämän esimerkin, voit tutkia IPv6-suodatinta itse harjoituksena (ip6 and tcp dst port 666) ja suodatin yleiseen tapaukseen (tcp dst port 666). Joten meitä kiinnostava suodatin näyttää tältä:

$ sudo tcpdump -i eth0 -d ip and tcp dst port 666
(000) ldh      [12]
(001) jeq      #0x800           jt 2    jf 10
(002) ldb      [23]
(003) jeq      #0x6             jt 4    jf 10
(004) ldh      [20]
(005) jset     #0x1fff          jt 10   jf 6
(006) ldxb     4*([14]&0xf)
(007) ldh      [x + 16]
(008) jeq      #0x29a           jt 9    jf 10
(009) ret      #262144
(010) ret      #0

Tiedämme jo, mitä rivit 0 ja 1 tekevät. Rivillä 2 olemme jo tarkistaneet, että tämä on IPv4-paketti (Ether Type = 0x800) ja lataa se rekisteriin A Paketin 24. tavu. Pakettimme näyttää

       14            8      1     1
|ethernet header|ip fields|ttl|protocol|...|

mikä tarkoittaa, että lataamme rekisteriin A IP-otsikon Protocol-kenttä, mikä on loogista, koska haluamme kopioida vain TCP-paketteja. Vertaamme pöytäkirjaa 0x6 (IPPROTO_TCP) rivillä 3.

Riveillä 4 ja 5 lataamme osoitteessa 20 olevat puolisanat ja käytämme komentoa jset tarkista, onko jokin kolmesta asetettu liput - myönnetty maski päällä jset kolme tärkeintä bittiä tyhjennetään. Kaksi kolmesta bitistä kertoo meille, onko paketti osa pirstoutunutta IP-pakettia, ja jos on, onko se viimeinen fragmentti. Kolmas bitti on varattu ja sen on oltava nolla. Emme halua tarkistaa puutteellisia tai rikkoutuneita paketteja, joten tarkistamme kaikki kolme bittiä.

Rivi 6 on tämän listauksen mielenkiintoisin. Ilmaisu ldxb 4*([14]&0xf) tarkoittaa, että lataamme rekisteriin X paketin viidennentoista tavun vähiten merkitsevät neljä bittiä kerrottuna 4:llä. Viidennentoista tavun vähiten merkitsevät neljä bittiä on kenttä Internet-otsikon pituus IPv4-otsikko, joka tallentaa otsikon pituuden sanoina, joten sinun täytyy sitten kertoa 4:llä. Mielenkiintoista on, että lauseke 4*([14]&0xf) on nimitys erityiselle osoitusjärjestelmälle, jota voidaan käyttää vain tässä muodossa ja vain rekisterissä X, eli emme myöskään osaa sanoa ldb 4*([14]&0xf) tai ldxb 5*([14]&0xf) (voimme määrittää vain erilaisen siirtymän, esim. ldxb 4*([16]&0xf)). On selvää, että tämä osoitejärjestelmä lisättiin BPF:ään juuri vastaanottamista varten X (indeksirekisteri) IPv4-otsikon pituus.

Joten rivillä 7 yritämme ladata puoli sanaa osoitteessa (X+16). Muista, että Ethernet-otsikko varaa 14 tavua ja X sisältää IPv4-otsikon pituuden, ymmärrämme, että A TCP-kohdeportti on ladattu:

       14           X           2             2
|ethernet header|ip header|source port|destination port|

Lopuksi rivillä 8 verrataan kohdeporttia haluttuun arvoon ja rivillä 9 tai 10 palautetaan tulos - kopioidaanko paketti vai ei.

Tcpdump: latautuu

Edellisissä esimerkeissä emme tarkastellut yksityiskohtaisesti, kuinka BPF-tavukoodi ladataan ytimeen pakettisuodatusta varten. Yleisesti ottaen, tcpdump siirretty moniin järjestelmiin ja suodattimien kanssa työskentelemiseen tcpdump käyttää kirjastoa libpcap. Lyhyesti sanottuna suodattimen sijoittaminen käyttöliittymään käyttämällä libpcap, sinun on tehtävä seuraava:

Nähdäksesi kuinka toiminto pcap_setfilter toteutettu Linuxissa, käytämme strace (jotkut rivit on poistettu):

$ sudo strace -f -e trace=%network tcpdump -p -i eth0 ip
socket(AF_PACKET, SOCK_RAW, 768)        = 3
bind(3, {sa_family=AF_PACKET, sll_protocol=htons(ETH_P_ALL), sll_ifindex=if_nametoindex("eth0"), sll_hatype=ARPHRD_NETROM, sll_pkttype=PACKET_HOST, sll_halen=0}, 20) = 0
setsockopt(3, SOL_SOCKET, SO_ATTACH_FILTER, {len=4, filter=0xb00bb00bb00b}, 16) = 0
...

Luomme kahdelle ensimmäiselle riville raaka pistorasia lukeaksesi kaikki Ethernet-kehykset ja sitoaksesi sen liitäntään eth0. Alkaen ensimmäinen esimerkkimme tiedämme, että suodatin ip koostuu neljästä BPF-ohjeesta, ja kolmannella rivillä näemme, kuinka vaihtoehtoa käytetään SO_ATTACH_FILTER järjestelmäpuhelu setsockopt lataamme ja kytkemme suodattimen, jonka pituus on 4. Tämä on suodattimemme.

On syytä huomata, että klassisessa BPF:ssä suodattimen lataaminen ja kytkeminen tapahtuu aina atomioperaationa, ja BPF:n uudessa versiossa ohjelman lataaminen ja sitominen tapahtumageneraattoriin erotetaan ajallisesti.

Piilotettu Totuus

Hieman täydellisempi versio lähdöstä näyttää tältä:

$ sudo strace -f -e trace=%network tcpdump -p -i eth0 ip
socket(AF_PACKET, SOCK_RAW, 768)        = 3
bind(3, {sa_family=AF_PACKET, sll_protocol=htons(ETH_P_ALL), sll_ifindex=if_nametoindex("eth0"), sll_hatype=ARPHRD_NETROM, sll_pkttype=PACKET_HOST, sll_halen=0}, 20) = 0
setsockopt(3, SOL_SOCKET, SO_ATTACH_FILTER, {len=1, filter=0xbeefbeefbeef}, 16) = 0
recvfrom(3, 0x7ffcad394257, 1, MSG_TRUNC, NULL, NULL) = -1 EAGAIN (Resource temporarily unavailable)
setsockopt(3, SOL_SOCKET, SO_ATTACH_FILTER, {len=4, filter=0xb00bb00bb00b}, 16) = 0
...

Kuten edellä mainittiin, lataamme ja kiinnitämme suodattimen linjan 5 liitäntään, mutta mitä tapahtuu linjoilla 3 ja 4? Osoittautuu, että tämä libpcap huolehtii meistä - jotta suodattimemme tulos ei sisällä paketteja, jotka eivät täytä sitä, kirjasto yhdistää tyhjä suodatin ret #0 (pudota kaikki paketit), vaihtaa socketin estotilaan ja yrittää vähentää kaikki paketit, jotka voivat jäädä aiemmista suodattimista.

Kaiken kaikkiaan, jotta voit suodattaa paketteja Linuxissa käyttämällä klassista BPF:ää, sinulla on oltava suodatin rakenteen muodossa struct sock_fprog ja avoin pistoke, jonka jälkeen suodatin voidaan kiinnittää kantaan järjestelmäkutsulla setsockopt.

Mielenkiintoista on, että suodatin voidaan kiinnittää mihin tahansa pistorasiaan, ei vain raakaan. Tässä esimerkki ohjelma, joka katkaisee kaikki paitsi kaksi ensimmäistä tavua kaikista saapuvista UDP-datagrameista. (Lisäsin koodiin kommentteja, jotta en sotkenut artikkelia.)

Tarkemmat tiedot käytöstä setsockopt suodattimien liittäminen, katso pistorasia (7), vaan omien suodattimien kirjoittamisesta, kuten struct sock_fprog ilman apua tcpdump puhumme osiossa BPF:n ohjelmointi omin käsin.

Klassinen BPF ja XNUMX-luku

BPF sisällytettiin Linuxiin vuonna 1997 ja on pysynyt työhevosena pitkään libpcap ilman erityisiä muutoksia (Linux-kohtaiset muutokset tietysti, Olimme, mutta ne eivät muuttaneet yleiskuvaa). Ensimmäiset vakavat merkit BPF:n kehittymisestä tulivat vuonna 2011, kun Eric Dumazet ehdotti läikkä, joka lisää ytimeen Just In Time -kääntäjän - kääntäjän BPF-tavukoodin muuntamiseen alkuperäiseksi x86_64 koodi.

JIT-kääntäjä oli ensimmäinen muutosketjussa: vuonna 2012 ilmestyi kyky kirjoittaa suodattimia seccomp, käyttäen BPF:ää, tammikuussa 2013 oli lisätty moduuli xt_bpf, jonka avulla voit kirjoittaa sääntöjä iptables BPF:n avulla, ja lokakuussa 2013 oli lisätty myös moduuli cls_bpf, jonka avulla voit kirjoittaa liikenneluokituksia BPF:n avulla.

Tarkastelemme kaikkia näitä esimerkkejä tarkemmin pian, mutta ensin on hyödyllistä oppia kirjoittamaan ja kääntämään mielivaltaisia ​​ohjelmia BPF:lle, koska kirjaston tarjoamat ominaisuudet libpcap rajoitettu (yksinkertainen esimerkki: suodatin luotu libpcap voi palauttaa vain kaksi arvoa - 0 tai 0x40000) tai yleensä, kuten seccompin tapauksessa, ne eivät sovellu.

BPF:n ohjelmointi omin käsin

Tutustutaan BPF-ohjeiden binäärimuotoon, se on hyvin yksinkertaista:

   16    8    8     32
| code | jt | jf |  k  |

Kukin käsky vie 64 bittiä, joista ensimmäiset 16 bittiä ovat ohjekoodi, sitten on kaksi kahdeksan bitin sisennystä, jt и jf, ja 32 bittiä argumentille K, jonka tarkoitus vaihtelee käskystä toiseen. Esimerkiksi komento ret, joka lopettaa ohjelman, jolla on koodi 6, ja palautusarvo otetaan vakiosta K. C:ssä yksittäinen BPF-käsky esitetään rakenteena

struct sock_filter {
        __u16   code;
        __u8    jt;
        __u8    jf;
        __u32   k;
}

ja koko ohjelma on rakenteen muodossa

struct sock_fprog {
        unsigned short len;
        struct sock_filter *filter;
}

Osaamme siis jo kirjoittaa ohjelmia (esim. tunnemme ohjekoodit mistä [1]). Suodatin tulee näyttämään tältä ip6 ja ensimmäinen esimerkkimme:

struct sock_filter code[] = {
        { 0x28, 0, 0, 0x0000000c },
        { 0x15, 0, 1, 0x000086dd },
        { 0x06, 0, 0, 0x00040000 },
        { 0x06, 0, 0, 0x00000000 },
};
struct sock_fprog prog = {
        .len = ARRAY_SIZE(code),
        .filter = code,
};

Ohjelma prog voimme käyttää laillisesti puhelussa

setsockopt(sk, SOL_SOCKET, SO_ATTACH_FILTER, &prog, sizeof(prog))

Ohjelmien kirjoittaminen konekoodien muodossa ei ole kovin kätevää, mutta joskus se on välttämätöntä (esimerkiksi virheenkorjaukseen, yksikkötestien luomiseen, Habré-artikkeleiden kirjoittamiseen jne.). Tiedoston käyttömukavuuden vuoksi <linux/filter.h> auttajamakrot on määritelty - sama esimerkki kuin yllä voidaan kirjoittaa uudelleen

struct sock_filter code[] = {
        BPF_STMT(BPF_LD|BPF_H|BPF_ABS, 12),
        BPF_JUMP(BPF_JMP|BPF_JEQ|BPF_K, ETH_P_IPV6, 0, 1),
        BPF_STMT(BPF_RET|BPF_K, 0x00040000),
        BPF_STMT(BPF_RET|BPF_K, 0),
}

Tämä vaihtoehto ei kuitenkaan ole kovin kätevä. Tätä Linux-ytimen ohjelmoijat päättelivät ja siksi hakemistossa tools/bpf ytimistä löydät kokoajan ja virheenkorjaajan työskennelläksesi klassisen BPF:n kanssa.

Assembly-kieli on hyvin samankaltainen kuin debug-tulosteen tcpdump, mutta lisäksi voimme määrittää symbolisia tunnisteita. Esimerkiksi tässä on ohjelma, joka pudottaa kaikki paketit paitsi TCP/IPv4:n:

$ cat /tmp/tcp-over-ipv4.bpf
ldh [12]
jne #0x800, drop
ldb [23]
jneq #6, drop
ret #-1
drop: ret #0

Oletusarvoisesti kokoaja luo koodin muodossa <количество инструкций>,<code1> <jt1> <jf1> <k1>,..., esimerkiksi TCP:n kanssa se on

$ tools/bpf/bpf_asm /tmp/tcp-over-ipv4.bpf
6,40 0 0 12,21 0 3 2048,48 0 0 23,21 0 1 6,6 0 0 4294967295,6 0 0 0,

C-ohjelmoijien mukavuuden vuoksi voidaan käyttää eri tulostusmuotoa:

$ tools/bpf/bpf_asm -c /tmp/tcp-over-ipv4.bpf
{ 0x28,  0,  0, 0x0000000c },
{ 0x15,  0,  3, 0x00000800 },
{ 0x30,  0,  0, 0x00000017 },
{ 0x15,  0,  1, 0x00000006 },
{ 0x06,  0,  0, 0xffffffff },
{ 0x06,  0,  0, 0000000000 },

Tämä teksti voidaan kopioida tyyppirakenteen määritelmään struct sock_filter, kuten teimme tämän osion alussa.

Linux- ja netsniff-ng-laajennukset

Standardin BPF:n lisäksi Linux ja tools/bpf/bpf_asm tukea ja ei-standardi setti. Periaatteessa ohjeita käytetään pääsyyn rakenteen kenttiin struct sk_buff, joka kuvaa ytimessä olevaa verkkopakettia. On kuitenkin olemassa myös muunlaisia ​​apuohjeita mm ldw cpu latautuu rekisteriin A ydinfunktion suorittamisen tulos raw_smp_processor_id(). (BPF:n uudessa versiossa näitä epästandardeja laajennuksia on laajennettu tarjoamaan ohjelmille joukko ytimen apulaitteita muistiin, rakenteisiin ja tapahtumien luomiseen.) Tässä on mielenkiintoinen esimerkki suodattimesta, jossa kopioimme vain pakettien otsikot käyttäjätilaan laajennuksen avulla poff, hyötykuorman siirtymä:

ld poff
ret a

BPF-laajennuksia ei voi käyttää tcpdump, mutta tämä on hyvä syy tutustua hyödyllisyyspakettiin netsniff-ng, joka sisältää muun muassa edistyneen ohjelman netsniff-ng, joka sisältää BPF-suodatuksen lisäksi tehokkaan liikennegeneraattorin ja edistyneemmän kuin tools/bpf/bpf_asm, kutsui BPF:n kokoaja bpfc. Paketti sisältää melko yksityiskohtaisen dokumentaation, katso myös artikkelin lopussa olevat linkit.

seccomp

Joten tiedämme jo kuinka kirjoittaa mielivaltaisen monimutkaisia ​​BPF-ohjelmia ja olemme valmiita katsomaan uusia esimerkkejä, joista ensimmäinen on seccomp-tekniikka, jonka avulla BPF-suodattimia käyttämällä voidaan hallita käytettävissä olevia järjestelmäkutsuargumenttien joukkoa ja joukkoa. tietty prosessi ja sen jälkeläiset.

Ensimmäinen seccomp-versio lisättiin ytimeen vuonna 2005, eikä se ollut kovin suosittu, koska se tarjosi vain yhden vaihtoehdon - rajoittaa prosessin käytettävissä olevien järjestelmäkutsujen joukko seuraaviin: read, write, exit и sigreturn, ja sääntöjä rikkonut prosessi tapettiin käyttämällä SIGKILL. Vuonna 2012 seccomp kuitenkin lisäsi mahdollisuuden käyttää BPF-suodattimia, jolloin voit määrittää joukon sallittuja järjestelmäkutsuja ja jopa tarkistaa niiden argumentteja. (Mielenkiintoista kyllä, Chrome oli yksi tämän toiminnon ensimmäisistä käyttäjistä, ja Chrome-ihmiset kehittävät parhaillaan KRSI-mekanismia, joka perustuu BPF:n uuteen versioon ja mahdollistaa Linuxin suojausmoduulien mukauttamisen.) Linkit lisädokumentaatioihin löytyvät lopusta. artikkelista.

Huomaa, että keskittimessä on jo kirjoitettu artikkeleita seccompin käytöstä, ehkä joku haluaa lukea ne ennen (tai sen sijaan) lukea seuraavat alakohdat. Artikkelissa Kontit ja turvallisuus: seccomp tarjoaa esimerkkejä seccompin käytöstä, sekä vuoden 2007 versiosta että BPF:ää käyttävästä versiosta (suodattimet luodaan libseccompilla), puhuu seccompin kytkemisestä Dockeriin ja tarjoaa myös monia hyödyllisiä linkkejä. Artikkelissa Demonien eristäminen systemd:llä tai "et tarvitse Dockeria tähän!" Se kattaa erityisesti kuinka lisätä mustia tai sallittuja listoja järjestelmäkutsuista systemd:tä käyttäville demoneille.

Seuraavaksi näemme, kuinka suodattimia kirjoitetaan ja ladataan seccomp paljaalla C:llä ja käyttämällä kirjastoa libseccomp ja mitkä ovat kunkin vaihtoehdon edut ja haitat, ja lopuksi katsotaan kuinka ohjelma käyttää seccompia strace.

Kirjoitus- ja lataussuodattimet seccompille

Osaamme jo kirjoittaa BPF-ohjelmia, joten katsotaanpa ensin seccomp-ohjelmointirajapintaa. Voit asettaa suodattimen prosessitasolla, ja kaikki aliprosessit perivät rajoitukset. Tämä tehdään järjestelmäkutsulla seccomp(2):

seccomp(SECCOMP_SET_MODE_FILTER, flags, &filter)

missä &filter - Tämä on osoitus meille jo tutusta rakenteesta struct sock_fprog, eli BPF ohjelma.

Miten seccomp-ohjelmat eroavat socket-ohjelmista? Lähetetty konteksti. Sockettien tapauksessa saimme paketin sisältävän muistialueen ja seccompin tapauksessa rakenteen kuten

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

Täällä nr on aloitettavan järjestelmäkutsun numero, arch - nykyinen arkkitehtuuri (lisätietoja alla), args - enintään kuusi järjestelmäkutsuargumenttia ja instruction_pointer on osoitin käyttäjätilan käskyyn, joka teki järjestelmäkutsun. Näin esimerkiksi järjestelmän kutsunumeron lataamiseen rekisteriin A meidän on sanottava

ldw [0]

seccomp-ohjelmissa on muitakin ominaisuuksia, esimerkiksi kontekstiin pääsee käsiksi vain 32-bittisellä tasauksella etkä voi ladata puolta sanaa tai tavua - kun yrität ladata suodatinta ldh [0] järjestelmäpuhelu seccomp palaa EINVAL. Toiminto tarkistaa ladatut suodattimet seccomp_check_filter() ytimiä. (Hauska asia on, että alkuperäisessä commitissa, joka lisäsi seccomp-toiminnon, he unohtivat lisätä luvan käyttää ohjetta tähän toimintoon mod (jakojäännös) ja se ei ole nyt saatavilla seccomp BPF -ohjelmille sen lisäämisen jälkeen katkeaa BI.)

Periaatteessa tiedämme jo kaiken seccomp-ohjelmien kirjoittamisesta ja lukemisesta. Yleensä ohjelmalogiikka on järjestetty valkoiseksi tai mustaksi listaksi järjestelmäkutsuista, esimerkiksi ohjelmasta

ld [0]
jeq #304, bad
jeq #176, bad
jeq #239, bad
jeq #279, bad
good: ret #0x7fff0000 /* SECCOMP_RET_ALLOW */
bad: ret #0

tarkistaa mustan listan neljästä järjestelmäkutsusta numeroilla 304, 176, 239, 279. Mitä nämä järjestelmäkutsut ovat? Emme voi sanoa varmaksi, koska emme tiedä, mille arkkitehtuurille ohjelma on kirjoitettu. Siksi seccompin kirjoittajat tarjous käynnistä kaikki ohjelmat arkkitehtuurin tarkistuksella (nykyinen arkkitehtuuri ilmoitetaan kontekstissa kentällä arch rakenteet struct seccomp_data). Kun arkkitehtuuri on tarkistettu, esimerkin alku näyttää tältä:

ld [4]
jne #0xc000003e, bad_arch ; SCMP_ARCH_X86_64

ja sitten järjestelmämme kutsunumerot saisivat tietyt arvot.

Kirjoitamme ja lataamme suodattimia seccomp-käyttöön libseccomp

Suodattimien kirjoittaminen natiivikoodiin tai BPF-kokoonpanoon mahdollistaa tuloksen täyden hallinnan, mutta samaan aikaan on joskus parempi käyttää kannettavaa ja/tai luettavaa koodia. Kirjasto auttaa meitä tässä libseccomp, joka tarjoaa vakiokäyttöliittymän mustien tai valkoisten suodattimien kirjoittamiseen.

Kirjoitetaan esimerkiksi ohjelma, joka suorittaa käyttäjän valitseman binaaritiedoston, kun hän on aiemmin asentanut mustan listan järjestelmäkutsuista yllä oleva artikkeli (ohjelmaa on yksinkertaistettu luettavuuden parantamiseksi, täysversio löytyy täällä):

#include <seccomp.h>
#include <unistd.h>
#include <err.h>

static int sys_numbers[] = {
        __NR_mount,
        __NR_umount2,
       // ... еще 40 системных вызовов ...
        __NR_vmsplice,
        __NR_perf_event_open,
};

int main(int argc, char **argv)
{
        scmp_filter_ctx ctx = seccomp_init(SCMP_ACT_ALLOW);

        for (size_t i = 0; i < sizeof(sys_numbers)/sizeof(sys_numbers[0]); i++)
                seccomp_rule_add(ctx, SCMP_ACT_TRAP, sys_numbers[i], 0);

        seccomp_load(ctx);

        execvp(argv[1], &argv[1]);
        err(1, "execlp: %s", argv[1]);
}

Ensin määritämme taulukon sys_numbers yli 40 estettävästä järjestelmänumerosta. Alusta sitten konteksti ctx ja kerro kirjastolle, mitä haluamme sallia (SCMP_ACT_ALLOW) oletuksena kaikki järjestelmäkutsut (mustien listojen luominen on helpompaa). Sitten lisäämme yksitellen kaikki järjestelmäkutsut mustalta listalta. Pyydämme vastauksena luettelosta tulevaan järjestelmäkutsuun SCMP_ACT_TRAP, tässä tapauksessa seccomp lähettää signaalin prosessille SIGSYS ja kuvauksen siitä, mikä järjestelmäpuhelu rikkoi sääntöjä. Lopuksi lataamme ohjelman ytimeen käyttämällä seccomp_load, joka kääntää ohjelman ja liittää sen prosessiin järjestelmäkutsulla seccomp(2).

Onnistunut kääntäminen edellyttää, että ohjelma on linkitetty kirjastoon libseccomp, esimerkiksi:

cc -std=c17 -Wall -Wextra -c -o seccomp_lib.o seccomp_lib.c
cc -o seccomp_lib seccomp_lib.o -lseccomp

Esimerkki onnistuneesta käynnistämisestä:

$ ./seccomp_lib echo ok
ok

Esimerkki estetystä järjestelmäkutsusta:

$ sudo ./seccomp_lib mount -t bpf bpf /tmp
Bad system call

käyttö straceyksityiskohtia varten:

$ sudo strace -e seccomp ./seccomp_lib mount -t bpf bpf /tmp
seccomp(SECCOMP_SET_MODE_FILTER, 0, {len=50, filter=0x55d8e78428e0}) = 0
--- SIGSYS {si_signo=SIGSYS, si_code=SYS_SECCOMP, si_call_addr=0xboobdeadbeef, si_syscall=__NR_mount, si_arch=AUDIT_ARCH_X86_64} ---
+++ killed by SIGSYS (core dumped) +++
Bad system call

Mistä voimme tietää, että ohjelma lopetettiin laittoman järjestelmäkutsun käytön vuoksi mount(2).

Joten kirjoitimme suodattimen kirjaston avulla libseccomp, sovittamalla ei-triviaali koodin neljälle riville. Yllä olevassa esimerkissä, jos järjestelmäkutsuja on paljon, suoritusaikaa voidaan lyhentää huomattavasti, koska tarkistus on vain lista vertailuista. Optimointia varten libseccompilla oli äskettäin laastari mukana, joka lisää tuen suodatinattribuutille SCMP_FLTATR_CTL_OPTIMIZE. Tämän attribuutin arvoksi asettaminen 2 muuntaa suodattimen binäärihakuohjelmaksi.

Jos haluat nähdä, miten binäärihakusuodattimet toimivat, katso yksinkertainen käsikirjoitus, joka luo tällaisia ​​ohjelmia BPF assemblerissä valitsemalla järjestelmän kutsunumerot, esimerkiksi:

$ echo 1 3 6 8 13 | ./generate_bin_search_bpf.py
ld [0]
jeq #6, bad
jgt #6, check8
jeq #1, bad
jeq #3, bad
ret #0x7fff0000
check8:
jeq #8, bad
jeq #13, bad
ret #0x7fff0000
bad: ret #0

Et voi kirjoittaa mitään merkittävästi nopeammin, koska BPF-ohjelmat eivät voi suorittaa sisennyshyppyjä (emme voi tehdä esim. jmp A tai jmp [label+X]) ja siksi kaikki siirtymät ovat staattisia.

seccomp ja strace

Kaikki tietävät hyödyn strace on välttämätön työkalu prosessien käyttäytymisen tutkimiseen Linuxissa. Monet ovat kuitenkin myös kuulleet siitä suorituskykyongelmia kun käytät tätä apuohjelmaa. Tosiasia on, että strace toteutettu käyttämällä ptrace(2), ja tässä mekanismissa emme voi määritellä, missä järjestelmäkutsujen joukossa meidän on pysäytettävä prosessi, eli esimerkiksi komennot

$ time strace du /usr/share/ >/dev/null 2>&1

real    0m3.081s
user    0m0.531s
sys     0m2.073s

и

$ time strace -e open du /usr/share/ >/dev/null 2>&1

real    0m2.404s
user    0m0.193s
sys     0m1.800s

käsitellään suunnilleen samassa ajassa, vaikka toisessa tapauksessa haluamme jäljittää vain yhden järjestelmäkutsun.

Uusi vaihtoehto --seccomp-bpf, lisätty strace versio 5.3, mahdollistaa prosessin nopeuttamisen monta kertaa ja käynnistysaika yhden järjestelmäkutsun jälkeen on jo verrattavissa tavalliseen käynnistykseen:

$ time strace --seccomp-bpf -e open du /usr/share/ >/dev/null 2>&1

real    0m0.148s
user    0m0.017s
sys     0m0.131s

$ time du /usr/share/ >/dev/null 2>&1

real    0m0.140s
user    0m0.024s
sys     0m0.116s

(Tässä on tietysti pieni petos, että emme jäljitä tämän komennon pääjärjestelmäkutsua. Jos jäljittäisimme esim. newfsstat, Sitten strace jarruttaisi yhtä voimakkaasti kuin ilman --seccomp-bpf.)

Miten tämä vaihtoehto toimii? Ilman häntä strace muodostaa yhteyden prosessiin ja aloittaa sen käytön PTRACE_SYSCALL. Kun hallittu prosessi lähettää (mikä tahansa) järjestelmäkutsun, ohjaus siirretään strace, joka tarkastelee järjestelmäkutsun argumentteja ja suorittaa sen kanssa PTRACE_SYSCALL. Jonkin ajan kuluttua prosessi päättää järjestelmäkutsun ja siitä poistuttaessa ohjaus siirtyy uudelleen strace, joka tarkastelee palautusarvoja ja aloittaa prosessin käyttämällä PTRACE_SYSCALL, ja niin edelleen.

BPF pienimmille, osa nolla: klassinen BPF

Seccompilla tämä prosessi voidaan kuitenkin optimoida juuri niin kuin haluamme. Nimittäin, jos haluamme katsoa vain järjestelmäkutsua X, niin voimme kirjoittaa BPF-suodattimen, joka on X palauttaa arvon SECCOMP_RET_TRACE, ja puheluille, jotka eivät kiinnosta meitä - SECCOMP_RET_ALLOW:

ld [0]
jneq #X, ignore
trace: ret #0x7ff00000
ignore: ret #0x7fff0000

Tässä tapauksessa strace aloittaa prosessin aluksi nimellä PTRACE_CONT, suodattimemme käsitellään jokaiselle järjestelmäkutsulle, jos järjestelmäkutsu ei ole X, prosessi jatkuu, mutta jos tämä X, seccomp siirtää ohjauksen stracejoka tarkastelee argumentteja ja aloittaa prosessin kuten PTRACE_SYSCALL (koska seccomp ei pysty ajamaan ohjelmaa järjestelmäkutsusta poistuttaessa). Kun järjestelmäkutsu palaa, strace käynnistää prosessin uudelleen käyttämällä PTRACE_CONT ja odottaa uusia viestejä seccompilta.

BPF pienimmille, osa nolla: klassinen BPF

Kun käytät vaihtoehtoa --seccomp-bpf on kaksi rajoitusta. Ensinnäkin ei ole mahdollista liittyä jo olemassa olevaan prosessiin (vaihtoehto -p ohjelmat strace), koska seccomp ei tue tätä. Toiseksi, mahdollisuutta ei ole ei katso lapsiprosesseja, koska seccomp-suodattimet perivät kaikki lapsiprosessit ilman mahdollisuutta poistaa tätä käytöstä.

Hieman tarkemmin kuinka tarkalleen strace työskennellä seccomp löytyy osoitteesta tuore raportti. Meille mielenkiintoisin tosiasia on, että seccompin edustama klassinen BPF on käytössä edelleen.

xt_bpf

Palataan nyt takaisin verkkojen maailmaan.

Taustaa: Kauan sitten, vuonna 2007, ydin oli lisätty moduuli xt_u32 netfilterille. Se kirjoitettiin analogisesti vielä muinaisemman liikenteen luokittelulaitteen kanssa cls_u32 ja voit kirjoittaa mielivaltaisia ​​binäärisääntöjä iptablesille seuraavien yksinkertaisten toimintojen avulla: lataa 32 bittiä paketista ja suorita niille joukko aritmeettisia operaatioita. Esimerkiksi,

sudo iptables -A INPUT -m u32 --u32 "6&0xFF=1" -j LOG --log-prefix "seen-by-xt_u32"

Lataa IP-otsikon 32 bittiä alkaen täytteestä 6 ja lisää niille maskin 0xFF (ota alhainen tavu). Tämä kenttä protocol IP-otsikko ja vertaamme sitä 1:een (ICMP). Voit yhdistää useita tarkistuksia yhteen sääntöön, ja voit myös suorittaa operaattorin @ - siirrä X tavua oikealle. Esimerkiksi sääntö

iptables -m u32 --u32 "6&0xFF=0x6 && 0>>22&0x3C@4=0x29"

tarkistaa, onko TCP-sekvenssinumero erilainen 0x29. En mene yksityiskohtiin, koska on jo selvää, että tällaisten sääntöjen kirjoittaminen käsin ei ole kovin kätevää. Artikkelissa BPF - unohtunut tavukoodi, siellä on useita linkkejä, joissa on esimerkkejä käytöstä ja sääntöjen luomisesta xt_u32. Katso myös tämän artikkelin lopussa olevat linkit.

Vuodesta 2013 lähtien moduuli moduulin sijaan xt_u32 voit käyttää BPF-pohjaista moduulia xt_bpf. Jokaisen, joka on lukenut tähän asti, pitäisi jo olla selvä sen toimintaperiaatteesta: suorita BPF-tavukoodi iptables-säännöinä. Voit luoda uuden säännön esimerkiksi näin:

iptables -A INPUT -m bpf --bytecode <байткод> -j LOG

täällä <байткод> - tämä on koodi assembler-tulostusmuodossa bpf_asm oletuksena esim.

$ cat /tmp/test.bpf
ldb [9]
jneq #17, ignore
ret #1
ignore: ret #0

$ bpf_asm /tmp/test.bpf
4,48 0 0 9,21 0 1 17,6 0 0 1,6 0 0 0,

# iptables -A INPUT -m bpf --bytecode "$(bpf_asm /tmp/test.bpf)" -j LOG

Tässä esimerkissä suodatamme kaikki UDP-paketit. Konteksti BPF-ohjelmalle moduulissa xt_bpf, tietenkin, osoittaa pakettidataan, iptablesin tapauksessa IPv4-otsikon alkuun. Palautusarvo BPF-ohjelmasta booleanMissä false tarkoittaa, että paketti ei täsmää.

On selvää, että moduuli xt_bpf tukee monimutkaisempia suodattimia kuin yllä oleva esimerkki. Katsotaanpa todellisia esimerkkejä Cloudfaresta. Viime aikoihin asti he käyttivät moduulia xt_bpf suojaamaan DDoS-hyökkäyksiä vastaan. Artikkelissa Esittelyssä BPF-työkalut he selittävät, kuinka (ja miksi) he luovat BPF-suodattimia ja julkaisevat linkkejä tällaisten suodattimien luomiseen tarkoitettuihin apuohjelmiin. Esimerkiksi apuohjelman avulla bpfgen voit luoda BPF-ohjelman, joka vastaa nimen DNS-kyselyä habr.com:

$ ./bpfgen --assembly dns -- habr.com
ldx 4*([0]&0xf)
ld #20
add x
tax

lb_0:
    ld [x + 0]
    jneq #0x04686162, lb_1
    ld [x + 4]
    jneq #0x7203636f, lb_1
    ldh [x + 8]
    jneq #0x6d00, lb_1
    ret #65535

lb_1:
    ret #0

Ohjelmassa lataamme ensin rekisteriin X rivin alun osoite x04habrx03comx00 UDP-datagrammin sisällä ja tarkista sitten pyyntö: 0x04686162 <-> "x04hab" jne.

Hieman myöhemmin Cloudfare julkaisi p0f -> BPF-kääntäjäkoodin. Artikkelissa Esittelyssä p0f BPF -kääntäjä he puhuvat siitä, mitä p0f on ja kuinka p0f-allekirjoitukset muunnetaan BPF:ksi:

$ ./bpfgen p0f -- 4:64:0:0:*,0::ack+:0
39,0 0 0 0,48 0 0 8,37 35 0 64,37 0 34 29,48 0 0 0,
84 0 0 15,21 0 31 5,48 0 0 9,21 0 29 6,40 0 0 6,
...

Tällä hetkellä ei enää käytä Cloudfarea xt_bpf, koska he siirtyivät XDP:hen - yksi BPF:n uuden version käyttövaihtoehdoista, katso. L4Drop: XDP DDoS Mitigations.

cls_bpf

Viimeinen esimerkki klassisen BPF:n käytöstä ytimessä on luokitin cls_bpf Linuxin liikenteenohjauksen alijärjestelmää varten, joka lisättiin Linuxiin vuoden 2013 lopussa ja korvaa käsitteellisesti vanhan cls_u32.

Emme kuitenkaan nyt kuvaile työtä cls_bpf, koska klassisen BPF:n tietämyksen kannalta tämä ei anna meille mitään - olemme jo tutustuneet kaikkiin toimintoihin. Lisäksi seuraavissa artikkeleissa, joissa puhutaan Extended BPF:stä, tapaamme tämän luokituksen useammin kuin kerran.

Toinen syy olla puhumatta klassisen BPF c:n käytöstä cls_bpf Ongelmana on, että Extended BPF:ään verrattuna sovellettavuus on tässä tapauksessa kapeampi: klassiset ohjelmat eivät voi muuttaa pakettien sisältöä eivätkä tallentaa tilaa puhelujen välillä.

Joten on aika sanoa hyvästit klassiselle BPF:lle ja katsoa tulevaisuuteen.

Hyvästit klassiselle BPF:lle

Tarkastelimme, kuinka 32-luvun alussa kehitetty BPF-tekniikka eli neljännesvuosisadan menestyksekkäästi ja löysi loppuun asti uusia sovelluksia. Kuitenkin samalla tavalla kuin siirtyminen pinokoneista RISC:hen, joka toimi sysäyksenä klassisen BPF:n kehitykselle, 64-luvulla tapahtui siirtyminen XNUMX-bittisistä koneista XNUMX-bittisiin koneisiin ja klassinen BPF alkoi vanhentua. Lisäksi klassisen BPF:n ominaisuudet ovat hyvin rajalliset, ja vanhentuneen arkkitehtuurin lisäksi meillä ei ole mahdollisuutta tallentaa tilaa BPF-ohjelmien kutsujen välillä, ei ole mahdollisuutta suoraan käyttäjän vuorovaikutukseen, ei ole mahdollisuutta vuorovaikutukseen ytimen kanssa, lukuun ottamatta rajoitetun määrän rakennekenttien lukemista sk_buff ja käynnistämällä yksinkertaisimmat aputoiminnot, et voi muuttaa pakettien sisältöä ja ohjata niitä uudelleen.

Itse asiassa tällä hetkellä Linuxin klassisesta BPF:stä on jäljellä vain API-rajapinta, ja ytimen sisällä kaikki klassiset ohjelmat, olivatpa ne sitten socket-suodattimet tai seccomp-suodattimet, käännetään automaattisesti uuteen muotoon, Extended BPF -muotoon. (Puhumme tarkalleen kuinka tämä tapahtuu seuraavassa artikkelissa.)

Siirtyminen uuteen arkkitehtuuriin alkoi vuonna 2013, kun Aleksei Starovoitov ehdotti BPF-päivitysjärjestelmää. Vuonna 2014 vastaavat korjaukset alkoi ilmestyä ytimessä. Ymmärtääkseni alkuperäinen suunnitelma oli vain optimoida arkkitehtuuri ja JIT-kääntäjä toimimaan tehokkaammin 64-bittisissä koneissa, mutta sen sijaan nämä optimoinnit merkitsivät uuden luvun alkua Linux-kehityksessä.

Tämän sarjan muut artikkelit käsittelevät uuden tekniikan arkkitehtuuria ja sovelluksia, jotka tunnettiin alun perin nimellä sisäinen BPF, sitten laajennettu BPF ja nyt yksinkertaisesti BPF.

viittaukset

  1. Steven McCanne ja Van Jacobson, "BSD Packet Filter: A New Architecture for User-level Packet Capture", https://www.tcpdump.org/papers/bpf-usenix93.pdf
  2. Steven McCanne, "libpcap: An Architecture and Optimization Methodology for Packet Capture", https://sharkfestus.wireshark.org/sharkfest.11/presentations/McCanne-Sharkfest'11_Keynote_Address.pdf
  3. tcpdump, libpcap: https://www.tcpdump.org/
  4. IPtable U32 Match Tutorial.
  5. BPF - unohtunut tavukoodi: https://blog.cloudflare.com/bpf-the-forgotten-bytecode/
  6. Esittelyssä BPF-työkalu: https://blog.cloudflare.com/introducing-the-bpf-tools/
  7. bpf_cls: http://man7.org/linux/man-pages/man8/tc-bpf.8.html
  8. Seccomp-katsaus: https://lwn.net/Articles/656307/
  9. https://github.com/torvalds/linux/blob/master/Documentation/userspace-api/seccomp_filter.rst
  10. habr: Kontit ja turvallisuus: seccomp
  11. habr: Demonien eristäminen systemd:llä tai "et tarvitse Dockeria tähän!"
  12. Paul Chaignon, "strace --seccomp-bpf: katse konepellin alle", https://fosdem.org/2020/schedule/event/debugging_strace_bpf/
  13. netsniff-ng: http://netsniff-ng.org/

Lähde: will.com

Lisää kommentti