00 BPF LSM Neden?
SELinux ve AppArmor güçlü güvenlik modülleridir; ancak politika dili karmaşıklığı, dinamik güncelleme zorluğu ve gözlemlenebilirlik eksikliği gömülü sistemlerde ciddi engeller oluşturur. BPF LSM bu sorunları köklü biçimde çözer.
Geleneksel LSM Sorunları
BPF LSM Avantajları
Gereksinim Özeti
Kernel 5.7+ CONFIG_BPF_LSM=y CONFIG_LSM="lockdown,yama,bpf" (veya mevcut listeye ,bpf ekle) CONFIG_DEBUG_INFO_BTF=y (CO-RE için) Kullanıcı Alanı Araçları libbpf >= 0.6 clang >= 12 (BPF hedefi için) bpftool linux-headers veya vmlinux.h (bpftool btf dump ile)
01 LSM Hook Altyapısı
Linux LSM altyapısı, kernel içindeki kritik güvenlik noktalarına hook'lar yerleştirir. BPF LSM bu hook'lara eBPF programları bağlamasına izin vererek mevcut LSM çerçevesini programlanabilir hale getirir.
security_* Hook'ları
Kernel kaynak kodunda security_*() ile başlayan fonksiyonlar LSM hook çağrı noktalarıdır. Bu noktaların her birinde kayıtlı LSM'ler (SELinux, BPF LSM vb.) sırayla çağrılır; herhangi biri ret != 0 döndürürse işlem reddedilir.
| Hook | Tetiklenme Noktası | Dönüş Değeri |
|---|---|---|
| lsm/file_open | Dosya açılırken | 0: izin, negatif: reddet |
| lsm/inode_permission | İzin kontrolünde | 0: izin, -EACCES: reddet |
| lsm/socket_connect | Bağlantı kurulurken | 0: izin, -EPERM: reddet |
| lsm/bprm_check_security | execve() öncesi | 0: izin, -EACCES: reddet |
| lsm/task_kill | Sinyal gönderilirken | 0: izin, -EPERM: reddet |
| lsm/sb_mount | Dosya sistemi bağlanırken | 0: izin, -EPERM: reddet |
BPF_PROG_TYPE_LSM
BPF LSM programları BPF_PROG_TYPE_LSM tipinde tanımlanır ve SEC("lsm/hook_adı") bölüm etiketiyle hangi hook'a bağlandıkları belirtilir:
SEC("lsm/file_open")
int BPF_PROG(my_file_open, struct file *file)
{
/* Güvenlik kararı burada verilir */
return 0; /* izin ver */
}
Sleepable ve Non-Sleepable Programlar
Kernel BTF ve CO-RE
BPF LSM programları CO-RE (Compile Once, Run Everywhere) prensibini kullanır. vmlinux.h dosyası kernel BTF bilgisinden üretilir ve tüm kernel veri yapılarını içerir:
# vmlinux.h üret
bpftool btf dump file /sys/kernel/btf/vmlinux format c > vmlinux.h
02 İlk BPF LSM Programı
libbpf skeleton yaklaşımı, BPF LSM programlarını en az boilerplate kodla derleme ve yükleme imkanı sunar. Aşağıdaki örnek dosya açma işlemlerini izler.
Kernel Tarafı: file_monitor.bpf.c
// file_monitor.bpf.c
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>
#include <bpf/bpf_core_read.h>
char LICENSE[] SEC("license") = "GPL";
/* Olay yapısı -- ring buffer'a yazılacak */
struct event {
u32 pid;
u32 uid;
char comm[16];
char filename[128];
int ret;
};
/* Ring buffer map */
struct {
__uint(type, BPF_MAP_TYPE_RINGBUF);
__uint(max_entries, 256 * 1024);
} events SEC(".maps");
SEC("lsm/file_open")
int BPF_PROG(monitor_file_open, struct file *file)
{
struct event *e;
struct dentry *dentry;
const unsigned char *name;
/* Ring buffer'a alan ayır */
e = bpf_ringbuf_reserve(&events, sizeof(*e), 0);
if (!e)
return 0;
e->pid = bpf_get_current_pid_tgid() >> 32;
e->uid = bpf_get_current_uid_gid() & 0xFFFFFFFF;
bpf_get_current_comm(e->comm, sizeof(e->comm));
/* Dosya adını oku (CO-RE ile güvenli) */
dentry = BPF_CORE_READ(file, f_path.dentry);
name = BPF_CORE_READ(dentry, d_name.name);
bpf_probe_read_kernel_str(e->filename,
sizeof(e->filename), name);
e->ret = 0; /* bu hook izin veriyor */
bpf_ringbuf_submit(e, 0);
return 0; /* erişime izin ver */
}
Kullanıcı Alanı: file_monitor.c
#include <stdio.h>
#include <bpf/libbpf.h>
#include "file_monitor.skel.h"
static int handle_event(void *ctx, void *data, size_t sz)
{
struct event *e = data;
printf("PID=%-6u UID=%-4u COMM=%-16s FILE=%s\n",
e->pid, e->uid, e->comm, e->filename);
return 0;
}
int main(void)
{
struct file_monitor_bpf *skel;
struct ring_buffer *rb;
int err;
/* Skeleton yükle ve BPF programı kernel'e at */
skel = file_monitor_bpf__open_and_load();
if (!skel) { fprintf(stderr, "Yükleme hatası\n"); return 1; }
err = file_monitor_bpf__attach(skel);
if (err) { fprintf(stderr, "Attach hatası\n"); goto cleanup; }
/* Ring buffer dinleyici */
rb = ring_buffer__new(bpf_map__fd(skel->maps.events),
handle_event, NULL, NULL);
if (!rb) { err = -1; goto cleanup; }
printf("Dosya erişimleri izleniyor...\n");
while (ring_buffer__poll(rb, 100) >= 0)
; /* sonsuz döngü */
ring_buffer__free(rb);
cleanup:
file_monitor_bpf__destroy(skel);
return err;
}
Derleme
# BPF nesne dosyası derle
clang -O2 -target bpf -D__TARGET_ARCH_arm64 \
-I/usr/include/bpf \
-c file_monitor.bpf.c -o file_monitor.bpf.o
# Skeleton üret
bpftool gen skeleton file_monitor.bpf.o \
name file_monitor_bpf > file_monitor.skel.h
# Kullanıcı alanı derle
gcc -O2 file_monitor.c -o file_monitor -lbpf
03 Dosya Erişim Kontrolü
lsm/inode_permission hook'u, inode bazında okuma/yazma/yürütme izinlerini denetler. Bu hook ile hassas dizinlere erişimi belirli süreçlerle kısıtlamak mümkündür.
İzin Reddetme Örneği
// deny_write.bpf.c
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>
#include <bpf/bpf_core_read.h>
#include <errno.h>
char LICENSE[] SEC("license") = "GPL";
/* Korunan dizin inode numarası (user space'den doldurulur) */
struct {
__uint(type, BPF_MAP_TYPE_ARRAY);
__uint(max_entries, 1);
__type(key, u32);
__type(value, u64);
} protected_inode SEC(".maps");
SEC("lsm/inode_permission")
int BPF_PROG(deny_write_to_protected, struct inode *inode,
int mask)
{
u32 key = 0;
u64 *target_ino;
u64 ino;
/* Yalnızca yazma girişimlerini denetle */
if (!(mask & MAY_WRITE))
return 0;
target_ino = bpf_map_lookup_elem(&protected_inode, &key);
if (!target_ino)
return 0;
ino = BPF_CORE_READ(inode, i_ino);
if (ino == *target_ino) {
bpf_printk("BPF LSM: inode %llu yazma reddedildi\n", ino);
return -EACCES;
}
return 0;
}
Path Filtreleme
Daha esnek bir yaklaşım için lsm/file_open ile dosya adını string karşılaştırma:
SEC("lsm/file_open")
int BPF_PROG(block_etc_shadow, struct file *file)
{
char fname[32];
struct dentry *dentry;
const unsigned char *name;
dentry = BPF_CORE_READ(file, f_path.dentry);
name = BPF_CORE_READ(dentry, d_name.name);
bpf_probe_read_kernel_str(fname, sizeof(fname), name);
/* /etc/shadow erişimini root olmayan herkes için reddet */
if (__builtin_memcmp(fname, "shadow", 6) == 0) {
u32 uid = bpf_get_current_uid_gid() & 0xFFFFFFFF;
if (uid != 0)
return -EACCES;
}
return 0;
}
İzin Kararı Akışı
open("shadow", O_RDONLY)
|
v
VFS katmanı
|
v
security_file_open() çağrısı
|
+---> SELinux hook (önce)
|
+---> BPF LSM hook (sonra)
|
+-- uid != 0 ? --> return -EACCES --> EPERM kullanıcıya
|
+-- uid == 0 ? --> return 0 ---------> VFS devam eder
Whitelist Yaklaşımı
Yalnızca belirli süreçlere belirli dosyalara erişim izni vermek için MAP tabanlı whitelist:
04 Ağ Politikası
lsm/socket_connect hook'u, bir sürecin TCP/UDP bağlantısı kurmasından önce çağrılır. IP adresi ve port bazında filtreleme ile süreç başına ağ politikası uygulanabilir.
Bağlantı Engelleme Örneği
// net_policy.bpf.c
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>
#include <bpf/bpf_endian.h>
#include <errno.h>
char LICENSE[] SEC("license") = "GPL";
/* Yasaklı portlar haritası: port -> 1 */
struct {
__uint(type, BPF_MAP_TYPE_HASH);
__uint(max_entries, 256);
__type(key, u16);
__type(value, u8);
} blocked_ports SEC(".maps");
SEC("lsm/socket_connect")
int BPF_PROG(block_ports, struct socket *sock,
struct sockaddr *address, int addrlen)
{
struct sockaddr_in *addr4;
u16 port;
u8 *blocked;
char comm[16];
/* Yalnızca IPv4 TCP/UDP kontrol et */
if (address->sa_family != AF_INET)
return 0;
addr4 = (struct sockaddr_in *)address;
port = bpf_ntohs(addr4->sin_port);
blocked = bpf_map_lookup_elem(&blocked_ports, &port);
if (blocked && *blocked) {
bpf_get_current_comm(comm, sizeof(comm));
bpf_printk("BPF LSM: %s port %u'e bağlantı reddedildi\n",
comm, port);
return -EPERM;
}
return 0;
}
Kullanıcı Alanından Port Listesi Güncelleme
// net_policy_user.c (basitleştirilmiş)
#include <bpf/libbpf.h>
#include "net_policy.skel.h"
int main(void)
{
struct net_policy_bpf *skel;
u16 port;
u8 val = 1;
skel = net_policy_bpf__open_and_load();
net_policy_bpf__attach(skel);
/* Telnet portunu engelle */
port = 23;
bpf_map__update_elem(skel->maps.blocked_ports,
&port, sizeof(port),
&val, sizeof(val),
BPF_ANY);
/* FTP portunu engelle */
port = 21;
bpf_map__update_elem(skel->maps.blocked_ports,
&port, sizeof(port),
&val, sizeof(val),
BPF_ANY);
pause(); /* sinyal gelene kadar bekle */
net_policy_bpf__destroy(skel);
return 0;
}
cgroup ile Birleştirme
Süreç bazlı değil konteyner/uygulama grubu bazlı politika için cgroup ID kullanılır:
SEC("lsm/socket_connect")
int BPF_PROG(cgroup_net_policy, struct socket *sock,
struct sockaddr *address, int addrlen)
{
u64 cgroup_id = bpf_get_current_cgroup_id();
/* Yalnızca kısıtlı cgroup'a uygula (cgroup ID harici ayarlanır) */
if (cgroup_id == RESTRICTED_CGROUP_ID) {
/* ... politika uygula ... */
}
return 0;
}
Desteklenen Ağ Hook'ları
| Hook | Tetiklenme | Parametre |
|---|---|---|
| lsm/socket_create | Socket oluşturmada | family, type, protocol |
| lsm/socket_connect | Bağlantı kurulurken | socket, sockaddr |
| lsm/socket_bind | Porta bind sırasında | socket, sockaddr |
| lsm/socket_sendmsg | Mesaj göndermede | socket, msghdr |
| lsm/socket_recvmsg | Mesaj almada | socket, msghdr |
05 Süreç Denetimi
lsm/bprm_check_security hook'u, execve() sistem çağrısının güvenlik denetiminde tetiklenir. Bu hook sayesinde hangi binary'nin çalıştırılabileceği, hash doğrulaması ve imza kontrolü gibi politikalar uygulanabilir.
execve İzleme
// exec_monitor.bpf.c
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>
#include <bpf/bpf_core_read.h>
char LICENSE[] SEC("license") = "GPL";
struct exec_event {
u32 pid;
u32 ppid;
u32 uid;
char comm[16];
char filename[128];
};
struct {
__uint(type, BPF_MAP_TYPE_RINGBUF);
__uint(max_entries, 512 * 1024);
} exec_events SEC(".maps");
SEC("lsm/bprm_check_security")
int BPF_PROG(monitor_exec, struct linux_binprm *bprm)
{
struct exec_event *e;
struct task_struct *task;
const char *fname;
e = bpf_ringbuf_reserve(&exec_events, sizeof(*e), 0);
if (!e) return 0;
task = bpf_get_current_task_btf();
e->pid = BPF_CORE_READ(task, pid);
e->ppid = BPF_CORE_READ(task, real_parent, pid);
e->uid = bpf_get_current_uid_gid() & 0xFFFFFFFF;
bpf_get_current_comm(e->comm, sizeof(e->comm));
fname = BPF_CORE_READ(bprm, filename);
bpf_probe_read_kernel_str(e->filename,
sizeof(e->filename), fname);
bpf_ringbuf_submit(e, 0);
return 0;
}
Binary Whitelist ile Çalıştırma Engeli
/* İzin verilen binary adları MAP'i */
struct {
__uint(type, BPF_MAP_TYPE_HASH);
__uint(max_entries, 64);
__type(key, char[128]);
__type(value, u8);
} allowed_binaries SEC(".maps");
SEC("lsm/bprm_check_security")
int BPF_PROG(whitelist_exec, struct linux_binprm *bprm)
{
char filename[128];
const char *fname;
u8 *allowed;
/* Yalnızca uid=1000 süreçlere uygula */
u32 uid = bpf_get_current_uid_gid() & 0xFFFFFFFF;
if (uid != 1000)
return 0;
fname = BPF_CORE_READ(bprm, filename);
bpf_probe_read_kernel_str(filename, sizeof(filename), fname);
allowed = bpf_map_lookup_elem(&allowed_binaries, filename);
if (!allowed) {
bpf_printk("BPF LSM: %s calistirilmasi reddedildi\n",
filename);
return -EACCES;
}
return 0;
}
Parent-Child İlişkisi Denetimi
Güvenilir olmayan bir sürecin alt süreç (fork+exec) başlatmasını engelleme senaryosu gömülü sistemlerde yaygındır. Örneğin, bir ağ servisi yalnızca önceden tanımlanmış yardımcı programları çalıştırabilir:
06 MAP ile Dinamik Politika
BPF MAP'leri, kullanıcı alanından kernel içindeki BPF programlarına veri aktarmanın temel mekanizmasıdır. Bu sayede güvenlik politikaları çalışma anında güncellenebilir.
Kara Liste MAP'i
// blacklist.bpf.c -- PID bazlı kara liste
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>
#include <errno.h>
char LICENSE[] SEC("license") = "GPL";
/* PID kara listesi: pid -> reason_code */
struct {
__uint(type, BPF_MAP_TYPE_HASH);
__uint(max_entries, 1024);
__type(key, u32);
__type(value, u32);
} pid_blacklist SEC(".maps");
SEC("lsm/file_open")
int BPF_PROG(blacklist_pid_file, struct file *file)
{
u32 pid = bpf_get_current_pid_tgid() >> 32;
u32 *reason;
reason = bpf_map_lookup_elem(&pid_blacklist, &pid);
if (reason) {
bpf_printk("BPF LSM: PID %u kara listede (reason=%u)\n",
pid, *reason);
return -EACCES;
}
return 0;
}
Kullanıcı Alanından Politika Güncelleme
// policy_manager.c
#include <bpf/libbpf.h>
#include <bpf/bpf.h>
#include "blacklist.skel.h"
static int add_to_blacklist(struct blacklist_bpf *skel, u32 pid, u32 reason)
{
return bpf_map__update_elem(skel->maps.pid_blacklist,
&pid, sizeof(pid),
&reason, sizeof(reason),
BPF_ANY);
}
static int remove_from_blacklist(struct blacklist_bpf *skel, u32 pid)
{
return bpf_map__delete_elem(skel->maps.pid_blacklist,
&pid, sizeof(pid), 0);
}
int main(int argc, char **argv)
{
struct blacklist_bpf *skel;
u32 pid, reason;
skel = blacklist_bpf__open_and_load();
blacklist_bpf__attach(skel);
/* PID 1234'ü kara listeye al */
add_to_blacklist(skel, 1234, 42);
/* Kullanıcı komutlarını dinle (basit REPL) */
while (scanf("add %u %u", &pid, &reason) == 2)
add_to_blacklist(skel, pid, reason);
blacklist_bpf__destroy(skel);
return 0;
}
MAP Türleri ve Kullanım Senaryoları
| MAP Türü | Kullanım Senaryosu | Avantaj |
|---|---|---|
| BPF_MAP_TYPE_HASH | PID/UID kara-beyaz liste | O(1) arama, dinamik boyut |
| BPF_MAP_TYPE_ARRAY | Tek yapılandırma değeri | Çok hızlı erişim, sabit boyut |
| BPF_MAP_TYPE_RINGBUF | Olay raporlama | Düşük overhead, sıralı iletim |
| BPF_MAP_TYPE_LPM_TRIE | IP adresi prefix eşleme | CIDR blok filtreleme |
| BPF_MAP_TYPE_PERCPU_HASH | Performans sayaçları | CPU başına kilit yok |
Atomik Politika Geçişi
MAP güncellemeleri atomiktir; okuma ile güncelleme arasında tutarsız durum oluşmaz. Büyük politika geçişlerinde BPF_F_LOCK bayrağı ile spin kilit koruması eklenebilir.
07 Gömülü Güvenlik Senaryosu
Gerçek bir IoT cihazı örneği: MQTT telemetri servisi yalnızca bulut sunucusuna (belirli IP:port) bağlanabilmeli, diğer tüm ağ bağlantıları engellenmelidir. Yetkisiz binary çalıştırma da kısıtlanmıştır.
Senaryo Gereksinimleri
iot_policy.bpf.c
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>
#include <bpf/bpf_core_read.h>
#include <bpf/bpf_endian.h>
#include <errno.h>
char LICENSE[] SEC("license") = "GPL";
/* Yapılandırma: izin verilen sunucu IP ve port */
struct iot_config {
__be32 allowed_ip;
__be16 allowed_port;
u32 mqtt_uid;
};
struct {
__uint(type, BPF_MAP_TYPE_ARRAY);
__uint(max_entries, 1);
__type(key, u32);
__type(value, struct iot_config);
} iot_cfg SEC(".maps");
/* Ağ politikası */
SEC("lsm/socket_connect")
int BPF_PROG(iot_net_policy, struct socket *sock,
struct sockaddr *address, int addrlen)
{
struct sockaddr_in *addr4;
struct iot_config *cfg;
u32 key = 0, uid;
if (address->sa_family != AF_INET)
return 0;
cfg = bpf_map_lookup_elem(&iot_cfg, &key);
if (!cfg) return 0;
uid = bpf_get_current_uid_gid() & 0xFFFFFFFF;
if (uid != cfg->mqtt_uid)
return 0; /* diğer kullanıcılara dokunma */
addr4 = (struct sockaddr_in *)address;
/* İzin verilen IP+port kontrolü */
if (addr4->sin_addr.s_addr == cfg->allowed_ip &&
addr4->sin_port == cfg->allowed_port)
return 0;
bpf_printk("IoT LSM: uid=%u yetkisiz baglanti engellendi\n",
uid);
return -EPERM;
}
/* Dosya erişim politikası */
SEC("lsm/file_open")
int BPF_PROG(iot_file_policy, struct file *file)
{
char fname[32];
struct dentry *dentry;
const unsigned char *name;
u32 uid;
dentry = BPF_CORE_READ(file, f_path.dentry);
name = BPF_CORE_READ(dentry, d_name.name);
bpf_probe_read_kernel_str(fname, sizeof(fname), name);
/* "certs" dizinine erişimi kısıtla */
if (__builtin_memcmp(fname, "certs", 5) == 0) {
struct iot_config *cfg;
u32 key = 0;
cfg = bpf_map_lookup_elem(&iot_cfg, &key);
uid = bpf_get_current_uid_gid() & 0xFFFFFFFF;
if (cfg && uid != cfg->mqtt_uid)
return -EACCES;
}
return 0;
}
Politika Güncelleme (Sunucu IP Değişimi)
/* Yeni sunucu adresini yükle -- yeniden başlatma gerekmez */
struct iot_config new_cfg = {
.allowed_ip = htonl(0xC0A80165), /* 192.168.1.101 */
.allowed_port = htons(8883),
.mqtt_uid = 1001,
};
u32 key = 0;
bpf_map__update_elem(skel->maps.iot_cfg,
&key, sizeof(key),
&new_cfg, sizeof(new_cfg),
BPF_ANY);
08 Hata Ayıklama ve Doğrulama
BPF LSM programlarını doğrulamak ve hata ayıklamak için bpftool, bpftrace ve kernel verifier mesajları temel araçlardır. CAP_BPF yetki gereksinimleri de dikkate alınmalıdır.
bpftool ile Program Listeleme
# Yüklü BPF programlarını listele
bpftool prog list
# LSM programlarını filtrele
bpftool prog list | grep lsm
# Belirli programın detayları
bpftool prog show id 42
# Programın BPF bytecode'unu göster
bpftool prog dump xlated id 42
# JIT sonrası makine kodu
bpftool prog dump jited id 42
bpftrace ile Anlık İzleme
# Tüm LSM hook çağrılarını say
bpftrace -e 'kprobe:security_file_open { @[comm] = count(); }'
# BPF LSM reddedilen işlemleri izle
bpftrace -e 'kretprobe:bpf_lsm_file_open /retval != 0/ {
printf("DENY: %s ret=%d\n", comm, retval);
}'
# execve çağrılarını izle
bpftrace -e 'tracepoint:syscalls:sys_enter_execve {
printf("exec: %s %s\n", comm, str(args->filename));
}'
Verifier Hataları ve Çözümleri
CAP_BPF Gereksinimleri
# BPF LSM programı yüklemek için gereken yetkiler:
# CAP_BPF -- BPF programı yükleme
# CAP_MAC_ADMIN -- LSM hook'a program bağlama
# Test amaçlı (üretimde kaçının):
setcap cap_bpf,cap_mac_admin+eip ./my_lsm_tool
# systemd servis olarak (güvenli yol):
# [Service]
# AmbientCapabilities=CAP_BPF CAP_MAC_ADMIN
# CapabilityBoundingSet=CAP_BPF CAP_MAC_ADMIN
BPF Log Seviyesi
# bpf_printk çıktılarını göster
cat /sys/kernel/debug/tracing/trace_pipe
# Verifier ayrıntılı log (yükleme hatası durumunda)
# libbpf ile:
libbpf_set_print(LIBBPF_DEBUG, ...);
Performans Ölçümü
BPF LSM hook'larının gecikme etkisini ölçmek için perf ya da bpftrace kullanılabilir. Kritik yolda çalışan hook'lar (file_open, socket_connect) mikrosaniye mertebesinde etkiye sahip olabilir; MAP erişimlerini en aza indirin ve gereksiz bpf_printk çağrılarını üretimde devre dışı bırakın.
# file_open hook gecikmesini ölç
bpftrace -e '
kprobe:bpf_lsm_file_open { @start[tid] = nsecs; }
kretprobe:bpf_lsm_file_open /@start[tid]/ {
@latency = hist(nsecs - @start[tid]);
delete(@start[tid]);
}
'