openat2 Menjadikan Kebijakan Resolusi Path Bagian dari Operasi Open

Sebuah layanan menerima pathname relatif dan bermaksud membuka hanya objek di bawah direktori yang sudah dipercaya. Pemeriksaan leksikal dapat menolak komponen .. yang jelas, tetapi namespace filesystem dapat memuat symbolic link, mount point, atau rename bersamaan yang mengubah path walk setelah pemeriksaan tersebut. Keputusan keamanan dan operasi pembukaan file lalu menggambarkan dua momen berbeda.

Linux openat2() menyediakan batas yang lebih sempit. Flag resolve membatasi operasi resolusi path kernel yang menghasilkan file descriptor. Mekanisme ini tidak membuat penanganan path arbitrer otomatis aman, tetapi dapat memindahkan beberapa aturan pembatasan dari logika pemeriksaan awal string ke lookup yang benar-benar memilih objek.

Directory descriptor menyediakan titik acuan

Seperti openat(), openat2() menerima directory file descriptor dan me-resolve pathname relatif dari direktori tersebut. Ekstensi pentingnya adalah struct open_how, dengan field resolve yang menerapkan pembatasan pada komponen selama path walk.

RESOLVE_BENEATH menolak resolusi yang keluar ke atas dari direktori yang diberikan. Pathname absolut dan symbolic link absolut tidak sesuai dengan kebijakan ini. Sifat tersebut berbeda secara material dari pemeriksaan bahwa string yang sudah dinormalisasi diawali prefix tertentu: kernel mengevaluasi pembatasan ketika melintasi namespace.

RESOLVE_IN_ROOT memiliki semantik berbeda. Flag ini memperlakukan directory descriptor sebagai root sementara untuk lookup tersebut. Pathname absolut diinterpretasikan dari direktori itu, sedangkan upaya melintasi ke atas dengan .. tetap berada pada root sementara. Efeknya menyerupai perubahan root per operasi, bukan chroot() untuk seluruh proses.

Kedua kebijakan menyatakan kontrak berbeda. BENEATH menolak upaya keluar; IN_ROOT membatasi resolusi ke root yang diberikan. Kode perlu memilih kontrak yang sesuai dengan operasi, bukan memperlakukan keduanya sebagai sakelar pembatasan yang setara.

Containment tidak selalu berarti setiap symbolic link harus ditolak. Symbolic link relatif dapat tetap berada di dalam tree yang diizinkan dan masih memenuhi batas bergaya beneath.

Saat batas keamanan mensyaratkan tidak ada traversal symlink sama sekali, RESOLVE_NO_SYMLINKS menerapkan aturan itu ke setiap komponen yang ditemui lookup. Hal ini berbeda dari O_NOFOLLOW, yang mengatur symbolic link pada komponen terakhir.

Perbedaan tersebut relevan untuk path seperti tenant/current/report, ketika current adalah symlink perantara. Aturan komponen terakhir tidak mengatur traversal perantara tersebut. Aturan resolusi menyeluruh mengaturnya.

Linux juga menyediakan RESOLVE_NO_MAGICLINKS untuk magic link seperti entri terkait di procfs. Perilaku RESOLVE_BENEATH dan RESOLVE_IN_ROOT saat ini juga memblokir resolusi magic link, tetapi dokumentasi interface memperingatkan bahwa caller yang memerlukan properti tersebut harus meminta RESOLVE_NO_MAGICLINKS secara eksplisit, bukan bergantung pada efek samping perilaku saat ini.

Lintas mount membentuk batas lain

Pathname dapat tetap berada secara leksikal di bawah sebuah direktori sambil masuk ke filesystem lain yang di-mount. Bind mount membuat kondisi ini mudah terlewat karena mount dapat menempelkan subtree lain ke hierarki direktori yang tampak biasa.

RESOLVE_NO_XDEV menolak perpindahan melalui mount point, termasuk bind mount. Flag ini menambahkan kondisi topologi filesystem di luar containment pathname.

Pembatasan tersebut bukan default hardening universal. Sistem lazim memakai mount point dan bind mount dalam tata letak direktori yang sah. Penerapan RESOLVE_NO_XDEV tanpa kebutuhan batas yang konkret dapat membuat path valid gagal. Properti keamanan harus sesuai dengan model deployment.

Deteksi race dapat menghasilkan kegagalan yang bisa dicoba ulang

Kebijakan resolusi yang ditegakkan kernel tidak berarti setiap mutasi namespace bersamaan dapat disembunyikan dari caller. Dengan RESOLVE_BENEATH atau RESOLVE_IN_ROOT, openat2() dapat mengembalikan EAGAIN ketika kernel tidak dapat memastikan traversal .. tetap terbatas akibat rename atau perubahan mount yang terjadi bersamaan.

Kegagalan tersebut merupakan bagian dari kontrak keamanan. Menganggapnya sebagai alasan untuk fallback ke open() tanpa pembatasan akan membuang properti yang menyebabkan call gagal. Caller dapat mencoba ulang operasi dengan pembatasan yang sama jika semantik aplikasi mengizinkan retry.

Kegagalan kebijakan lain memiliki arti berbeda. Escape yang terdeteksi pada BENEATH atau IN_ROOT dapat menghasilkan EXDEV; traversal symlink terlarang pada RESOLVE_NO_SYMLINKS dapat menghasilkan ELOOP; lintas mount pada RESOLVE_NO_XDEV juga dapat menghasilkan EXDEV. Penanganan error karena itu menjadi bagian dari batas, bukan sekadar pelaporan masalah I/O.

Descriptor hasil menutup fase lookup

Call yang berhasil mengembalikan file descriptor untuk objek yang dipilih oleh path walk dengan pembatasan. Operasi berbasis descriptor berikutnya bekerja pada objek yang sudah dibuka, bukan mengulang lookup pathname awal.

Hal ini tidak membekukan objek atau filesystem di sekitarnya. Isi file dapat berubah, metadata dapat berubah sesuai izin, dan directory entry dapat di-rename. Properti yang relevan lebih sempit: kebijakan pathname dan akuisisi objek terjadi dalam satu operasi resolusi kernel, lalu descriptor yang dihasilkan merujuk ke objek yang diperoleh oleh operasi tersebut.

Batas itu melengkapi desain berbasis descriptor. Layanan dapat mempertahankan descriptor untuk root tepercaya, me-resolve nama relatif yang tidak tepercaya dengan pembatasan eksplisit, lalu mempertahankan operasi sensitif berikutnya pada descriptor jika memungkinkan.

Flag resolusi tidak menggantikan otorisasi

openat2() membatasi resolusi pathname; interface ini tidak memutuskan apakah principal terautentikasi berhak atas objek yang dipilih. Pemeriksaan izin filesystem normal tetap berlaku, dan otorisasi tingkat aplikasi tetap merupakan lapisan terpisah.

Interface ini juga tidak memvalidasi isi file, menetapkan ekspektasi ownership, mencegah semua race filesystem setelah open, atau mengubah directory tree yang mutable menjadi sandbox immutable. Caller yang memerlukan regular file, metadata tertentu, atau relasi ownership khusus tetap perlu menyatakan dan memeriksa kondisi tersebut dengan interface yang sesuai.

Dukungan kernel juga merupakan kondisi implementasi. openat2() bersifat khusus Linux dan diperkenalkan pada Linux 5.6. Perangkat lunak dengan jalur kompatibilitas untuk kernel lebih lama harus memastikan fallback mempertahankan properti keamanan yang dibutuhkan; mengganti resolusi terbatas secara diam-diam dengan pembukaan pathname tanpa pembatasan bukan operasi yang setara.

Keuntungan arsitekturalnya bersifat spesifik. Batas path yang hanya ada pada validasi string dapat menyimpang dari filesystem walk sesudahnya. openat2() dapat menempelkan aturan containment, symlink, magic link, dan lintas mount langsung pada walk tersebut, sehingga file descriptor menjadi hasil operasi yang dibatasi, bukan hasil lookup berikutnya yang memiliki pembatasan lebih lemah.