Pathname bukan referensi objek yang stabil. Di antara direktori awal dan komponen akhir, resolusi path Linux dapat mengikuti symbolic link, melewati mount point, memproses .., atau menjumpai link khusus dari pseudo-filesystem. openat2() memungkinkan caller memasang batas pada operasi resolusi tersebut sehingga kernel dapat menolak lookup yang keluar dari boundary yang ditetapkan.
Perbedaannya lebih kuat daripada memeriksa string yang sudah dinormalisasi sebelum open(). Validasi string memeriksa sintaks. openat2() dapat membatasi traversal aktual oleh kernel ketika objek filesystem dan topologi mount ikut menentukan hasil lookup.
Descriptor awal menjadi anchor
Seperti openat(), openat2() menerima file descriptor direktori dan sebuah pathname. Pathname relatif dimulai dari descriptor tersebut, bukan dari working directory proses.
struct open_how how = {
.flags = O_RDONLY | O_CLOEXEC,
.resolve = RESOLVE_BENEATH,
};
int fd = syscall(SYS_openat2, rootfd, "assets/logo.svg",
&how, sizeof(how));Dengan RESOLVE_BENEATH, setiap komponen harus selesai di bawah rootfd. Pathname absolut ditolak, demikian pula traversal yang keluar dari anchor melalui .., symbolic link absolut, atau path absolut yang diberikan magic link.
Dengan demikian, descriptor direktori menjadi bagian dari boundary otoritas. Aplikasi tidak perlu membentuk ulang pathname absolut milik rootfd, dan perubahan working directory proses tidak memindahkan anchor tersebut.
RESOLVE_IN_ROOT memberi root lokal pada komponen absolut
RESOLVE_IN_ROOT memiliki semantik berbeda. Selama lookup ini, direktori yang dirujuk dirfd bertindak sebagai root sementara. Input path absolut diinterpretasikan relatif terhadap direktori tersebut, dan symbolic link absolut juga diresolusikan dari root lokal yang sama.
Komponen .. pada root itu tidak dapat keluar ke atasnya. Efek ini menyerupai root resolusi path dengan scope satu operasi; ia tidak mengubah root directory proses dan tidak setara dengan chroot().
Perbedaan RESOLVE_BENEATH dan RESOLVE_IN_ROOT dapat diamati. Yang pertama menolak upaya resolusi yang keluar dari subtree. Yang kedua mengarahkan resolusi yang relatif terhadap root ke dalam boundary direktori yang diberikan. Software perlu memilih kontrak yang dibutuhkan, bukan menganggap kedua flag sebagai hardening switch yang dapat dipertukarkan.
Kebijakan symlink terpisah dari kebijakan boundary
RESOLVE_NO_SYMLINKS menolak symbolic link pada setiap komponen path. Ini lebih ketat daripada O_NOFOLLOW, yang dalam penggunaan biasa mencegah symbolic link pada komponen akhir pathname agar tidak diikuti.
Sebuah service mungkin tetap memerlukan symlink di dalam tree yang dikontrol. Dalam kondisi itu, RESOLVE_BENEATH atau RESOLVE_IN_ROOT dapat mempertahankan fitur tersebut sambil membatasi escape. Service lain mungkin mensyaratkan path yang seluruh komponennya bukan symlink dan dapat menambahkan RESOLVE_NO_SYMLINKS.
RESOLVE_NO_MAGICLINKS menargetkan magic link seperti entry tertentu di /proc. Link ini dapat memiliki perilaku di luar teks symlink biasa dan dapat merujuk resource proses. Kernel saat ini mengimplikasikan RESOLVE_NO_MAGICLINKS ketika RESOLVE_NO_SYMLINKS diminta, tetapi bergantung pada implikasi itu dapat menyamarkan intent jika magic link merupakan batas yang sebenarnya diperlukan.
Perpindahan mount merupakan constraint terpisah
Sebuah path dapat tetap berada secara leksikal di bawah direktori tetapi masuk ke filesystem lain yang di-mount. RESOLVE_NO_XDEV menolak perpindahan mount point selama resolusi, termasuk bind mount.
Kebijakan ini independen dari kebijakan symlink. Bind mount yang ditempatkan di dalam tree direktori terkontrol tidak memerlukan symlink untuk mengarahkan lookup ke view filesystem lain. Sebaliknya, larangan perpindahan mount tidak dengan sendirinya melarang symlink yang tetap berada dalam mount yang diizinkan.
Pemisahan tersebut relevan bagi aplikasi yang memetakan tree direktori ke boundary keamanan atau kepemilikan data. Kebijakan yang diperlukan dapat mencakup traversal namespace sekaligus topologi mount.
Constraint resolusi menutup celah check-use
Filter path yang umum melakukan canonicalization atau inspeksi pathname terlebih dahulu, lalu membukanya. Keduanya merupakan operasi terpisah. Jika entry direktori atau symlink yang mutable berubah di antara pemeriksaan dan open, lookup kedua dapat melintasi graph objek yang berbeda.
openat2() menerapkan aturan resolve selama lookup yang menghasilkan descriptor. Tidak ada pathname yang sudah lolos pemeriksaan lalu harus diresolusikan kembali untuk operasi open yang sama.
Hal ini tidak membuat seluruh operasi berikutnya bebas race. Jika software menerima descriptor direktori lalu menjalankan operasi path tambahan di bawahnya, setiap resolusi berikutnya tetap memerlukan kontrak yang sesuai. File descriptor hasil open menstabilkan referensi ke objek yang dibuka; descriptor itu tidak membekukan namespace di sekelilingnya.
Kegagalan merupakan bagian dari kontrak interface
Lookup dengan constraint dapat gagal dengan EXDEV ketika aturan resolusi seperti RESOLVE_BENEATH, RESOLVE_IN_ROOT, atau RESOLVE_NO_XDEV mendeteksi escape atau crossing yang dilarang. ELOOP digunakan untuk traversal symbolic link atau magic link yang dilarang oleh flag terkait.
Aplikasi perlu memperlakukan kegagalan tersebut sebagai hasil kebijakan, bukan sinyal untuk diam-diam mencoba ulang dengan openat() biasa. Fallback yang menghapus constraint juga menghapus properti yang diminta caller.
openat2() juga memakai ukuran struct open_how sebagai boundary ekstensibilitas. Caller sebaiknya melakukan zero-initialize pada struktur, mengisi hanya field yang didefinisikan, dan mengirim ukuran struktur yang disediakan. Field tak dikenal yang bernilai nonzero atau bit flag yang tidak didukung ditolak alih-alih diabaikan, sehingga negosiasi capability bersifat eksplisit.
Resolusi oleh kernel tidak menggantikan access control
Field resolve mengontrol traversal path. Field flags dan mode tetap memiliki semantik open, dan pemeriksaan permission filesystem normal tetap berlaku. Path yang tetap di bawah rootfd tidak berarti caller memiliki izin untuk membukanya, dan akses yang berhasil tidak membuktikan bahwa direktori di sekelilingnya dapat dipercaya.
Boundary juga bergantung pada descriptor direktori yang diberikan aplikasi. Jika attacker dapat mengganti descriptor tersebut dalam state aplikasi atau membuat service memilih anchor yang tidak semestinya, constraint resolusi path akan tetap menegakkan boundary yang salah itu secara konsisten.
Untuk desain berbasis descriptor, pola yang lebih kuat adalah menetapkan anchor sekali, mempertahankannya sebagai descriptor dengan lifetime terkontrol, lalu melakukan setiap lookup relatif yang tidak tepercaya dengan constraint resolusi eksplisit. Dengan pola ini, interpretasi pathname, kebijakan namespace, dan akuisisi objek berada dalam satu operasi kernel.