BPF Map'ler ve State Yonetimi
Cross-syscall state tracking
Önceki alıştırmada tüm read() çağrılarını yakaladık. Peki yalnızca /tmp/password gibi belirli bir dosyadan yapılan okumalarla ilgileniyorsak ne yapmalıyız?
Sorun şu: read() dosya adlarıyla değil, file descriptor’larla çalışır. Bir process open("/tmp/password") çağırdığında, kernel bir file descriptor (fd) döndürür, bu process’e özgü benzersiz bir sayıdır (örneğin 4). Sonraki read çağrıları bu sayıyı kullanır: read(4, buf, ...).
read() syscall’ını gördüğümüzde, elimizde yalnızca fd bulunur. Dosya adı artık erişilebilir değildir.
Dosya adına göre okuma işlemlerini filtrelemek için open() ve read() syscall’larını ilişkilendirmemiz gerekir: open anında hangi fd’lerin /tmp/password’a karşılık geldiğini takip ederiz, ardından read’leri yakalarken bu bilgiyi kullanırız.
open ve read’i ilişkilendirme
Dört event’e hook olup aralarında bilgi aktarmamız gerekiyor:
Bir process /tmp/password dosyasını açtığında:
- open entry - Dosya adını görürüz ve hedefimizle eşleşip eşleşmediğini kontrol edebiliriz
- open exit - Bu dosyaya atanan fd’yi elde ederiz
Aynı process fd’den okuduğunda:
- read entry - Hangi fd’den okunduğunu ve hedef buffer’ı görürüz
- read exit - Kaç byte yazıldığını görürüz
Buradaki zorluk noktaları birbirine bağlamaktır: 3. adımda fd 4’ün 1. adımdaki ilgilendiğimiz fd olduğunu nasıl bileceğiz?
Üç map ile takip
Bu akış boyunca bilgi aktarmak için üç map kullanacağız.
open sırasında geçici işaretleyici:
open entry noktasında, /tmp/password açan PID’leri open_curr_fd_interesting map’inde işaretleriz. Exit noktasında, döndürülen fd’nin ilgi çekici olup olmadığını anlamak için bu işareti kontrol ederiz.
Güvenle PID’yi key olarak kullanabiliriz, çünkü thread syscall sırasında blokludur, bu çağrı tamamlanana kadar başka bir open() çağrısı yapamaz.
İlgi çekici fd’lerin kalıcı takibi:
PID 123’ün /tmp/password için fd 4 aldığını öğrendiğimizde, bu ilişkiyi hatırlamamız gerekir. Daha sonra read(4, ...) gördüğümüzde, (123, 4) çiftinin map’imizde olup olmadığını kontrol edebiliriz.
Bu, ilk composite key’imizi gerektirir:
struct pid_fd_key {
u64 pid;
u32 fd;
};
struct {
__uint(type, BPF_MAP_TYPE_HASH);
__uint(max_entries, 1024);
__type(key, struct pid_fd_key);
__type(value, u8); // marker
} open_interesting_fds SEC(".maps");
Bir process aynı anda birden fazla file descriptor açık tutabileceği için key’te hem PID hem de FD’ye ihtiyacımız var. fd 4 /tmp/password olabilirken fd 5 /etc/config olabilir.
read sırasında geçici depolama:
Önceki alıştırmada olduğu gibi, read entry’den read exit’e buffer pointer’ı kaydetmek için PID’yi key olarak kullanarak read_curr_fd_buf tanımlayacağız.
Handler pattern’i
Dört hook’un her biri belirli bir rol oynar. İşte open entry handler — dosya adını kontrol et, eşleşiyorsa PID’yi işaretle:
SEC("tracepoint/syscalls/sys_enter_open")
int trace_open_entry(struct trace_event_raw_sys_enter *ctx)
{
u64 pid = bpf_get_current_pid_tgid();
char pathname[32];
bpf_probe_read_user_str(pathname, sizeof(pathname),
(void *)ctx->args[0]);
if (bpf_strncmp(pathname, 14, "/tmp/password") == 0) {
u8 mark = 1;
bpf_map_update_elem(&open_curr_fd_interesting, &pid, &mark, BPF_ANY);
}
return 0;
}
Ve open exit’te — bu PID işaretlenmişse fd’yi yakala:
SEC("tracepoint/syscalls/sys_exit_open")
int trace_open_exit(struct trace_event_raw_sys_exit *ctx)
{
u64 pid = bpf_get_current_pid_tgid();
if (ctx->ret < 0) return 0; // open başarısız
u8 *marked = bpf_map_lookup_elem(&open_curr_fd_interesting, &pid);
if (!marked) return 0;
bpf_map_delete_elem(&open_curr_fd_interesting, &pid);
struct pid_fd_key key = { .pid = pid, .fd = ctx->ret };
u8 val = 1;
bpf_map_update_elem(&open_interesting_fds, &key, &val, BPF_ANY);
return 0;
}
read handler’ları önceki alıştırmada kullandığınız aynı entry/exit pattern’ini takip eder — fd’yi open_interesting_fds’te kontrol edin, buffer pointer’ını kaydedin, ardından exit’te içeriği okuyun.
Akışın tamamı:
Görev
Bir program birden fazla dosyayı açıp okuyor. Yalnızca /tmp/password parolayı içerir.
Yapmanız gereken
open ve read syscall’larını ilişkilendirmek için dört eBPF programını implement edin. Başlangıç kodu map tanımlarını ve mantığınızı eklemeniz gereken yerleri işaretleyen TODO’ları içerir.
Get current process and thread ID
Compare two strings for equality
- Args:
char* bufdynamic buffer to compareu32 buf_szlength of dynamic bufferconst char* buf2literal string to compare against
Insert or update map entry
- Args:
void* mappointer to mapconst void* keypointer to keyconst void* valuepointer to valueu64 flagsBPF_ANY (create or update), BPF_NOEXIST (create only), or BPF_EXIST (update only)
Get value from map by key
- Args:
void* mappointer to mapconst void* keypointer to key
Remove entry from map
- Args:
void* mappointer to mapconst void* keypointer to key to delete