Boken "BPF för övervakning av Linux"

Boken "BPF för övervakning av Linux"Hej, Habrörjare! Virtuell BPF-maskin Ă€r en av de viktigaste komponenterna i Linux-kĂ€rnan. En korrekt anvĂ€ndning av den kommer att hjĂ€lpa systemtekniker att identifiera fel och lösa Ă€ven de mest komplexa problemen. Du kommer att lĂ€ra dig att skapa program som spĂ„rar och modifierar kĂ€rnans beteende, sĂ€kert införa kod för att övervaka hĂ€ndelser i kĂ€rnan och mycket mer. David Calavera och Lorenzo Fontana hjĂ€lper dig att utforska BPF:s möjligheter. Utöka dina kunskaper om prestandaoptimering, nĂ€tverk och sĂ€kerhet. — AnvĂ€nd BPF för att spĂ„ra och modifiera Linux-kĂ€rnans beteende. — Införa kod för sĂ€ker övervakning av hĂ€ndelser i kĂ€rnan — utan att behöva kompilera om kĂ€rnan eller starta om systemet. — AnvĂ€nd praktiska kodexempel i C, Go eller Python. — Hantera situationen genom att behĂ€rska livscykeln för BPF-programmet.

Linux-kÀrnans sÀkerhet, dess möjligheter och Seccomp

BPF erbjuder ett kraftfullt sÀtt att utöka kÀrnan utan att Àventyra stabilitet, sÀkerhet och hastighet. Av denna anledning tyckte kÀrnutvecklarna att det skulle vara bra att utnyttja dess mÄngsidighet för att förbÀttra processisolationen i Seccomp genom att implementera Seccomp-filter som stöds av BPF-program, Àven kÀnda som Seccomp BPF. I detta kapitel kommer vi att förklara vad Seccomp Àr och hur det tillÀmpas. DÀrefter fÄr du veta hur man skriver Seccomp-filter med hjÀlp av BPF-program. Vi kommer ocksÄ att titta pÄ inbyggda BPF-hÀndelser som finns i kÀrnan för Linux-sÀkerhetsmoduler.

Linux sÀkerhetsmoduler (LSM) Àr en plattform som erbjuder en uppsÀttning funktioner som kan tillÀmpas för en standardiserad implementering av olika sÀkerhetsmodeller. LSM kan anvÀndas direkt i kÀrnans kÀllkodstrÀd, exempelvis Apparmor, SELinux och Tomoyo.

LÄt oss börja med att diskutera Linux möjligheter.

Funktioner

KÀrnan av Linux-funktioner ligger i att du behöver ge en icke-privilegierad process tillstÄnd att utföra en viss uppgift, men utan att anvÀnda suid för detta syfte, eller pÄ annat sÀtt göra processen privilegierad, vilket minskar risken för attacker och ger processen möjlighet att utföra specifika uppgifter. Till exempel, om din applikation behöver öppna en privilegierad port, lÄt oss sÀga 80, istÀllet för att köra processen som root kan du helt enkelt ge den möjlighet att anvÀnda CAP_NET_BIND_SERVICE.

LÄt oss övervÀga ett Go-program med namnet main.go:

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

Detta program driver en HTTP-server pÄ port 80 (detta Àr en privilegierad port). Vanligtvis kör vi den direkt efter kompileringen:

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

Men eftersom vi inte ger root-rÀttigheter kommer denna kod att ge ett fel vid bindning av porten:

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

capsh (skalet för hantering) Àr ett verktyg som kör ett skal med en specifik uppsÀttning funktioner.

I det hÀr fallet, som redan nÀmnts, istÀllet för att ge fullstÀndiga root-rÀttigheter kan vi tillÄta bindning av privilegierade portar genom att ge funktionen cap_net_bind_service tillsammans med alla andra som redan finns i programmet. För detta kan vi omge vÄrt program med capsh:

# capsh --caps='cap_net_bind_service+eip cap_setpcap,cap_setuid,cap_setgid+ep' 
   --keep=1 --user="nobody" 
   --addamb=cap_net_bind_service -- -c "./capabilities"

LÄt oss gÄ igenom detta kommando.

  • capsh — vi anvĂ€nder capsh som skal.
  • —caps='cap_net_bind_service+eip cap_setpcap,cap_setuid,cap_setgid+ep' — eftersom vi behöver byta anvĂ€ndare (vi vill inte köra med root-rĂ€ttigheter), anges cap_net_bind_service och rĂ€ttigheten att faktiskt Ă€ndra anvĂ€ndar-ID frĂ„n root till nobody, det vill sĂ€ga cap_setuid och cap_setgid.
  • —keep=1 — vi vill behĂ„lla de instĂ€llda funktionerna nĂ€r vi byter frĂ„n root-kontot.
  • —user="nobody" — den slutgiltiga anvĂ€ndaren som kör programmet kommer att vara nobody.
  • —addamb=cap_net_bind_service — vi specificerar att rensa de relaterade funktionerna efter att vi har bytt frĂ„n root-lĂ€get.
  • — -c "./capabilities" — vi kör helt enkelt programmet.

Relaterade funktioner Àr en speciell typ av funktioner som Àrvs av barnprogram nÀr den nuvarande programmet kör dem med hjÀlp av execve(). Endast funktioner som har tillÄtits som relaterade kan Àrvas, eller med andra ord som miljöfunktioner.

Du kanske undrar vad +eip betyder efter att möjligheten angivits i alternativet —caps. Dessa flaggor anvĂ€nds för att definiera att möjligheten:

-mÄste aktiveras (p);

-Àr tillgÀnglig för anvÀndning (e);

-kan Àrvas av underprocesser (i).

Eftersom vi vill anvÀnda cap_net_bind_service, mÄste vi göra detta med flaggan e. DÀrefter kör vi en shell i kommandot. Som ett resultat kommer binÀrfilen capabilities att startas och vi mÄste mÀrka den med flaggan i. Slutligen vill vi att möjligheten ska aktiveras (det gjorde vi utan att Àndra UID) med hjÀlp av p. Det ser ut som cap_net_bind_service+eip.

Du kan kontrollera resultatet med hjÀlp av ss. Vi kommer att förkorta utdata nÄgot sÄ att den fÄr plats pÄ sidan, men den kommer att visa den relaterade porten och anvÀndar-ID:t, som inte Àr 0, i detta fall 65 534:

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

I detta exempel anvÀnde vi capsh, men du kan skriva en shell med hjÀlp av libcap. För mer information, se man 3 libcap.

NÀr en utvecklare skriver program vet han ofta inte i förvÀg alla möjligheter som programmet behöver under körning; dessutom kan dessa möjligheter Àndras i nya versioner.

För att bÀttre förstÄ möjligheterna i vÄrt program kan vi anvÀnda verktyget BCC capable, som sÀtter kprobe för kÀrnfunktionen cap_capable:

/usr/share/bcc/tools/capable
TIME      UID  PID   TID   COMM               CAP    NAME           AUDIT
10:12:53 0 424     424     systemd-udevd 12 CAP_NET_ADMIN         1
10:12:57 0 1103   1101   timesync        25 CAP_SYS_TIME         1
10:12:57 0 19545 19545 capabilities       10 CAP_NET_BIND_SERVICE 1

Vi kan uppnÄ samma sak genom att anvÀnda bpftrace med en enraders kprobe i kÀrnfunktionen cap_capable:

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

Detta kommer att ge nÄgot i stil med följande, om möjligheterna i vÄrt program har aktiverats efter kprobe:

12:01:56 1000 13524 capabilities 21 0
12:01:56 1000 13524 capabilities 21 0
12:01:56 1000 13524 capabilities 21 0
12:01:56 1000 13524 capabilities 12 0
12:01:56 1000 13524 capabilities 12 0
12:01:56 1000 13524 capabilities 12 0
12:01:56 1000 13524 capabilities 12 0
12:01:56 1000 13524 capabilities 10 1

Den femte kolumnen representerar de möjligheter som processen behöver, och eftersom dessa utdata inkluderar icke-auditloggade hÀndelser, ser vi alla icke-auditkontroller och slutligen den erforderliga möjligheten med auditflaggan (den sista i utdata) instÀlld pÄ 1. Den möjlighet som intresserar oss Àr CAP_NET_BIND_SERVICE, som definieras som en konstant i kÀrnans kÀllkod i filen include/uapi/linux/ability.h med identifieraren 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">

Möjligheterna anvĂ€nds ofta under körning av containrar, som runC eller Docker, för att de ska fungera i ett icke-privilegerat lĂ€ge, men de fĂ„r endast de möjligheter som krĂ€vs för att köra de flesta applikationer. NĂ€r en applikation krĂ€ver specifika möjligheter kan de tillhandahĂ„llas i Docker med hjĂ€lp av —cap-add:

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

Detta kommando kommer att ge containern möjligheten CAP_NET_ADMIN, vilket gör att den kan konfigurera nÀtverkskopplingen för att lÀgga till grÀnssnittet dummy0.

I nÀsta avsnitt visas anvÀndningen av sÄdana möjligheter, som filtrering, men med en annan metod som lÄter oss programmera vÄra egna filter.

Seccomp

Seccomp stĂ„r för Secure Computing, det Ă€r en sĂ€kerhetsnivĂ„ som implementeras i Linux-kĂ€rnan och lĂ„ter utvecklare filtrera specifika systemanrop. Även om Seccomp Ă€r jĂ€mförbart med Linux-möjligheter, gör dess förmĂ„ga att hantera specifika systemanrop det mycket mer flexibelt i jĂ€mförelse med dem.

Seccomp och Linux-möjligheter utesluter inte varandra, de anvÀnds ofta tillsammans för att dra nytta av bÄda tillvÀgagÄngssÀtten. Till exempel kan du vilja ge en process möjligheten CAP_NET_ADMIN, men inte tillÄta den att ta emot anslutningar via socket genom att blockera systemanropen accept och accept4.

Filtreringsmetoden för Seccomp Àr baserad pÄ BPF-filter som fungerar i SECCOMP_MODE_FILTER-lÀge, och filtreringssystemanropen utförs pÄ samma sÀtt som för paket.

Seccomp-filter laddas med hjÀlp av prctl genom operationen PR_SET_SECCOMP. Dessa filter har formen av ett BPF-program som körs för varje Seccomp-paket, som tillhandahÄlls med hjÀlp av strukturen seccomp_data. Denna struktur innehÄller referensarkitektur, en pekare till instruktionerna för processorn under systemanropet och högst sex argument för systemanropet, uttryckt som uint64.

SÄ hÀr ser strukturen seccomp_data ut frÄn kÀllkoden i filen linux/seccomp.h:

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

Som framgÄr av denna struktur kan vi filtrera baserat pÄ systemanropet, dess argument eller deras kombination.

Efter att varje Seccomp-filterpaket har mottagits mÄste det genomföra en bearbetning för att fatta ett slutligt beslut och meddela kÀrnan vad som ska göras hÀrnÀst. Det slutliga beslutet uttrycks med en av de returnerade vÀrdena (statuskoder).

— SECCOMP_RET_KILL_PROCESS — avslutning av hela processen omedelbart efter filtrering av systemanropet, vilket inte genomförs pĂ„ grund av detta.

— SECCOMP_RET_KILL_THREAD — avslutning av den aktuella trĂ„den omedelbart efter filtrering av systemanropet, vilket inte genomförs pĂ„ grund av detta.

— SECCOMP_RET_KILL — alias för SECCOMP_RET_KILL_THREAD, kvarlĂ€mnat för bakĂ„tkompatibilitet.

— SECCOMP_RET_TRAP — systemanropet Ă€r förbjudet, och signalen SIGSYS (Bad System Call) skickas till den anropande uppgiften.

— SECCOMP_RET_ERRNO — systemanropet genomförs inte, och en del av det returnerade vĂ€rdet frĂ„n filtret SECCOMP_RET_DATA överförs till anvĂ€ndarrummet som vĂ€rdet av errno. Beroende pĂ„ felets orsak returneras olika errno-vĂ€rden. Listan över felnummer finns i nĂ€sta avsnitt.

— SECCOMP_RET_TRACE — anvĂ€nds för att informera spĂ„raren ptrace med hjĂ€lp av — PTRACE_O_TRACESECCOMP för att fĂ„nga upp nĂ€r ett systemanrop utförs för att se och kontrollera denna process. Om spĂ„raren inte Ă€r ansluten returneras ett fel, errno stĂ€lls in pĂ„ -ENOSYS, och systemanropet genomförs inte.

— SECCOMP_RET_LOG — systemanropet Ă€r tillĂ„tet och registreras i loggen.

— SECCOMP_RET_ALLOW — systemanropet tillĂ„ts helt enkelt.

ptrace Àr ett systemanrop för att implementera spÄrningsmekanismer i en process, kallad tracee, med möjlighet att observera och kontrollera körningen av processen. SpÄrningsprogrammet kan effektivt pÄverka körningen och Àndra registrerna i tracee. I kontexten av Seccomp anvÀnds ptrace nÀr SECCOMP_RET_TRACE-kod körs, vilket gör att spÄraren kan förhindra att systemanropet genomförs och implementera sin egen logik.

Seccomp-fel

Ibland, nÀr du arbetar med Seccomp, kommer du att stöta pÄ olika fel som identifieras av returvÀrdet av typen SECCOMP_RET_ERRNO. För att rapportera ett fel kommer systemanropet seccomp att returnera -1 istÀllet för 0.

Följande fel kan förekomma:

— EACCESS — den anropande parten har inte behörighet att göra systemanrop. Detta sker vanligtvis pĂ„ grund av att den saknar CAP_SYS_ADMIN-behörigheter eller att no_new_privs inte har stĂ€llts in med hjĂ€lp av prctl (vi kommer att diskutera detta senare);

— EFAULT — de angivna parametrarna (args i strukturen seccomp_data) har ingen giltig adress;

— EINVAL — hĂ€r kan det finnas fyra orsaker:

- den begÀrda operationen Àr okÀnd eller stöds inte av kÀrnan i den aktuella konfigurationen;

- de angivna flaggorna Àr ogiltiga för den begÀrda operationen;

- operationen inkluderar BPF_ABS, men det finns problem med det angivna offsetet som kan överskrida storleken pÄ strukturen seccomp_data;

- antalet instruktioner som skickas till filtret överskrider maximalt tillÄten;

— ENOMEM — inte tillrĂ€ckligt med minne för att köra programmet;

— EOPNOTSUPP — operationen angav att SECCOMP_GET_ACTION_AVAIL Ă„tgĂ€rden var tillgĂ€nglig, men kĂ€rnan stöder inte Ă„tervĂ€ndande i argumenten;

— ESRCH — det uppstod ett problem med att synkronisera en annan trĂ„d;

— ENOSYS — ingen spĂ„rningsprogram kopplad till SECCOMP_RET_TRACE Ă„tgĂ€rden.

prctl Àr ett systemanrop som lÄter program i anvÀndarlÀge hantera (stÀlla in och hÀmta) specifika aspekter av processen, sÄsom byteordning, trÄdnamn, skyddade berÀkningslÀgen (Seccomp), behörigheter, prestanda hÀndelser osv.

Seccomp kan verka som en sandlÄdeteknik, men sÄ Àr det inte. Seccomp Àr ett verktyg som tillÄter anvÀndare att utveckla mekanismen för sandlÄda. LÄt oss nu titta pÄ hur program för anvÀndarinteraktion skapas med hjÀlp av en filter som anropas direkt av systemanropet Seccomp.

Exempel pÄ BPF Seccomp-filter

HÀr visar vi hur vi kopplar samman de tvÄ ÄtgÀrderna som diskuterats tidigare, nÀmligen:

— vi skriver ett Seccomp BPF-program som kommer att tillĂ€mpas som en filter med olika returkoder beroende pĂ„ de beslut som fattas;

— vi laddar upp filtret med hjĂ€lp av prctl.

Först behövs rubriker frÄn standardbiblioteket och Linux-kÀrnan:

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

Innan vi försöker köra detta exempel mÄste vi sÀkerstÀlla att kÀrnan Àr kompilera med CONFIG_SECCOMP och CONFIG_SECCOMP_FILTER satta pÄ y. Detta kan kontrolleras pÄ en arbetsmaskin sÄ hÀr:

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

Den andra delen av koden utgörs av funktionen install_filter, som bestÄr av tvÄ delar. Den första delen innehÄller vÄr lista med BPF-filterinstruktioner:

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

Instruktionerna sÀtts in med makron BPF_STMT och BPF_JUMP, definierade i filen linux/filter.h.
LÄt oss gÄ igenom instruktionerna.

— BPF_STMT(BPF_LD + BPF_W + BPF_ABS (offsetof(struct seccomp_data, arch))) — systemet laddar och ackumulerar med BPF_LD i form av ett ord BPF_W, paketdata finns pĂ„ ett fast offset BPF_ABS.

— BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, arch, 0, 3) — kontrollerar med BPF_JEQ om vĂ€rdet av arkitekturen i konstantens ackumulator BPF_K Ă€r lika med arch. Om sĂ„ Ă€r fallet, gĂ„r den vidare med offset 0 till nĂ€sta instruktion, annars hoppar den med offset 3 (i detta fall), eftersom arch inte matchar.

— BPF_STMT(BPF_LD + BPF_W + BPF_ABS (offsetof(struct seccomp_data, nr))) — laddar och ackumulerar med BPF_LD i form av ett ord BPF_W, vilket Ă€r nummer pĂ„ systemanropet som finns pĂ„ ett fast offset BPF_ABS.

— BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, nr, 0, 1) — jĂ€mför nummer pĂ„ systemanropet med vĂ€rdet av variabeln nr. Om de Ă€r lika gĂ„r den vidare till nĂ€sta instruktion och förhindrar systemanropet; annars tillĂ„ter den systemanropet med SECCOMP_RET_ALLOW.

— BPF_STMT(BPF_RET + BPF_K, SECCOMP_RET_ERRNO | (error & SECCOMP_RET_DATA)) — avslutar programmet med BPF_RET och returnerar ett fel SECCOMP_RET_ERRNO med numret frĂ„n variabeln err.

— BPF_STMT(BPF_RET + BPF_K, SECCOMP_RET_ALLOW) — avslutar programmet med BPF_RET och tillĂ„ter exekvering av systemanropet med SECCOMP_RET_ALLOW.

SECCOMP — ÄR CBPF
Kanske undrar du varför en lista med instruktioner anvÀnds istÀllet för ett ELFO-objekt som kompilerats eller ett C-program som kompilerats med JIT.

Det finns tvÄ skÀl till detta.

‱ För det första tillĂ€mpar Seccomp cBPF (klassisk BPF) istĂ€llet för eBPF, vilket innebĂ€r: det har inga register utan endast en ackumulator för att lagra det senaste resultatet av berĂ€kningarna, som man kan se i exemplet.

‱ För det andra tar Seccomp en pekare till en BPF-instruktionsarray direkt och inget mer. De makron vi anvĂ€nde hjĂ€lper bara till att specificera dessa instruktioner i ett programmerarvĂ€nligt format.

Om du behöver mer hjÀlp för att förstÄ denna sammansÀttning, övervÀg pseudokoden som gör samma sak:

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

Efter att ha definierat filterkoden i socket_filter-strukturen mÄste vi definiera sock_fprog som innehÄller koden och den berÀknade lÀngden pÄ filtret. Denna datastruktur Àr nödvÀndig som argument för att deklarera processens arbete vidare:

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

Det finns bara en sak kvar att göra i funktionen install_filter — ladda sjĂ€lva programmet! För detta anvĂ€nder vi prctl, med PR_SET_SECCOMP som alternativ för att gĂ„ in i skyddade berĂ€kningslĂ€gen. Vi anger sedan lĂ€get att ladda filtret med hjĂ€lp av SECCOMP_MODE_FILTER, som finns i variabeln prog av typen sock_fprog:

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

Slutligen kan vi dra nytta av vÄr funktion install_filter, men innan dess mÄste vi aktivera prctl för att stÀlla in PR_SET_NO_NEW_PRIVS för den aktuella körningen och pÄ sÄ sÀtt undvika att barnprocesserna fÄr bredare privilegier Àn förÀldrarna. Under denna process kan vi göra följande prctl-anrop i funktionen install_filter utan root-behörighet.

Nu kan vi kalla funktionen install_filter. Vi blockerar alla systemanrop write relaterade till X86-64-arkitekturen och ger helt enkelt tillÄtelse, vilket blockerar alla försök. Efter att filtret har installerats fortsÀtter vi med att anvÀnda den första argumentet:

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

LÄt oss börja. För att kompilera vÄrt program kan vi anvÀnda antingen clang eller gcc; i vilket fall som helst handlar det bara om att kompilera filen main.c utan speciella alternativ:

clang main.c -o filter-write

Som nĂ€mnts har vi blockerat alla skrivningar i programmet. För att kontrollera detta behövs ett program som skriver ut nĂ„got — ls verkar vara en bra kandidat. SĂ„ hĂ€r fungerar det vanligtvis:

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

Perfekt! SÄ hÀr ser det ut nÀr vi anvÀnder vÄrt skalprogram: vi skickar helt enkelt programmet vi vill testa som det första argumentet:

.\/filter-write "ls -la"

Efter att ha körts ger detta program helt tom utdata. Vi kan dock anvÀnda strace för att se vad som hÀnder:

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

Resultatet Ă€r starkt förkortat, men den relevanta delen visar att skrivningarna blockeras med felet EPERM — det som vi konfigurerade. Det betyder att programmet inte skriver ut nĂ„got eftersom det inte kan fĂ„ tillgĂ„ng till systemanropet write:

[pid 25099] write(2, "ls: ", 4) = -1 EPERM (ÅtgĂ€rd inte tillĂ„ten)
[pid 25099] write(2, "write error", 11) = -1 EPERM (ÅtgĂ€rd inte tillĂ„ten)
[pid 25099] write(2, "n", 1) = -1 EPERM (ÅtgĂ€rd inte tillĂ„ten)

Nu förstÄr du hur Seccomp BPF fungerar, och du har en bra uppfattning om vad som kan göras med det. Men skulle det inte vara bÀttre att Ästadkomma samma sak med eBPF istÀllet för cBPF för att utnyttja dess hela potential?

NĂ€r det gĂ€ller eBPF-program tĂ€nker de flesta att de bara skriver dem och laddar upp dem med administratörsrĂ€ttigheter. Även om detta pĂ„stĂ„ende oftast Ă€r sant, implementerar kĂ€rnan en uppsĂ€ttning mekanismer för att skydda eBPF-objekt pĂ„ olika nivĂ„er. Dessa mekanismer kallas BPF LSM-traps.

BPF LSM-fÀllor

För att sÀkerstÀlla en arkitekturoberoende kontroll av systemhÀndelser implementerar LSM begreppet fÀllor. Tekniskt sett Àr ett fÀllanrop liknande ett systemanrop, men det Àr oberoende av systemet och integrerat med infrastrukturen. LSM erbjuder ett nytt koncept dÀr abstraheringsnivÄn kan hjÀlpa till att undvika problem som uppstÄr vid arbete med systemanrop pÄ olika arkitekturer.

Vid tidpunkten för skrivandet av boken hade kÀrnan sju fÀllor kopplade till BPF-program, och SELinux Àr den enda inbyggda LSM som implementerar dem.

KÀllkoden för fÀllorna finns i kÀrnans trÀd i filen 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);

Varje av dem kommer att anropas i olika steg av utförandet:

— security_bpf — genomför en första kontroll av de systemanrop som utförs av BPF;

— security_bpf_map — kontrollerar nĂ€r kĂ€rnan returnerar en filbeskrivare för en karta;

— security_bpf_prog — kontrollerar nĂ€r kĂ€rnan returnerar en filbeskrivare för ett eBPF-program;

— security_bpf_map_alloc — kontrollerar om sĂ€kerhetsfĂ€ltet inuti BPF-kartor Ă€r initierat;

— security_bpf_map_free — kontrollerar om sĂ€kerhetsfĂ€ltet inuti BPF-kartor rensas;

— security_bpf_prog_alloc — kontrollerar om sĂ€kerhetsfĂ€ltet inuti BPF-program Ă€r initierat;

— security_bpf_prog_free — kontrollerar om sĂ€kerhetsfĂ€ltet inuti BPF-program rensas.

Nu, nÀr vi ser allt detta, förstÄr vi: Idén med LSM BPF interceptorer Àr att de kan skydda varje eBPF-objekt genom att sÀkerstÀlla att endast de som har de nödvÀndiga privilegierna kan utföra operationer pÄ kartor och program.

Sammanfattning

SÀkerhet Àr inget som du kan implementera universellt för allt du vill skydda. Det Àr viktigt att kunna skydda system pÄ olika nivÄer och pÄ olika sÀtt. Tro pÄ det eller inte, det bÀsta sÀttet att sÀkra ett system Àr att organisera olika sÀkerhetsnivÄer frÄn olika positioner sÄ att en nedgradering av sÀkerheten pÄ en nivÄ inte tillÄter Ätkomst till hela systemet. KÀrnutvecklarna har gjort ett stort arbete för att ge oss en uppsÀttning olika lager och kontaktpunkter. Vi hoppas att vi har gett dig en bra förstÄelse för vad lager Àr och hur man anvÀnder BPF-program för att arbeta med dem.

Om författarna

David Calavera Àr teknisk direktör pÄ Netlify. Han har arbetat inom support för Docker och varit involverad i utvecklingen av Runc, Go och BCC-verktyg, samt andra öppna kÀllkodsprojekt. KÀnd för sitt arbete med Docker-projekt och utvecklingen av Docker-plugin-ecosystemet. David Àlskar flamescanning och strÀvar alltid efter att optimera prestanda.

Lorenzo Fontana Arbetar i ett team av programvaruutvecklare med öppen kĂ€llkod pĂ„ Sysdig, dĂ€r han frĂ€mst arbetar med Falco – ett projekt frĂ„n Cloud Native Computing Foundation som sĂ€kerstĂ€ller sĂ€kerhet för containerkörningsmiljöer och upptĂ€ckte avvikelser genom kĂ€rnmodul och eBPF. Han Ă€r passionerad om distribuerade system, programvarudefinierade nĂ€tverk, Linux-kĂ€rnan och prestandaanalys.

” För mer information om boken kan man lĂ€sa pĂ„ förlagets webbplats
» InnehÄllsförteckning
» Utdrag

För Habr-följare Ă€r det 25 % rabatt med koden — Linux

Vid betalning av den tryckta versionen av boken skickas en e-bok till e-post.

KĂ€lla: habr.com

Köp pĂ„litlig webbhotell med DDoS-skydd, VPS VDS-servrar đŸ”„ Köp pĂ„litlig webbhotell med DDoS-skydd, VPS VDS-servrar | ProHoster