Aklat na "BPF para sa Linux Monitoring"

Aklat na "BPF para sa Linux Monitoring"Kumusta, mga residente ng Khabro! Ang BPF virtual machine ay isa sa pinakamahalagang bahagi ng Linux kernel. Ang wastong paggamit nito ay magbibigay-daan sa mga inhinyero ng system na makahanap ng mga pagkakamali at malutas kahit na ang pinaka kumplikadong mga problema. Matututuhan mo kung paano magsulat ng mga program na sumusubaybay at nagbabago sa pag-uugali ng kernel, kung paano ligtas na ipatupad ang code upang masubaybayan ang mga kaganapan sa kernel, at marami pang iba. Tutulungan ka nina David Calavera at Lorenzo Fontana na i-unlock ang kapangyarihan ng BPF. Palawakin ang iyong kaalaman sa pag-optimize ng pagganap, networking, seguridad. - Gamitin ang BPF upang subaybayan at baguhin ang pag-uugali ng Linux kernel. - Mag-inject ng code para secure na masubaybayan ang mga kaganapan sa kernel nang hindi kinakailangang i-recompile ang kernel o i-reboot ang system. — Gumamit ng mga maginhawang halimbawa ng code sa C, Go o Python. - Kontrolin sa pamamagitan ng pagmamay-ari ng lifecycle ng BPF program.

Linux Kernel Security, Mga Tampok at Seccomp

Nagbibigay ang BPF ng isang makapangyarihang paraan upang mapalawak ang kernel nang hindi isinasakripisyo ang katatagan, seguridad, o bilis. Para sa kadahilanang ito, naisip ng mga developer ng kernel na magandang ideya na gamitin ang versatility nito upang pahusayin ang paghihiwalay ng proseso sa Seccomp sa pamamagitan ng pagpapatupad ng mga filter ng Seccomp na sinusuportahan ng mga programa ng BPF, na kilala rin bilang Seccomp BPF. Sa kabanatang ito ay ipapaliwanag natin kung ano ang Seccomp at kung paano ito ginagamit. Pagkatapos ay matututunan mo kung paano magsulat ng mga filter ng Seccom gamit ang mga programang BPF. Pagkatapos nito, titingnan natin ang mga built-in na BPF hook na kasama sa kernel para sa mga module ng seguridad ng Linux.

Ang Linux Security Modules (LSM) ay isang balangkas na nagbibigay ng isang hanay ng mga function na maaaring magamit upang ipatupad ang iba't ibang modelo ng seguridad sa isang standardized na paraan. Maaaring gamitin ang LSM nang direkta sa kernel source tree, tulad ng Apparmor, SELinux at Tomoyo.

Magsimula tayo sa pagtalakay sa mga kakayahan ng Linux.

Mga Kakayahan

Ang kakanyahan ng mga kakayahan ng Linux ay kailangan mong magbigay ng pahintulot sa prosesong walang karapatan na magsagawa ng isang partikular na gawain, ngunit nang hindi gumagamit ng suid para sa layuning iyon, o kung hindi man ay gawing pribilehiyo ang proseso, binabawasan ang posibilidad ng pag-atake at pinapayagan ang proseso na magsagawa ng ilang partikular na gawain. Halimbawa, kung ang iyong aplikasyon ay kailangang magbukas ng isang privileged port, sabihin 80, sa halip na patakbuhin ang proseso bilang root, maaari mo lamang itong bigyan ng CAP_NET_BIND_SERVICE na kakayahan.

Isaalang-alang ang isang Go program na pinangalanang main.go:

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

Naghahain ang program na ito ng HTTP server sa port 80 (ito ay isang privileged port). Kadalasan ay pinapatakbo namin ito kaagad pagkatapos ng compilation:

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

Gayunpaman, dahil hindi kami nagbibigay ng mga pribilehiyo sa ugat, ang code na ito ay maglalagay ng error kapag nagbubuklod sa port:

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

Ang capsh (shell manager) ay isang tool na nagpapatakbo ng isang shell na may partikular na hanay ng mga kakayahan.

Sa kasong ito, tulad ng nabanggit na, sa halip na magbigay ng ganap na mga karapatan sa ugat, maaari mong paganahin ang privileged port binding sa pamamagitan ng pagbibigay ng cap_net_bind_service capability kasama ang lahat ng iba pa na nasa programa. Upang gawin ito, maaari naming ilakip ang aming programa sa 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"

Intindihin natin ng kaunti ang pangkat na ito.

  • capsh - gumamit ng capsh bilang isang shell.
  • —caps='cap_net_bind_service+eip cap_setpcap,cap_setuid,cap_setgid+ep' - dahil kailangan naming baguhin ang user (hindi namin gustong tumakbo bilang root), tutukuyin namin ang cap_net_bind_service at ang kakayahang aktwal na baguhin ang user ID mula sa root to nobody, namely cap_setuid at cap_setgid.
  • —keep=1 — gusto naming panatilihin ang mga naka-install na kakayahan kapag lumipat mula sa root account.
  • —user=“nobody” — ang end user na nagpapatakbo ng program ay walang tao.
  • —addamb=cap_net_bind_service — itakda ang pag-clear ng mga kaugnay na kakayahan pagkatapos lumipat mula sa root mode.
  • - -c "./capabilities" - patakbuhin lang ang program.

Ang mga naka-link na kakayahan ay isang espesyal na uri ng mga kakayahan na minana ng mga child program kapag ang kasalukuyang programa ay nagpapatupad ng mga ito gamit ang execve(). Tanging ang mga kakayahan na pinapayagang iugnay, o sa madaling salita, bilang mga kakayahan sa kapaligiran, ang maaaring mamana.

Marahil ay nagtataka ka kung ano ang ibig sabihin ng +eip pagkatapos tukuyin ang kakayahan sa --caps na opsyon. Ang mga flag na ito ay ginagamit upang matukoy na ang kakayahan ay:

-dapat i-activate (p);

- magagamit para sa paggamit (e);

-maaring mamana ng mga proseso ng bata (i).

Dahil gusto naming gumamit ng cap_net_bind_service, kailangan naming gawin ito gamit ang e flag. Pagkatapos ay sisimulan natin ang shell sa command. Tatakbo ito sa binary ng mga kakayahan at kailangan nating markahan ito ng i flag. Sa wakas, gusto naming paganahin ang feature (ginawa namin ito nang hindi binabago ang UID) na may p. Mukhang cap_net_bind_service+eip.

Maaari mong suriin ang resulta gamit ang ss. Paikliin natin ng kaunti ang output upang magkasya sa pahina, ngunit ipapakita nito ang nauugnay na port at user ID maliban sa 0, sa kasong ito 65:

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

Sa halimbawang ito ginamit namin ang capsh, ngunit maaari kang magsulat ng shell gamit ang libcap. Para sa karagdagang impormasyon, tingnan ang man 3 libcap.

Kapag nagsusulat ng mga programa, madalas na hindi alam ng developer nang maaga ang lahat ng mga tampok na kailangan ng programa sa oras ng pagtakbo; Bukod dito, maaaring magbago ang mga feature na ito sa mga bagong bersyon.

Upang mas maunawaan ang mga kakayahan ng aming programa, maaari naming gamitin ang tool na may kakayahang BCC, na nagtatakda ng kprobe para sa cap_capable kernel function:

/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

Makakamit natin ang parehong bagay sa pamamagitan ng paggamit ng bpftrace na may one-liner na kprobe sa cap_capable kernel function:

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

Maglalabas ito ng isang bagay tulad ng sumusunod kung ang mga kakayahan ng aming programa ay pinagana pagkatapos ng 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

Ang ikalimang column ay ang mga kakayahan na kailangan ng proseso, at dahil ang output na ito ay may kasamang non-audit na mga kaganapan, nakikita namin ang lahat ng hindi pag-audit na mga pagsusuri at panghuli ang kinakailangang kakayahan na may audit flag (huling nasa output) na nakatakda sa 1. Capability. Ang isa sa aming interesado ay ang CAP_NET_BIND_SERVICE, ito ay tinukoy bilang isang pare-pareho sa kernel source code sa file kasama ang/uapi/linux/ability.h na may identifier 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">

Kadalasang pinapagana ang mga kakayahan sa runtime para sa mga container gaya ng runC o Docker para payagan silang tumakbo sa unprivileged mode, ngunit pinapayagan lang ang mga ito na mga kakayahan na kailangan para patakbuhin ang karamihan ng mga application. Kapag ang isang application ay nangangailangan ng ilang partikular na kakayahan, maaaring ibigay ng Docker ang mga ito gamit ang --cap-add:

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

Ang utos na ito ay magbibigay sa lalagyan ng kakayahan na CAP_NET_ADMIN, na magbibigay-daan dito na mag-configure ng link ng network upang idagdag ang dummy0 interface.

Ang susunod na seksyon ay nagpapakita kung paano gamitin ang mga tampok tulad ng pag-filter, ngunit ang paggamit ng ibang pamamaraan na nagbibigay-daan sa aming programmatically ipatupad ang aming sariling mga filter.

Seccomp

Ang Seccomp ay nangangahulugang Secure Computing at isang security layer na ipinatupad sa Linux kernel na nagbibigay-daan sa mga developer na i-filter ang ilang mga tawag sa system. Bagama't maihahambing ang Seccomp sa mga kakayahan sa Linux, ang kakayahan nitong pamahalaan ang ilang mga tawag sa system ay ginagawa itong mas nababaluktot kumpara sa kanila.

Ang mga tampok ng Seccom at Linux ay hindi eksklusibo sa isa't isa at kadalasang ginagamit nang magkasama upang makinabang mula sa parehong mga diskarte. Halimbawa, maaaring gusto mong bigyan ang isang proseso ng kakayahan ng CAP_NET_ADMIN ngunit hindi ito pinapayagang tumanggap ng mga koneksyon sa socket, na humaharang sa accept at accept4 na mga tawag sa system.

Ang paraan ng pag-filter ng Seccomp ay batay sa mga filter ng BPF na tumatakbo sa SECCOMP_MODE_FILTER mode, at ang pag-filter ng system call ay ginagawa sa parehong paraan tulad ng para sa mga packet.

Ang mga filter ng Seccomp ay nilo-load gamit ang prctl sa pamamagitan ng PR_SET_SECCOMP na operasyon. Ang mga filter na ito ay nasa anyo ng isang BPF program na isinasagawa para sa bawat Seccom packet na kinakatawan ng seccomp_data structure. Ang istrukturang ito ay naglalaman ng reference na arkitektura, isang pointer sa mga tagubilin ng processor sa oras ng system call, at isang maximum na anim na argumento ng system call, na ipinahayag bilang uint64.

Ito ang hitsura ng seccomp_data structure mula sa kernel source code sa linux/seccomp.h file:

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

Gaya ng nakikita mo mula sa istrukturang ito, maaari naming i-filter ayon sa system call, mga argumento nito, o kumbinasyon ng pareho.

Pagkatapos matanggap ang bawat Seccomp packet, ang filter ay dapat magsagawa ng pagproseso upang makagawa ng pangwakas na desisyon at sabihin sa kernel kung ano ang susunod na gagawin. Ang pangwakas na desisyon ay ipinahayag ng isa sa mga halaga ng pagbabalik (status code).

- SECCOMP_RET_KILL_PROCESS - pinapatay kaagad ang buong proseso pagkatapos mag-filter ng isang tawag sa system na hindi naisagawa dahil dito.

- SECCOMP_RET_KILL_THREAD - tinatapos kaagad ang kasalukuyang thread pagkatapos mag-filter ng isang tawag sa system na hindi naisagawa dahil dito.

— SECCOMP_RET_KILL — alias para sa SECCOMP_RET_KILL_THREAD, naiwan para sa backward compatibility.

- SECCOMP_RET_TRAP - ipinagbabawal ang system call, at ang signal ng SIGSYS (Bad System Call) ay ipinadala sa gawain na tumatawag dito.

- SECCOMP_RET_ERRNO - Hindi naisagawa ang system call, at ang bahagi ng SECCOMP_RET_DATA filter return value ay ipinapasa sa user space bilang errno value. Depende sa sanhi ng error, iba't ibang mga halaga ng errno ang ibinalik. Ang isang listahan ng mga numero ng error ay ibinigay sa susunod na seksyon.

- SECCOMP_RET_TRACE - Ginagamit upang ipaalam ang ptrace tracer gamit ang - PTRACE_O_TRACESECCOMP upang humarang kapag ang isang system call ay naisakatuparan upang makita at kontrolin ang prosesong iyon. Kung ang isang tracer ay hindi nakakonekta, isang error ang ibabalik, ang errno ay nakatakda sa -ENOSYS, at ang system call ay hindi naisakatuparan.

- SECCOMP_RET_LOG - ang system call ay naresolba at naka-log.

- SECCOMP_RET_ALLOW - pinapayagan lang ang system call.

Ang ptrace ay isang tawag sa system upang ipatupad ang mga mekanismo ng pagsubaybay sa isang proseso na tinatawag na tracee, na may kakayahang subaybayan at kontrolin ang pagsasagawa ng proseso. Ang trace program ay maaaring epektibong maimpluwensyahan ang pagpapatupad at baguhin ang memory registers ng tracee. Sa konteksto ng Seccomp, ang ptrace ay ginagamit kapag na-trigger ng SECCOMP_RET_TRACE status code, upang mapigilan ng tracer ang system call mula sa pagpapatupad at pagpapatupad ng sarili nitong lohika.

Mga error sa seccom

Paminsan-minsan, habang nagtatrabaho sa Seccomp, makakatagpo ka ng iba't ibang mga error, na natukoy sa pamamagitan ng isang return value na uri ng SECCOMP_RET_ERRNO. Upang mag-ulat ng isang error, ang seccomp system call ay babalik -1 sa halip na 0.

Posible ang mga sumusunod na error:

- EACCESS - Hindi pinapayagan ang tumatawag na gumawa ng system call. Karaniwan itong nangyayari dahil wala itong mga pribilehiyo ng CAP_SYS_ADMIN o hindi nakatakda ang no_new_privs gamit ang prctl (pag-uusapan natin ito mamaya);

— EFAULT — ang mga naipasa na argumento (args sa seccomp_data structure) ay walang wastong address;

— EINVAL — maaaring mayroong apat na dahilan dito:

-ang hiniling na operasyon ay hindi alam o hindi sinusuportahan ng kernel sa kasalukuyang pagsasaayos;

-ang tinukoy na mga flag ay hindi wasto para sa hiniling na operasyon;

Kasama sa operasyon ang BPF_ABS, ngunit may mga problema sa tinukoy na offset, na maaaring lumampas sa laki ng istraktura ng seccomp_data;

-ang bilang ng mga tagubilin na ipinasa sa filter ay lumampas sa maximum;

— ENOMEM — hindi sapat na memorya upang maisagawa ang programa;

- EOPNOTSUPP - ipinahiwatig ng operasyon na sa SECCOMP_GET_ACTION_AVAIL ang aksyon ay magagamit, ngunit ang kernel ay hindi sumusuporta sa mga pagbabalik sa mga argumento;

— ESRCH — nagkaroon ng problema kapag nagsi-synchronize ng isa pang stream;

- ENOSYS - Walang tracer na naka-attach sa SECCOMP_RET_TRACE na aksyon.

Ang prctl ay isang system call na nagpapahintulot sa isang user-space program na manipulahin (itakda at makuha) ang mga partikular na aspeto ng isang proseso, tulad ng byte endianness, mga pangalan ng thread, secure na computation mode (Seccomp), mga pribilehiyo, mga kaganapan sa Perf, atbp.

Ang Seccom ay maaaring mukhang isang teknolohiya ng sandbox para sa iyo, ngunit hindi. Ang Seccomp ay isang utility na nagbibigay-daan sa mga user na bumuo ng mekanismo ng sandbox. Ngayon tingnan natin kung paano nilikha ang mga programa sa pakikipag-ugnayan ng user gamit ang isang filter na direktang tinatawag ng Seccomp system call.

Halimbawa ng BPF Seccom Filter

Dito namin ipapakita kung paano pagsamahin ang dalawang aksyon na tinalakay kanina, ibig sabihin:

— susulat kami ng programang Seccom BPF, na gagamitin bilang isang filter na may iba't ibang mga return code depende sa mga desisyong ginawa;

— i-load ang filter gamit ang prctl.

Una kailangan mo ng mga header mula sa karaniwang library at ang Linux kernel:

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

Bago subukan ang halimbawang ito, dapat nating tiyakin na ang kernel ay pinagsama-sama sa CONFIG_SECCOMP at CONFIG_SECCOMP_FILTER na nakatakda sa y. Sa isang gumaganang makina maaari mong suriin ito tulad nito:

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

Ang natitirang bahagi ng code ay isang dalawang bahaging install_filter function. Ang unang bahagi ay naglalaman ng aming listahan ng mga tagubilin sa pag-filter ng BPF:

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

Ang mga tagubilin ay itinakda gamit ang BPF_STMT at BPF_JUMP macro na tinukoy sa linux/filter.h file.
Dumaan tayo sa mga tagubilin.

- BPF_STMT(BPF_LD + BPF_W + BPF_ABS (offsetof(struct seccomp_data, arch))) - ang system ay naglo-load at nag-iipon mula sa BPF_LD sa anyo ng salitang BPF_W, ang packet data ay matatagpuan sa isang nakapirming offset na BPF_ABS.

- BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, arch, 0, 3) - sinusuri gamit ang BPF_JEQ kung ang halaga ng arkitektura sa accumulator constant na BPF_K ay katumbas ng arch. Kung gayon, tumalon sa offset 0 sa susunod na pagtuturo, kung hindi man ay tumalon sa offset 3 (sa kasong ito) upang magtapon ng error dahil hindi tumutugma ang arko.

- BPF_STMT(BPF_LD + BPF_W + BPF_ABS (offsetof(struct seccomp_data, nr))) - Naglo-load at nag-iipon mula sa BPF_LD sa anyo ng salitang BPF_W, na siyang system call number na nasa fixed offset ng BPF_ABS.

— BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, nr, 0, 1) — inihahambing ang numero ng system call sa halaga ng nr variable. Kung pantay ang mga ito, lumipat sa susunod na pagtuturo at idi-disable ang system call, kung hindi, pinapayagan ang system call na may SECCOMP_RET_ALLOW.

- BPF_STMT(BPF_RET + BPF_K, SECCOMP_RET_ERRNO | (error & SECCOMP_RET_DATA)) - tinatapos ang program gamit ang BPF_RET at bilang resulta ay gumagawa ng error SECCOMP_RET_ERRNO na may numero mula sa err variable.

- BPF_STMT(BPF_RET + BPF_K, SECCOMP_RET_ALLOW) - tinatapos ang program gamit ang BPF_RET at pinapayagan ang system call na maisagawa gamit ang SECCOMP_RET_ALLOW.

Ang SECCOMP AY CBPF
Maaaring nagtataka ka kung bakit ginagamit ang isang listahan ng mga tagubilin sa halip na isang pinagsama-samang ELF object o isang JIT compiled C program.

Mayroong dalawang dahilan para dito.

• Una, gumagamit ang Seccomp ng cBPF (classic na BPF) at hindi eBPF, na nangangahulugang: wala itong mga rehistro, ngunit isang accumulator lamang upang mag-imbak ng huling resulta ng pagkalkula, tulad ng makikita sa halimbawa.

• Pangalawa, ang Seccomp ay tumatanggap ng isang pointer sa isang hanay ng mga tagubilin ng BPF nang direkta at wala nang iba pa. Ang mga macro na ginamit namin ay nakakatulong lamang na tukuyin ang mga tagubiling ito sa isang programmer-friendly na paraan.

Kung kailangan mo ng karagdagang tulong sa pag-unawa sa pagpupulong na ito, isaalang-alang ang pseudocode na gumagawa ng parehong bagay:

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

Pagkatapos tukuyin ang filter code sa socket_filter structure, kailangan mong tukuyin ang isang sock_fprog na naglalaman ng code at ang kinakalkula na haba ng filter. Ang istraktura ng data na ito ay kinakailangan bilang isang argumento para sa pagdedeklara ng proseso na tatakbo sa ibang pagkakataon:

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

Mayroon lamang isang bagay na natitira upang gawin sa install_filter function - i-load ang program mismo! Upang gawin ito, ginagamit namin ang prctl, na kumukuha ng PR_SET_SECCOMP bilang isang opsyon upang makapasok sa secure na computing mode. Pagkatapos ay sasabihin namin sa mode na i-load ang filter gamit ang SECCOMP_MODE_FILTER, na nakapaloob sa prog variable ng uri ng sock_fprog:

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

Sa wakas, maaari naming gamitin ang aming install_filter function, ngunit bago iyon kailangan naming gumamit ng prctl upang itakda ang PR_SET_NO_NEW_PRIVS para sa kasalukuyang pagpapatupad at sa gayon ay maiwasan ang sitwasyon kung saan ang mga proseso ng bata ay tumatanggap ng higit pang mga pribilehiyo kaysa sa kanilang mga magulang. Sa pamamagitan nito, maaari naming gawin ang mga sumusunod na prctl na tawag sa install_filter function nang walang mga karapatan sa ugat.

Ngayon ay maaari nating tawagan ang install_filter function. I-block natin ang lahat ng write system call na nauugnay sa arkitektura ng X86-64 at magbigay lang ng pahintulot na humaharang sa lahat ng pagtatangka. Pagkatapos i-install ang filter, nagpapatuloy kami sa pagpapatupad gamit ang unang argumento:

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

Magsimula na tayo. Upang i-compile ang aming programa maaari naming gamitin ang alinman sa clang o gcc, alinman sa paraan ay kino-compile lang nito ang main.c file nang walang mga espesyal na opsyon:

clang main.c -o filter-write

Tulad ng nabanggit, hinarangan namin ang lahat ng mga entry sa programa. Upang subukan ito kailangan mo ng isang programa na naglalabas ng isang bagay - tila isang mahusay na kandidato. Ganito siya karaniwang kumikilos:

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

Kahanga-hanga! Ganito ang hitsura gamit ang aming wrapper program: Ipapasa lang namin ang program na gusto naming subukan bilang unang argumento:

./filter-write "ls -la"

Kapag naisakatuparan, ang program na ito ay gumagawa ng ganap na walang laman na output. Gayunpaman, maaari naming gamitin ang strace upang makita kung ano ang nangyayari:

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

Ang resulta ng trabaho ay lubos na pinaikli, ngunit ang kaukulang bahagi nito ay nagpapakita na ang mga talaan ay naharang sa EPERM error - ang parehong na-configure namin. Nangangahulugan ito na ang programa ay hindi naglalabas ng anuman dahil hindi nito ma-access ang write system call:

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

Ngayon naiintindihan mo na kung paano gumagana ang Seccom BPF at may magandang ideya kung ano ang magagawa mo dito. Ngunit hindi mo ba gustong makamit ang parehong bagay sa eBPF sa halip na cBPF upang magamit ang buong kapangyarihan nito?

Kapag nag-iisip tungkol sa mga programang eBPF, iniisip ng karamihan na isusulat lang nila ang mga ito at nilo-load ang mga ito ng mga pribilehiyo ng administrator. Bagama't karaniwang totoo ang pahayag na ito, ang kernel ay nagpapatupad ng isang hanay ng mga mekanismo upang protektahan ang mga bagay na eBPF sa iba't ibang antas. Ang mga mekanismong ito ay tinatawag na BPF LSM traps.

BPF LSM bitag

Upang magbigay ng independiyenteng arkitektura na pagsubaybay sa mga kaganapan sa system, ipinapatupad ng LSM ang konsepto ng mga bitag. Ang isang hook call ay teknikal na katulad ng isang system call, ngunit ito ay independyente ng system at isinama sa imprastraktura. Nagbibigay ang LSM ng bagong konsepto kung saan makakatulong ang abstraction layer na maiwasan ang mga problemang nararanasan kapag nakikitungo sa mga system call sa iba't ibang arkitektura.

Sa oras ng pagsulat, ang kernel ay may pitong kawit na nauugnay sa mga programa ng BPF, at ang SELinux ay ang tanging built-in na LSM na nagpapatupad ng mga ito.

Ang source code para sa mga traps ay matatagpuan sa kernel tree sa file kasama ang/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);

Ang bawat isa sa kanila ay tatawagin sa iba't ibang yugto ng pagpapatupad:

— security_bpf — nagsasagawa ng paunang pagsusuri ng mga naisagawang BPF system call;

- security_bpf_map - sinusuri kung kailan nagbabalik ang kernel ng file descriptor para sa mapa;

- security_bpf_prog - sinusuri kung kailan nagbabalik ang kernel ng file descriptor para sa eBPF program;

— security_bpf_map_alloc — sinusuri kung ang field ng seguridad sa loob ng mga mapa ng BPF ay sinisimulan;

- security_bpf_map_free - sinusuri kung ang field ng seguridad ay na-clear sa loob ng mga mapa ng BPF;

— security_bpf_prog_alloc — sinusuri kung ang field ng seguridad ay sinisimulan sa loob ng mga programa ng BPF;

- security_bpf_prog_free - sinusuri kung ang field ng seguridad ay na-clear sa loob ng mga programa ng BPF.

Ngayon, nakikita ang lahat ng ito, naiintindihan namin: ang ideya sa likod ng mga interceptor ng LSM BPF ay na makakapagbigay sila ng proteksyon sa bawat bagay na eBPF, na tinitiyak na ang mga may naaangkop na pribilehiyo lamang ang makakapagsagawa ng mga operasyon sa mga card at programa.

Buod

Ang seguridad ay hindi isang bagay na maaari mong ipatupad sa isang sukat na angkop sa lahat para sa lahat ng bagay na gusto mong protektahan. Mahalagang maprotektahan ang mga system sa iba't ibang antas at sa iba't ibang paraan. Maniwala ka man o hindi, ang pinakamahusay na paraan upang ma-secure ang isang system ay ang pag-aayos ng iba't ibang antas ng proteksyon mula sa iba't ibang posisyon, upang ang pagbabawas ng seguridad ng isang antas ay hindi nagpapahintulot ng access sa buong system. Ang mga pangunahing developer ay gumawa ng mahusay na trabaho sa pagbibigay sa amin ng isang hanay ng iba't ibang mga layer at touchpoint. Umaasa kaming nabigyan ka namin ng isang mahusay na pag-unawa sa kung ano ang mga layer at kung paano gamitin ang mga programa ng BPF upang gumana sa kanila.

Tungkol sa mga may-akda

David Calavera ay ang CTO sa Netlify. Nagtrabaho siya sa suporta ng Docker at nag-ambag sa pagbuo ng mga tool ng Runc, Go at BCC, pati na rin ang iba pang mga open source na proyekto. Kilala sa kanyang trabaho sa mga proyekto ng Docker at pagbuo ng ecosystem ng Docker plugin. Napakahilig ni David sa mga flame graph at palaging naghahanap upang i-optimize ang pagganap.

Lorenzo Fontana gumagana sa open source team sa Sysdig, kung saan siya ay pangunahing nakatuon sa Falco, isang proyekto ng Cloud Native Computing Foundation na nagbibigay ng container runtime security at anomalya detection sa pamamagitan ng kernel module at eBPF. Siya ay madamdamin tungkol sa mga distributed system, software na tinukoy sa networking, ang Linux kernel, at performance analysis.

» Higit pang mga detalye tungkol sa aklat ay matatagpuan sa website ng publisher
» Talaan ng nilalaman
» Sipi

Para sa Khabrozhiteley 25% na diskwento gamit ang kupon - Linux

Sa pagbabayad ng papel na bersyon ng aklat, isang elektronikong libro ang ipapadala sa pamamagitan ng e-mail.

Pinagmulan: www.habr.com

Magdagdag ng komento