Sebuah thread yang bersiap melewati batas execve() dapat perlu menghapus seluruh file descriptor di atas sekumpulan kecil descriptor yang dipertahankan ketika thread lain masih memakai tabel descriptor yang sama. Penutupan descriptor satu per satu membuka race: thread lain dapat mengalokasikan descriptor baru ke interval tersebut saat pembersihan masih berjalan. Di Linux, close_range() dengan CLOSE_RANGE_UNSHARE mengubah batas pemakaian bersama tabel sebelum operasi range diterapkan.

Perilaku ini penting karena nomor file descriptor hanya merupakan indeks pada tabel descriptor proses. Dengan CLONE_FILES, beberapa task dapat merujuk tabel yang sama, sehingga penutupan oleh satu task mengubah visibilitas descriptor bagi seluruh task yang berbagi tabel tersebut. CLOSE_RANGE_UNSHARE memberi task pemanggil tabel descriptor privat sebagai bagian dari operasi.

Penutupan range menggantikan enumerasi descriptor satu per satu

System call ini memiliki antarmuka:

int close_range(unsigned int first, unsigned int last, int flags);

Dengan flags bernilai nol, Linux menutup descriptor terbuka yang nomornya berada pada interval inklusif dari first sampai last. Batas yang umum saat menjalankan proses baru adalah:

close_range(3, ~0U, 0);

Operasi itu mempertahankan standard input, output, dan error sambil menutup descriptor bernomor lebih tinggi.

Bentuk range menghindari pemindaian /proc/self/fd dan rangkaian pemanggilan close(). Operasi juga tidak bergantung pada mount procfs hanya untuk melakukan enumerasi descriptor. Error dari penutupan descriptor individual diabaikan oleh close_range(); kegagalan system call itu sendiri dilaporkan melalui nilai balik dan errno.

Penutupan range biasa belum menyelesaikan semua kasus concurrency. Jika task lain berbagi tabel descriptor, alokasi dan penutupan tetap bekerja pada tabel yang sama.

UNSHARE memindahkan pembersihan ke tabel privat

CLOSE_RANGE_UNSHARE meminta kernel memisahkan tabel descriptor milik pemanggil sebelum menutup range yang diminta. Secara konseptual, operasi ini setara dengan:

unshare(CLONE_FILES);
close_range(first, last, 0);

Operasi gabungan tersebut memiliki batas race yang lebih kuat dibanding penerapan langkah serupa melalui loop di user space. Setelah pemanggil memiliki tabel privat, aktivitas descriptor pada task yang tetap memakai tabel lama tidak dapat mengisi kembali tabel milik pemanggil.

Untuk batas atas ~0U yang umum, Linux dapat mengoptimalkan operasi. Ketika range yang diminta melampaui tabel descriptor yang saat itu dialokasikan, kernel dapat membuat tabel privat yang hanya berisi descriptor di bawah first, alih-alih menyalin seluruh tabel lalu menutup setiap entri yang lebih tinggi. Ini merupakan optimasi implementasi, bukan properti API yang portabel.

CLOSE_RANGE_UNSHARE bersifat spesifik Linux. close_range() hadir sejak Linux 5.9, sedangkan API ini tidak memiliki status standar POSIX. Kode yang bergantung pada batas tersebut perlu memiliki jalur khusus platform atau kebijakan fallback yang eksplisit.

CLOEXEC mengubah waktu penutupan

CLOSE_RANGE_CLOEXEC mengubah operasi dari penutupan langsung menjadi pemasangan flag close-on-exec pada seluruh interval:

close_range(3, ~0U, CLOSE_RANGE_CLOEXEC);

Descriptor tetap dapat digunakan sebelum execve(), tetapi eksekusi program image baru yang berhasil akan menutup descriptor yang membawa flag tersebut. Sifat ini berguna ketika tahap penyiapan antara penandaan range dan execve() masih membutuhkan descriptor yang tidak boleh bertahan pada image baru.

Flag ini juga mengubah interaksi dengan syscall filtering. Proses dapat menandai range luas sebagai CLOEXEC sebelum memasang kebijakan seccomp, lalu membiarkan execve() menerapkan batas descriptor. Dengan pola ini, syscall untuk menutup descriptor tidak perlu tersedia setelah filter aktif.

CLOSE_RANGE_CLOEXEC dan CLOSE_RANGE_UNSHARE menangani properti berbeda. Yang pertama mengatur waktu penutupan; yang kedua mengatur pemakaian bersama tabel descriptor. Semantik keduanya tidak tepat jika dilebur menjadi satu konsep pembersihan descriptor generik.

Unsharing tidak mencabut referensi yang dimiliki pihak lain

Pemisahan tabel descriptor pemanggil tidak membatalkan file description yang masih dirujuk oleh tabel descriptor lain. Task yang mempertahankan tabel lama dapat terus memakai descriptornya. Operasi ini mengubah entri descriptor yang dapat dijangkau pemanggil; operasi ini bukan mekanisme revocation untuk objek kernel.

Pembedaan tersebut mengikuti model descriptor Linux. Menutup sebuah entri menghapus satu referensi descriptor. Open file description di bawahnya tetap hidup selama referensi lain masih ada, termasuk descriptor hasil duplikasi atau descriptor pada tabel lain.

Karena itu, CLOSE_RANGE_UNSHARE merupakan batas isolasi lokal. Mekanisme ini sesuai untuk menyiapkan namespace descriptor milik satu task sebelum transisi seperti execve(), bukan untuk memaksa task lain melepaskan resource.

Batas ini melekat pada kebijakan transisi proses

Pembersihan descriptor sering digambarkan sebagai loop atas bilangan bulat, tetapi properti yang sulit justru kepemilikan tabel selama loop berlangsung. close_range() menyatakan himpunan yang terdampak secara langsung, sedangkan CLOSE_RANGE_UNSHARE memindahkan himpunan itu ke tabel yang tidak lagi dipakai bersama peer task.

Kombinasi tersebut membuat batas transisi proses menjadi eksplisit: pertahankan descriptor di bawah first, isolasi tabel ketika pemakaian bersama mungkin terjadi, lalu tutup atau tandai interval sisanya sesuai semantik execve() yang diperlukan. Jaminan akhirnya berlaku pada tabel descriptor milik pemanggil, bukan pada umur objek secara global.