«Linux мониторингіне арналған BPF» кітабы

«Linux мониторингіне арналған BPF» кітабыСәлем, Хабро тұрғындары! BPF виртуалды машинасы Linux ядросының ең маңызды құрамдастарының бірі болып табылады. Оны дұрыс пайдалану жүйе инженерлеріне ақауларды табуға және тіпті ең күрделі мәселелерді шешуге мүмкіндік береді. Сіз ядро ​​әрекетін бақылайтын және өзгертетін бағдарламаларды жазуды, ядродағы оқиғаларды бақылау үшін кодты қауіпсіз енгізу жолын және т.б. үйренесіз. Дэвид Калавера мен Лоренцо Фонтана сізге BPF қуатын ашуға көмектеседі. Өнімділікті оңтайландыру, желіні құру, қауіпсіздік туралы біліміңізді кеңейтіңіз. - Linux ядросының әрекетін бақылау және өзгерту үшін BPF пайдаланыңыз. - Ядроны қайта құрастырмай немесе жүйені қайта жүктемей, ядро ​​оқиғаларын қауіпсіз бақылау үшін кодты енгізіңіз. — C, Go немесе Python тіліндегі ыңғайлы код мысалдарын пайдаланыңыз. - BPF бағдарламасының өмірлік циклін иелену арқылы бақылауды алыңыз.

Linux ядросының қауіпсіздігі, оның мүмкіндіктері және Seccomp

BPF тұрақтылықты, қауіпсіздікті немесе жылдамдықты жоғалтпай ядроны кеңейтудің қуатты әдісін ұсынады. Осы себепті ядроны әзірлеушілер Seccomp BPF деп те аталатын BPF бағдарламалары қолдайтын Seccomp сүзгілерін енгізу арқылы Seccomp жүйесінде процесті оқшаулауды жақсарту үшін оның әмбебаптығын пайдалану жақсы идея деп ойлады. Бұл тарауда Seccomp деген не екенін және оның қалай қолданылатынын түсіндіреміз. Содан кейін BPF бағдарламалары арқылы Seccomp сүзгілерін жазуды үйренесіз. Осыдан кейін біз Linux қауіпсіздік модульдері үшін ядроға кіретін кірістірілген BPF ілмектерін қарастырамыз.

Linux қауіпсіздік модульдері (LSM) әртүрлі қауіпсіздік үлгілерін стандартталған түрде жүзеге асыру үшін пайдалануға болатын функциялар жинағын қамтамасыз ететін құрылым болып табылады. LSM Apparmor, SELinux және Tomoyo сияқты ядроның бастапқы тармағында тікелей пайдаланылуы мүмкін.

Linux мүмкіндіктерін талқылаудан бастайық.

Ерекшеліктері

Linux мүмкіндіктерінің мәні мынада: белгілі бір тапсырманы орындау үшін артықшылықсыз процесс рұқсатын беру керек, бірақ ол үшін suid қолданбай немесе басқа жолмен процесті артықшылыққа ие етіп, шабуыл мүмкіндігін азайтып, процеске белгілі бір тапсырмаларды орындауға мүмкіндік береді. Мысалы, қолданбаңызға артықшылықты портты ашу қажет болса, айталық 80, процесті түбір ретінде іске қосудың орнына, оған жай ғана CAP_NET_BIND_SERVICE мүмкіндігін бере аласыз.

main.go деп аталатын Go бағдарламасын қарастырайық:

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

Бұл бағдарлама 80 портындағы HTTP серверіне қызмет көрсетеді (бұл артықшылықты порт). Әдетте біз оны құрастырудан кейін бірден іске қосамыз:

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

Дегенмен, біз түбірлік артықшылықтарды бермегендіктен, бұл код портты байланыстыру кезінде қате жібереді:

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

capsh (қабық менеджері) — белгілі бір мүмкіндіктер жиынтығы бар қабықты іске қосатын құрал.

Бұл жағдайда, бұрын айтылғандай, толық түбірлік құқықтарды берудің орнына, бағдарламада бұрыннан бар барлық басқа нәрселермен бірге cap_net_bind_service мүмкіндігін қамтамасыз ету арқылы артықшылықты порт байланыстыруды қосуға болады. Ол үшін бағдарламамызды бас әріппен қоса аламыз:

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

Бұл команданы аздап түсінейік.

  • capsh - қабық ретінде capsh пайдаланыңыз.
  • —caps='cap_net_bind_service+eip cap_setpcap,cap_setuid,cap_setgid+ep' - пайдаланушыны өзгерту керек болғандықтан (біз root ретінде іске қосқымыз келмейді), біз cap_net_bind_service және пайдаланушы идентификаторын нақты өзгерту мүмкіндігін көрсетеміз. түбірі ешкімге, атап айтқанда cap_setuid және cap_setgid.
  • —keep=1 — біз түбірлік тіркелгіден ауысқан кезде орнатылған мүмкіндіктерді сақтағымыз келеді.
  • —user=“nobody” — бағдарламаны іске қосатын соңғы пайдаланушы ешкім болмайды.
  • —addamb=cap_net_bind_service — түбірлік режимнен ауысқаннан кейін қатысты мүмкіндіктерді тазалауды орнатыңыз.
  • - -c "./capabilities" - жай ғана бағдарламаны іске қосыңыз.

Байланыстырылған мүмкіндіктер - ағымдағы бағдарлама оларды execve() арқылы орындаған кезде еншілес бағдарламалар мұраға алатын мүмкіндіктердің ерекше түрі. Тек байланыстыруға рұқсат етілген мүмкіндіктер немесе басқаша айтқанда, орта мүмкіндіктері ретінде мұраға алынуы мүмкін.

Мүмкін сіз --caps опциясында мүмкіндікті көрсеткеннен кейін +eip нені білдіретінін сұрайтын шығарсыз. Бұл жалаушалар мүмкіндіктерді анықтау үшін пайдаланылады:

-белсенді болуы керек (p);

- пайдалануға қолжетімді (e);

- еншілес процестермен тұқым қуалауы мүмкін (i).

Біз cap_net_bind_service пайдаланғымыз келгендіктен, біз мұны e жалауымен жасауымыз керек. Содан кейін командадағы қабықты бастаймыз. Бұл екілік мүмкіндіктерді іске қосады және біз оны i жалауымен белгілеуіміз керек. Соңында, біз мүмкіндіктің қосылғанын қалаймыз (біз мұны UID өзгертпей жасадық) p. Бұл cap_net_bind_service+eip сияқты.

Нәтижені ss арқылы тексеруге болады. Бетке сәйкестендіру үшін шығысты сәл қысқартайық, бірақ ол байланысты портты және 0-ден басқа пайдаланушы идентификаторын көрсетеді, бұл жағдайда 65:

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

Бұл мысалда біз capsh қолдандық, бірақ libcap көмегімен қабықша жазуға болады. Қосымша ақпаратты man 3 libcap бөлімінен қараңыз.

Бағдарламаларды жазу кезінде әзірлеуші ​​​​көбінесе бағдарламаның орындалу уақытында қажет барлық мүмкіндіктерді алдын ала білмейді; Сонымен қатар, бұл мүмкіндіктер жаңа нұсқаларда өзгеруі мүмкін.

Бағдарламамыздың мүмкіндіктерін жақсырақ түсіну үшін cap_capable ядро ​​функциясы үшін kprobe орнататын BCC қабілетті құралын аламыз:

/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

cap_capable ядро ​​функциясында бір сызықты kprobe бар bpftrace пайдалану арқылы бірдей нәрсеге қол жеткізе аламыз:

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

Егер бағдарламамыздың мүмкіндіктері 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

Бесінші баған процеске қажетті мүмкіндіктер болып табылады және бұл нәтиже аудиттен тыс оқиғаларды қамтитындықтан, біз барлық аудиторлық емес тексерулерді және соңында аудит жалаушасымен (шығыстағы соңғы) 1-ге орнатылған қажетті мүмкіндікті көреміз. бізді қызықтыратыны CAP_NET_BIND_SERVICE, ол 10 идентификаторы бар include/uapi/linux/ability.h файлындағы ядроның бастапқы кодында тұрақты мән ретінде анықталады:

/* Allows binding to TCP/UDP sockets below 1024 */
/* Allows binding to ATM VCIs below 32 */
#define CAP_NET_BIND_SERVICE 10<source lang="go">

Мүмкіндіктер runC немесе Docker сияқты контейнерлер үшін артықшылықсыз режимде жұмыс істеуге мүмкіндік беру үшін жиі жұмыс уақытында қосылады, бірақ оларға көптеген қолданбаларды іске қосу үшін қажетті мүмкіндіктерге ғана рұқсат етіледі. Қолданба белгілі бір мүмкіндіктерді қажет еткенде, Docker оларды --cap-add көмегімен қамтамасыз ете алады:

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

Бұл пәрмен контейнерге CAP_NET_ADMIN мүмкіндігін береді, бұл оған dummy0 интерфейсін қосу үшін желі сілтемесін конфигурациялауға мүмкіндік береді.

Келесі бөлім сүзу сияқты мүмкіндіктерді қалай пайдалану керектігін көрсетеді, бірақ жеке сүзгілерімізді бағдарламалы түрде жүзеге асыруға мүмкіндік беретін басқа әдісті пайдалану.

Seccomp

Seccomp – Secure Computing дегенді білдіреді және әзірлеушілерге белгілі бір жүйелік қоңырауларды сүзуге мүмкіндік беретін Linux ядросында енгізілген қауіпсіздік деңгейі. Seccomp мүмкіндіктері бойынша Linux-пен салыстыруға болатынына қарамастан, оның белгілі бір жүйелік қоңырауларды басқару мүмкіндігі олармен салыстырғанда оны әлдеқайда икемді етеді.

Seccomp және Linux мүмкіндіктері бір-бірін жоққа шығармайды және екі тәсілдің де пайдасын алу үшін жиі бірге пайдаланылады. Мысалы, процеске CAP_NET_ADMIN мүмкіндігін бергіңіз келуі мүмкін, бірақ жүйе қоңырауларын қабылдау және қабылдау4 бұғаттап, сокет қосылымдарын қабылдауға рұқсат бермеуіңіз мүмкін.

Seccomp сүзу әдісі SECCOMP_MODE_FILTER режимінде жұмыс істейтін BPF сүзгілеріне негізделген және жүйелік шақыруды сүзгілеу пакеттер сияқты орындалады.

Seccomp сүзгілері PR_SET_SECCOMP әрекеті арқылы prctl арқылы жүктеледі. Бұл сүзгілер seccomp_data құрылымымен ұсынылған әрбір Seccomp пакеті үшін орындалатын BPF бағдарламасының пішінін алады. Бұл құрылымда анықтамалық архитектура, жүйелік шақыру кезінде процессор нұсқауларына арналған көрсеткіш және uint64 ретінде көрсетілген ең көбі алты жүйелік шақыру аргументтері бар.

Linux/seccomp.h файлындағы ядроның бастапқы кодынан seccomp_data құрылымы осылай көрінеді:

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

Бұл құрылымнан көріп отырғаныңыздай, біз жүйелік шақыру, оның аргументтері немесе екеуінің комбинациясы бойынша сүзгілеуге болады.

Әрбір Seccomp пакетін алғаннан кейін сүзгі соңғы шешім қабылдау үшін өңдеуді орындауы керек және ядроға әрі қарай не істеу керектігін айтуы керек. Соңғы шешім қайтару мәндерінің (күй кодтары) бірімен көрсетіледі.

- SECCOMP_RET_KILL_PROCESS - осыған байланысты орындалмаған жүйелік шақыруды сүзгеннен кейін бірден бүкіл процесті жояды.

- SECCOMP_RET_KILL_THREAD - осыған байланысты орындалмаған жүйелік шақыруды сүзгеннен кейін бірден ағымдағы ағынды тоқтатады.

— SECCOMP_RET_KILL — SECCOMP_RET_KILL_THREAD үшін бүркеншік ат, кері үйлесімділік үшін қалдырылған.

- SECCOMP_RET_TRAP - жүйелік шақыруға тыйым салынады және оны шақыратын тапсырмаға SIGSYS (нашар жүйе қоңырауы) сигналы жіберіледі.

- SECCOMP_RET_ERRNO - Жүйелік шақыру орындалмайды және SECCOMP_RET_DATA сүзгісінің қайтару мәнінің бір бөлігі пайдаланушы кеңістігіне қате мәні ретінде жіберіледі. Қатенің себебіне байланысты әртүрлі қате мәндері қайтарылады. Қате нөмірлерінің тізімі келесі бөлімде берілген.

- SECCOMP_RET_TRACE - Бұл процесті көру және басқару үшін жүйелік шақыру орындалған кезде ұстап тұру үшін - PTRACE_O_TRACESECCOMP арқылы ptrace трекеріне хабарлау үшін пайдаланылады. Егер тресер қосылмаса, қате қайтарылады, errno мәні -ENOSYS мәніне орнатылады және жүйелік шақыру орындалмайды.

- SECCOMP_RET_LOG - жүйелік шақыру шешілді және журналға жазылады.

- SECCOMP_RET_ALLOW - жүйелік қоңырауға жай ғана рұқсат етілген.

ptrace - процестің орындалуын бақылау және бақылау мүмкіндігі бар tracee деп аталатын процесте бақылау механизмдерін енгізуге арналған жүйелік шақыру. Бақылау бағдарламасы орындауға тиімді әсер ете алады және бақылаудың жад регистрлерін өзгерте алады. Seccomp мәтінмәнінде ptrace SECCOMP_RET_TRACE күй коды арқылы іске қосылғанда пайдаланылады, сондықтан бақылаушы жүйелік шақыруды орындауға және өз логикасын жүзеге асыруға жол бермейді.

Seccomp қателері

Кейде Seccomp-пен жұмыс істеу кезінде сіз SECCOMP_RET_ERRNO түріндегі қайтару мәнімен анықталатын әртүрлі қателерге тап боласыз. Қатені хабарлау үшін, seccomp жүйелік қоңырауы 1 орнына -0 қайтарады.

Келесі қателер болуы мүмкін:

- EACCESS - Қоңырау шалушыға жүйелік қоңырау шалуға рұқсат етілмейді. Бұл әдетте оның CAP_SYS_ADMIN артықшылықтары жоқ немесе prctl арқылы no_new_privs орнатылмағандықтан болады (бұл туралы кейінірек айтатын боламыз);

— EFAULT — жіберілген аргументтер (seccomp_data құрылымындағы аргтар) жарамды мекенжайға ие емес;

— EINVAL — мұнда төрт себеп болуы мүмкін:

-сұралған операция белгісіз немесе ағымдағы конфигурацияда ядро ​​қолдамайды;

-көрсетілген жалаулар сұралған операция үшін жарамсыз;

-операцияға BPF_ABS кіреді, бірақ көрсетілген ығысумен проблемалар бар, олар seccomp_data құрылымының өлшемінен асуы мүмкін;

-сүзгіге берілген нұсқаулар саны максимумнан асады;

— ENOMEM — программаны орындау үшін жады жеткіліксіз;

- EOPNOTSUPP - операция SECCOMP_GET_ACTION_AVAIL көмегімен әрекет қол жетімді екенін көрсетті, бірақ ядро ​​​​аргументтердегі қайтаруды қолдамайды;

— ESRCH — басқа ағынды синхрондау кезінде ақаулық орын алды;

- ENOSYS - SECCOMP_RET_TRACE әрекетіне тіркелген бақылаушы жоқ.

prctl — пайдаланушы-кеңістік бағдарламасына процестің нақты аспектілерін өңдеуге (орнатуға және алуға) мүмкіндік беретін жүйелік шақыру, мысалы, байт ағыны, ағын атаулары, қауіпсіз есептеу режимі (Seccomp), артықшылықтар, Perf оқиғалары және т.б.

Seccomp сізге құмсалғыш технологиясы болып көрінуі мүмкін, бірақ олай емес. Seccomp — пайдаланушыларға құмсалғыш механизмін жасауға мүмкіндік беретін қызметтік бағдарлама. Енді Seccomp жүйелік шақыруымен тікелей шақырылатын сүзгіні қолдану арқылы пайдаланушының өзара әрекеттесу бағдарламалары қалай жасалатынын қарастырайық.

BPF Seccomp сүзгісінің мысалы

Мұнда біз бұрын талқыланған екі әрекетті біріктіру жолын көрсетеміз, атап айтқанда:

— біз Seccomp BPF бағдарламасын жазамыз, ол қабылданған шешімдерге байланысты әртүрлі қайтару кодтары бар сүзгі ретінде пайдаланылады;

— prctl арқылы сүзгіні жүктеңіз.

Алдымен сізге стандартты кітапхана мен Linux ядросының тақырыптары қажет:

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

Бұл мысалды қолданбас бұрын, ядроның CONFIG_SECCOMP және CONFIG_SECCOMP_FILTER y мәніне орнатылғанына көз жеткізуіміз керек. Жұмыс істейтін машинада мұны келесідей тексеруге болады:

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

Кодтың қалған бөлігі екі бөліктен тұратын install_filter функциясы болып табылады. Бірінші бөлімде 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),
  };

Нұсқаулар linux/filter.h файлында анықталған BPF_STMT және BPF_JUMP макростары арқылы орнатылады.
Нұсқауларды қарастырайық.

- BPF_STMT(BPF_LD + BPF_W + BPF_ABS (offsetof(struct seccomp_data, arch))) - жүйе BPF_LD-ден BPF_W сөзі түрінде жүктеледі және жинақталады, пакеттік деректер BPF_ABS бекітілген ығысуында орналасады.

- BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, arch, 0, 3) - BPF_JEQ көмегімен BPF_K аккумулятор тұрақтысындағы архитектуралық мән arch мәніне тең екенін тексереді. Олай болса, келесі нұсқауға 0 ығысуына секіреді, әйтпесе арка сәйкес келмейтіндіктен қате жіберу үшін 3 (бұл жағдайда) ығысуына секіреді.

- BPF_STMT(BPF_LD + BPF_W + BPF_ABS (offsetof(struct seccomp_data, nr))) - BPF_LD ішінен BPF_ABS тіркелген ығысуында қамтылған жүйелік шақыру нөмірі болып табылатын BPF_W сөзі түрінде жүктеледі және жинақталады.

— BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, nr, 0, 1) — жүйелік шақыру нөмірін nr айнымалысының мәнімен салыстырады. Егер олар тең болса, келесі нұсқауға өтеді және жүйелік шақыруды өшіреді, әйтпесе SECCOMP_RET_ALLOW арқылы жүйелік қоңырауға рұқсат береді.

- BPF_STMT(BPF_RET + BPF_K, SECCOMP_RET_ERRNO | (қате & SECCOMP_RET_DATA)) - бағдарламаны BPF_RET арқылы тоқтатады және нәтижесінде err айнымалысының нөмірімен SECCOMP_RET_ERRNO қатесін шығарады.

- BPF_STMT(BPF_RET + BPF_K, SECCOMP_RET_ALLOW) - BPF_RET көмегімен бағдарламаны аяқтайды және SECCOMP_RET_ALLOW көмегімен жүйелік шақыруды орындауға мүмкіндік береді.

SECCOMP - CBPF
Неліктен құрастырылған ELF нысанының немесе JIT құрастырылған C бағдарламасының орнына нұсқаулар тізімі пайдаланылады деген сұрақ туындауы мүмкін.

Мұның екі себебі бар.

• Біріншіден, Seccomp eBPF емес, cBPF (классикалық BPF) пайдаланады, бұл дегеніміз: оның регистрлері жоқ, бірақ мысалда көрініп тұрғандай соңғы есептеу нәтижесін сақтауға арналған аккумулятор ғана.

• Екіншіден, Seccomp BPF нұсқауларының массивіне көрсеткішті тікелей қабылдайды, басқа ештеңе жоқ. Біз пайдаланған макростар бұл нұсқауларды бағдарламашыға ыңғайлы етіп көрсетуге көмектеседі.

Егер сізге осы жинақты түсінуге қосымша көмек қажет болса, дәл сол нәрсені жасайтын псевдокодты қарастырыңыз:

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

socket_filter құрылымында сүзгі кодын анықтағаннан кейін кодты және сүзгінің есептелген ұзындығын қамтитын sock_fprog анықтау керек. Бұл деректер құрылымы кейінірек іске қосу үшін процесті жариялау үшін дәлел ретінде қажет:

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

install_filter функциясында бір ғана нәрсе қалды - бағдарламаның өзін жүктеңіз! Мұны істеу үшін біз қауіпсіз есептеу режиміне кіру опциясы ретінде PR_SET_SECCOMP алып, prctl қолданамыз. Содан кейін режимге сүзгіні sock_fprog түріндегі айнымалы мәнде қамтылған SECCOMP_MODE_FILTER арқылы жүктеуді айтамыз:

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

Соңында, install_filter функциясын пайдалана аламыз, бірақ оған дейін ағымдағы орындау үшін PR_SET_NO_NEW_PRIVS орнату үшін prctl пайдалануымыз керек және осылайша еншілес процестер ата-аналарына қарағанда көбірек артықшылықтар алатын жағдайдан аулақ болуымыз керек. Осы арқылы біз root құқықтарынсыз install_filter функциясында келесі prctl қоңырауларын жасай аламыз.

Енді install_filter функциясын шақыра аламыз. X86-64 архитектурасына қатысты барлық жазу жүйесі қоңырауларын блоктайық және жай ғана барлық әрекеттерді блоктайтын рұқсат берейік. Сүзгіні орнатқаннан кейін бірінші аргумент арқылы орындауды жалғастырамыз:

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

Бастайық. Бағдарламамызды құрастыру үшін біз clang немесе gcc пайдалана аламыз, кез келген жағдайда ол арнайы опцияларсыз main.c файлын құрастырады:

clang main.c -o filter-write

Жоғарыда айтылғандай, біз бағдарламадағы барлық жазбаларды блоктадық. Мұны тексеру үшін сізге бірдеңе шығаратын бағдарлама қажет - ls жақсы үміткер сияқты. Ол әдетте өзін осылай ұстайды:

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

Керемет! Біздің орауыш бағдарламамызды пайдалану келесідей: біз бірінші аргумент ретінде сынағымыз келетін бағдарламаны өткіземіз:

./filter-write "ls -la"

Орындалған кезде бұл бағдарлама толығымен бос нәтиже береді. Дегенмен, біз не болып жатқанын көру үшін strace пайдалана аламыз:

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

Жұмыстың нәтижесі айтарлықтай қысқарды, бірақ оның сәйкес бөлігі жазбалардың EPERM қатесі арқылы бұғатталғанын көрсетеді - біз конфигурациялаған бірдей. Бұл бағдарлама ештеңе шығармайтынын білдіреді, себебі ол жүйелік жазу шақыруына қол жеткізе алмайды:

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

Енді сіз Seccomp BPF қалай жұмыс істейтінін түсінесіз және онымен не істеуге болатынын жақсы түсіндіңіз. Бірақ сіз оның толық қуатын пайдалану үшін cBPF орнына eBPF көмегімен бірдей нәрсеге қол жеткізгіңіз келмейді ме?

eBPF бағдарламалары туралы ойлағанда, адамдардың көпшілігі оларды жай ғана жазып, әкімші артықшылықтарымен жүктейді деп ойлайды. Бұл мәлімдеме әдетте дұрыс болғанымен, ядро ​​әртүрлі деңгейлерде eBPF нысандарын қорғау механизмдерінің жиынтығын жүзеге асырады. Бұл механизмдер BPF LSM тұзақтары деп аталады.

BPF LSM тұзақтары

Жүйе оқиғаларының архитектуралық тәуелсіз мониторингін қамтамасыз ету үшін LSM тұзақтар тұжырымдамасын жүзеге асырады. Қоңырау шалу техникалық тұрғыдан жүйелік қоңырауға ұқсас, бірақ жүйеден тәуелсіз және инфрақұрылыммен біріктірілген. LSM абстракциялық қабат әртүрлі архитектуралардағы жүйелік шақырулармен жұмыс істеу кезінде кездесетін мәселелерді болдырмауға көмектесетін жаңа тұжырымдаманы ұсынады.

Жазу кезінде ядроның BPF бағдарламаларымен байланысты жеті ілгегі бар және SELinux оларды жүзеге асыратын жалғыз кіріктірілген LSM болып табылады.

Тұзақтардың бастапқы коды 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);

Олардың әрқайсысы орындаудың әртүрлі кезеңдерінде шақырылады:

— security_bpf — орындалған BPF жүйелік шақыруларын бастапқы тексеруді жүзеге асырады;

- security_bpf_map - ядро ​​карта үшін файл дескрипторын қайтарған кезде тексереді;

- security_bpf_prog - ядро ​​eBPF бағдарламасы үшін файл дескрипторын қайтарған кезде тексереді;

— security_bpf_map_alloc — BPF карталарының ішіндегі қауіпсіздік өрісінің инициализацияланғанын тексереді;

- security_bpf_map_free - қауіпсіздік өрісінің BPF карталарының ішінде тазартылғанын тексереді;

— security_bpf_prog_alloc — қауіпсіздік өрісінің BPF бағдарламаларының ішінде инициализацияланғанын тексереді;

- security_bpf_prog_free - қауіпсіздік өрісінің BPF бағдарламаларының ішінде тазартылғанын тексереді.

Енді осының бәрін көре отырып, біз түсінеміз: LSM BPF интерцепторларының идеясы - олар әрбір eBPF нысанын қорғауды қамтамасыз ете алады, бұл тек тиісті артықшылықтары бар адамдар карталар мен бағдарламаларда операцияларды орындай алады.

Резюме

Қауіпсіздік - бұл сіз қорғағыңыз келетін барлық нәрсе үшін бір өлшемді түрде жүзеге асыра алатын нәрсе емес. Жүйені әртүрлі деңгейде және әртүрлі тәсілдермен қорғай білу маңызды. Сенсеңіз де, сенбесеңіз де, жүйені қорғаудың ең жақсы жолы - әртүрлі позициялардан қорғаудың әртүрлі деңгейлерін ұйымдастыру, осылайша бір деңгейдің қауіпсіздігін төмендету бүкіл жүйеге қол жеткізуге мүмкіндік бермейді. Негізгі әзірлеушілер бізге әртүрлі қабаттар мен жанасу нүктелерінің жиынтығын беруде тамаша жұмыс жасады. Біз сізге қабаттардың қандай екенін және олармен жұмыс істеу үшін BPF бағдарламаларын қалай пайдалану керектігін жақсы түсіндік деп үміттенеміз.

Авторлар туралы

Дэвид Калавера Netlify компаниясының техникалық директоры. Ол Docker қолдауында жұмыс істеді және Runc, Go және BCC құралдарын, сондай-ақ басқа да ашық бастапқы жобаларды дамытуға үлес қосты. Docker жобаларындағы жұмысымен және Docker плагинінің экожүйесін дамытумен танымал. Дэвид жалын графиктеріне өте құмар және әрқашан өнімділікті оңтайландыруға тырысады.

Лоренцо Фонтана Sysdig-тегі ашық бастапқы топта жұмыс істейді, мұнда ол негізінен Falco жобасына, Cloud Native Computing Foundation жобасына бағытталған, ол контейнердің орындалу уақытының қауіпсіздігін және ядро ​​модулі және eBPF арқылы аномалияларды анықтауды қамтамасыз етеді. Ол таратылған жүйелерге, бағдарламалық қамтамасыз етумен анықталған желіге, Linux ядросына және өнімділікті талдауға құмар.

» Кітап туралы толық ақпаратты мына жерден табуға болады баспагердің веб-сайты
» Мазмұны
» Үзінді

Khabrozhiteley үшін купонды пайдалану арқылы 25% жеңілдік - Linux

Кітаптың қағаз нұсқасын төлегеннен кейін электронды кітап электронды поштаға жіберіледі.

Ақпарат көзі: www.habr.com

пікір қалдыру