"Linux İzleme için BPF" Kitabı

"Linux İzleme için BPF" KitabıMerhaba Khabro sakinleri! BPF sanal makinesi, Linux çekirdeğinin en önemli bileşenlerinden biridir. Doğru kullanımı, sistem mühendislerinin hataları bulmasına ve en karmaşık sorunları bile çözmesine olanak tanır. Çekirdeğin davranışını izleyen ve değiştiren programların nasıl yazılacağını, çekirdekteki olayları izlemek için kodun güvenli bir şekilde nasıl uygulanacağını ve çok daha fazlasını öğreneceksiniz. David Calavera ve Lorenzo Fontana, BPF'nin gücünü ortaya çıkarmanıza yardımcı olacak. Performans optimizasyonu, ağ iletişimi ve güvenlik bilginizi genişletin. - Linux çekirdeğinin davranışını izlemek ve değiştirmek için BPF'yi kullanın. - Çekirdeği yeniden derlemeye veya sistemi yeniden başlatmaya gerek kalmadan çekirdek olaylarını güvenli bir şekilde izlemek için kod enjekte edin. — C, Go veya Python'daki uygun kod örneklerini kullanın. - BPF program yaşam döngüsüne sahip olarak kontrolü elinize alın.

Linux Çekirdek Güvenliği, Özellikleri ve Secomp

BPF, kararlılık, güvenlik veya hızdan ödün vermeden çekirdeği genişletmenin güçlü bir yolunu sağlar. Bu nedenle çekirdek geliştiricileri, Secomp BPF olarak da bilinen BPF programları tarafından desteklenen Seccomp filtrelerini uygulayarak Seccomp'ta süreç izolasyonunu geliştirmek için çok yönlülüğünü kullanmanın iyi bir fikir olacağını düşündüler. Bu bölümde Secomp'un ne olduğunu ve nasıl kullanıldığını açıklayacağız. Daha sonra BPF programlarını kullanarak Secomp filtrelerini nasıl yazacağınızı öğreneceksiniz. Bundan sonra, Linux güvenlik modülleri için çekirdekte bulunan yerleşik BPF kancalarına bakacağız.

Linux Güvenlik Modülleri (LSM), çeşitli güvenlik modellerini standart bir şekilde uygulamak için kullanılabilecek bir dizi işlev sağlayan bir çerçevedir. LSM, Apparmor, SELinux ve Tomoyo gibi doğrudan çekirdek kaynak ağacında kullanılabilir.

Linux'un yeteneklerini tartışarak başlayalım.

fırsatlar

Linux'un yeteneklerinin özü, ayrıcalıksız bir işleme belirli bir görevi gerçekleştirmek için izin vermeniz, ancak bu amaçla suid kullanmadan veya başka bir şekilde süreci ayrıcalıklı hale getirerek saldırı olasılığını azaltmanız ve sürecin belirli görevleri gerçekleştirmesine izin vermeniz gerektiğidir. Örneğin, uygulamanızın ayrıcalıklı bir bağlantı noktası (örneğin 80) açması gerekiyorsa, işlemi root olarak çalıştırmak yerine ona CAP_NET_BIND_SERVICE yeteneğini verebilirsiniz.

Main.go adlı bir Go programını düşünün:

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

Bu program, 80 numaralı bağlantı noktasında (bu ayrıcalıklı bir bağlantı noktasıdır) bir HTTP sunucusuna hizmet eder. Genellikle derlemeden hemen sonra çalıştırırız:

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

Ancak kök ayrıcalıkları vermediğimiz için bu kod, bağlantı noktasını bağlarken hata verecektir:

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

capsh (kabuk yöneticisi), belirli yeteneklere sahip bir kabuğu çalıştıran bir araçtır.

Bu durumda, daha önce de belirtildiği gibi, tam kök hakları vermek yerine, programda zaten bulunan diğer her şeyin yanı sıra cap_net_bind_service özelliğini de sağlayarak ayrıcalıklı bağlantı noktası bağlamayı etkinleştirebilirsiniz. Bunu yapmak için programımızı capsh içine alabiliriz:

# 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 takımı biraz anlayalım.

  • capsh - capsh'i kabuk olarak kullanın.
  • —caps='cap_net_bind_service+eip cap_setpcap,cap_setuid,cap_setgid+ep' - kullanıcıyı değiştirmemiz gerektiğinden (root olarak çalıştırmak istemiyoruz), cap_net_bind_service'i ve kullanıcı kimliğini gerçekten değiştirme yeteneğini belirteceğiz kimseye kök salmayın, yani cap_setuid ve cap_setgid.
  • —keep=1 - kök hesaptan geçiş yaparken yüklü yetenekleri korumak istiyoruz.
  • —user=“nobody” — programı çalıştıran son kullanıcı hiç kimse olmayacaktır.
  • —addamb=cap_net_bind_service - kök modundan geçişten sonra ilgili yeteneklerin temizlenmesini ayarlayın.
  • - -c "./cacapability" - programı çalıştırmanız yeterli.

Bağlantılı yetenekler, geçerli program onları execve() kullanarak çalıştırdığında alt programlar tarafından devralınan özel bir yetenek türüdür. Yalnızca ilişkilendirilmesine veya başka bir deyişle ortam yetenekleri olarak izin verilen yetenekler devralınabilir.

--caps seçeneğinde yeteneği belirttikten sonra muhtemelen +eip'in ne anlama geldiğini merak ediyorsunuzdur. Bu bayraklar aşağıdaki yetenekleri belirlemek için kullanılır:

-etkinleştirilmelidir (p);

- kullanıma hazır (e);

-çocuk süreçler (i) tarafından miras alınabilir.

cap_net_bind_service kullanmak istediğimiz için bunu e flag ile yapmamız gerekiyor. Daha sonra komutta kabuğu başlatacağız. Bu, yetenek ikilisini çalıştıracak ve bunu i bayrağıyla işaretlememiz gerekiyor. Son olarak p ile özelliğin aktif olmasını istiyoruz (bunu UID değiştirmeden yaptık). Cap_net_bind_service+eip'e benziyor.

Sonucu ss kullanarak kontrol edebilirsiniz. Çıktıyı sayfaya sığacak şekilde biraz kısaltalım, ancak ilgili bağlantı noktasını ve 0 dışında kullanıcı kimliğini (bu durumda 65) gösterecektir:

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

Bu örnekte biz capsh kullandık ama siz libcap kullanarak da kabuk yazabilirsiniz. Daha fazla bilgi için man 3 libcap'e bakın.

Program yazarken, geliştirici çoğu zaman programın çalışma zamanında ihtiyaç duyduğu tüm özellikleri önceden bilmez; Üstelik bu özellikler yeni sürümlerde değişiklik gösterebilir.

Programımızın yeteneklerini daha iyi anlamak için, cap_capable çekirdek fonksiyonu için kprobe'u ayarlayan BCC özellikli aracı kullanabiliriz:

/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 çekirdek fonksiyonunda bpftrace'i tek satırlık bir kprobe ile kullanarak aynı şeyi başarabiliriz:

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

Eğer programımızın yetenekleri kprobe'dan sonra etkinleştirilirse, bu çıktı aşağıdaki gibi olacaktır:

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ı, denetim dışı olayları da içerdiğinden, tüm denetim dışı kontrolleri ve son olarak gerekli yeteneği, denetim bayrağı (çıkışta sonuncu) 1 olarak ayarlandığında görüyoruz. ilgilendiğimiz CAP_NET_BIND_SERVICE'tır, include/uapi/linux/ability.h dosyasındaki çekirdek kaynak kodunda 10 tanımlayıcıyla bir sabit olarak tanımlanı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">

Yetenekler genellikle runC veya Docker gibi kapsayıcıların ayrıcalıksız modda çalışmasına izin vermek için çalışma zamanında etkinleştirilir, ancak bunlara yalnızca çoğu uygulamayı çalıştırmak için gereken yeteneklere izin verilir. Bir uygulama belirli yetenekler gerektirdiğinde Docker bunları --cap-add kullanarak sağlayabilir:

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

Bu komut, konteynere CAP_NET_ADMIN yeteneğini verecek ve dummy0 arayüzünü eklemek için bir ağ bağlantısı yapılandırmasına olanak tanıyacaktır.

Bir sonraki bölümde filtreleme gibi özelliklerin nasıl kullanılacağı, ancak kendi filtrelerimizi programlı olarak uygulamamıza olanak tanıyan farklı bir teknik kullanılarak gösterilmektedir.

Secomp

Secomp, Güvenli Bilgi İşlem anlamına gelir ve geliştiricilerin belirli sistem çağrılarını filtrelemesine olanak tanıyan, Linux çekirdeğinde uygulanan bir güvenlik katmanıdır. Secomp, yetenekleri açısından Linux ile karşılaştırılabilir olsa da, belirli sistem çağrılarını yönetebilme yeteneği, onu onlara kıyasla çok daha esnek hale getiriyor.

Seccomp ve Linux özellikleri birbirini dışlamaz ve her iki yaklaşımdan da yararlanmak için sıklıkla birlikte kullanılır. Örneğin, bir işleme CAP_NET_ADMIN yeteneğini vermek, ancak soket bağlantılarını kabul etmesine izin vermemek, kabul ve kabul4 sistem çağrılarını engellemek isteyebilirsiniz.

Seccomp filtreleme yöntemi, SECCOMP_MODE_FILTER modunda çalışan BPF filtrelerine dayanır ve sistem çağrısı filtrelemesi, paketlerle aynı şekilde gerçekleştirilir.

Secomp filtreleri PR_SET_SECCOMP işlemi aracılığıyla prctl kullanılarak yüklenir. Bu filtreler seccomp_data yapısıyla temsil edilen her Seccomp paketi için yürütülen bir BPF programı biçimini alır. Bu yapı, referans mimarisini, sistem çağrısı sırasındaki işlemci talimatlarını gösteren bir işaretçiyi ve uint64 olarak ifade edilen maksimum altı sistem çağrısı argümanını içerir.

linux/seccomp.h dosyasındaki çekirdek kaynak kodundan seccomp_data yapısı şu şekilde görünüyor:

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

Bu yapıdan da görebileceğiniz gibi, sistem çağrısına, argümanlarına veya her ikisinin birleşimine göre filtreleme yapabiliriz.

Her Secomp paketini aldıktan sonra, filtrenin son kararı vermek ve çekirdeğe bundan sonra ne yapacağını söylemek için işlem yapması gerekir. Nihai karar, dönüş değerlerinden biri (durum kodları) ile ifade edilir.

- SECCOMP_RET_KILL_PROCESS - bu nedenle yürütülmeyen bir sistem çağrısını filtreledikten hemen sonra tüm süreci sonlandırır.

- SECCOMP_RET_KILL_THREAD - bu nedenle yürütülmeyen bir sistem çağrısını filtreledikten hemen sonra mevcut iş parçacığını sonlandırır.

— SECCOMP_RET_KILL — SECCOMP_RET_KILL_THREAD takma adı, geriye dönük uyumluluk için bırakılmıştır.

- SECCOMP_RET_TRAP - sistem çağrısı yasaktır ve onu çağıran göreve SIGSYS (Kötü Sistem Çağrısı) sinyali gönderilir.

- SECCOMP_RET_ERRNO - Sistem çağrısı yürütülmez ve SECCOMP_RET_DATA filtre dönüş değerinin bir kısmı kullanıcı alanına errno değeri olarak aktarılır. Hatanın nedenine bağlı olarak farklı errno değerleri döndürülür. Bir sonraki bölümde hata numaralarının bir listesi verilmektedir.

- SECCOMP_RET_TRACE - Bir sistem çağrısı yürütüldüğünde bu işlemi görmek ve kontrol etmek için müdahale etmek üzere - PTRACE_O_TRACESECCOMP kullanarak ptrace izleyicisine bildirimde bulunmak için kullanılır. Tracer bağlı değilse bir hata döndürülür, errno -ENOSYS olarak ayarlanır ve sistem çağrısı yürütülmez.

- SECOMP_RET_LOG - sistem çağrısı çözümlenir ve günlüğe kaydedilir.

- SECOMP_RET_ALLOW - sistem çağrısına basitçe izin verilir.

ptrace, tracee adı verilen bir süreçte, sürecin yürütülmesini izleme ve kontrol etme becerisine sahip izleme mekanizmalarını uygulamaya yönelik bir sistem çağrısıdır. İzleme programı, yürütmeyi etkili bir şekilde etkileyebilir ve tracee'nin bellek kayıtlarını değiştirebilir. Seccomp bağlamında, SECCOMP_RET_TRACE durum kodu tarafından tetiklendiğinde ptrace kullanılır, böylece izleyici sistem çağrısının kendi mantığını yürütmesini ve uygulamasını engelleyebilir.

Secomp hataları

Seccomp ile çalışırken zaman zaman SECCOMP_RET_ERRNO tipi dönüş değeriyle tanımlanan çeşitli hatalarla karşılaşırsınız. Bir hatayı bildirmek için seccomp sistem çağrısı 1 yerine -0 değerini döndürecektir.

Aşağıdaki hatalar mümkündür:

- EACCESS - Arayanın sistem çağrısı yapmasına izin verilmez. Bu genellikle CAP_SYS_ADMIN ayrıcalıklarına sahip olmadığı veya no_new_privs'in prctl kullanılarak ayarlanmadığı için olur (bunun hakkında daha sonra konuşacağız);

— EFAULT — aktarılan argümanların (seccomp_data yapısındaki argümanlar) geçerli bir adresi yok;

— EINVAL — bunun dört nedeni olabilir:

-istenen işlem bilinmiyor veya mevcut yapılandırmada çekirdek tarafından desteklenmiyor;

-belirtilen bayraklar istenen işlem için geçerli değil;

-işlem BPF_ABS'yi içerir, ancak belirtilen ofset ile ilgili seccomp_data yapısının boyutunu aşabilecek sorunlar vardır;

-filtreye iletilen talimatların sayısı maksimumu aşıyor;

— ENOMEM — programı yürütmek için yeterli bellek yok;

- EOPNOTSUPP - işlem, SECCOMP_GET_ACTION_AVAIL ile eylemin mevcut olduğunu ancak çekirdeğin argümanlardaki dönüşleri desteklemediğini gösterdi;

— ESRCH — başka bir akışı senkronize ederken bir sorun oluştu;

- ENOSYS - SECCOMP_RET_TRACE eylemine eklenmiş izleyici yok.

prctl, bir kullanıcı alanı programının bayt endianness, iş parçacığı adları, güvenli hesaplama modu (Seccomp), ayrıcalıklar, Perf olayları vb. gibi bir sürecin belirli yönlerini değiştirmesine (ayarlamasına ve almasına) olanak tanıyan bir sistem çağrısıdır.

Seccomp size bir sanal alan teknolojisi gibi görünebilir, ancak öyle değil. Secomp, kullanıcıların bir sanal alan mekanizması geliştirmesine olanak tanıyan bir yardımcı programdır. Şimdi doğrudan Secomp sistem çağrısı tarafından çağrılan bir filtre kullanılarak kullanıcı etkileşim programlarının nasıl oluşturulduğuna bakalım.

BPF Secomp Filtre Örneği

Burada daha önce tartışılan iki eylemin nasıl birleştirileceğini göstereceğiz:

— alınan kararlara bağlı olarak farklı dönüş kodlarıyla filtre olarak kullanılacak bir Secomp BPF programı yazacağız;

— filtreyi prctl kullanarak yükleyin.

Öncelikle standart kütüphaneden ve Linux çekirdeğinden başlıklara ihtiyacı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 örneği denemeden önce, çekirdeğin CONFIG_SECCOMP ve CONFIG_SECCOMP_FILTER'ın y'ye ayarlanmış şekilde derlendiğinden emin olmalıyız. Çalışan bir makinede bunu şu şekilde kontrol edebilirsiniz:

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

Kodun geri kalanı iki parçalı bir install_filter işlevidir. İlk bölüm BPF filtreleme talimatları listemizi içerir:

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

Talimatlar, linux/filter.h dosyasında tanımlanan BPF_STMT ve BPF_JUMP makroları kullanılarak ayarlanır.
Talimatları gözden geçirelim.

- BPF_STMT(BPF_LD + BPF_W + BPF_ABS (offsetof(struct seccomp_data, arch))) - sistem BPF_LD'den BPF_W kelimesi biçiminde yüklenir ve biriktirilir, paket verileri sabit bir BPF_ABS ofsetinde bulunur.

- BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, arch, 0, 3) - BPF_JEQ kullanarak akümülatör sabiti BPF_K'deki mimari değerinin arch'a eşit olup olmadığını kontrol eder. Eğer öyleyse, ofset 0'da bir sonraki talimata atlar, aksi halde ofset 3'te (bu durumda) atlayarak kemer eşleşmediğinden bir hata oluşturur.

- BPF_STMT(BPF_LD + BPF_W + BPF_ABS (offsetof(struct seccomp_data, nr)))) - BPF_ABS'nin sabit ofsetinde yer alan sistem çağrı numarası olan BPF_W sözcüğü biçiminde BPF_LD'den yükler ve biriktirir.

— BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, nr, 0, 1) — sistem çağrı numarasını nr değişkeninin değeriyle karşılaştırır. Eğer eşitse bir sonraki talimata geçer ve sistem çağrısını devre dışı bırakır, aksi halde SECCOMP_RET_ALLOW ile sistem çağrısına izin verir.

- BPF_STMT(BPF_RET + BPF_K, SECCOMP_RET_ERRNO | (error & SECCOMP_RET_DATA)) - programı BPF_RET ile sonlandırır ve sonuç olarak err değişkeninden gelen sayıyla birlikte SECCOMP_RET_ERRNO hatası üretir.

- BPF_STMT(BPF_RET + BPF_K, SECCOMP_RET_ALLOW) - programı BPF_RET ile sonlandırır ve sistem çağrısının SECCOMP_RET_ALLOW kullanılarak yürütülmesine izin verir.

SECCOMP CBPF'dir
Derlenmiş bir ELF nesnesi veya JIT ile derlenmiş bir C programı yerine neden bir talimat listesinin kullanıldığını merak ediyor olabilirsiniz.

Bunun iki nedeni var.

• İlk olarak Seccomp, eBPF'yi değil cBPF'yi (klasik BPF) kullanır, bu da şu anlama gelir: örnekte görülebileceği gibi kayıtları yoktur, yalnızca hesaplamanın son sonucunu saklayan bir akümülatör vardır.

• İkinci olarak Secomp, bir dizi BPF talimatına yönelik işaretçiyi doğrudan kabul eder, başka hiçbir şeyi kabul etmez. Kullandığımız makrolar, bu talimatların programcı dostu bir şekilde belirlenmesine yardımcı olur.

Bu derlemeyi anlamak için daha fazla yardıma ihtiyacınız varsa aynı şeyi yapan sözde kodu göz önünde bulundurun:

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

Socket_filter yapısında filtre kodunu tanımladıktan sonra, kodu ve filtrenin hesaplanan uzunluğunu içeren bir sock_fprog tanımlamanız gerekir. Bu veri yapısına, sürecin daha sonra çalışacağını bildirmek için bir argüman olarak ihtiyaç duyulur:

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

install_filter işlevinde yapılacak tek bir şey kaldı - programın kendisini yüklemek! Bunu yapmak için, güvenli bilgi işlem moduna girme seçeneği olarak PR_SET_SECOMP'u alarak prctl'yi kullanırız. Daha sonra moda, sock_fprog tipindeki prog değişkeninde bulunan SECCOMP_MODE_FILTER'ı kullanarak filtreyi yüklemesini söyleriz:

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

Son olarak, install_filter işlevimizi kullanabiliriz, ancak bundan önce mevcut yürütme için PR_SET_NO_NEW_PRIVS'yi ayarlamak ve böylece alt süreçlerin ebeveynlerinden daha fazla ayrıcalık alması durumundan kaçınmak için prctl kullanmamız gerekir. Bununla root haklarına sahip olmadan install_filter fonksiyonunda aşağıdaki prctl çağrılarını yapabiliriz.

Artık install_filter fonksiyonunu çağırabiliriz. X86-64 mimarisi ile ilgili tüm yazma sistem çağrılarını engelleyelim ve tüm girişimleri engelleyen bir izin verelim. Filtreyi kurduktan sonra ilk argümanı kullanarak yürütmeye devam ediyoruz:

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şlayalım. Programımızı derlemek için clang veya gcc'yi kullanabiliriz, her iki durumda da bu sadece main.c dosyasını özel seçenekler olmadan derlemek anlamına gelir:

clang main.c -o filter-write

Belirtildiği gibi programdaki tüm girişleri engelledik. Bunu test etmek için bir çıktı veren bir programa ihtiyacınız var - ls iyi bir aday gibi görünüyor. Genellikle şu şekilde 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

Müthiş! Sarmalayıcı programımızı kullanmak şuna benzer: Test etmek istediğimiz programı ilk argüman olarak iletiyoruz:

./filter-write "ls -la"

Bu program çalıştırıldığında tamamen boş çıktı üretir. Ancak neler olduğunu görmek için strace'i kullanabiliriz:

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

Çalışmanın sonucu büyük ölçüde kısaltıldı, ancak ilgili kısmı, kayıtların yapılandırdığımız EPERM hatasıyla engellendiğini gösteriyor. Bu, programın yazma sistemi çağrısına erişemediği için hiçbir çıktı vermediği anlamına gelir:

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

Artık Secomp BPF'nin nasıl çalıştığını anlıyorsunuz ve onunla neler yapabileceğinize dair iyi bir fikriniz var. Ancak aynı şeyi cBPF yerine eBPF ile başarmak ve onun tüm gücünden yararlanmak istemez misiniz?

eBPF programlarını düşünürken çoğu kişi bunları basitçe yazıp yönetici ayrıcalıklarıyla yüklediklerini düşünür. Bu ifade genel olarak doğru olsa da çekirdek, eBPF nesnelerini çeşitli düzeylerde korumak için bir dizi mekanizma uygular. Bu mekanizmalara BPF LSM tuzakları denir.

BPF LSM tuzakları

Sistem olaylarının mimariden bağımsız izlenmesini sağlamak için LSM, tuzak kavramını uygular. Kanca çağrısı teknik olarak sistem çağrısına benzer ancak sistemden bağımsızdır ve altyapıyla entegredir. LSM, farklı mimarilerdeki sistem çağrılarıyla uğraşırken karşılaşılan sorunların önlenmesine bir soyutlama katmanının yardımcı olabileceği yeni bir konsept sağlar.

Bu yazının yazıldığı sırada, çekirdeğin BPF programlarıyla ilişkili yedi kancası vardır ve SELinux bunları uygulayan tek yerleşik LSM'dir.

Tuzakların kaynak kodu çekirdek ağacındaki include/linux/security.h dosyasında bulunur:

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

Her biri yürütmenin farklı aşamalarında çağrılacak:

— Security_bpf — yürütülen BPF sistem çağrılarının ilk kontrolünü gerçekleştirir;

- Security_bpf_map - çekirdeğin harita için bir dosya tanımlayıcı döndürdüğünü kontrol eder;

- Security_bpf_prog - çekirdeğin eBPF programı için ne zaman bir dosya tanımlayıcı döndürdüğünü kontrol eder;

— Security_bpf_map_alloc — BPF haritalarındaki güvenlik alanının başlatılıp başlatılmadığını kontrol eder;

- Security_bpf_map_free - BPF haritalarındaki güvenlik alanının temizlenip temizlenmediğini kontrol eder;

— Security_bpf_prog_alloc — güvenlik alanının BPF programlarında başlatılıp başlatılmadığını kontrol eder;

-security_bpf_prog_free - BPF programları içindeki güvenlik alanının temizlenip temizlenmediğini kontrol eder.

Şimdi tüm bunları görünce şunu anlıyoruz: LSM BPF önleyicilerinin arkasındaki fikir, bunların her eBPF nesnesine koruma sağlayabilmesi, yalnızca uygun ayrıcalıklara sahip olanların kartlar ve programlar üzerinde işlem yapabilmesini sağlamasıdır.

Özet

Güvenlik, korumak istediğiniz her şey için herkese uyacak tek bir yöntemle uygulayabileceğiniz bir şey değildir. Sistemleri farklı seviyelerde ve farklı şekillerde koruyabilmek önemlidir. İster inanın ister inanmayın, bir sistemi güvence altına almanın en iyi yolu, farklı konumlardan farklı koruma seviyelerini organize etmektir, böylece bir seviyenin güvenliğinin azaltılması tüm sisteme erişime izin vermez. Çekirdek geliştiriciler bize bir dizi farklı katman ve temas noktası sağlayarak harika bir iş çıkardılar. Katmanların ne olduğu ve onlarla çalışmak için BPF programlarının nasıl kullanılacağı konusunda size iyi bir anlayış sağladığımızı umuyoruz.

Yazarlar hakkında

David Calavera Netlify'ın CTO'sudur. Docker desteğinde çalıştı ve Runc, Go ve BCC araçlarının yanı sıra diğer açık kaynaklı projelerin geliştirilmesine katkıda bulundu. Docker projeleri ve Docker eklenti ekosisteminin geliştirilmesi üzerine yaptığı çalışmalarla tanınır. David alev grafikleri konusunda oldukça tutkulu ve her zaman performansı optimize etmeye çalışıyor.

Lorenzo Fontana Sysdig'deki açık kaynak ekibinde çalışıyor ve burada öncelikli olarak, bir çekirdek modülü ve eBPF aracılığıyla konteyner çalışma zamanı güvenliği ve anormallik tespiti sağlayan bir Bulut Yerel Bilgi İşlem Vakfı projesi olan Falco'ya odaklanıyor. Dağıtılmış sistemler, yazılım tanımlı ağ oluşturma, Linux çekirdeği ve performans analizi konularında tutkuludur.

» Kitapla ilgili daha detaylı bilgiye şu adresten ulaşabilirsiniz: yayıncının web sitesi
» içindekiler
» Alıntı

Khabrozhiteley için kupon kullanarak %25 indirim - Linux

Kitabın basılı versiyonunun ödenmesi üzerine e-posta yoluyla bir elektronik kitap gönderilecektir.

Kaynak: habr.com

Yorum ekle