00 Landlock Neden?
Landlock, Linux 5.13'te ana çekirdeğe dahil edilen ve süreçlerin kendi erişim haklarını programatik olarak kısıtlamasına olanak tanıyan bir LSM (Linux Security Module) mekanizmasıdır. Geleneksel güvenlik modellerinde kısıtlamalar dışarıdan (root, sistem yöneticisi) uygulanırken Landlock bu kısıtlamayı uygulamanın kendisine bırakır.
Gömülü sistemlerde daemon güvenliği kritik bir konudur: bir saldırgan savunmasız bir sistem servisini (MQTT broker, HTTP sunucusu, CANBUS daemon) ele geçirirse tüm dosya sistemine erişim kazanabilir. Landlock bu saldırı yüzeyini dramatik biçimde daraltır; daemon yalnızca gerçekten ihtiyaç duyduğu dizinlere ve ağ işlemlerine erişebilir.
Landlock öncesi — saldırı yüzeyi:
Güvenlik açığı bulunan daemon
|
v
Tüm dosya sistemine erişim
/etc/passwd, /root/.ssh, /var/lib/...
Tüm ağ portlarına bağlanma
[saldırgan tam kontrolü ele geçirir]
Landlock sonrası — saldırı yüzeyi:
Güvenlik açığı bulunan daemon
|
v
Yalnızca /var/lib/mqtt/ okuma/yazma
Yalnızca port 1883'te dinleme
Başka hiçbir dizin veya port erişilemez
[saldırgan etkisi kapsamlı biçimde sınırlandırılır]
seccomp ile karşılaştırma: seccomp sistem çağrısı düzeyinde filtre uygular (hangi syscall'ların kullanılabileceğini kısıtlar); Landlock ise kaynak erişimi düzeyinde çalışır (hangi dosya ve ağ kaynaklarına erişilebileceğini kısıtlar). İki mekanizma birbirini tamamlar ve sıkı güvenlik politikaları için birlikte kullanılabilir.
| Özellik | seccomp | Landlock | SELinux/AppArmor |
|---|---|---|---|
| Kısıtlama düzeyi | Syscall | Kaynak erişimi | Kaynak + syscall |
| Politika konumu | Uygulama içi | Uygulama içi | Sistem geneli |
| Kök gerektiriyor mu | Hayır (PR_SET_NO_NEW_PRIVS) | Hayır | Evet (politika yüklemek için) |
| Kernel versiyonu | 3.5+ | 5.13+ | Çok eski |
| Yapılandırma karmaşıklığı | Orta | Düşük | Yüksek |
| Gömülü kullanım kolaylığı | İyi | Çok iyi | Orta |
01 Landlock Kavramları
Landlock üç temel kavram üzerine inşa edilmiştir: ruleset (kural kümesi), rule (kural) ve handled_access_fs bayrakları. Bu kavramları anlamak doğru bir sandbox politikası tasarlamak için gereklidir.
Ruleset (kural kümesi): Bir Landlock politikasının en üst düzey nesnesidir. Ruleset, hangi erişim türlerinin bu politika tarafından yönetileceğini tanımlar. Örneğin "bu ruleset dosya yazma ve dosya çalıştırma erişimlerini yönetir" şeklinde bir tanım yapılır.
Rule (kural): Belirli bir kaynak (dosya yolu, ağ portu) için izin verilen erişim türlerini belirtir. Ruleset varsayılan olarak her şeyi reddeder; kurallar yalnızca izin verilen kaynakları açıkça tanımlar.
Handled access bayrakları: Ruleset'in aktif olarak yönettiği erişim türleridir. Bir erişim türü handled olarak işaretlenmemişse Landlock o erişim türü üzerinde herhangi bir kısıtlama uygulamaz.
Landlock politika modeli:
landlock_create_ruleset(handled_access_fs = OKUMA | YAZMA | ÇALISTIRMA)
|
v
Ruleset oluşturuldu (her şey varsayılan olarak reddedildi)
|
+-- landlock_add_rule(ruleset, "/var/lib/app", OKUMA | YAZMA)
+-- landlock_add_rule(ruleset, "/usr/bin/app", OKUMA | ÇALISTIRMA)
+-- landlock_add_rule(ruleset, "/lib", OKUMA)
|
v
landlock_restrict_self(ruleset)
|
v
Süreç artık yalnızca tanımlı kaynaklara erişebilir
Landlock ABI versiyonu zaman içinde genişlemiştir: ABI 1 (kernel 5.13) temel dosya
sistemi kısıtlarını, ABI 2 (kernel 5.19) sembolik bağ oluşturma desteğini,
ABI 3 (kernel 6.2) inode başına kural sayısı artışını, ABI 4 (kernel 6.7) ağ
kısıtlarını getirmiştir. Uygulamanızın desteklediği ABI'yi tespit etmek için
landlock_create_ruleset syscall'ını özel bir flag ile çağırabilirsiniz.
/* Desteklenen Landlock ABI versiyonunu tespit et */
#include <linux/landlock.h>
#include <sys/syscall.h>
#include <unistd.h>
#include <stdio.h>
int landlock_get_abi_version(void) {
int abi = syscall(SYS_landlock_create_ruleset,
NULL, 0,
LANDLOCK_CREATE_RULESET_VERSION);
return abi;
}
int main(void) {
int abi = landlock_get_abi_version();
if (abi < 0) {
perror("Landlock desteklenmiyor");
return 1;
}
printf("Landlock ABI versiyonu: %d\n", abi);
return 0;
}
02 Temel Kullanım
Landlock politikası uygulamak üç syscall ile gerçekleşir:
landlock_create_ruleset, landlock_add_rule ve
landlock_restrict_self. Bu bölümde eksiksiz bir C örneği üzerinden
temel kullanım gösterilmektedir.
Önemli ön koşul: landlock_restrict_self çağrısından önce sürecin
no_new_privs bayrağını ayarlaması gerekir. Bu, Landlock kısıtlamalarını
setuid ikilileri aracılığıyla atlatma girişimlerini engeller.
/* landlock_demo.c — temel Landlock kullanımı */
#define _GNU_SOURCE
#include <linux/landlock.h>
#include <sys/syscall.h>
#include <sys/prctl.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
/* Landlock syscall sarmalayıcıları (glibc henüz eklemediyse) */
static int landlock_create_ruleset(
const struct landlock_ruleset_attr *attr,
size_t size, uint32_t flags) {
return syscall(SYS_landlock_create_ruleset, attr, size, flags);
}
static int landlock_add_rule(int ruleset_fd,
enum landlock_rule_type type,
const void *attr, uint32_t flags) {
return syscall(SYS_landlock_add_rule, ruleset_fd, type, attr, flags);
}
static int landlock_restrict_self(int ruleset_fd, uint32_t flags) {
return syscall(SYS_landlock_restrict_self, ruleset_fd, flags);
}
/* Yardımcı: bir dizine okuma/yazma erişimi ekle */
static int add_path_rule(int ruleset_fd,
const char *path,
uint64_t allowed_access) {
struct landlock_path_beneath_attr attr = {
.allowed_access = allowed_access,
};
int fd = open(path, O_PATH | O_CLOEXEC);
if (fd < 0) {
fprintf(stderr, "Yol acilamadi: %s: %s\n", path, strerror(errno));
return -1;
}
attr.parent_fd = fd;
int ret = landlock_add_rule(ruleset_fd,
LANDLOCK_RULE_PATH_BENEATH,
&attr, 0);
close(fd);
return ret;
}
int main(void) {
/* 1. Adım: Ruleset oluştur */
struct landlock_ruleset_attr ruleset_attr = {
.handled_access_fs =
LANDLOCK_ACCESS_FS_READ_FILE |
LANDLOCK_ACCESS_FS_WRITE_FILE |
LANDLOCK_ACCESS_FS_READ_DIR |
LANDLOCK_ACCESS_FS_EXECUTE,
};
int ruleset_fd = landlock_create_ruleset(&ruleset_attr,
sizeof(ruleset_attr), 0);
if (ruleset_fd < 0) {
perror("landlock_create_ruleset basarisiz");
return 1;
}
/* 2. Adım: İzin verilen kaynakları tanımla */
/* Yalnızca /tmp dizinine okuma+yazma izni */
if (add_path_rule(ruleset_fd, "/tmp",
LANDLOCK_ACCESS_FS_READ_FILE |
LANDLOCK_ACCESS_FS_WRITE_FILE |
LANDLOCK_ACCESS_FS_READ_DIR) < 0) {
close(ruleset_fd);
return 1;
}
/* /usr/bin dizinine yalnızca okuma+çalıştırma izni */
if (add_path_rule(ruleset_fd, "/usr/bin",
LANDLOCK_ACCESS_FS_READ_FILE |
LANDLOCK_ACCESS_FS_EXECUTE |
LANDLOCK_ACCESS_FS_READ_DIR) < 0) {
close(ruleset_fd);
return 1;
}
/* 3. Adım: no_new_privs bayragini ayarla (zorunlu) */
if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) < 0) {
perror("prctl PR_SET_NO_NEW_PRIVS basarisiz");
close(ruleset_fd);
return 1;
}
/* 4. Adım: Kısıtlamayı uygula */
if (landlock_restrict_self(ruleset_fd, 0) < 0) {
perror("landlock_restrict_self basarisiz");
close(ruleset_fd);
return 1;
}
close(ruleset_fd);
printf("Landlock kısıtlaması aktif.\n");
printf("/tmp/test.txt yazma deneyi...\n");
/* Bu izin verildi: /tmp altında */
int fd = open("/tmp/test.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (fd >= 0) {
write(fd, "merhaba\n", 8);
close(fd);
printf("OK: /tmp/test.txt yazildi\n");
}
/* Bu reddedilecek: /etc izin verilmedi */
fd = open("/etc/passwd", O_RDONLY);
if (fd < 0)
printf("REDDEDILDI: /etc/passwd erisilemez (beklenen)\n");
else {
printf("HATA: /etc/passwd erisilebildi!\n");
close(fd);
}
return 0;
}
# Derleme
gcc -o landlock_demo landlock_demo.c
# Çalıştır
./landlock_demo
03 Dosya Sistemi Kısıtları
Landlock dosya sistemi erişim bayrakları, uygulamanın hangi işlemleri hangi dizinlerde yapabileceğini ince tanecikli (fine-grained) şekilde belirler. Her bayrağın anlamını ve tipik kullanım senaryosunu bu bölümde ele alacağız.
LANDLOCK_ACCESS_FS_* bayraklarının tamamı:
Tipik bir daemon için minimum izin kümesi örneği:
/*
* Minimal daemon izin kümesi:
* - /etc/myapp/ : yapılandırmayı oku
* - /var/lib/myapp: durum verisi okuma/yazma
* - /var/log/myapp: log dosyaları yaz
* - /tmp : geçici dosyalar
* - /lib, /usr/lib: kütüphaneleri oku/çalıştır
* - /usr/bin : yardımcı ikililer
*/
struct {
const char *path;
uint64_t access;
} rules[] = {
{ "/etc/myapp", FS_READ },
{ "/var/lib/myapp", FS_READ | FS_WRITE | FS_MAKE_REG | FS_REMOVE_FILE },
{ "/var/log/myapp", FS_WRITE | FS_MAKE_REG | FS_REMOVE_FILE },
{ "/tmp", FS_READ | FS_WRITE | FS_MAKE_REG | FS_REMOVE_FILE },
{ "/lib", FS_READ | FS_EXECUTE },
{ "/usr/lib", FS_READ | FS_EXECUTE },
{ "/usr/bin", FS_READ | FS_EXECUTE },
{ NULL, 0 }
};
/* Buradaki FS_READ, FS_WRITE vb. kısaltmalardır;
* gerçek kodda LANDLOCK_ACCESS_FS_READ_FILE | LANDLOCK_ACCESS_FS_READ_DIR
* gibi tam bayrak adları kullanılmalıdır. */
Dikkat gerektiren nokta: Bir dizine WRITE_FILE izni
vermek yeni dosya oluşturmayı kapsamaz. Yeni dosya oluşturabilmek için
MAKE_REG bayrağı da gereklidir. Benzer şekilde var olan bir dosyayı
silmek için REMOVE_FILE, alt dizin silmek için REMOVE_DIR
ayrıca belirtilmelidir.
04 Ağ Kısıtları
Linux 6.7 ile birlikte Landlock, TCP ağ operasyonlarını da kısıtlayabilir hale gelmiştir.
LANDLOCK_ACCESS_NET_BIND_TCP ve LANDLOCK_ACCESS_NET_CONNECT_TCP
bayrakları ile sürecin hangi portlarda dinleyebileceği ve bağlanabileceği kontrol altına
alınır.
Ağ kısıtları dosya sistemi kısıtlarından bağımsızdır; aynı ruleset içinde her ikisi birlikte tanımlanabilir veya ayrı ruleset'ler oluşturulabilir.
/* Landlock ağ kısıtlaması örneği (kernel 6.7+) */
#define _GNU_SOURCE
#include <linux/landlock.h>
#include <sys/syscall.h>
#include <sys/prctl.h>
#include <unistd.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
/* ABI versiyonunu kontrol et */
static int check_landlock_abi(void) {
return syscall(SYS_landlock_create_ruleset,
NULL, 0, LANDLOCK_CREATE_RULESET_VERSION);
}
int apply_network_sandbox(void) {
int abi = check_landlock_abi();
if (abi < 4) {
fprintf(stderr, "Ag kisitlari icin ABI 4 gerekli (kernel 6.7+), mevcut: %d\n", abi);
return -1;
}
struct landlock_ruleset_attr rs_attr = {
.handled_access_net =
LANDLOCK_ACCESS_NET_BIND_TCP |
LANDLOCK_ACCESS_NET_CONNECT_TCP,
};
int ruleset_fd = syscall(SYS_landlock_create_ruleset,
&rs_attr, sizeof(rs_attr), 0);
if (ruleset_fd < 0) {
perror("ruleset olusturulamadi");
return -1;
}
/* Yalnızca port 1883 (MQTT) üzerinde dinlemeye izin ver */
struct landlock_net_port_attr net_attr = {
.allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP,
.port = 1883,
};
if (syscall(SYS_landlock_add_rule, ruleset_fd,
LANDLOCK_RULE_NET_PORT, &net_attr, 0) < 0) {
perror("net kural eklenemedi (bind 1883)");
close(ruleset_fd);
return -1;
}
/* Port 5432 (PostgreSQL) bağlantısına izin ver */
net_attr.allowed_access = LANDLOCK_ACCESS_NET_CONNECT_TCP;
net_attr.port = 5432;
if (syscall(SYS_landlock_add_rule, ruleset_fd,
LANDLOCK_RULE_NET_PORT, &net_attr, 0) < 0) {
perror("net kural eklenemedi (connect 5432)");
close(ruleset_fd);
return -1;
}
if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) < 0) {
perror("prctl basarisiz");
close(ruleset_fd);
return -1;
}
if (syscall(SYS_landlock_restrict_self, ruleset_fd, 0) < 0) {
perror("restrict_self basarisiz");
close(ruleset_fd);
return -1;
}
close(ruleset_fd);
printf("Ag sandbox aktif: yalnizca bind:1883 ve connect:5432\n");
return 0;
}
Ağ kısıt bayrakları:
Önemli sınırlamalar: Landlock ağ kısıtları yalnızca TCP'yi kapsar; UDP, Unix domain soketleri veya ham soketler (raw sockets) için Landlock ağ kısıtları uygulanamaz. Bu erişimleri kısıtlamak için seccomp ile birlikte kullanmak gerekmektedir. Ayrıca halihazırda açık olan soket bağlantıları Landlock politikası uygulandıktan sonra da çalışmaya devam eder; politika yalnızca yeni bağlantı girişimlerini etkiler.
05 Kalıtım ve fork
Landlock kısıtlamaları süreç kalıtımı yoluyla child süreçlere aktarılır.
fork() ile oluşturulan alt süreçler, üst sürecin Landlock politikasını
devralır ve bu politika daraltılabilir ancak genişletilemez.
Bu davranış güvenlik açısından kritik bir garantidir: kısıtlı bir süreç
fork() ile bir child oluşturduğunda, o child daha az kısıtlı olamaz.
Alt süreç ek kısıtlamalar ekleyebilir ancak mevcut kısıtlamaları kaldıramaz.
Landlock kalıtım modeli:
Ana süreç (kısıtsız)
|
| landlock_restrict_self (politika A: /tmp, /var/lib/app)
v
Ana süreç (politika A aktif)
|
| fork()
|
+----------> Child süreç (politika A devralındı)
|
| landlock_restrict_self (politika B: yalnızca /tmp)
v
Child süreç (politika A + B: kesişim = /tmp)
[Politika A'yı kaldıramaz, yalnızca daraltabilir]
no_new_privs zorunluluğu: landlock_restrict_self
çağrısından önce prctl(PR_SET_NO_NEW_PRIVS, 1, ...) ayarlanmalıdır.
Bu bayrak da kalıtımla child süreçlere geçer; bir kez ayarlandığında geri alınamaz.
Amaç: setuid veya setcap ikilileri çalıştırarak Landlock kısıtlamalarını atlatmayı
engellemektir.
/* Çok işlemli daemon — güvenli kalıtım örneği */
#include <sys/prctl.h>
#include <unistd.h>
#include <stdio.h>
void apply_worker_sandbox(void); /* ek kısıtlamalar */
int main(void) {
/* 1. Adım: Ana süreç için Landlock kur (geniş izinler) */
setup_landlock_main();
/* 2. Adım: Worker fork'larını başlat */
for (int i = 0; i < WORKER_COUNT; i++) {
pid_t pid = fork();
if (pid == 0) {
/* Child: ana politikayı devraldı, daha da kısıt */
apply_worker_sandbox();
worker_loop();
_exit(0);
}
}
/* Ana süreç: kabul döngüsü */
accept_loop();
return 0;
}
exec() ve Landlock: execve() sonrasında Landlock
kısıtlamaları korunur. Yeni çalıştırılan ikili, önceki süreçten kalan politikayı
devralır. Bu davranış sayesinde bir sarmalayıcı (wrapper) script veya launcher
süreci Landlock'u kurup ardından asıl programı exec() ile başlatabilir;
asıl program Landlock API'sini hiç çağırmak zorunda kalmaz.
/* Landlock launcher: politikayı kur, ardından programı exec et */
int main(int argc, char *argv[]) {
if (argc < 2) {
fprintf(stderr, "Kullanim: %s PROGRAM [ARGS...]\n", argv[0]);
return 1;
}
/* Politikayı kur */
if (setup_landlock_policy() < 0)
return 1;
/* Asıl programı başlat — politika devredilir */
execvp(argv[1], &argv[1]);
perror("execvp basarisiz");
return 1;
}
06 Gömülü Daemon Sandboxing
Gerçek dünya gömülü senaryolarında MQTT broker, hafif web sunucusu ve CANBUS daemon uygulamaları için Landlock sandbox politikaları tasarlanmaktadır. Her uygulamanın ihtiyaç duyduğu kaynaklar dikkatle analiz edilmelidir.
MQTT Broker (Mosquitto) Sandbox: Mosquitto tipik olarak yapılandırma dosyasını, kalıcı mesaj veritabanını ve PID dosyasını kullanır; ağda yalnızca 1883 (MQTT) ve 8883 (MQTT/TLS) portlarında dinler.
/* mosquitto_sandbox.c — Mosquitto için Landlock sarmalayıcı */
#include <linux/landlock.h>
#include <sys/syscall.h>
#include <sys/prctl.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#define FS_R (LANDLOCK_ACCESS_FS_READ_FILE | LANDLOCK_ACCESS_FS_READ_DIR)
#define FS_RW (FS_R | LANDLOCK_ACCESS_FS_WRITE_FILE | \
LANDLOCK_ACCESS_FS_MAKE_REG | LANDLOCK_ACCESS_FS_REMOVE_FILE)
#define FS_RX (FS_R | LANDLOCK_ACCESS_FS_EXECUTE)
static const struct {
const char *path;
uint64_t access;
} mosquitto_rules[] = {
{ "/etc/mosquitto", FS_R }, /* yapılandırma */
{ "/var/lib/mosquitto", FS_RW }, /* kalıcı veritabanı */
{ "/var/log/mosquitto", FS_RW }, /* log dosyaları */
{ "/var/run", FS_RW }, /* PID dosyası */
{ "/tmp", FS_RW }, /* geçici dosyalar */
{ "/lib", FS_RX }, /* sistem kütüphaneleri */
{ "/usr/lib", FS_RX }, /* uygulama kütüphaneleri */
{ "/usr/sbin", FS_RX }, /* mosquitto ikilisi */
{ "/etc/ssl", FS_R }, /* TLS sertifikaları */
{ "/dev/urandom", FS_R }, /* rassal sayı üretici */
{ NULL, 0 }
};
int apply_mosquitto_sandbox(void) {
/* Tam uygulama için önceki bölümlerdeki yardımcı işlevler kullanılır */
return 0; /* kısaltılmış örnek */
}
CANBUS daemon sandbox: SocketCAN kullanan bir CAN daemon genellikle yapılandırma dizinine okuma, log dizinine yazma ve CAN soketlerine erişim ister. Ağ soketleri (SOCK_RAW CAN) Landlock ağ kısıtlamalarının kapsamı dışındadır; bu kısıtlama için seccomp filtreleri kullanılmalıdır.
/* can_daemon_sandbox.c — CAN daemon için minimal politika */
static const struct {
const char *path;
uint64_t access;
} can_daemon_rules[] = {
{ "/etc/can-daemon", FS_R }, /* yapılandırma */
{ "/var/lib/can-daemon", FS_RW }, /* durum verisi */
{ "/var/log/can-daemon", FS_RW }, /* günlükler */
{ "/tmp", FS_RW }, /* geçici dosyalar */
{ "/lib", FS_RX }, /* kütüphaneler */
{ "/usr/lib", FS_RX }, /* kütüphaneler */
{ "/dev", FS_R }, /* aygıt dosyası okuma (vcan0 vb.) */
{ NULL, 0 }
/* Not: CAN ham soketleri ağ kısıtları yerine seccomp ile yönetilir */
};
Web sunucusu sandbox (lighttpd örneği):
static const struct {
const char *path;
uint64_t access;
} lighttpd_rules[] = {
{ "/etc/lighttpd", FS_R }, /* yapılandırma */
{ "/var/www/html", FS_R }, /* statik web içeriği */
{ "/var/log/lighttpd", FS_RW }, /* erişim ve hata logları */
{ "/var/run", FS_RW }, /* PID dosyası, FastCGI soketleri */
{ "/tmp", FS_RW }, /* geçici dosyalar */
{ "/lib", FS_RX },
{ "/usr/lib", FS_RX },
{ "/usr/sbin", FS_RX },
{ "/etc/ssl", FS_R }, /* HTTPS sertifikaları */
{ "/etc/mime.types", FS_R }, /* MIME türleri */
{ NULL, 0 }
};
07 Mevcut Araçlarla Entegrasyon
Landlock, systemd servis birimlerindeki ek güvenlik seçenekleri, bubblewrap sandbox aracı ve diğer Linux güvenlik mekanizmaları ile birlikte kullanılabilir.
systemd servis birimi ile Landlock: systemd 254+ sürümünde
LandlockAccessFS ve ilgili direktifler eklenmiştir. Bu sayede C kodu
yazmadan systemd birim dosyası üzerinden Landlock politikası tanımlanabilir.
Ancak eski systemd sürümlerinde (gömülü dağıtımlarda yaygın) bu direktifler
yoktur; bu durumda servis başlatma betiği içinden Landlock uygulanması gerekir.
# /etc/systemd/system/mqtt-broker.service — systemd 254+ özelliği
[Unit]
Description=MQTT Broker (Mosquitto)
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=mosquitto
Group=mosquitto
ExecStart=/usr/sbin/mosquitto -c /etc/mosquitto/mosquitto.conf
# Landlock dosya sistemi kısıtları (systemd 254+)
# Tüm dosya sistemi erişimini kısıtla
LandlockInodesAccessFilesystems=read execute
# Güvenlik katmanlarını birleştir
NoNewPrivileges=yes
PrivateTmp=yes
ProtectSystem=strict
ReadWritePaths=/var/lib/mosquitto /var/log/mosquitto
[Install]
WantedBy=multi-user.target
Eski systemd sürümleri için ExecStartPre yaklaşımı:
# /etc/systemd/system/mqtt-broker.service — eski systemd uyumlu
[Service]
User=mosquitto
NoNewPrivileges=yes
ExecStartPre=/usr/sbin/mqtt-sandbox-setup
ExecStart=/usr/sbin/mosquitto -c /etc/mosquitto/mosquitto.conf
bubblewrap ile karşılaştırma: bubblewrap (bwrap),
namespace tabanlı izolasyon sağlar ve daha kapsamlı bir sandbox oluşturabilir.
Landlock ise daha hafif ve uygulama içine entegre bir çözümdür. İkisi farklı
sorunları çözer ve birlikte kullanılabilir.
| Özellik | Landlock | bubblewrap |
|---|---|---|
| Mekanizma | LSM (çekirdek yetki bayrakları) | Linux namespace (mount, pid, net vb.) |
| Uygulama değişikliği | Gerekli (syscall çağrısı) | Gerekmez (dışarıdan sarmalama) |
| Çekirdek gereksinimleri | Linux 5.13+, CONFIG_SECURITY_LANDLOCK=y | Namespace desteği (çoğu çekirdekte mevcut) |
| Overhead | Çok düşük | Orta (namespace kurulum maliyeti) |
| Ağ yalıtımı | Yalnızca TCP port (6.7+) | Tam ağ namespace yalıtımı |
| Gömülü uygunluk | Yüksek | Orta (namespace desteği gerektirir) |
Landlock ve seccomp kombinasyonu gömülü güvenlik için güçlü bir temel oluşturur: seccomp syscall yüzeyini minimize eder, Landlock ise erişilebilir kaynakları kısıtlar. İkisi birlikte kullanıldığında saldırı yüzeyi çok katmanlı biçimde daraltılır.
/* Landlock + seccomp kombinasyonu */
int secure_daemon_init(void) {
/* 1. Landlock: dosya sistemi ve ağ kısıtları */
if (apply_landlock_policy() < 0)
return -1;
/* 2. seccomp: gereksiz syscall'ları kapat */
if (apply_seccomp_filter() < 0)
return -1;
/* 3. Artık güvenli ortamda çalışıyoruz */
return 0;
}
08 Hata Ayıklama
Landlock politikası uygulandıktan sonra reddedilen erişimleri teşhis etmek için strace ile syscall izleme, EACCES/EPERM hata ayrımı ve audit log mekanizmasından yararlanılır.
strace ile Landlock syscall'larını izle:
# Landlock syscall'larını izle
strace -e trace=landlock_create_ruleset,landlock_add_rule,landlock_restrict_self \
./myapp
# Tüm dosya erişimlerini ve hataları izle
strace -e trace=openat,open,read,write,execve \
-e fault=openat:error=EACCES \
./myapp 2>&1 | grep -E "EACCES|EPERM"
# Daha ayrıntılı: reddedilen tüm sistem çağrıları
strace -P /etc/passwd ./myapp 2>&1
EACCES ve EPERM ayrımı:
/* Hata yönetimi örneği */
int fd = open("/path/to/file", O_RDONLY);
if (fd < 0) {
if (errno == EACCES) {
fprintf(stderr, "Landlock reddi: %s\n", strerror(errno));
/* Landlock politikasında ilgili dizini kontrol et */
} else if (errno == EPERM) {
fprintf(stderr, "Yetki hatasi: %s\n", strerror(errno));
/* no_new_privs veya başka bir LSM kontrolü */
} else {
perror("Beklenmedik hata");
}
}
Linux audit ile Landlock olaylarını kaydet:
# auditd kurulumu
sudo apt-get install auditd
# Landlock reddiyatlarını kaydet
sudo auditctl -a always,exit -F arch=b64 \
-S landlock_create_ruleset,landlock_add_rule,landlock_restrict_self \
-k landlock_ops
# Redleri izle
sudo ausearch -k landlock_ops | aureport --file
# Gerçek zamanlı audit izleme
sudo tail -f /var/log/audit/audit.log | grep landlock
Politika geliştirme iş akışı:
Politika geliştirme adımları:
1. Uygulamayı strace altında çalıştır
strace -e trace=openat,connect ./myapp 2>trace.log
2. Erişilen kaynakları analiz et
grep "openat" trace.log | awk '{print $2}' | sort -u
3. Minimal Landlock politikası oluştur
(yalnızca adım 2'de tespit edilen kaynaklar)
4. Politika ile test et
./myapp 2>test.log
grep "EACCES\|EPERM" test.log
5. Eksik izinleri ekle ve adım 4'e dön
6. Production öncesi güvenlik incelemesi:
izin verilen kaynaklar gerçekten gerekli mi?
Çekirdek yapılandırması doğrulama: Landlock'un aktif olup olmadığını ve hangi ABI versiyonunun desteklendiğini doğrulamak için:
# Landlock çekirdek yapılandırmasını kontrol et
zcat /proc/config.gz | grep LANDLOCK
# Beklenen çıktı:
# CONFIG_SECURITY_LANDLOCK=y
# LSM listesini kontrol et
cat /sys/kernel/security/lsm
# Çıktıda "landlock" görünmelidir
# ABI versiyonunu C'den değil shell'den test et
python3 -c "
import ctypes, os
libc = ctypes.CDLL(None, use_errno=True)
# SYS_landlock_create_ruleset = 444 (x86_64)
abi = libc.syscall(444, None, 0, 1)
print(f'Landlock ABI: {abi}')
"
Landlock desteği yoksa (CONFIG_SECURITY_LANDLOCK ayarlanmamış veya
LSM listesinde "landlock" yok) uygulamanızın Landlock olmadan da çalışabilmesi
için ABI kontrolünü zorunlu adım yerine isteğe bağlı güvenlik katmanı olarak
tasarlamanız önerilir. Bu yaklaşım farklı kernel versiyonlarında çalışan
taşınabilir bir gömülü uygulama için doğru pratiktir.