Kirja "BPF Linux Monitoringille"

Kirja "BPF Linux Monitoringille"Hei Khabron asukkaat! BPF-virtuaalikone on yksi Linux-ytimen tärkeimmistä komponenteista. Sen oikea käyttö antaa järjestelmäsuunnittelijoille mahdollisuuden löytää vikoja ja ratkaista monimutkaisimmatkin ongelmat. Opit kirjoittamaan ohjelmia, jotka valvovat ja muokkaavat ytimen toimintaa, kuinka turvallisesti toteuttaa koodia ytimen tapahtumien seuraamiseksi ja paljon muuta. David Calavera ja Lorenzo Fontana auttavat sinua vapauttamaan BPF:n voiman. Laajenna tietämystäsi suorituskyvyn optimoinnista, verkottumisesta ja turvallisuudesta. - Käytä BPF:ää Linux-ytimen toiminnan seuraamiseen ja muokkaamiseen. - Lisää koodia valvoaksesi ytimen tapahtumia turvallisesti ilman, että sinun tarvitsee kääntää ydintä tai käynnistää järjestelmää uudelleen. — Käytä käteviä koodiesimerkkejä C:ssä, Gossa tai Pythonissa. - Ota hallintaasi omistamalla BPF-ohjelman elinkaaren.

Linux Kernel Security, sen ominaisuudet ja Seccomp

BPF tarjoaa tehokkaan tavan laajentaa ydintä tinkimättä vakaudesta, turvallisuudesta tai nopeudesta. Tästä syystä ytimen kehittäjät ajattelivat, että olisi hyvä idea käyttää sen monipuolisuutta parantamaan prosessien eristämistä Seccompissa ottamalla käyttöön BPF-ohjelmien tukemia Seccomp-suodattimia, jotka tunnetaan myös nimellä Seccomp BPF. Tässä luvussa selitämme, mitä Seccomp on ja miten sitä käytetään. Sitten opit kirjoittamaan Seccomp-suodattimia BPF-ohjelmilla. Sen jälkeen tarkastelemme sisäänrakennettuja BPF-koukkuja, jotka sisältyvät Linux-suojausmoduulien ytimeen.

Linux Security Modules (LSM) on kehys, joka tarjoaa joukon toimintoja, joiden avulla voidaan toteuttaa erilaisia ​​tietoturvamalleja standardoidulla tavalla. LSM:ää voidaan käyttää suoraan ytimen lähdepuussa, kuten Apparmor, SELinux ja Tomoyo.

Aloitetaan keskustelemalla Linuxin ominaisuuksista.

Kyvyt

Linuxin ominaisuuksien ydin on, että sinun on myönnettävä etuoikeutetulle prosessille lupa suorittaa tietty tehtävä, mutta käyttämättä suidia tähän tarkoitukseen, tai muutoin tehdä prosessista etuoikeutettu, mikä vähentää hyökkäyksen mahdollisuutta ja antaa prosessille mahdollisuuden suorittaa tiettyjä tehtäviä. Jos sovelluksesi on esimerkiksi avattava etuoikeutettu portti, esimerkiksi 80, sen sijaan, että suorittaisit prosessin pääkäyttäjänä, voit antaa sille CAP_NET_BIND_SERVICE-ominaisuuden.

Harkitse Go-ohjelmaa nimeltä main.go:

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

Tämä ohjelma palvelee HTTP-palvelinta portissa 80 (tämä on etuoikeutettu portti). Yleensä suoritamme sen heti kääntämisen jälkeen:

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

Koska emme kuitenkaan myönnä pääkäyttäjän oikeuksia, tämä koodi antaa virheen porttia sitoessaan:

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

capsh (shell manager) on työkalu, joka suorittaa komentotulkin tietyillä ominaisuuksilla.

Tässä tapauksessa, kuten jo mainittiin, täydellisten pääoikeuksien myöntämisen sijaan voit ottaa käyttöön etuoikeutetun portin sitomisen tarjoamalla cap_net_bind_service-ominaisuuden sekä kaiken muun, mitä ohjelmassa on jo. Tätä varten voimme liittää ohjelmamme capsh-kirjaimella:

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

Ymmärretään vähän tätä joukkuetta.

  • capsh - käytä capshia kuorena.
  • —caps='cap_net_bind_service+eip cap_setpcap,cap_setuid,cap_setgid+ep' - koska meidän on vaihdettava käyttäjää (emme halua suorittaa pääkäyttäjänä), määritämme cap_net_bind_service ja mahdollisuuden muuttaa käyttäjätunnusta root ei kenellekään, eli cap_setuid ja cap_setgid.
  • —keep=1 — haluamme säilyttää asennetut ominaisuudet, kun vaihdat root-tililtä.
  • —user="nobody" — ohjelmaa suorittava loppukäyttäjä ei ole kukaan.
  • —addamb=cap_net_bind_service — määritä asiaan liittyvien ominaisuuksien tyhjennys juuritilasta vaihtamisen jälkeen.
  • - -c "./capabilities" - suorita vain ohjelma.

Linkitetyt ominaisuudet ovat erityislaatuisia ominaisuuksia, jotka aliohjelmat perivät, kun nykyinen ohjelma suorittaa ne käyttämällä execve()-komentoa. Vain sellaiset ominaisuudet, jotka on sallittu liittää, eli toisin sanoen ympäristöominaisuuksiksi, voidaan periä.

Ihmettelet luultavasti mitä +eip tarkoittaa, kun olet määrittänyt ominaisuuden --caps-vaihtoehdossa. Näitä lippuja käytetään määrittämään, että kyky:

-on oltava aktivoituna (p);

- käytettävissä (e);

-voi periä lapsiprosessit (i).

Koska haluamme käyttää cap_net_bind_servicea, meidän on tehtävä tämä e-lipulla. Sitten käynnistämme komentotulkin komennossa. Tämä suorittaa ominaisuudet binäärimuodossa ja meidän on merkittävä se i-lipulla. Lopuksi haluamme, että ominaisuus otetaan käyttöön (teimme tämän vaihtamatta UID:tä) p. Se näyttää tältä cap_net_bind_service+eip.

Voit tarkistaa tuloksen käyttämällä ss. Lyhennetään hieman tulostetta, jotta se mahtuu sivulle, mutta siinä näkyy siihen liittyvä portti ja käyttäjätunnus, joka ei ole 0, tässä tapauksessa 65:

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

Tässä esimerkissä käytimme capshia, mutta voit kirjoittaa komentotulkin libcapilla. Katso lisätietoja kohdasta man 3 libcap.

Ohjelmia kirjoittaessaan kehittäjä ei useinkaan tiedä etukäteen kaikkia ominaisuuksia, joita ohjelma tarvitsee ajon aikana; Lisäksi nämä ominaisuudet voivat muuttua uusissa versioissa.

Ymmärtääksemme paremmin ohjelmamme ominaisuuksia, voimme ottaa BCC-yhteensopivan työkalun, joka asettaa kprobe-arvon cap_capable-ytimen funktiolle:

/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

Voimme saavuttaa saman käyttämällä bpftracea yhden rivin kprobella cap_capable-ytimen funktiossa:

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

Tämä tulostaa jotain seuraavanlaista, jos ohjelmamme ominaisuudet ovat käytössä kprobe:n jälkeen:

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

Viides sarake on prosessin tarvitsemat ominaisuudet, ja koska tämä tulos sisältää ei-auditointitapahtumia, näemme kaikki ei-tarkastukset ja lopuksi vaaditun kyvyn, kun tarkastuslippu (tulossa viimeinen) on asetettu arvoon 1. Capability. yksi, josta olemme kiinnostuneita, on CAP_NET_BIND_SERVICE, se määritellään vakiona ytimen lähdekoodissa tiedostossa include/uapi/linux/ability.h tunnisteella 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">

Säilöt, kuten runC tai Docker, ottavat ominaisuudet usein käyttöön ajon aikana, jotta ne voivat toimia etuoikeutetussa tilassa, mutta niille sallitaan vain useimpien sovellusten suorittamiseen tarvittavat ominaisuudet. Kun sovellus vaatii tiettyjä ominaisuuksia, Docker voi tarjota ne käyttämällä --cap-add:

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

Tämä komento antaa säilölle CAP_NET_ADMIN-ominaisuuden, jolloin se voi määrittää verkkolinkin dummy0-liitännän lisäämiseksi.

Seuraavassa osiossa näytetään, kuinka voit käyttää ominaisuuksia, kuten suodatusta, mutta käyttämällä erilaista tekniikkaa, jonka avulla voimme toteuttaa ohjelmallisesti omia suodattimiamme.

Seccomp

Seccomp on lyhenne sanoista Secure Computing ja se on Linux-ytimeen toteutettu suojakerros, jonka avulla kehittäjät voivat suodattaa tiettyjä järjestelmäkutsuja. Vaikka Seccomp on ominaisuuksiltaan verrattavissa Linuxiin, sen kyky hallita tiettyjä järjestelmäkutsuja tekee siitä paljon joustavamman niihin verrattuna.

Seccomp- ja Linux-ominaisuudet eivät sulje toisiaan pois, ja niitä käytetään usein yhdessä hyötymään molemmista lähestymistavoista. Voit esimerkiksi haluta antaa prosessille CAP_NET_ADMIN-ominaisuuden, mutta et salli sen hyväksyä socket-yhteyksiä, mikä estää hyväksymis- ja hyväksy4-järjestelmäkutsut.

Seccomp-suodatusmenetelmä perustuu BPF-suodattimiin, jotka toimivat SECCOMP_MODE_FILTER-tilassa, ja järjestelmäkutsusuodatus suoritetaan samalla tavalla kuin paketeille.

Seccomp-suodattimet ladataan prctl:llä PR_SET_SECCOMP-toiminnon kautta. Nämä suodattimet ovat BPF-ohjelman muodossa, joka suoritetaan kullekin seccomp_data-rakenteen edustamalle Seccomp-paketille. Tämä rakenne sisältää referenssiarkkitehtuurin, osoittimen prosessorikäskyihin järjestelmäkutsun aikana ja enintään kuusi järjestelmäkutsuargumenttia ilmaistuna uint64:nä.

Tältä seccomp_data-rakenne näyttää ytimen lähdekoodista linux/seccomp.h-tiedostossa:

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

Kuten tästä rakenteesta näkyy, voimme suodattaa järjestelmäkutsun, sen argumenttien tai molempien yhdistelmän perusteella.

Kun suodatin on vastaanottanut jokaisen Seccomp-paketin, sen on suoritettava käsittely tehdäkseen lopullisen päätöksen ja kertovan ytimelle, mitä tehdä seuraavaksi. Lopullinen päätös ilmaistaan ​​yhdellä palautusarvoista (tilakoodit).

- SECCOMP_RET_KILL_PROCESS - lopettaa koko prosessin heti suodatuksen jälkeen järjestelmäkutsun, jota ei suoriteta tämän vuoksi.

- SECCOMP_RET_KILL_THREAD - lopettaa nykyisen säikeen välittömästi suodatuksen jälkeen järjestelmäkutsun, jota ei suoriteta tämän vuoksi.

— SECCOMP_RET_KILL — SECCOMP_RET_KILL_THREAD:n alias, jäljellä taaksepäin yhteensopivuutta varten.

- SECCOMP_RET_TRAP - järjestelmäkutsu on kielletty ja SIGSYS (Bad System Call) -signaali lähetetään sitä kutsuvalle tehtävälle.

- SECCOMP_RET_ERRNO - Järjestelmäkutsua ei suoriteta, ja osa SECCOMP_RET_DATA-suodattimen palautusarvosta välitetään käyttäjätilaan errno-arvona. Virheen syystä riippuen palautetaan erilaisia ​​errno-arvoja. Luettelo virhenumeroista on seuraavassa osiossa.

- SECCOMP_RET_TRACE - Käytetään ilmoittamaan ptrace tracerille käyttämällä - PTRACE_O_TRACESECCOMP siepatakseen, kun järjestelmäkutsu suoritetaan prosessin näkemiseksi ja ohjaamiseksi. Jos jäljitintä ei ole kytketty, palautetaan virheilmoitus, errno asetetaan arvoon -ENOSYS, eikä järjestelmäkutsua suoriteta.

- SECCOMP_RET_LOG - järjestelmäkutsu on ratkaistu ja kirjattu lokiin.

- SECCOMP_RET_ALLOW - järjestelmäkutsu on yksinkertaisesti sallittu.

ptrace on järjestelmäkutsu jäljitysmekanismien toteuttamiseksi tracee-nimisessä prosessissa, jonka avulla voidaan valvoa ja ohjata prosessin suorittamista. Jäljitysohjelma voi tehokkaasti vaikuttaa suoritukseen ja muokata traceen muistirekistereitä. Seccomp-kontekstissa ptracea käytetään, kun SECCOMP_RET_TRACE-tilakoodi laukaisee sen, joten jäljitin voi estää järjestelmäkutsua suorittamasta ja toteuttaa oman logiikkansa.

Seccompin virheet

Ajoittain työskennellessäsi Seccompin kanssa kohtaat erilaisia ​​virheitä, jotka tunnistetaan palautusarvolla SECCOMP_RET_ERRNO. Virheestä ilmoittamiseksi seccomp-järjestelmäkutsu palauttaa -1 arvon 0 sijasta.

Seuraavat virheet ovat mahdollisia:

- EACCESS - Soittaja ei saa soittaa järjestelmäpuhelua. Tämä tapahtuu yleensä, koska sillä ei ole CAP_SYS_ADMIN-oikeuksia tai no_new_privs ei ole asetettu prctl:llä (puhumme tästä myöhemmin);

— EFAULT — välitetyillä argumenteilla (argit seccomp_data-rakenteessa) ei ole kelvollista osoitetta;

— EINVAL — tähän voi olla neljä syytä:

- pyydetty toiminto on tuntematon tai ydin ei tue sitä nykyisessä kokoonpanossa;

- määritetyt liput eivät kelpaa pyydetylle toiminnolle;

-toiminto sisältää BPF_ABS:n, mutta määritetyssä offsetissa on ongelmia, jotka voivat ylittää seccomp_data-rakenteen koon;

-suodattimelle välitettyjen ohjeiden määrä ylittää enimmäismäärän;

— ENOMEM — muisti ei riitä ohjelman suorittamiseen;

- EOPNOTSUPP - toiminto osoitti, että SECCOMP_GET_ACTION_AVAIL-toiminnolla toiminto oli käytettävissä, mutta ydin ei tue palautuksia argumenteissa;

— ESRCH — ongelma toista virtaa synkronoitaessa;

- ENOSYS - SECCOMP_RET_TRACE-toimintoon ei ole liitetty jäljitettä.

prctl on järjestelmäkutsu, jonka avulla käyttäjätilaohjelma voi manipuloida (asettaa ja saada) prosessin tiettyjä näkökohtia, kuten tavun endiannessa, säikeiden nimet, suojattu laskentatila (Seccomp), oikeudet, Perf-tapahtumat jne.

Seccomp saattaa näyttää sinusta hiekkalaatikkotekniikalta, mutta se ei ole sitä. Seccomp on apuohjelma, jonka avulla käyttäjät voivat kehittää hiekkalaatikkomekanismin. Katsotaan nyt kuinka käyttäjävuorovaikutusohjelmat luodaan käyttämällä suodatinta, jota Seccomp-järjestelmäkutsu kutsuu suoraan.

Esimerkki BPF Seccomp -suodattimesta

Tässä näytämme kuinka yhdistää kaksi aiemmin käsiteltyä toimintaa, nimittäin:

— kirjoitamme Seccomp BPF -ohjelman, jota käytetään suodattimena erilaisilla palautuskoodeilla tehdyistä päätöksistä riippuen;

— Lataa suodatin prctl:llä.

Ensin tarvitset otsikot vakiokirjastosta ja Linux-ytimestä:

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

Ennen kuin yritämme tätä esimerkkiä, meidän on varmistettava, että ydin on käännetty siten, että CONFIG_SECCOMP ja CONFIG_SECCOMP_FILTER on asetettu arvoon y. Toimivalla koneella voit tarkistaa tämän seuraavasti:

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

Loput koodista on kaksiosainen install_filter-funktio. Ensimmäinen osa sisältää luettelomme BPF-suodatusohjeista:

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

Ohjeet asetetaan linux/filter.h-tiedostossa määritettyjen BPF_STMT- ja BPF_JUMP-makrojen avulla.
Käydään ohjeet läpi.

- BPF_STMT(BPF_LD + BPF_W + BPF_ABS (offsetof(struct seccomp_data, arch))) - järjestelmä lataa ja kerää BPF_LD:stä sanan BPF_W muodossa, pakettidata sijaitsee kiinteässä siirtymässä BPF_ABS.

- BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, kaari, 0, 3) - tarkistaa käyttämällä BPF_JEQ:ta, onko arkkitehtuuriarvo akkuvakiossa BPF_K yhtä suuri kuin kaari. Jos näin on, hyppää offsetista 0 seuraavaan käskyyn, muussa tapauksessa hyppää offsetissa 3 (tässä tapauksessa) antaakseen virheen, koska kaari ei täsmää.

- BPF_STMT(BPF_LD + BPF_W + BPF_ABS (offsetof(struct seccomp_data, nr))) - Lataa ja kerää BPF_LD:stä sanan BPF_W muodossa, joka on järjestelmän kutsunumero, joka sisältyy BPF_ABS:n kiinteään siirtymään.

— BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, nr, 0, 1) — vertaa järjestelmän kutsunumeroa nro-muuttujan arvoon. Jos ne ovat yhtä suuret, siirtyy seuraavaan käskyyn ja poistaa järjestelmäkutsun käytöstä, muussa tapauksessa sallii järjestelmäkutsun komennolla SECCOMP_RET_ALLOW.

- BPF_STMT(BPF_RET + BPF_K, SECCOMP_RET_ERRNO | (virhe & SECCOMP_RET_DATA)) - lopettaa ohjelman komennolla BPF_RET ja aiheuttaa virheen SECCOMP_RET_ERRNO virhemuuttujan numerolla.

- BPF_STMT(BPF_RET + BPF_K, SECCOMP_RET_ALLOW) - lopettaa ohjelman BPF_RET:llä ja sallii järjestelmäkutsun suorittamisen käyttämällä SECCOMP_RET_ALLOW:a.

SECCOMP ON CBPF
Saatat ihmetellä, miksi käskyluetteloa käytetään käännetyn ELF-objektin tai JIT-käänteen C-ohjelman sijaan.

Tähän on kaksi syytä.

• Ensinnäkin Seccomp käyttää cBPF:ää (classic BPF) eikä eBPF:ää, mikä tarkoittaa: sillä ei ole rekistereitä, vaan ainoastaan ​​akku, joka tallentaa laskennan viimeisen tuloksen, kuten esimerkistä näkyy.

• Toiseksi Seccomp hyväksyy osoittimen BPF-käskyjen joukkoon suoraan eikä mitään muuta. Käyttämämme makrot auttavat yksinkertaisesti määrittämään nämä ohjeet ohjelmoijaystävällisellä tavalla.

Jos tarvitset lisää apua tämän kokoonpanon ymmärtämiseen, harkitse pseudokoodia, joka tekee saman asian:

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

Kun olet määrittänyt suodatinkoodin socket_filter-rakenteessa, sinun on määritettävä sock_fprog, joka sisältää koodin ja suodattimen lasketun pituuden. Tätä tietorakennetta tarvitaan argumenttina prosessin julistamiseksi suoritettavaksi myöhemmin:

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

Install_filter-funktiossa on enää yksi tehtävä - lataa itse ohjelma! Käytämme tätä varten prctl:ää ja otamme PR_SET_SECCOMP vaihtoehtona siirtyäksesi suojattuun tietojenkäsittelytilaan. Sitten käskemme tilaa ladata suodatin käyttämällä SECCOMP_MODE_FILTER, joka sisältyy sock_fprog-tyypin prog-muuttujaan:

  if (prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog)) {
    perror("prctl(PR_SET_SECCOMP)");
    return 1;
  }
  return 0;
}

Lopuksi voimme käyttää install_filter-toimintoamme, mutta sitä ennen meidän on käytettävä prctl-komentoa asettaaksemme PR_SET_NO_NEW_PRIVS nykyiselle suoritukselle ja näin vältetään tilanne, jossa lapsiprosessit saavat enemmän oikeuksia kuin heidän vanhempansa. Tämän avulla voimme tehdä seuraavat prctl-kutsut install_filter-funktiossa ilman pääkäyttäjän oikeuksia.

Nyt voimme kutsua install_filter-funktiota. Estetään kaikki X86-64-arkkitehtuuriin liittyvät kirjoitusjärjestelmäkutsut ja annetaan yksinkertaisesti lupa, joka estää kaikki yritykset. Suodattimen asentamisen jälkeen jatkamme suorittamista käyttämällä ensimmäistä argumenttia:

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

Aloitetaan. Ohjelmamme kääntämiseen voimme käyttää joko clangia tai gcc:tä, joka tapauksessa se on vain main.c-tiedoston kääntämistä ilman erikoisasetuksia:

clang main.c -o filter-write

Kuten todettiin, olemme estäneet kaikki ohjelman merkinnät. Tämän testaamiseksi tarvitset ohjelman, joka tuottaa jotain - se näyttää hyvältä ehdokkaalta. Näin hän yleensä käyttäytyy:

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

Ihana! Tältä kääreohjelman käyttäminen näyttää: Välitämme yksinkertaisesti testattavan ohjelman ensimmäisenä argumenttina:

./filter-write "ls -la"

Kun tämä ohjelma suoritetaan, se tuottaa täysin tyhjän tulosteen. Voimme kuitenkin käyttää stracea nähdäksemme, mitä tapahtuu:

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

Työn tulos lyhenee huomattavasti, mutta sen vastaava osa osoittaa, että tietueet on estetty EPERM-virheellä - sama, jonka määritimme. Tämä tarkoittaa, että ohjelma ei tulosta mitään, koska se ei voi käyttää kirjoitusjärjestelmän kutsua:

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

Nyt ymmärrät, miten Seccomp BPF toimii, ja sinulla on hyvä käsitys siitä, mitä voit tehdä sillä. Mutta etkö haluaisi saavuttaa saman asian eBPF:llä cBPF:n sijaan hyödyntääksesi sen täyden tehon?

Ajatellessaan eBPF-ohjelmia useimmat ihmiset ajattelevat, että he vain kirjoittavat ne ja lataavat ne järjestelmänvalvojan oikeuksin. Vaikka tämä väite on yleensä totta, ydin toteuttaa joukon mekanismeja eBPF-objektien suojaamiseksi eri tasoilla. Näitä mekanismeja kutsutaan BPF LSM ansoksiksi.

BPF LSM ansoja

Tarjotakseen arkkitehtuurista riippumattoman järjestelmätapahtumien valvonnan LSM toteuttaa ansojen käsitteen. Koukkupuhelu on teknisesti samanlainen kuin järjestelmäpuhelu, mutta on järjestelmästä riippumaton ja integroitu infrastruktuuriin. LSM tarjoaa uuden konseptin, jossa abstraktiokerros voi auttaa välttämään ongelmia, joita kohdataan käsiteltäessä järjestelmäkutsuja eri arkkitehtuureissa.

Kirjoitushetkellä ytimessä on seitsemän BPF-ohjelmiin liittyvää koukkua, ja SELinux on ainoa sisäänrakennettu LSM, joka toteuttaa ne.

Trappien lähdekoodi sijaitsee ytimen puussa tiedostossa 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);

Jokaista niistä kutsutaan eri suoritusvaiheissa:

— security_bpf — suorittaa suoritettujen BPF-järjestelmäkutsujen alustavan tarkistuksen;

- security_bpf_map - tarkistaa, milloin ydin palauttaa kartan tiedostokuvaajan;

- security_bpf_prog - tarkistaa, milloin ydin palauttaa eBPF-ohjelman tiedostokuvaajan;

— security_bpf_map_alloc — tarkistaa, onko BPF-karttojen suojakenttä alustettu;

- security_bpf_map_free - tarkistaa, onko suojauskenttä tyhjennetty BPF-kartoissa;

— security_bpf_prog_alloc — tarkistaa, onko suojauskenttä alustettu BPF-ohjelmien sisällä;

- security_bpf_prog_free - tarkistaa, onko suojauskenttä tyhjennetty BPF-ohjelmien sisällä.

Nyt, kun näemme tämän kaiken, ymmärrämme: LSM BPF -sieppaajien ideana on, että ne voivat tarjota suojan jokaiselle eBPF-objektille ja varmistaa, että vain ne, joilla on asianmukaiset oikeudet, voivat suorittaa toimintoja korteilla ja ohjelmilla.

Yhteenveto

Turvallisuus ei ole asia, jota voit toteuttaa yksiselitteisesti kaikkeen, jota haluat suojata. On tärkeää pystyä suojaamaan järjestelmiä eri tasoilla ja eri tavoilla. Usko tai älä, mutta paras tapa turvata järjestelmä on järjestää eri suojaustasot eri paikoista siten, että yhden tason turvallisuuden vähentäminen ei mahdollista pääsyä koko järjestelmään. Ydinkehittäjät ovat tehneet hienoa työtä tarjotakseen meille joukon erilaisia ​​tasoja ja kosketuspisteitä. Toivomme, että olemme antaneet sinulle hyvän käsityksen siitä, mitä tasot ovat ja kuinka BPF-ohjelmia käytetään niiden kanssa työskentelemiseen.

Tietoja kirjoittajista

David Calavera on Netlifyn teknologiajohtaja. Hän työskenteli Dockerin tuessa ja osallistui Runc-, Go- ja BCC-työkalujen sekä muiden avoimen lähdekoodin projektien kehittämiseen. Tunnettu työstään Docker-projekteissa ja Docker-laajennusten ekosysteemin kehittämisessä. David on erittäin intohimoinen liekkikaavioihin ja pyrkii aina optimoimaan suorituskykyä.

Lorenzo Fontana työskentelee avoimen lähdekoodin tiimissä Sysdigissä, jossa hän keskittyy ensisijaisesti Falcoon, Cloud Native Computing Foundation -projektiin, joka tarjoaa kontin ajonaikaisen suojauksen ja poikkeamien havaitsemisen ydinmoduulin ja eBPF:n kautta. Hän on intohimoinen hajautetuista järjestelmistä, ohjelmiston määrittämästä verkkotoiminnasta, Linux-ytimestä ja suorituskyvyn analysoinnista.

» Lisätietoja kirjasta on osoitteessa kustantajan verkkosivuilla
» sisällysluettelo
» Ote

Khabrozhitelille 25% alennus kupongista - Linux

Kun kirjan paperiversio on maksettu, sähköpostiin lähetetään e-kirja.

Lähde: will.com

Lisää kommentti