«Linux üçün BPF Monitorinqi» kitabı

«Linux üçün BPF Monitorinqi» kitabıSalam, Habrin istifadəçiləri! BPF virtual maşını Linux nüvəsinin mühüm komponentlərindən biridir. Onun düzgün istifadəsi sistem mühəndislərinə nasazlıqları tapmağa və ən mürəkkəb problemləri həll etməyə imkan verir. Siz nüvənin davranışını izləyən və dəyişdirən proqramlar yaradmağı öyrənəcəksiniz, nüvdə hadisələri izləmək üçün təhlükəsiz kod yerləşdirə biləcəksiniz və daha çoxunu. David Kalavera və Lorenzo Fontana sizə BPF-nin imkanlarını aşkara çıxarmaqda kömək edəcək. Performans, şəbəkələr, təhlükəsizlik haqqında biliklərinizi artırın. — BPF-dən Linux nüvəsinin davranışını izləmək və dəyişdirmək üçün istifadə edin. — Nüvdə təhlükəsiz hadisə monitorinqi üçün kod yerləşdirin — nüvəni yenidən tərtib etməyə və ya sistemi yenidən başladmağa ehtiyac olmadan. — C, Go və ya Python-dakı rahat kod nümunələrindən istifadə edin. — BPF proqramının yaşam dövrünü idarə edərək vəziyyəti əlinizdə saxlayın.

Linux nüvəsinin təhlükəsizliyi, onun imkanları və Seccomp

BPF, stabilik, təhlükəsizlik və sürətə ziyan vurmadan nüvəni genişləndirmək üçün güclü bir yol təqdim edir. Buna görə də nüvə inkişaf etdiriciləri, BPF proqramları tərəfindən dəstəklənən Seccomp filtrlərinin həyata keçirlməsi vasitəsilə proseslərin izolyasiyasını artırmaq üçün onun universallığını istifadə etmənin yaxşı olacağını düşündülər. Bu fəsildə Seccomp-un nə olduğunu və necə tətbiq olunduğunu izah edəcəyik. Sonra BPF proqramları ilə Seccomp filtrlərini necə yazacağınızı öyrənəcəksiniz. Daha sonra Linux təhlükəsizlik modulları üçün nüvədə olan daxili BPF dayanacaqlarını nəzərdən keçirəcəyik.

Linux Təhlükəsizlik Modulları (LSM) müxtəlif təhlükəsizlik modellərinin standartlaşdırılmış həyata keçirilməsi üçün istifadə oluna bilən bir platformadır. LSM, kodun əsas ağacına birbaşa daxil edilə bilər, məsələn, Apparmor, SELinux və Tomoyo.

Linux-un imkanlarını müzakirə etməyə başlayaq.

İmkanlar

Linux-un imkanlarının mahiyyəti, sizə bir məqsəd yerinə yetirmək üçün səlahiyyətli olmayan bir prosesi icazə vermək lazım olduğunda, lakin bunun üçün suid-dən istifadə etmədən və ya prosesi səlahiyyətli etmədən, hücum imkanlarını azaldaraq prosesi müəyyən tapşırıqlara yerinə yetirmək imkanı verməkdən ibarətdir. Məsələn, əgər tətbiqinizin 80-dəki səlahiyyətli porta girmək lazım olsa, prosesi root adı ilə işə salmaq əvəzinə, sadəcə ona CAP_NET_BIND_SERVICE icazəsini verə bilərsiniz.

main.go adlı Go proqramını nəzərdən keçirək:

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

Bu proqram 80 (səlahiyyətli port) porta HTTP serveri xidmət edir. Adətən, biz onu tərtib edəndən sonra dərhal işə salırıq:

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

Lakin, biz root icazəsi vermədiyimiz üçün, bu kod portu bağlamada səhv verəcək:

2019/04/25 23:17:06 tcp dinə bağlayıram: icazə verilmədi
çıxış statusu 1

capsh (şəbəkə idarəetmə aləti) — müəyyən imkanları olan bir shell açan vasitədir.

Bu halda, artıq qeyd edildiyi kimi, tam kök hüquqları vermək əvəzinə, cap_net_bind_service imkanını qadağa portlarının bağlanmasına icazə verməklə kömək edə bilərik, artıq proqramın imtiyazları olduğundan. Bunun üçün proqramımızı capsh-ə daxil edə bilərik:

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

Bu komandanı araşdıraq.

  • capsh — capsh-i bir shell olaraq istifadə edirik.
  • —caps='cap_net_bind_service+eip cap_setpcap,cap_setuid,cap_setgid+ep' — istifadəçini dəyişmək lazım olduğuna görə (biz kök hüquqları ilə işləmək istəmirik), cap_net_bind_service və faktiki olaraq kök istifadəçidən nobody-yə keçirilmək imtiyazını, yəni cap_setuid və cap_setgid-i göstəririk.
  • —keep=1 — kök hesabından keçirildikdə, təyin olunan imtiyazları saxlamaq istəyirik.
  • —user='nobody' — proqramı işə salan son istifadəçi nobody olacaq.
  • —addamb=cap_net_bind_service — kök rejimindən keçdikdən sonra əlaqəli imtiyazların təmizlənməsini göstəririk.
  • — -c './capabilities' — sadəcə, proqramı işə salırıq.

Əlaqəli imtiyazlar — cari proqramın execve() vasitəsilə işlətdiyi zaman törəmə proqramlara miras qalan xüsusi bir imtiyaz növüdür. Yalnız bağlı tədqiqat olaraq icazə verilmiş imtiyazlar miras qala bilər.

Bəlkə də, siz +eip-in imtiyaçıda göstərilməsindən sonra nə demək olduğunu merak edirsiniz. Bu bayraqlar, imtiyazın:

- aktiv olmalıdır (p);

- tətbiq üçün əlçatan olmalıdır (e);

- törəmə proseslər tərəfindən miras alınmalıdır (i).

cap_net_bind_service-i istifadə etmək istədiyimiz üçün bunu e bayrağı ilə yerinə yetirməliyik. Sonra komandada shell-i işə salacağıq. Nəticədə, capabilities ikili faylı işə düşəcək və biz onu i bayrağı ilə qeyd etməliyik. Nəhayət, imtiyaz aktiv olmalı (biz bunu UID-ni dəyişmədən etdik) p vasitəsilə. Bu, cap_net_bind_service+eip kimi görünür.

Sonucu ss vasitəsilə yoxlaya bilərsiniz. Çıxışı bir az qısaldırıq ki, səhifəyə sığsın, amma bağlı portu və istifadəçi identifikatorunu, bu halda isə 65 534-ü göstərəcək:

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

Bu nümunədə capsh-dan istifadə etdik, ancaq libcap ilə də bir shell yaza bilərsiniz. Əlavə məlumat üçün man 3 libcap-ə baxın.

Proqram yazarkən, inkişaf etdirici çox hallarda proqramın icra zamanı lazım olan bütün imtiyazları əvvəldən bilmir; daha doğrusu, yeni versiyalarda bu imtiyazlar dəyişə bilər.

Proqramımızın imtiyazlarını daha yaxşı anlamaq üçün BCC capable alətini götürə bilərik ki, bu da cap_capable kernel funksiyası üçün kprobe-yə yerləşdirir:

/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

Eyni nəticəyə bpftrace istifadə edərək cap_capable kernel funksiyasında bir sətir kprobe ilə nail ola bilərik:

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

Bu, kprobe'dan sonra programımızın imkanları etkinleştirildiyse, aşağıdakine benzer bir şey gösterecektir:

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

Beşinci sütun, sürecin ihtiyaç duyduğu yeteneklerdir ve bu çıktıda denetim dışı olayları da içerdiğinden, tüm denetim dışı kontrolleri, son olarak da denetim bayrağı olan gerekli yeteneği (çıktının sonundaki) 1 olarak görmekteyiz. İlginç olan yetenek CAP_NET_BIND_SERVICE'dir ve bu, kesin kimliği 10 olan include/uapi/linux/ability.h dosyasında çekirdek kaynak kodunda sabit olarak tanımlanmıştır.

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

Kapsayıcıların, örneğin runC veya Docker, ayrıcalıksız modda çalışabilmesi için genellikle yürütme sırasında yeteneklerin kullanıldığını görmekteyiz, ancak çoğu uygulamanın çalışması için gerekli olan yalnızca belirli yeteneklere izin verilmektedir. Bir uygulamanın belirli yeteneklere ihtiyaç duyması durumunda, Docker'da bunları --cap-add ile sağlayabilirsiniz:

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

Bu komut, CAP_NET_ADMIN yetkisini kapsayıcıya sağlayacak ve ona dummy0 arayüzünü eklemek için ağ bağlantısını yapılandırma yetkisi verecektir.

Sonraki bölüm, programatik olarak kendi filtrelerimizi uygulamaya izin verecek farklı bir yöntemle filtreleme gibi yeteneklerin kullanımını göstermektedir.

Seccomp

Seccomp, Linux çekirdeğinde belirli sistem çağrılarını filtrelemeye olanak tanıyan bir güvenlik seviyesi olan Secure Computing anlamına gelir. Seccomp, Linux yetenekleriyle karşılaştırılabilir, ancak belirli sistem çağrılarını yönetme yeteneği ile onlardan çok daha esnektir.

Seccomp ve Linux yetenekleri birbirini dışlamaz; genellikle her iki yaklaşımdan da faydalanmak için birlikte kullanılır. Örneğin, bir sürece CAP_NET_ADMIN yetkisini vermek isteyebilirsiniz, ancak o sürecin soket üzerinden bağlantı kabul etmesine izin vermeyerek accept ve accept4 sistem çağrılarını engelleyebilirsiniz.

Seccomp'un filtreleme yöntemi, SECCOMP_MODE_FILTER modunda çalışan BPF filtrelerine dayanır ve sistem çağrılarını filtreleme, paketler için olduğu gibi gerçekleştirilir.

Seccomp filtreleri, PR_SET_SECCOMP işlemi üzerinden prctl kullanılarak yüklenir. Bu filtreler, seccomp_data yapısı aracılığıyla sunulan her Seccomp paketi için çalışan bir BPF programı biçimindedir. Bu yapı, sistem çağrısı sırasında işlemci talimat göstericisi ve en fazla altı sistem çağrı argümanını uint64 olarak içerir.

Burada linux/seccomp.h faylındakı kernel kodunun seccomp_data strukturu belə görünür:

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

Bu struktura əsasən sistem çağırışı, onun arqumentləri və ya onların kombinasiyaları əsasında filtr etmək mümkündür.

Hər bir paket alındıqdan sonra Seccomp filtrinin bir qərar qəbul etməsi və kerneldən nə etməli olduğunu bildirməsi lazımdır. Nəticə qərarı geri qaytarılan dəyərlərdən (status kodları) biri ilə ifadə olunur.

— SECCOMP_RET_KILL_PROCESS — sistem çağırışı filtr edildikdən sonra dərhal bütün prosesi dayandırır, bu sistem çağırışı yerinə yetirilmir.

— SECCOMP_RET_KILL_THREAD — sistem çağırışı filtr edildikdən sonra dərhal cari ipi dayandırır, bu sistem çağırışı yerinə yetirilmir.

— SECCOMP_RET_KILL — SECCOMP_RET_KILL_THREAD üçün aliasdır, geri uyğunluq səbəbilə buraxılmışdır.

— SECCOMP_RET_TRAP — sistem çağırışı qadağan edilmişdir və SIGSYS (Bad System Call) siqnalı onu çağıran işə göndərilir.

— SECCOMP_RET_ERRNO — sistem çağırışı yerinə yetirilmir və SECCOMP_RET_DATA filtrinin bir hissəsi istifadəçi sahəsinə errno dəyəri kimi ötürülür. Xətanın səbəbindən asılı olaraq fərqli errno dəyərləri qaytarılır. Xəta nömrələrinin siyahısı aşağıdakı bölümde verilmişdir.

— SECCOMP_RET_TRACE — sistem çağırışının icra edildiyi zaman onu görmək və idarə etmək üçün ptrace tərəfindən bildiriş üçün istifadə edilir — PTRACE_O_TRACESECCOMP. Əgər izləyici qoşulmayıbsa, xəta qaytarılır, errno -ENOSYS-ə təyin edilir və sistem çağırışı yerinə yetirilmir.

— SECCOMP_RET_LOG — sistem çağırışı icazə verilir və jurnal qeydinə alınır.

— SECCOMP_RET_ALLOW — sistem çağırışı sadəcə icazə verilir.

ptrace — tracee adlanan prosesdə izləmə mexanizmlərinin həyata keçirilməsi üçün sistem çağırışıdır, prosesin icrasını izləmək və idarə etmək imkanı ilə. İzləmə proqramı tracee-nin icrasına təsir edə və onun yaddaş registrlərini dəyişdirə bilər. Seccomp kontekstində ptrace SECCOMP_RET_TRACE status kodu ilə başladıqda istifadə edilir, buna görə də izləyici sistem çağırışının icrasını dayandıraraq öz loqikanı həyata keçirə bilər.

Seccomp Xətaları

Seccomp ilə işləyərkən bəzən SECCOMP_RET_ERRNO tipli geri döndərmə dəyəri ilə tanınan müxtəlif xətalarla qarşılaşacaqsınız. Xətanı bildirmək üçün sistem çağırışı seccomp -1 dəyərini qaytaracaq, 0 yerinə.

Aşağıdakı xətalar mümkündür:

— EACCESS — çağıran tərəfə sistem çağırışını etmək icazəsi verilmir. Adətən, bu, onun CAP_SYS_ADMIN imtiyazına malik olmaması və ya prctl vasitəsilə no_new_privs-in qurulmaması ilə baş verir;

— EFAULT — ötürülmüş arqumentlər (seccomp_data strukturundakı args) etibarlı ünvanı yoxdur;

— EINVAL — burada dörd səbəb ola bilər:

- İstənilən əməliyyat məlum deyil və ya cari konfiqurasiyada nüvənin dəstəklədiyi əməliyyat deyil;

- Göstərilən bayraqlar istənilən əməliyyat üçün etibarsızdır;

- Əməliyyat BPF_ABS-i əhatə edir, lakin belirtilən ofsetting ilə bağlı problemlər var, bu, seccomp_data strukturu ölçüsünü aşmağına səbəb ola bilər;

- Filtrə verilən təlimatların sayı maksimumdan çoxdur;

— ENOMEM — proqramı icra etmək üçün kifayət qədər yaddaş yoxdur;

— EOPNOTSUPP — əməliyyat SECCOMP_GET_ACTION_AVAIL-yə istinad edərək əlçatan bir hərəkət göstərişini nümayiş etdirir, lakin nüvə argumentlərdə qaytarmağı dəstəkləmir;

— ESRCH — digər bir ipliklə senkronizasiya problemi meydana çıxdı;

— ENOSYS — SECCOMP_RET_TRACE fəaliyyətinə əlavə edilmiş bir izləyici yoxdur.

prctl — istifadəçi məkanındakı proqramın prosesin müəyyən aspektlərini idarə etməyə (qurmaq və əldə etmək) imkan tanıyan bir sistem çağırışı, məsələn, bayt sırasını, iplik adlarını, mühafizə olunmuş hesablama rejimi (Seccomp), imtiyazlar, Perf hadisələri və s.

Seccomp sizə bir sandıq texnologiyası kimi görünə bilər, amma belə deyil. Seccomp — istifadəçilərə sandıq mexanizmini inkişaf etdirmək imkanı verən bir vasitədir. İndi istifadəçi qarşılıqlı əlaqələri üçün proqramların istifadəsi üçün filtrin yaradılması necə baş verdiyini nəzərdən keçirək, bu filtr birbaşa Seccomp sistem çağırışı ilə çağrılır.

Seccomp BPF filtrinin nümunəsi

Burada əvvəlcədən müzakirə edilmiş iki hərəkəti bir araya gətirməyi göstərəcəyik:

— qərar qəbul edilənə görə müxtəlif dönüş kodları ilə filtr olaraq tətbiq ediləcək Seccomp BPF proqramını yazacağıq;

— prctl istifadə edərək filtr yükləyəcəyik.

Başlamaq üçün Linux-un standart kitabxanasından və nüvəsindən başlıqlara ehtiyac var:

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

Bu nümunəni icra etməyə çalışmazdan əvvəl, nüvənin CONFIG_SECCOMP və CONFIG_SECCOMP_FILTER-in y-ə quraşdırılmış olduğuna əmin olmalısınız. İşlək maşında bunu belə yoxlaya bilərsiniz:

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

Koddakı qalan hissə install_filter adlanan iki hissədən ibarət olan funksiyadır. İlk hissə bizə BPF filtr hesabatlarımızı təqdim edir:

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

Təlimatlar BPF_STMT və BPF_JUMP makrosları istifadə edilərək təyin edilir, bunlar linux/filter.h faylında müəyyən edilir.
Təlimatlarla tanış olaq.

— BPF_STMT(BPF_LD + BPF_W + BPF_ABS (offsetof(struct seccomp_data, arch))) — sistem saklanma sabitini BPF_LD istifadə edərək yükləyir və saxlayır, BPF_W şəklində sözü, paket məlumatları sabit ofsetting BPF_ABS istifadə edərək yerləşir.

— BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, arch, 0, 3) — arxitekturaya aid dəyərin BPF_K akkumulyatorundakı sabitə bərabər olub-olmamasını BPF_JEQ istifadə edərək yoxlayır. Əgər bərabərdirsə, 0 yerdən növbəti əmri icra edir, əks halda, 3 yerdən xətanı bildirmək üçün atlayır (bu halda), çünki arch bərabər deyil.

— BPF_STMT(BPF_LD + BPF_W + BPF_ABS (offsetof(struct seccomp_data, nr))) — sabit BPF_ABS-yə yerləşdirilmiş sistem zənginin nömrəsini BPF_LD şəklində yükləyir və toplayır.

— BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, nr, 0, 1) — sistem çağırışı nömrəsini nr dəyişəninin dəyəri ilə müqayisə edir. Əgər bərabərdirsə, növbəti əmrə keçir və sistem çağırışını qadağan edir, əks halda SECCOMP_RET_ALLOW ilə sistem çağırışını icazə verir.

— BPF_STMT(BPF_RET + BPF_K, SECCOMP_RET_ERRNO | (error & SECCOMP_RET_DATA)) — proqramı BPF_RET ilə bitirərək, xətanın nömrəsi err dəyişənindən SECCOMP_RET_ERRNO xətasındakı nəticəni verir.

— BPF_STMT(BPF_RET + BPF_K, SECCOMP_RET_ALLOW) — proqramı BPF_RET ilə bitirərək, SECCOMP_RET_ALLOW ilə sistem çağırışının icra olunmasına icazə verir.

SECCOMP — BU CBPF-dir
Bəlkə də, JIT ilə tərtib olunmuş C proqramı və ya ELF obyekti əvəzinə niyə bir təlimat siyahısının istifadə olunduğunu düşünürsünüz.

Bunun iki səbəbi var.

• Birincisi, Seccomp cBPF (klassik BPF) tətbiq edir, eBPF deyil, bu o deməkdir ki: onun qeydçiləri yoxdur, yalnız son hesablama nəticəsini saxlamaq üçün akkumulyator var, bunu nümunədə görə bilərsiniz.

• İkincisi, Seccomp birbaşa BPF təlimatları massivinin göstəricisini qəbul edir və daha heç nə. İstifadə etdiyimiz makrolar, yalnız bu təlimatları proqramçılar üçün daha rahat bir şəkildə göstərməyə kömək edir.

Əgər bu tərtibatı anlamaq üçün daha çox kömək lazımdırsa, eyni şeyi edən psevdokodu nəzərdən keçirin:

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

Socket_filter strukturunda filtr kodu təyin edildikdən sonra, kodu və hesablanmış filterin uzunluğunu saxlayan sock_fprog-u təyin etmək lazımdır. Bu məlumat strukturunu daha sonra prosesin elan edilməsi üçün arqument kimi istifadə etmək lazımdır:

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

Install_filter funksiyasında yüklənməsi lazım olan bir şey qalıb — proqramı yükləmək! Bunun üçün prctl istifadə edəcəyik, PR_SET_SECCOMP seçimini alaraq müdafiəli hesablama rejiminə daxil olacağıq. Sonra filtresi yükleməsini sağlayan SECCOMP_MODE_FILTER rejimini prog tiplə sock_fprogda göstərəcəyik:

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

Nəhayət, install_filter funksiyamızdan istifadə edə bilərik, amma bunun üçün əvvəlcə prctl-i işə salmalıyıq ki, təqdim edilən icra üçün PR_SET_NO_NEW_PRIVS quraşdıraq və beləliklə, yaradılan proseslərin ana prosesdən daha geniş hüquqlara sahib olmasının qarşısını alaq. Bu zaman install_filter funksiyasında prctl-i aşağıdakı kimi çağırmaq mümkündür, root hüquqları olmadan.

İndi install_filter funksiyasını çağıra bilərik. X86-64 arxitekturasına aid bütün yazma sistem çağırışlarını bloklayacağıq və birbaşa cəhdlərin qarşısını alan icazə verəcəyik. Filtri quraşdırdıqdan sonra, ilk arqumentdən istifadə edərək icrası davam etdiririk:

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

Başlayaq. Proqramımızı tərtib etmək üçün ya clang, ya da gcc istifadə edə bilərik, hər halda bu, main.c faylını xüsusi seçimsiz tərtib etməkdir:

clang main.c -o filter-write

Qeyd edildiyi kimi, proqramda bütün yazma əməliyyatlarını bloklamışıq. Bunu yoxlamaq üçün bir şey çıxaran bir proqram lazımdır - ls yaxşı namizəd kimi görünür. Budur, onun adi davranışı:

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

Möhtəşəm! Proqramımızın istifadə görünüşü belədir: sadəcə test etmək istədiyimiz proqramı birinci arqument olaraq veririk:

.\/filter-write "ls -la"

İcra edildikdən sonra bu proqram tamamilə boş çıxış verir. Buna baxmayaraq, baş verənləri görmək üçün strace tətbiq edə bilərik:

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

İşin nəticəsi xeyli qısaldılıb, amma müvafiq hissəsi göstərir ki, yazmalar EPERM xətası ilə bloklanır - düz bir şəkildə tənzimlədiyimiz o eyni. Bu deməkdir ki, proqram heç nə çıxarmır çünki write sistem çağırışına çıxış əldə edə bilmir:

[pid 25099] write(2, "ls: ", 4) = -1 EPERM (İşləmə icazə verilmir)
[pid 25099] write(2, "yazılma xətası", 11) = -1 EPERM (İşləmə icazə verilmir)
[pid 25099] write(2, "n", 1) = -1 EPERM (İşləmə icazə verilmir)

İndi Seccomp BPF-in necə işlədiyini başa düşürsünüz və bununla nə edə biləcəyiniz barədə əla bir təsəvvürünüz var. Amma cBPF əvəzinə eBPF istifadə etməklə eyni nəticəni əldə etmək istəməzdinizmi, onun bütün gücündən istifadə etmək üçün?

eBPF proqramları haqqında düşünərkən, əksər insanlar onların sadəcə yazılaraq administrator hüquqları ilə yüklənməsini düşünür. Həqiqətən, bu ifadə ümumilikdə doğrudur, lakin nüvə müxtəlif səviyyələrdə eBPF obyektlərini qorumaq üçün bir sıra mexanizmləri həyata keçirir. Bu mexanizmlər BPF LSM tələləri adlanır.

BPF LSM tələləri

Mimarlıqdan asılı olmayaraq sistem hadisələrinin müstəqil nəzarətini təmin etmək üçün LSM məhv konceptini həyata keçirir. Texniki baxımdan, məhv çağırışı sistem çağırışına bənzəyir, lakin sistemdən müstəqildir və infrastrukturla inteqrasiya olunur. LSM, abstraksiya səviyyəsinin sistem çağırışları ilə müxtəlif arxitekturalarda iş zamanı yaranan problemləri aşmağa kömək edə biləcəyi yeni bir konsepsiya təqdim edir.

Kitabın yazıldığı zaman, nüvədə BPF proqramları ilə bağlı yeddi məhv vardı və SELinux - inteqrasiya edilmiş yeganə LSM idi.

Məhvinin mənbə kodu nüvə ağacında include/linux/security.h faylında yerləşir:

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

Hər biri icra müddətinin müxtəlif mərhələlərində çağırılacaq:

— security_bpf — BPF sistem çağırışlarının ilkin yoxlamasını həyata keçirir;

— security_bpf_map — nüvə bir xəritə üçün fayl deskriptorunu qaytardığı zaman yoxlayır;

— security_bpf_prog — nüvə eBPF proqramı üçün fayl deskriptorunu qaytardığı zaman yoxlayır;

— security_bpf_map_alloc — BPF xəritələri içində təhlükəsizlik sahəsinin təşkili təmin edilibmi; yoxlayır;

— security_bpf_map_free — BPF xəritələri içində təhlükəsizlik sahəsinin təmizlənməsi həyata keçirilirmi; yoxlayır;

— security_bpf_prog_alloc — BPF proqramları içində təhlükəsizlik sahəsinin təşkili təmin edilibmi; yoxlayır;

— security_bpf_prog_free — BPF proqramları içində təhlükəsizlik sahəsinin təmizlənməsi həyata keçirilirmi; yoxlayır.

İndi bunların hamısını gördükdən sonra başa düşürük: LSM BPF-nin məhv ideyası odur ki, onlar hər bir eBPF obyektinin mühafizəsini təmin edə bilərlər, yalnız müvafiq imtiyaza malik olanların xəritələr və proqramlar üzərində əməliyyatlar edə biləcəyini təmin edirlər.

Xülasə

Təhlükəsizlik, qorumaq istədiyiniz hər şey üçün ümumi bir şəkildə soyun deyi mükəmməl bir şey deyil. Müxtəlif səviyyələrdə və müxtəlif yollarla sistemləri qorumaq bacarığına malik olmaq vacibdir. İstəyirsiniz inanın, istəyirsiniz inanmayın, sistemi qorumağın ən yaxşı yoluna, fərqli mövqelərdən fərqli qoruma səviyyələrinin təşkil edilməsidir, belə ki, bir səviyyənin təhlükəsizliyinin azalması sistemə tam daxil olma imkanını verməsin. Nüvənin inkişaf etdiriciləri, bizə fərqli qatlar və qarşılıqlı əlaqə nöqtələri təqdim edərək böyük iş görüblər. Biz ümid edirik ki, sizə qatların nə olduqdan və BPF proqramları ilə onlarla necə işlənməsindən yaxşı bir anlayış təqdim etdik.

Müəlliflər haqqında

David Calavera David Netlify-də texniki direktor vəzifəsindədir. O, Docker dəstək xidmətində çalışıb və Runc, Go və BCC kimi açıq mənbəli layihələrin inkişafında iştirak edib, eləcə də digər açıq mənbə layihələrində çalışıb. Docker layihələri və Docker plagin ekosisteminin inkişafı üzrə tanınır. David, flayn qrafiklərini çox sevir və həmişə performansı optimallaşdırmağa çalışır.

Lorenzo Fontana Sysdig-də açıq mənbə proqram inkişaf etdiricisi komandasında çalışır, burada əsasən Falco layihəsi ilə məşğuldur — Cloud Native Computing Foundation tərəfindən təmin edilən, konteynerlərin icra mühitini qoruyan və kernel modulu ilə eBPF vasitəsilə anomaliyaları aşkar edən bir layihə. O, paylanmış sistemlər, proqramla idarə olunan şəbəkələr, Linux nüvəsi və performans analizi ilə maraqlanır.

» Kitabla daha ətraflı tanış olmaq üçün nəşriyyat saytına
» Mündəricat
» Mətn

Habr istifadəçiləri üçün kuponla 25% endirim — Linux

Kitabın kağız versiyasının ödənişi başa çatdıqda, elektron kitab e-mail vasitəsilə göndəriləcək.

Mənbə: habr.com

DDoS qoruması olan saytlara etibarlı hosting satın alın, VPS VDS serverlər 🔥 DDoS qoruması olan saytlara etibarlı hosting satın alın, VPS VDS serverlər | ProHoster