Kitab "Linux Monitorinqi üçün BPF"

Kitab "Linux Monitorinqi üçün BPF"Salam, Khabro sakinləri! BPF virtual maşını Linux nüvəsinin ən vacib komponentlərindən biridir. Onun düzgün istifadəsi sistem mühəndislərinə nasazlıqları tapmağa və hətta ən mürəkkəb problemləri həll etməyə imkan verəcək. Siz nüvənin davranışını izləyən və dəyişdirən proqramları necə yazmağı, nüvədəki hadisələri izləmək üçün kodu təhlükəsiz şəkildə həyata keçirməyi və s. öyrənəcəksiniz. David Calavera və Lorenzo Fontana sizə BPF-nin gücünü açmağa kömək edəcək. Performansın optimallaşdırılması, şəbəkə, təhlükəsizlik haqqında biliklərinizi genişləndirin. - Linux nüvəsinin davranışını izləmək və dəyişdirmək üçün BPF-dən istifadə edin. - Kerneli yenidən kompilyasiya etmədən və ya sistemi yenidən başlatmadan kernel hadisələrini təhlükəsiz şəkildə izləmək üçün kodu daxil edin. — C, Go və ya Python dillərində rahat kod nümunələrindən istifadə edin. - BPF proqramının həyat dövrünə sahib olmaqla nəzarəti ələ keçirin.

Linux Kernel Təhlükəsizliyi, Xüsusiyyətləri və Seccomp

BPF sabitlik, təhlükəsizlik və sürətdən ödün vermədən nüvəni genişləndirmək üçün güclü bir yol təqdim edir. Bu səbəbdən kernel tərtibatçıları Seccomp BPF kimi tanınan BPF proqramları tərəfindən dəstəklənən Seccomp filtrlərini tətbiq etməklə Seccomp-da prosesin izolyasiyasını yaxşılaşdırmaq üçün onun çox yönlülüyündən istifadə etməyin yaxşı bir fikir olacağını düşündülər. Bu fəsildə Seccomp-un nə olduğunu və necə istifadə edildiyini izah edəcəyik. Sonra BPF proqramlarından istifadə edərək Seccomp filtrlərini necə yazmağı öyrənəcəksiniz. Bundan sonra biz Linux təhlükəsizlik modulları üçün nüvəyə daxil olan daxili BPF qarmaqlarına baxacağıq.

Linux Təhlükəsizlik Modulları (LSM) müxtəlif təhlükəsizlik modellərini standartlaşdırılmış şəkildə həyata keçirmək üçün istifadə edilə bilən funksiyalar dəstini təmin edən çərçivədir. LSM birbaşa Apparmor, SELinux və Tomoyo kimi nüvə mənbəyi ağacında istifadə edilə bilər.

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

İmkanları

Linux-un imkanlarının mahiyyəti ondan ibarətdir ki, siz müəyyən tapşırığı yerinə yetirmək üçün imtiyazsız proses icazəsi verməlisiniz, lakin bu məqsəd üçün suid istifadə etmədən və ya başqa şəkildə prosesi imtiyazlı etmək, hücum ehtimalını azaltmaq və prosesin müəyyən tapşırıqları yerinə yetirməsinə imkan verməkdir. Məsələn, əgər tətbiqinizin imtiyazlı portu, məsələn, 80-ni açması lazımdırsa, prosesi kök kimi işə salmaq əvəzinə, sadəcə ona CAP_NET_BIND_SERVICE qabiliyyətini verə bilərsiniz.

main.go adlı bir Go proqramını nəzərdən keçirin:

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

Bu proqram 80 portda HTTP serverinə xidmət edir (bu, imtiyazlı portdur). Adətən onu tərtib etdikdən dərhal sonra işə salırıq:

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

Bununla belə, biz kök imtiyazları vermədiyimiz üçün bu kod portu bağlayan zaman xəta verəcək:

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

capsh (qabıq meneceri) müəyyən imkanlar dəsti ilə bir qabıq işlədən bir vasitədir.

Bu halda, artıq qeyd olunduğu kimi, tam kök hüquqlarının verilməsi əvəzinə, artıq proqramda olan hər şeylə birlikdə cap_net_bind_service imkanını təmin etməklə imtiyazlı port bağlamasını aktivləşdirə bilərsiniz. Bunun üçün proqramımızı capsh hərfinə əlavə 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"

Gəlin bu komandanı bir az başa düşək.

  • capsh - qabıq kimi capsh istifadə edin.
  • —caps='cap_net_bind_service+eip cap_setpcap,cap_setuid,cap_setgid+ep' - istifadəçini dəyişməli olduğumuz üçün (kök kimi işləmək istəmirik), biz cap_net_bind_service və istifadəçi identifikatorunu faktiki olaraq dəyişmək imkanını təyin edəcəyik. kök heç kimə, yəni cap_setuid və cap_setgid.
  • —keep=1 — biz kök hesabdan keçid zamanı quraşdırılmış imkanları saxlamaq istəyirik.
  • —user=“heç kim” — proqramı işlədən son istifadəçi heç kim olmayacaq.
  • —addamb=cap_net_bind_service — kök rejimindən keçdikdən sonra əlaqədar imkanların təmizlənməsini təyin edin.
  • - -c "./capabilities" - sadəcə proqramı işə salın.

Əlaqəli imkanlar, cari proqram execve() istifadə edərək onları icra etdikdə uşaq proqramlar tərəfindən miras qalan imkanların xüsusi növüdür. Yalnız əlaqələndirilməsinə icazə verilən və ya başqa sözlə, ətraf mühitin imkanları kimi miras alına bilər.

Yəqin ki, siz --caps seçimində qabiliyyəti göstərdikdən sonra +eip-in nə demək olduğunu düşünürsünüz. Bu bayraqlar qabiliyyəti müəyyən etmək üçün istifadə olunur:

-aktivləşdirilməlidir (p);

-istifadə üçün mövcuddur (e);

-uşaq prosesləri ilə miras qala bilər (i).

cap_net_bind_service istifadə etmək istədiyimiz üçün bunu e bayrağı ilə etməliyik. Sonra əmrdə qabığı işə salacağıq. Bu, imkanları binar rejimdə işlədəcək və biz onu i bayrağı ilə qeyd etməliyik. Nəhayət, funksiyanın aktiv olmasını istəyirik (biz bunu UID-i dəyişmədən etdik) p. cap_net_bind_service+eip kimi görünür.

Nəticəni ss istifadə edərək yoxlaya bilərsiniz. Gəlin səhifəyə sığdırmaq üçün çıxışı bir az qısaldaq, lakin o, 0-dan başqa əlaqəli port və istifadəçi ID-sini göstərəcək, bu halda 65:

# 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 istifadə etdik, lakin siz libcap istifadə edərək qabıq yaza bilərsiniz. Əlavə məlumat üçün man 3 libcap-ə baxın.

Proqramları yazarkən, çox vaxt tərtibatçı proqramın işləmə zamanı ehtiyac duyduğu bütün xüsusiyyətləri əvvəlcədən bilmir; Üstəlik, bu xüsusiyyətlər yeni versiyalarda dəyişə bilər.

Proqramımızın imkanlarını daha yaxşı başa düşmək üçün cap_capable kernel funksiyası üçün kprobe təyin edən BCC qabiliyyətli aləti götürə bilərik:

/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 kernel funksiyasında bir laynerli kprobe ilə bpftrace istifadə etməklə eyni şeyə 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

Proqramımızın imkanları kprobe-dan sonra işə salınarsa, bu, aşağıdakı kimi bir şey çıxaracaq:

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 prosesin ehtiyac duyduğu imkanlardır və bu çıxış qeyri-audit hadisələrini ehtiva etdiyi üçün biz bütün qeyri-audit yoxlamalarını və nəhayət audit bayrağı (çıxışda sonuncu) 1-ə təyin edilmiş tələb olunan qabiliyyəti görürük. Bizi maraqlandıran CAP_NET_BIND_SERVICE-dir, o, identifikator 10 olan include/uapi/linux/ability.h faylında nüvə mənbə kodunda sabit kimi müəyyən edilir:

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

İmtiyazsız rejimdə işləmək üçün imkanlar tez-tez runC və ya Docker kimi konteynerlər tərəfindən işə salınır, lakin onlara yalnız əksər tətbiqləri işə salmaq üçün lazım olan imkanlara icazə verilir. Tətbiq müəyyən imkanlar tələb etdikdə, Docker onları --cap-add istifadə edərək təmin edə bilər:

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

Bu əmr konteynerə CAP_NET_ADMIN qabiliyyətini verəcək və ona dummy0 interfeysini əlavə etmək üçün şəbəkə bağlantısını konfiqurasiya etməyə imkan verəcək.

Növbəti bölmə filtrləmə kimi xüsusiyyətlərin necə istifadə ediləcəyini göstərir, lakin bizə öz filtrlərimizi proqramlı şəkildə həyata keçirməyə imkan verən fərqli bir texnikadan istifadə etməklə.

Seccomp

Seccomp Təhlükəsiz Hesablama mənasını verir və tərtibatçılara müəyyən sistem zənglərini süzgəcdən keçirməyə imkan verən Linux nüvəsində həyata keçirilən təhlükəsizlik təbəqəsidir. Seccomp imkanlarına görə Linux ilə müqayisə olunsa da, müəyyən sistem zənglərini idarə etmək qabiliyyəti onu onlarla müqayisədə daha çevik edir.

Seccomp və Linux xüsusiyyətləri bir-birini istisna etmir və hər iki yanaşmadan faydalanmaq üçün tez-tez birlikdə istifadə olunur. Məsələn, siz prosesə CAP_NET_ADMIN imkanını vermək istəyə bilərsiniz, lakin onun soket bağlantılarını qəbul etməsinə icazə verməyərək qəbul və qəbul4 sistem zənglərini bloklaya bilərsiniz.

Seccomp filtrləmə metodu SECCOMP_MODE_FILTER rejimində işləyən BPF filtrlərinə əsaslanır və sistem zənglərinin filtrasiyası paketlər üçün olduğu kimi həyata keçirilir.

Seccomp filtrləri PR_SET_SECCOMP əməliyyatı vasitəsilə prctl istifadə edərək yüklənir. Bu filtrlər seccomp_data strukturu ilə təmsil olunan hər bir Seccomp paketi üçün icra edilən BPF proqramı formasını alır. Bu struktur istinad arxitekturasını, sistem çağırışı zamanı prosessor təlimatlarına göstəricini və uint64 kimi ifadə edilən maksimum altı sistem çağırışı arqumentini ehtiva edir.

Linux/seccomp.h faylındakı nüvə mənbə kodundan seccomp_data strukturu belə görünür:

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

Bu strukturdan da göründüyü kimi, biz sistem çağırışı, onun arqumentləri və ya hər ikisinin kombinasiyası ilə filtrasiya edə bilərik.

Hər bir Seccomp paketini aldıqdan sonra filtr son qərar vermək üçün emal etməli və nüvəyə bundan sonra nə edəcəyini söyləməlidir. Yekun qərar geri qaytarma dəyərlərindən biri (status kodları) ilə ifadə edilir.

- SECCOMP_RET_KILL_PROCESS - bu səbəbdən icra olunmayan sistem çağırışını süzgəcdən keçirdikdən dərhal sonra bütün prosesi öldürür.

- SECCOMP_RET_KILL_THREAD - bu səbəbdən yerinə yetirilməyən sistem çağırışını süzgəcdən keçirdikdən dərhal sonra cari mövzunu dayandırır.

— SECCOMP_RET_KILL — SECCOMP_RET_KILL_THREAD üçün ləqəb, geriyə uyğunluq üçün qalıb.

- SECCOMP_RET_TRAP - sistem çağırışı qadağandır və onu çağıran tapşırığa SIGSYS (Bad System Call) siqnalı göndərilir.

- SECCOMP_RET_ERRNO - Sistem çağırışı yerinə yetirilmir və SECCOMP_RET_DATA filtrinin qaytarılması dəyərinin bir hissəsi errno dəyəri kimi istifadəçi sahəsinə ötürülür. Xətanın səbəbindən asılı olaraq, müxtəlif səhv dəyərləri qaytarılır. Səhv nömrələrinin siyahısı növbəti hissədə verilmişdir.

- SECCOMP_RET_TRACE - Bu prosesi görmək və idarə etmək üçün sistem çağırışı yerinə yetirildikdə ələ keçirmək üçün - PTRACE_O_TRACESECCOMP istifadə edərək ptrace izləyicisini xəbərdar etmək üçün istifadə olunur. İzlə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ışı həll edilir və qeyd olunur.

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

ptrace, prosesin icrasına nəzarət etmək və nəzarət etmək imkanı ilə tracee adlanan prosesdə izləmə mexanizmlərini tətbiq etmək üçün sistem çağırışıdır. İzləmə proqramı icraya effektiv təsir göstərə və izləyicinin yaddaş registrlərini dəyişdirə bilər. Seccomp kontekstində ptrace SECCOMP_RET_TRACE status kodu tərəfindən işə salındıqda istifadə olunur, beləliklə izləyici sistem çağırışının yerinə yetirilməsinə mane ola və öz məntiqini həyata keçirə bilər.

Seccomp səhvləri

Zaman zaman Seccomp ilə işləyərkən SECCOMP_RET_ERRNO tipli qaytarma dəyəri ilə müəyyən edilən müxtəlif xətalarla qarşılaşacaqsınız. Səhv barədə məlumat vermək üçün seccomp sistem çağırışı 1 əvəzinə -0 qaytaracaq.

Aşağıdakı səhvlər mümkündür:

- EACCESS - Zəng edənə sistem zəngi etməyə icazə verilmir. Bu, adətən onun CAP_SYS_ADMIN imtiyazlarına malik olmadığı və ya prctl istifadə edərək no_new_privs təyin edilmədiyi üçün baş verir (bu barədə sonra danışacağıq);

— EFAULT — ötürülən arqumentlərin (seccomp_data strukturunda args) etibarlı ünvanı yoxdur;

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

-tələb olunan əməliyyat məlum deyil və ya cari konfiqurasiyada nüvə tərəfindən dəstəklənmir;

- göstərilən bayraqlar tələb olunan əməliyyat üçün etibarlı deyil;

-əməliyyat BPF_ABS daxildir, lakin seccomp_data strukturunun ölçüsünü aşa bilən göstərilən ofsetlə bağlı problemlər var;

-filtreyə ötürülən təlimatların sayı maksimumdan artıqdır;

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

- EOPNOTSUPP - əməliyyat SECCOMP_GET_ACTION_AVAIL ilə əməliyyatın mövcud olduğunu göstərdi, lakin kernel arqumentlərdə qaytarılmaları dəstəkləmir;

— ESRCH — başqa bir axını sinxronlaşdırarkən problem yarandı;

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

prctl istifadəçi məkan proqramına prosesin spesifik aspektlərini, məsələn, bayt endikasiyası, mövzu adları, təhlükəsiz hesablama rejimi (Seccomp), imtiyazlar, Perf hadisələri və s. kimi manipulyasiya etməyə (quraşdırmağa və əldə etməyə) imkan verən sistem çağırışıdır.

Seccomp sizə sandbox texnologiyası kimi görünə bilər, lakin bu belə deyil. Seccomp istifadəçilərə sandbox mexanizmini inkişaf etdirməyə imkan verən bir yardım proqramıdır. İndi isə birbaşa Seccomp sistem çağırışı ilə çağırılan filtrdən istifadə etməklə istifadəçinin qarşılıqlı əlaqə proqramlarının necə yaradıldığına baxaq.

BPF Seccomp Filtr nümunəsi

Burada əvvəllər müzakirə olunan iki hərəkəti necə birləşdirəcəyimizi göstərəcəyik, yəni:

— biz Seccomp BPF proqramını yazacağıq, bu proqram qəbul edilmiş qərarlardan asılı olaraq müxtəlif qaytarma kodları ilə filtr kimi istifadə olunacaq;

— prctl istifadə edərək filtri yükləyin.

Əvvəlcə standart kitabxanadan və Linux nüvəsindən başlıqlara ehtiyacınız 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əyə cəhd etməzdən əvvəl, nüvənin CONFIG_SECCOMP və CONFIG_SECCOMP_FILTER ilə y olaraq təyin olunduğundan əmin olmalıyıq. İşləyən maşında bunu belə yoxlaya bilərsiniz:

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

Kodun qalan hissəsi iki hissəli install_filter funksiyasıdır. Birinci hissədə BPF filtrləmə təlimatlarımızın siyahısı var:

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 linux/filter.h faylında müəyyən edilmiş BPF_STMT və BPF_JUMP makrolarından istifadə etməklə təyin edilir.
Gəlin təlimatları nəzərdən keçirək.

- BPF_STMT(BPF_LD + BPF_W + BPF_ABS (offsetof(struct seccomp_data, arch))) - sistem BPF_LD-dən BPF_W sözü şəklində yüklənir və yığılır, paket məlumatları BPF_ABS sabit ofsetində yerləşir.

- BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, arch, 0, 3) - BPF_JEQ istifadə edərək BPF_K akkumulyator sabitində arxitektura dəyərinin arch-ə bərabər olub olmadığını yoxlayır. Əgər belədirsə, 0 ofsetindən növbəti təlimata keçir, əks halda arch uyğun olmadığı üçün xəta atmaq üçün ofset 3-ə (bu halda) tullanır.

- BPF_STMT(BPF_LD + BPF_W + BPF_ABS (offsetof(struct seccomp_data, nr))) - BPF_LD-dən BPF_ABS-in sabit ofsetində olan sistem çağırış nömrəsi olan BPF_W sözü şəklində yüklənir və yığılır.

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

- BPF_STMT(BPF_RET + BPF_K, SECCOMP_RET_ERRNO | (xəta & SECCOMP_RET_DATA)) - proqramı BPF_RET ilə dayandırır və nəticədə err dəyişəninin nömrəsi ilə SECCOMP_RET_ERRNO xətası yaradır.

- BPF_STMT(BPF_RET + BPF_K, SECCOMP_RET_ALLOW) - proqramı BPF_RET ilə dayandırır və SECCOMP_RET_ALLOW istifadə edərək sistem çağırışını yerinə yetirməyə imkan verir.

SECCOMP CBPF-dir
Tərtib edilmiş ELF obyekti və ya JIT tərtib edilmiş C proqramı əvəzinə niyə təlimatlar siyahısının istifadə edildiyi ilə maraqlana bilərsiniz.

Bunun iki səbəbi var.

• Birincisi, Seccomp eBPF deyil, cBPF (klassik BPF) istifadə edir, yəni: onun registrləri yoxdur, nümunədə göründüyü kimi yalnız sonuncu hesablama nəticəsini saxlamaq üçün akkumulyator var.

• İkincisi, Seccomp birbaşa BPF təlimatları massivinə göstərici qəbul edir, başqa heç nə. İstifadə etdiyimiz makrolar sadəcə olaraq bu təlimatları proqramçıya uyğun şəkildə müəyyən etməyə kömək edir.

Bu montajı başa düşmək üçün daha çox köməyə ehtiyacınız varsa, 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 kodunu təyin etdikdən sonra kodu və filtrin hesablanmış uzunluğunu ehtiva edən sock_fprog təyin etməlisiniz. Bu məlumat strukturu prosesin sonra işə salınması üçün arqument kimi lazımdır:

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

install_filter funksiyasında görüləsi yalnız bir şey qalır - proqramın özünü yükləyin! Bunu etmək üçün təhlükəsiz hesablama rejiminə daxil olmaq üçün PR_SET_SECCOMP-u götürərək prctl-dən istifadə edirik. Sonra rejimə deyirik ki, sock_fprog tipli proqram dəyişəninə daxil olan SECCOMP_MODE_FILTER istifadə edərək filtri yükləsin:

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

Nəhayət, biz install_filter funksiyamızdan istifadə edə bilərik, lakin bundan əvvəl cari icra üçün PR_SET_NO_NEW_PRIVS təyin etmək üçün prctl-dən istifadə etməliyik və bununla da uşaq proseslərin valideynlərindən daha çox imtiyazlar alması vəziyyətindən qaçmalıyıq. Bununla biz root hüquqlarına malik olmadan install_filter funksiyasında aşağıdakı prctl çağırışlarını edə bilərik.

İndi biz install_filter funksiyasını çağıra bilərik. Gəlin X86-64 arxitekturasına aid bütün yazı sistemi zənglərini blok edək və sadəcə olaraq bütün cəhdləri bloklayan icazə verək. Filtri quraşdırdıqdan sonra birinci arqumentdən istifadə edərək icraya davam edirik:

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

Gəlin başlayaq. Proqramımızı tərtib etmək üçün ya clang və ya gcc istifadə edə bilərik, istənilən halda bu, sadəcə olaraq main.c faylını xüsusi seçimlər olmadan tərtib edir:

clang main.c -o filter-write

Qeyd edildiyi kimi, proqramdakı bütün girişləri blokladıq. Bunu yoxlamaq üçün sizə nəsə çıxaran proqram lazımdır - ls yaxşı namizəd kimi görünür. O, adətən belə davranır:

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

Əla! Sarmalayıcı proqramımızdan istifadə belə görünür: Biz sadəcə olaraq sınaqdan keçirmək istədiyimiz proqramı ilk arqument kimi keçirik:

./filter-write "ls -la"

İcra edildikdə, bu proqram tamamilə boş çıxış yaradır. Bununla belə, nə baş verdiyini görmək üçün strace-dən istifadə edə bilərik:

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

İşin nəticəsi çox qısaldılmışdır, lakin onun müvafiq hissəsi qeydlərin EPERM səhvi ilə bloklandığını göstərir - konfiqurasiya etdiyimiz eyni. Bu o deməkdir ki, proqram yazı sistemi çağırışına daxil ola bilmədiyi üçün heç nə çıxarmır:

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

İndi Seccomp BPF-nin necə işlədiyini başa düşürsünüz və onunla nə edə biləcəyinizi yaxşı bilirsiniz. Ancaq tam gücündən istifadə etmək üçün cBPF əvəzinə eBPF ilə eyni şeyə nail olmaq istəməzdinizmi?

eBPF proqramları haqqında düşünərkən insanların çoxu elə bilir ki, onları sadəcə yazıb idarəçi imtiyazları ilə yükləyirlər. Bu ifadə ümumiyyətlə doğru olsa da, nüvə müxtəlif səviyyələrdə eBPF obyektlərini qorumaq üçün bir sıra mexanizmlər tətbiq edir. Bu mexanizmlərə BPF LSM tələləri deyilir.

BPF LSM tələləri

Sistem hadisələrinin arxitekturadan asılı olmayaraq monitorinqini təmin etmək üçün LSM tələlər konsepsiyasını həyata keçirir. Qarmaqlı zəng texniki cəhətdən sistem çağırışına bənzəyir, lakin sistemdən müstəqildir və infrastrukturla inteqrasiya olunur. LSM yeni bir konsepsiya təqdim edir ki, burada abstraksiya təbəqəsi müxtəlif arxitekturalarda sistem çağırışları ilə məşğul olan zaman yaranan problemlərin qarşısını almağa kömək edə bilər.

Yazı zamanı nüvədə BPF proqramları ilə əlaqəli yeddi qarmaq var və SELinux onları həyata keçirən yeganə daxili LSM-dir.

Tələlərin mənbə kodu daxildir/linux/security.h faylının nüvə ağacı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);

Onların hər biri icranın müxtəlif mərhələlərində çağırılacaq:

— security_bpf — icra edilmiş BPF sistem zənglərinin ilkin yoxlanışını həyata keçirir;

- security_bpf_map - nüvənin xəritə üçün fayl deskriptorunu qaytardığını yoxlayır;

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

— security_bpf_map_alloc — BPF xəritələrindəki təhlükəsizlik sahəsinin işə salınıb-başlanılmadığını yoxlayır;

- security_bpf_map_free - təhlükəsizlik sahəsinin BPF xəritələrində təmizlənib-təmizlənmədiyini yoxlayır;

— security_bpf_prog_alloc — təhlükəsizlik sahəsinin BPF proqramları daxilində işə salınıb-başlanılmadığını yoxlayır;

- security_bpf_prog_free - təhlükəsizlik sahəsinin BPF proqramları daxilində təmizləndiyini yoxlayır.

İndi bütün bunları görəndə biz başa düşürük: LSM BPF interceptorlarının arxasında duran ideya ondan ibarətdir ki, onlar hər bir eBPF obyektini qoruya bilər və yalnız müvafiq imtiyazlara malik olanlar kartlar və proqramlar üzərində əməliyyatlar həyata keçirə bilər.

Xülasə

Təhlükəsizlik, qorumaq istədiyiniz hər şey üçün hər kəsə uyğun bir şəkildə həyata keçirə biləcəyiniz bir şey deyil. Sistemləri müxtəlif səviyyələrdə və müxtəlif üsullarla qoruya bilmək vacibdir. İster inanın, istər inanmayın, sistemin təhlükəsizliyini təmin etməyin ən yaxşı yolu müxtəlif mövqelərdən müxtəlif səviyyəli müdafiə təşkil etməkdir ki, bir səviyyənin təhlükəsizliyinin azaldılması bütün sistemə daxil olmağa imkan verməsin. Əsas tərtibatçılar bizə müxtəlif təbəqələr və toxunma nöqtələri dəsti vermək üçün əla iş görmüşlər. Ümid edirik ki, biz sizə təbəqələrin nə olduğunu və onlarla işləmək üçün BPF proqramlarından necə istifadə edəcəyinizi yaxşı başa düşmüşük.

Müəlliflər haqqında

David Kalavera Netlify-də texniki direktordur. O, Docker dəstəyində çalışıb və Runc, Go və BCC alətlərinin, eləcə də digər açıq mənbə layihələrinin inkişafına töhfə verib. Docker layihələrindəki işi və Docker plagin ekosisteminin inkişafı ilə tanınır. David alov qrafiklərinə çox həvəslidir və həmişə performansı optimallaşdırmağa çalışır.

Lorenzo Fontana Sysdig-də açıq mənbə komandasında işləyir, burada o, ilk növbədə nüvə modulu və eBPF vasitəsilə konteynerin işləmə müddətinin təhlükəsizliyini və anomaliyaların aşkarlanmasını təmin edən Cloud Native Computing Foundation layihəsinə diqqət yetirir. O, paylanmış sistemlər, proqram təminatı ilə müəyyən edilmiş şəbəkələr, Linux nüvəsi və performans təhlili ilə maraqlanır.

» Kitab haqqında ətraflı məlumat üçün müraciət edin nəşriyyat saytı
» Mündəricat
» Çıxarış

Khabrozhiteli üçün kuponda 25% endirim - Linux

Kitabın kağız versiyasının ödənişi edildikdən sonra elektron kitab e-poçta göndərilir.

Mənbə: www.habr.com

Добавить комментарий