Sebuah pathname dapat dimulai di bawah direktori tepercaya tetapi tetap keluar dari subtree tersebut selama resolusi. Komponen .., symbolic link, magic link, atau perpindahan mount dapat mengubah objek yang akhirnya dicapai meskipun directory file descriptor awal berasal dari lokasi tepercaya. Linux openat2() menempatkan constraint langsung di dalam resolusi pathname, sehingga kernel dapat menolak lookup yang melanggar batas yang dipilih.

Perilaku ini berbeda dari memeriksa string pathname sebelum memanggil open(). Resolusi path bekerja terhadap objek filesystem dan state namespace, bukan hanya teks. openat2() memperluas model openat() dengan struct open_how yang memiliki field resolve untuk mengendalikan traversal komponen pathname.

dirfd menetapkan anchor lookup

Untuk path relatif, openat2() memulai resolusi dari direktori yang direferensikan oleh dirfd, mengikuti model anchor dasar milik openat(). Descriptor tersebut memberi operasi referensi direktori yang stabil tanpa bergantung pada current working directory proses.

Anchor saja bukan aturan confinement. Dengan openat() biasa, path relatif dapat berisi .., dan symbolic link yang ditemui saat traversal dapat mengalihkan lookup ke lokasi lain. dirfd tepercaya menentukan titik awal lookup; descriptor itu sendiri tidak membatasi semua lokasi yang dapat dicapai lookup.

Field resolve menambahkan lapisan kebijakan tersebut. Nilainya berupa bit mask, sehingga beberapa restriction dapat digabungkan ketika semantiknya sesuai dengan operasi.

RESOLVE_BENEATH menolak escape di atas anchor

RESOLVE_BENEATH mensyaratkan resolusi yang berhasil tetap berada di bawah direktori yang diidentifikasi oleh dirfd. Path absolut ditolak, demikian pula symbolic link absolut yang akan membawa lookup keluar dari hierarki anchor.

Properti pentingnya adalah restriction ikut diterapkan dalam traversal kernel. Mekanisme ini bukan perbandingan prefix seperti memeriksa apakah sebuah string diawali /srv/data. Pemeriksaan prefix tidak dapat merepresentasikan traversal symlink, topologi mount, atau race yang mengubah relasi namespace saat lookup berlangsung.

Bentuk konseptual request-nya seperti berikut:

struct open_how how = {
    .flags = O_RDONLY | O_CLOEXEC,
    .resolve = RESOLVE_BENEATH | RESOLVE_NO_MAGICLINKS,
};

int fd = syscall(SYS_openat2, rootfd, path, &how, sizeof(how));

Jika kernel mendeteksi escape berdasarkan kebijakan resolusi yang dipilih, call gagal alih-alih mengembalikan descriptor untuk target di luar batas.

RESOLVE_IN_ROOT mengubah semantik path absolut per operasi

RESOLVE_IN_ROOT memperlakukan dirfd sebagai root directory untuk resolusi pathname tersebut. path absolut diinterpretasikan relatif terhadap direktori itu, dan symbolic link absolut juga diinterpretasikan di dalam root sementara tersebut. Komponen .. yang mencoba naik dari root terpilih tetap berada pada root itu, serupa dengan /.. pada root proses.

Efek ini menyerupai root sementara untuk satu lookup, bukan transisi chroot() untuk seluruh proses. Tidak ada state root-directory persisten yang diubah pada caller.

RESOLVE_BENEATH dan RESOLVE_IN_ROOT dengan demikian menyatakan batas yang berkaitan tetapi berbeda. Yang pertama menolak upaya keluar dari bawah anchor; yang kedua mengubah root lookup sehingga bentuk absolut diselesaikan di dalam direktori yang diberikan.

O_NOFOLLOW memengaruhi komponen pathname terakhir. Flag tersebut tidak melarang symbolic link pada komponen sebelumnya.

RESOLVE_NO_SYMLINKS berlaku pada seluruh komponen dan juga mengimplikasikan RESOLVE_NO_MAGICLINKS. Lookup seperti a/link/b/file gagal jika link merupakan symbolic link ketika restriction ini aktif, meskipun komponen terakhir adalah file biasa.

RESOLVE_NO_MAGICLINKS lebih sempit. Flag ini memblokir magic link kernel, seperti link yang disediakan oleh beberapa entry procfs, tanpa menolak symbolic link biasa hanya karena objek tersebut berupa symbolic link.

Flag tersebut perlu sesuai dengan batas yang benar-benar diterapkan. Menolak semua symbolic link dapat merusak layout filesystem yang valid, sehingga kebijakan RESOLVE_NO_SYMLINKS secara menyeluruh lebih kuat daripada yang diperlukan banyak operasi.

Traversal mount merupakan batas terpisah

Tetap berada di bawah sebuah directory tree tidak berarti tetap berada pada satu mount. Bind mount dan mount point biasa dapat memasukkan filesystem lain di bawah anchor.

RESOLVE_NO_XDEV menolak traversal melewati mount point, termasuk bind mount. Flag ini dapat digabungkan dengan restriction lain ketika masuk ke mount lain berada di luar model akses yang dimaksud.

Pembedaan ini penting bagi kebijakan keamanan. Sebuah pathname dapat tetap berada di bawah anchor secara tekstual dan hierarkis sambil masuk ke mounted tree dengan provenance atau lifecycle berbeda. Sebaliknya, aplikasi yang sengaja menyusun directory tree dari bind mount tidak dapat menerapkan RESOLVE_NO_XDEV tanpa seleksi karena path yang sah juga akan ditolak.

Race resolusi dapat menghasilkan EAGAIN

Untuk RESOLVE_BENEATH dan RESOLVE_IN_ROOT, kernel dapat menghadapi race yang membuatnya tidak dapat memastikan secara aman bahwa traversal .. tetap berada dalam batas yang diwajibkan. Pada kondisi tersebut openat2() dapat gagal dengan EAGAIN.

EAGAIN di sini merupakan bagian dari penerapan constraint ketika namespace berubah secara concurrent. Menganggapnya setara dengan validasi yang berhasil akan menghilangkan batas tersebut. Caller dapat mengulangi operasi sesuai kebijakan retry dan latency miliknya.

RESOLVE_CACHED memakai EAGAIN untuk kondisi berbeda: lookup tidak dapat diselesaikan hanya dari informasi path yang sudah berada di cache. Penanganan error karena itu perlu mempertimbangkan kumpulan resolve flag yang dipakai call.

Descriptor hasil merupakan capability setelah resolusi

Setelah openat2() berhasil, file descriptor yang dikembalikan mereferensikan objek yang dipilih oleh lookup terbatas yang telah selesai. Operasi berbasis descriptor berikutnya tidak perlu meresolusi ulang pathname asli untuk open file description tersebut.

Hal ini membuat batas resolusi relevan pada interface yang menerima path dari konteks dengan tingkat kepercayaan lebih rendah. Transisi sensitifnya adalah dari pathname menjadi descriptor. Menerapkan constraint selama transisi itu menghindari desain ketika user space terlebih dahulu mencoba membuktikan path aman, lalu meminta kernel meresolusi path tersebut lagi ketika state namespace mungkin sudah berubah.

Guarantee tersebut tetap terbatas pada lookup individual dan flag yang diberikan. openat2() tidak membuat seluruh proses terkurung pada satu filesystem tree, dan tidak menggantikan pemeriksaan access control. API ini memberi satu resolusi pathname kebijakan traversal yang diterapkan kernel, lalu mengembalikan descriptor hanya jika resolusi tersebut memenuhi kebijakan itu.