Sebuah pathname dapat berubah makna ketika proses sedang me-resolve-nya. Rename direktori, symbolic link, mount point, dan komponen .. dapat mengarahkan lookup keluar dari direktori yang dimaksud program sebagai batas.
Linux openat2() menempelkan kebijakan resolusi langsung pada proses lookup. struct open_how memiliki bit mask resolve, sehingga kernel dapat menolak path ketika resolusinya melanggar batas yang dipilih caller, bukan hanya mengandalkan pemeriksaan yang dilakukan sebelum open().
Directory file descriptor menetapkan titik awal
Seperti openat(), openat2() dapat me-resolve pathname relatif dari directory file descriptor:
struct open_how how = {
.flags = O_RDONLY | O_CLOEXEC,
};
int fd = syscall(SYS_openat2, dirfd, "assets/config.json",
&how, sizeof(how));Untuk path relatif, dirfd menentukan direktori tempat lookup dimulai. Pola ini tidak bergantung pada working directory proses, tetapi direktori awal saja belum mencegah seluruh bentuk escape. Path dapat memuat .., bertemu symbolic link, atau melintasi mount point.
Field resolve menambahkan batas pada transisi tersebut.
RESOLVE_BENEATH menolak escape di atas dirfd
RESOLVE_BENEATH mensyaratkan setiap komponen hasil resolusi tetap berada di bawah direktori yang ditunjuk dirfd.
struct open_how how = {
.flags = O_RDONLY | O_CLOEXEC,
.resolve = RESOLVE_BENEATH | RESOLVE_NO_MAGICLINKS,
};Path seperti images/logo.svg dapat di-resolve secara normal ketika seluruh komponennya tetap menjadi descendant dari dirfd. Upaya keluar melalui .. ditolak. Pathname absolut dan symbolic link absolut juga tidak sesuai dengan batas beneath.
Perilaku kernel saat ini juga memblokir traversal magic link ketika RESOLVE_BENEATH aktif. Dokumentasi API Linux secara eksplisit menyarankan penggunaan RESOLVE_NO_MAGICLINKS jika properti itu memang dibutuhkan, karena aplikasi tidak sebaiknya bergantung pada perilaku implisit saat ini untuk tetap permanen.
RESOLVE_IN_ROOT memberi root sementara untuk satu lookup
RESOLVE_IN_ROOT memiliki semantik berbeda. Flag ini memperlakukan dirfd sebagai root directory khusus untuk lookup tersebut.
struct open_how how = {
.flags = O_RDONLY | O_CLOEXEC,
.resolve = RESOLVE_IN_ROOT | RESOLVE_NO_MAGICLINKS,
};Pathname absolut kemudian ditafsirkan relatif terhadap dirfd. Symbolic link absolut juga diproses di dalam root sementara yang sama. Komponen .. pada root tidak bergerak ke atasnya.
Efek ini menyerupai batas root per operasi open, bukan chroot() yang berlaku pada proses. Root directory milik proses pemanggil tidak diubah secara permanen.
Karena itu, RESOLVE_BENEATH dan RESOLVE_IN_ROOT menangani masalah yang berkaitan tetapi berbeda: yang pertama menolak lookup yang keluar dari bawah direktori awal, sedangkan yang kedua mengubah semantik root untuk satu resolusi.
Symbolic link dan magic link memiliki kontrol terpisah
RESOLVE_NO_SYMLINKS menolak traversal symbolic link pada komponen path mana pun:
.resolve = RESOLVE_BENEATH |
RESOLVE_NO_SYMLINKS |
RESOLVE_NO_MAGICLINKS,Ketika symbolic link ditemukan saat RESOLVE_NO_SYMLINKS aktif, openat2() dapat gagal dengan ELOOP.
RESOLVE_NO_MAGICLINKS menargetkan magic link Linux, terutama objek seperti entri di /proc/<pid>/fd. Objek ini dapat memiliki perilaku yang melampaui substitusi teks symbolic link biasa. Menuliskan kedua flag secara eksplisit membuat kebijakan yang diminta tetap jelas walaupun satu pembatasan saat ini mengimplikasikan pembatasan lain pada perilaku kernel.
RESOLVE_NO_XDEV memblokir perpindahan mount
Sebuah directory tree dapat berisi mount biasa atau bind mount. Tetap berada di bawah satu prefix pathname tidak selalu berarti tetap berada pada satu mount.
RESOLVE_NO_XDEV menolak lookup yang melintasi mount point:
struct open_how how = {
.flags = O_RDONLY | O_CLOEXEC,
.resolve = RESOLVE_BENEATH |
RESOLVE_NO_MAGICLINKS |
RESOLVE_NO_XDEV,
};Aturan ini juga mencakup bind mount. Flag tersebut berguna untuk kebijakan yang mewajibkan lookup tetap pada mount awal, tetapi dapat menolak layout yang memang memakai mounted subtree. Batas ini terpisah dari containment berdasarkan descendant.
RESOLVE_CACHED menjadikan status cache sebagai hasil eksplisit
RESOLVE_CACHED mengharuskan operasi selesai dari informasi lookup yang sudah ada di cache. Jika resolusi membutuhkan revalidation atau I/O, call gagal dengan EAGAIN.
struct open_how how = {
.flags = O_RDONLY | O_CLOEXEC,
.resolve = RESOLVE_CACHED,
};Flag ini merupakan mekanisme kontrol latensi, bukan batas containment. Caller dapat memakai percobaan berbasis cache sebagai fast path dan memilih jalur eksekusi lain setelah menerima EAGAIN.
Error tersebut menjadi bagian dari kontrak interface: kondisi cache-only yang diminta tidak dapat dipenuhi, bukan berarti target pathname selalu tidak valid.
Kegagalan resolusi membawa informasi kebijakan
Beberapa error menunjukkan kelas batas yang menghentikan lookup. ELOOP dapat menandakan traversal symbolic link atau magic link yang dilarang. EXDEV dapat menandakan upaya escape di bawah RESOLVE_BENEATH atau RESOLVE_IN_ROOT, maupun perpindahan mount saat RESOLVE_NO_XDEV aktif.
EAGAIN juga dapat muncul ketika kernel tidak dapat memastikan containment secara aman saat terjadi race yang melibatkan ... Caller dapat mencoba kembali. Dengan RESOLVE_CACHED, EAGAIN menandakan resolusi berbasis cache saja tidak mencukupi.
Aplikasi sebaiknya menangani hasil tersebut sesuai kebijakan yang dimintanya, bukan menyamakan seluruh kegagalan dengan kondisi file tidak ditemukan.
Kernel menerapkan batas selama lookup
Urutan di userspace yang memeriksa path lalu membukanya kemudian memisahkan validasi dari penggunaan. State filesystem dapat berubah di antara dua operasi tersebut.
openat2() menempatkan batas yang dipilih di dalam operasi resolusi pathname milik kernel:
dirfd
|
+-- pathname
|
+-- kernel path walk
|
+-- RESOLVE_* policy
|
+-- file descriptor or errorBatas utamanya bukan normalisasi string. Batas tersebut adalah kumpulan transisi filesystem yang diizinkan kernel saat mengubah pathname menjadi file descriptor.
openat2() tidak menggantikan authorization aplikasi, file permission, atau sandboxing yang lebih luas. Primitive ini memberi aplikasi Linux batas yang lebih sempit: operasi open dengan traversal pathname yang dibatasi sebagai bagian dari operasi itu sendiri.