Pathname bukan referensi objek. Pathname merupakan instruksi untuk menelusuri namespace yang dapat berubah, sementara task lain dapat mengubah directory entry, symbolic link, atau mount pada saat traversal tersebut relevan bagi aplikasi. Linux openat2() menangani batas ini dengan menempatkan constraint resolusi path di dalam operasi kernel yang sama dengan operasi yang mengembalikan file descriptor.
Penempatan tersebut penting ketika program menerima pathname tetapi bermaksud membatasi resolusi pada sebuah tree direktori. Urutan di user space yang memeriksa komponen lalu memanggil open() memisahkan validasi dari penggunaan. openat2() dapat menjadikan aturan traversal terpilih sebagai bagian dari lookup itu sendiri.
dirfd menetapkan direktori awal
Seperti openat(), openat2() menerima file descriptor direktori dan sebuah pathname. Untuk pathname relatif, lookup dimulai dari dirfd, tidak harus dari current working directory milik proses.
Descriptor tersebut memberi operasi sebuah referensi direktori yang stabil meskipun pathname yang sebelumnya menamai direktori itu kemudian di-rename ke lokasi lain. Mekanisme ini juga menghindari perubahan current directory yang bersifat process-global hanya untuk memilih konteks lookup.
int rootfd = open("/srv/data", O_PATH | O_DIRECTORY | O_CLOEXEC);
struct open_how how = {
.flags = O_RDONLY | O_CLOEXEC,
.resolve = RESOLVE_BENEATH | RESOLVE_NO_MAGICLINKS,
};
int fd = syscall(SYS_openat2, rootfd, user_path, &how, sizeof(how));Contoh tersebut meminta kernel membuka user_path relatif terhadap rootfd sambil menolak resolusi yang keluar dari subtree direktori itu. Kebijakan tersebut melekat pada operasi ini; root proses dan current directory tidak berubah.
RESOLVE_BENEATH menolak escape saat traversal
RESOLVE_BENEATH mensyaratkan resolusi yang sukses tetap berada di bawah direktori yang dirujuk dirfd. Path input absolut ditolak, dan traversal melalui symbolic link absolut juga tidak sesuai dengan constraint tersebut.
Path seperti reports/2026.txt dapat diresolusikan secara normal ketika setiap komponen tetap berada di bawah direktori awal. Path yang mencoba keluar melalui parent traversal ditolak ketika kernel mendeteksi escape tersebut.
rootfd -> /srv/data
reports/q3.txt diizinkan jika lookup tetap di bawah rootfd
../secrets/key ditolak sebagai escape
/etc/passwd ditolak sebagai path absolutSifat ini lebih kuat daripada memeriksa teks pathname untuk mencari ... Traversal namespace bersifat semantik: symbolic link dan topologi mount memengaruhi objek yang akhirnya dicapai. Constraint bekerja pada resolusi path di kernel, bukan pada bentuk string.
Interface dapat mengembalikan EAGAIN ketika kernel tidak dapat memastikan secara aman bahwa traversal .. tetap berada di dalam batas akibat race rename yang berlangsung bersamaan. Hasil tersebut berbeda dari escape yang telah terkonfirmasi, yang dilaporkan dengan EXDEV untuk constraint resolusi ini.
RESOLVE_IN_ROOT mengubah interpretasi path absolut
RESOLVE_IN_ROOT memiliki kontrak berbeda. Flag ini memperlakukan dirfd sebagai root untuk lookup tersebut. Path input absolut diinterpretasikan relatif terhadap direktori itu, begitu pula target symbolic link absolut.
Dengan rootfd yang merujuk /srv/image, membuka /etc/app.conf dengan RESOLVE_IN_ROOT melakukan resolusi seolah /srv/image merupakan root untuk operasi tersebut. Parent traversal pada root sementara itu tidak bergerak ke atasnya.
Efek ini menyerupai batas root per operasi, tetapi bukan transisi chroot() untuk seluruh proses. Operasi path lain dalam proses tetap memakai root yang sudah ada kecuali operasi tersebut secara independen memakai interface dengan constraint serupa.
RESOLVE_BENEATH dan RESOLVE_IN_ROOT dengan demikian menyatakan kebijakan yang berkaitan tetapi tidak identik. Yang pertama menolak bentuk traversal yang keluar dari subtree awal; yang kedua mengubah root yang dipakai untuk menginterpretasikan path.
Kebijakan symbolic link memiliki cakupan per komponen
O_NOFOLLOW memengaruhi komponen terakhir pathname pada semantik keluarga open(). Flag tersebut tidak melarang symbolic link pada komponen sebelumnya. Path seperti current/config masih dapat menelusuri current ketika komponen itu merupakan symbolic link.
RESOLVE_NO_SYMLINKS berlaku sepanjang proses resolusi path dan juga mengimplikasikan RESOLVE_NO_MAGICLINKS. Ketika sebuah komponen memerlukan resolusi symbolic link, operasi gagal dengan ELOOP, dengan tetap mengikuti perilaku komponen akhir yang terdokumentasi ketika O_PATH dan O_NOFOLLOW digabungkan.
Cakupan yang lebih luas ini sesuai hanya ketika symbolic link memang berada di luar kontrak namespace yang diterima. Banyak layout filesystem normal sengaja menggunakannya, sehingga pelarangan seluruh symbolic link merupakan keputusan kebijakan, bukan switch hardening generik.
RESOLVE_NO_MAGICLINKS lebih sempit. Flag ini memblokir magic link seperti entry yang diekspos melalui bagian tertentu procfs tanpa melarang symbolic link biasa. Kode yang bergantung pada sifat tersebut sebaiknya meminta flag itu secara eksplisit, bukan bergantung pada perlakuan insidental flag lain terhadap magic link pada implementasi saat ini.
Traversal mount merupakan constraint terpisah
Tetap berada di bawah sebuah direktori tidak berarti tetap berada pada satu mount. Bind mount atau filesystem lain yang di-mount dapat muncul di dalam tree direktori dan masih merupakan descendant secara pathname.
RESOLVE_NO_XDEV menolak traversal melintasi mount point, termasuk bind mount. Dengan demikian, topologi mount menjadi bagian dari kebijakan lookup yang diterima. Constraint ini juga dapat menolak layout yang sebenarnya lazim, sehingga cocok untuk kasus yang secara spesifik melarang lintasan melewati batas mount.
Perbedaan tersebut mencegah dua kebijakan tercampur:
RESOLVE_BENEATH -> membatasi ancestry direktori
RESOLVE_NO_XDEV -> membatasi traversal mountCaller dapat menggabungkan keduanya ketika kedua sifat memang diperlukan.
RESOLVE_CACHED mengekspos batas lookup nonblocking
RESOLVE_CACHED menambahkan jenis constraint yang berbeda. Operasi hanya sukses ketika resolusi path dapat diselesaikan dari lookup cache kernel tanpa revalidation atau I/O filesystem. Jika syarat itu tidak terpenuhi, operasi mengembalikan EAGAIN.
Error tersebut tidak berarti target tidak ada atau tidak dapat diakses. Artinya, kondisi eksekusi cache-only yang diminta tidak dapat dipenuhi. Aplikasi dapat mengalihkan kasus itu ke jalur yang mengizinkan pekerjaan blocking, misalnya thread lain atau retry tanpa RESOLVE_CACHED.
Dengan mekanisme ini, kebijakan eksekusi menjadi observable melalui interface open yang sama. File descriptor yang dikembalikan tetap merepresentasikan file hasil resolusi; flag tambahan membatasi pekerjaan yang boleh dilakukan kernel untuk memperolehnya.
Ukuran open_how menjadi bagian dari evolusi ABI
openat2() menerima pointer ke struct open_how beserta ukurannya. Field baru dapat ditambahkan di bagian akhir struktur dari waktu ke waktu, dengan nilai nol mempertahankan perilaku yang setara dengan tidak adanya extension tersebut.
Caller karena itu perlu melakukan zero-initialization pada struktur sebelum mengisi field. Designated initializer memberikan sifat tersebut untuk field yang tidak diisi secara eksplisit.
struct open_how how = {
.flags = O_RDONLY | O_CLOEXEC,
.resolve = RESOLVE_IN_ROOT | RESOLVE_NO_MAGICLINKS,
};Kernel juga menolak nilai yang tidak dikenal atau saling bertentangan, alih-alih menerima bit flag arbitrer secara diam-diam. Dengan begitu, feature negotiation dan konfigurasi invalid terlihat pada batas system call.
Lookup dengan constraint menutup celah validasi dan penggunaan
Sifat utama openat2() bukan sekadar kumpulan flag open yang lebih besar. Kontrol resolusinya memungkinkan kernel mengevaluasi constraint namespace sambil meresolusikan pathname yang menghasilkan descriptor.
Hal ini mengubah bentuk kode yang menangani path dari pihak luar. Alih-alih lebih dulu membuktikan fakta mengenai satu keadaan namespace lalu melakukan open terhadap keadaan yang mungkin sudah berubah, caller menyatakan sifat yang wajib dipenuhi oleh lookup yang sukses itu sendiri. Pelanggaran batas kemudian dilaporkan oleh operasi yang mencoba melintasinya.
File descriptor hasil operasi tetap mengikuti aturan lifetime dan permission file descriptor biasa. Tambahan dari openat2() adalah mekanisme untuk menjadikan kebijakan traversal path sebagai syarat eksplisit saat memperoleh descriptor tersebut, dengan ancestry direktori, symbolic link, lintasan mount, dan eksekusi cache-only direpresentasikan sebagai constraint yang berbeda.