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:

  1. open entry - Dosya adını görürüz ve hedefimizle eşleşip eşleşmediğini kontrol edebiliriz
  2. open exit - Bu dosyaya atanan fd’yi elde ederiz

Aynı process fd’den okuduğunda:

  1. read entry - Hangi fd’den okunduğunu ve hedef buffer’ı görürüz
  2. 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ı:

OpenEnter TPSyscallExit TPstorecheckopen_curr_fdpidvoidUserspaceFDReadEnter TPSyscallExit TPstorecheckFD, bufread_curr_fdpidbuf addrinteresting_fds{pid, fd}voidstorecheck

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.

Quick reference
Get current process and thread ID
Function bpf_get_current_pid_tgid Full documentation
Returns Upper 32 bits are PID, lower 32 bits are TID.
Compare two strings for equality
Function bpf_strncmp Full documentation
Args:
char* bufdynamic buffer to compare
u32 buf_szlength of dynamic buffer
const char* buf2literal string to compare against
Returns 0 if strings match, non-zero if they differ
Insert or update map entry
Function bpf_map_update_elem Full documentation
Args:
void* mappointer to map
const void* keypointer to key
const void* valuepointer to value
u64 flagsBPF_ANY (create or update), BPF_NOEXIST (create only), or BPF_EXIST (update only)
On success, returns 0
On error, returns negative error code
Get value from map by key
Function bpf_map_lookup_elem Full documentation
Args:
void* mappointer to map
const void* keypointer to key
Returns pointer to value if found, NULL otherwise
Remove entry from map
Function bpf_map_delete_elem Full documentation
Args:
void* mappointer to map
const void* keypointer to key to delete
On success, returns 0
On error, returns negative error code if key not found
Run your code to see execution events here