Pathname yang diberikan ke openat2() dapat diresolusikan relatif terhadap file descriptor direktori sambil meminta kernel menegakkan batas pada proses resolusi itu sendiri. Perbedaan ini penting saat proses menerima komponen path dari sumber dengan tingkat kepercayaan lebih rendah. Pemeriksaan string dapat memeriksa teks pathname, tetapi pemeriksaan itu sendiri tidak dapat membekukan namespace filesystem selama lookup berlangsung.
openat2() menempatkan kebijakan bersama operasi lookup. struct open_how memisahkan flag open biasa dari flag resolve yang membatasi traversal. Batas yang dihasilkan berkaitan dengan semantik resolusi, bukan sekadar bentuk teks sebuah path.
Lookup relatif direktori menyediakan anchor
Seperti openat(), openat2() menerima dirfd dan pathname. Pathname relatif memulai lookup dari direktori yang dirujuk dirfd; pathname absolut mengikuti aturan absolute path milik system call tersebut dan tidak memakai descriptor itu sebagai titik awal.
Sebuah proses dapat mempertahankan descriptor untuk direktori yang disetujui lalu memakainya sebagai anchor bagi lookup berikutnya:
struct open_how how = {
.flags = O_RDONLY | O_CLOEXEC,
.resolve = RESOLVE_BENEATH,
};
int fd = syscall(SYS_openat2, rootfd, path, &how, sizeof(how));Objek penting di sini adalah rootfd, bukan prefix tekstual yang disalin ke path. Directory descriptor merujuk objek kernel yang telah dibuka, sehingga rename pada direktori tersebut tidak mengubah descriptor menjadi rujukan ke direktori lain. Perubahan namespace masih dapat memengaruhi entri yang dicapai di bawahnya, tetapi anchor lookup tetap berbasis descriptor.
RESOLVE_BENEATH menolak escape selama traversal
RESOLVE_BENEATH mensyaratkan resolusi tetap berada di bawah direktori yang diberikan melalui dirfd. Komponen yang akan keluar dari hierarki tersebut membuat operasi gagal. Path absolut juga ditolak dalam mode ini.
Perilaku ini berbeda dari menghapus komponen .. di kode aplikasi. Symbolic link yang ditemui selama lookup dapat mengarahkan traversal meskipun input awal tidak memuat komponen parent yang terlihat. Batas resolusi kernel berlaku pada path walk setelah objek filesystem dan link ikut berperan.
Jaminannya sengaja dibatasi pada satu lookup. Flag ini tidak membuat sandbox permanen untuk proses dan tidak membatasi file descriptor lain yang sudah dimiliki proses. Descriptor hasilnya adalah descriptor biasa yang tunduk pada mode open serta kemampuan API berikutnya.
RESOLVE_IN_ROOT mengubah arti root lookup
RESOLVE_IN_ROOT menyediakan kebijakan yang berkaitan tetapi berbeda. Selama lookup, direktori yang dirujuk dirfd bertindak sebagai root bagi resolusi path. Path absolut dan target symbolic link absolut diinterpretasikan relatif terhadap root tersebut, bukan root proses.
Perilaku ini berguna saat sintaks path perlu mempertahankan semantik relatif-root di dalam tree tertentu. Efeknya tidak sama dengan mengubah root proses secara global. Efek tersebut hanya berlaku pada operasi openat2() yang bersangkutan.
Karena itu, RESOLVE_BENEATH dan RESOLVE_IN_ROOT menyatakan kontrak berbeda. Yang pertama menolak upaya bergerak ke atas anchor; yang kedua memberi lookup interpretasi root sementara. Kode perlu memilih kontrak yang sesuai dengan protokol path, bukan memperlakukan kedua flag sebagai switch hardening yang dapat saling menggantikan.
Batas link mempersempit kanal pengalihan
RESOLVE_NO_SYMLINKS menolak symbolic link pada setiap komponen lookup. Batas ini lebih kuat daripada O_NOFOLLOW, yang pada operasi open biasa berkaitan dengan komponen terakhir. Saat intermediate link juga berada di luar model yang diterima, pembatasan yang hanya berlaku pada komponen terakhir masih menyisakan kanal pengalihan.
RESOLVE_NO_MAGICLINKS menargetkan magic link Linux seperti entri tertentu yang diekspos melalui procfs. Objek ini dapat memiliki semantik di luar symbolic link biasa. Flag tersebut memblokir kelas itu tanpa harus menolak seluruh symbolic link biasa.
Perbedaan tersebut memungkinkan interface menyatakan batas sebenarnya. Service yang mengizinkan symbolic link terkendali di dalam tree dapat memakai kebijakan berbeda dari service yang model objeknya mensyaratkan setiap komponen berupa entri direktori konkret.
Batas mount merupakan sumbu terpisah
RESOLVE_NO_XDEV mencegah resolusi melintasi mount point, termasuk bind mount. Tetap berada di bawah sebuah direktori dalam hierarki pathname tidak dengan sendirinya berarti tetap berada pada satu filesystem yang di-mount.
Pemisahan itu terlihat pada lingkungan tempat filesystem lain di-mount di dalam tree yang disetujui. RESOLVE_BENEATH masih dapat menganggap subtree hasil mount berada di bawah anchor, sedangkan RESOLVE_NO_XDEV menolak perpindahan tersebut.
Kebijakan mount perlu dinyatakan secara eksplisit saat identitas filesystem penting. Batas hierarki dan batas mount adalah dua properti berbeda, meskipun tree direktori sederhana dapat membuat keduanya tampak sama.
Race resolusi menjadi hasil API
Namespace filesystem dapat berubah secara konkuren. Komponen dapat di-rename, link dapat diganti, dan topologi mount dapat berubah saat thread atau proses lain menjalankan lookup. openat2() tidak membuat namespace menjadi statis.
Nilainya terletak pada pemeriksaan batas resolusi terpilih oleh kernel sebagai bagian dari lookup. Caller menerima descriptor yang dihasilkan di bawah batas tersebut atau menerima error. Ini menghindari desain ketika kode aplikasi lebih dulu memvalidasi state pathname lalu melakukan open terpisah yang traversal-nya dapat melihat namespace yang sudah berubah.
Race tertentu dapat muncul sebagai error seperti EAGAIN pada kondisi resolusi tertentu. Caller yang tangguh perlu memperlakukan kegagalan sebagai bagian dari kontrak dan menentukan apakah retry valid untuk operasinya. Retry adalah lookup baru terhadap state namespace yang mungkin berbeda; retry bukan kelanjutan dari path walk yang gagal.
Open flag dan resolution flag mengatur properti berbeda
Member flags pada struct open_how mengatur properti yang terkait dengan pembukaan objek akhir, memakai flag seperti O_RDONLY, O_CLOEXEC, atau O_DIRECTORY. Member resolve membatasi traversal yang memilih objek tersebut.
Memisahkan kedua dimensi ini mencegah kekeliruan model yang umum. Sebuah descriptor dapat memiliki mode akses yang diinginkan tetapi dicapai melalui rute traversal yang melanggar kebijakan namespace service. Sebaliknya, traversal ketat tidak memberikan akses yang ditolak oleh permission file, state mount, atau pemeriksaan kernel lainnya.
openat2() juga memvalidasi struktur argumennya lebih ketat daripada interface yang diam-diam mengabaikan sebagian bit flag yang tidak dikenal. Caller perlu menginisialisasi field yang tidak dipakai ke nol dan memberikan ukuran struktur sesuai kontrak ABI kernel.
Descriptor menjadi batas output yang stabil
Setelah pemanggilan berhasil, operasi berikutnya biasanya perlu memakai descriptor hasilnya alih-alih menyusun kembali pathname dan meresolusikannya lagi. Lookup pathname kedua merupakan observasi namespace kedua dan dapat memilih objek berbeda setelah perubahan konkuren.
Hal ini membentuk batas interface yang presisi: teks pathname adalah input bagi operasi resolusi yang dibatasi; file descriptor adalah referensi kernel yang dihasilkan operasi tersebut. Jika API berikutnya dapat bekerja langsung pada descriptor, mempertahankan referensi itu menjaga identitas yang dipilih oleh lookup yang telah selesai.
Pola ini tidak menyelesaikan seluruh persoalan otorisasi filesystem. Permission direktori, pewarisan descriptor, direktori writable, administrasi mount, serta semantik operasi berikutnya tetap relevan. Kontribusinya lebih sempit tetapi penting: kebijakan traversal path dapat dinyatakan sebagai bagian dari system call yang meresolusikan path, sehingga validasi dan akuisisi objek berada dalam satu operasi yang dimediasi kernel.