Sebuah launcher memilih executable dari suatu direktori, memeriksa atribut atau kontennya, lalu menjalankannya. Jika tahap pemilihan dan eksekusi masing-masing melakukan resolusi pathname secara terpisah, rename, perubahan symlink, atau penggantian direktori di antara kedua operasi dapat membuat objek yang dieksekusi berbeda dari objek yang diperiksa.
Linux execveat() dapat memindahkan batas tersebut dari lookup pathname kedua ke referensi file yang sudah diperoleh. Dengan AT_EMPTY_PATH, pathname kosong memberi instruksi kepada kernel untuk mengeksekusi objek yang dirujuk oleh dirfd. Descriptor tersebut dapat dibuka dengan O_PATH. Keputusan eksekusi tetap melewati pemeriksaan permission dan format executable normal di kernel, tetapi pemilihan objek tidak lagi bergantung pada resolusi ulang pathname asal.
Identitas objek bertahan terhadap penggantian pathname
File descriptor merujuk ke objek kernel yang sudah dibuka, bukan menyimpan pathname yang harus diresolusi ulang untuk setiap operasi. Setelah launcher membuka sebuah executable, proses lain dapat melakukan rename pada entri direktori atau mengganti pathname asal tanpa mengalihkan descriptor tersebut ke objek pengganti.
Properti ini mengubah bentuk urutan pemeriksaan lalu eksekusi. Urutan berbasis pathname dapat memeriksa satu objek lalu mengeksekusi objek lain jika keadaan namespace berubah di antara dua lookup. Urutan berbasis descriptor dapat memeriksa metadata melalui descriptor yang sudah diperoleh dan menyerahkan referensi yang sama ke execveat().
Batas ini bersifat spesifik. Mekanisme tersebut mengikat pemilihan pada objek yang sudah dibuka; mekanisme itu tidak membekukan byte milik objek tersebut. Jika suatu pihak dapat memodifikasi file dasar setelah launcher memverifikasi kontennya, eksekusi berbasis descriptor saja tidak mempertahankan urutan byte yang telah diverifikasi. Kepemilikan file, permission tulis, desain penyimpanan immutable, mekanisme sealing jika sesuai, atau kontrol integritas lain harus menangani mutasi konten.
AT_EMPTY_PATH menghapus lookup kedua
execveat() juga mendukung eksekusi pathname relatif terhadap directory descriptor. Bentuk tersebut tetap melakukan resolusi pathname untuk path relatif yang diberikan. Properti identitas yang lebih kuat bagi executable yang sudah dipilih berasal dari pathname kosong yang digabungkan dengan AT_EMPTY_PATH.
Secara konseptual, urutannya adalah:
int fd = open(candidate, O_PATH | O_CLOEXEC);
if (fd == -1)
fail();
verify_selected_object(fd);
char *const argv[] = { "worker", NULL };
char *const envp[] = { NULL };
execveat(fd, "", argv, envp, AT_EMPTY_PATH);Untuk binary native, pengaturan close-on-exec pada selection descriptor mencegah descriptor tersebut tetap terbuka di program baru setelah eksekusi berhasil. Descriptor itu telah menjalankan perannya sebagai capability pemilihan objek dan biasanya tidak lagi diperlukan.
AT_SYMLINK_NOFOLLOW menangani kasus yang berbeda. Saat path yang tidak kosong mengidentifikasi symbolic link, flag tersebut membuat pemanggilan gagal alih-alih mengikuti link terakhir. Flag ini tidak menggantikan manfaat identitas dari membuka target terlebih dahulu lalu mengeksekusi descriptor yang dihasilkan.
Script menimbulkan konflik close-on-exec
Interpreter script menghadirkan kondisi tepi yang penting. Untuk executable script yang diawali shebang, kernel menjalankan interpreter yang disebutkan dan menyediakan referensi agar interpreter dapat mengakses script. Dalam bentuk AT_EMPTY_PATH, referensi tersebut bergantung pada execution descriptor yang tetap tersedia cukup lama bagi jalur interpreter.
Jika close-on-exec diaktifkan pada descriptor yang merujuk ke script semacam itu, execveat() dapat gagal dengan ENOENT. Menonaktifkan close-on-exec menghindari kegagalan tersebut, tetapi descriptor kemudian diwariskan kepada interpreter. Eksekusi script secara rekursif dapat mengakumulasi descriptor yang diwariskan dan akhirnya menghabiskan kapasitas tabel descriptor.
Perilaku ini tidak sama dengan kasus binary native. Launcher yang menerima binary sekaligus script harus memperhitungkan format executable, bukan menerapkan satu aturan masa hidup descriptor untuk setiap target. Jika descriptor tidak boleh diwariskan sebagai batas yang ketat, eksekusi script langsung melalui pola ini dapat bertentangan dengan persyaratan tersebut.
Permission eksekusi tetap menjadi gerbang terpisah
Memegang descriptor tidak dengan sendirinya memberikan permission untuk mengeksekusi file yang dirujuk. execveat() menerapkan aturan eksekusi yang terkait dengan execve(), termasuk pemrosesan format executable dan pemeriksaan permission. Descriptor yang diperoleh dengan O_PATH karena itu merupakan referensi objek, bukan token otorisasi yang melewati kebijakan execute pada filesystem.
Aturan transisi proses lain juga tetap berlaku. Penanganan set-user-ID dan set-group-ID, file capabilities, no_new_privs, mount flag seperti nosuid, keadaan tracing, keputusan Linux Security Module, dan semantik execve() terkait tidak digantikan oleh pemilihan berbasis descriptor.
Pemisahan ini penting bagi klaim keamanan. execveat() dapat membuat identitas executable stabil terhadap perubahan namespace pathname, sementara kernel secara independen memutuskan apakah pemanggil boleh mengeksekusi objek tersebut dan transisi credential apa yang diizinkan.
Eksekusi dirfd relatif mempersempit ketergantungan namespace tanpa menghapusnya
Bentuk relative-path yang tidak kosong memiliki peran operasional berbeda. Pemberian directory descriptor membuat resolusi berlangsung relatif terhadap direktori tersebut, bukan terhadap current working directory proses. Ini dapat menjaga eksekusi tetap berjangkar pada objek direktori walaupun current working directory berubah atau prefix tekstual di bagian namespace lain diganti.
Komponen path terakhir tetap diresolusi pada saat pemanggilan, sehingga perubahan serentak di bawah direktori tersebut dapat memengaruhi objek yang dipilih. Jika launcher perlu mengikat eksekusi ke file tertentu, referensi file itu harus diperoleh sebelum pemanggilan eksekusi.
Perbedaan ini mengikuti pola desain Linux yang lebih luas dan berorientasi descriptor: directory descriptor dapat menstabilkan titik awal lookup, sedangkan file descriptor dapat menstabilkan identitas objek yang sudah dipilih. Keduanya berkaitan, tetapi membentuk batas yang berbeda.
Pemilihan descriptor tidak menetapkan immutability konten
Desain keamanan dapat memakai execveat() untuk menutup race pada pathname tetapi masih menyisakan race pada konten. Misalnya, launcher dengan privilege membuka file, menghitung hash kontennya, lalu mengeksekusinya melalui descriptor. Pihak yang tidak dapat mengganti objek milik descriptor tetapi masih dapat menulis ke inode yang sama dapat mengubah byte setelah hashing dan sebelum atau selama pemuatan executable.
Properti integritas yang diperlukan karena itu memiliki dua bagian. Pemilihan objek harus tetap melekat pada objek yang diperiksa, dan konten yang diperiksa tidak boleh tetap dapat dimutasi oleh pihak penyerang sepanjang interval verifikasi hingga eksekusi. Bagian pertama disediakan oleh identitas descriptor. Bagian kedua berasal dari kebijakan penyimpanan dan permission, atau dari mekanisme yang membuat konten terkait immutable selama interval yang diperlukan.
Pemisahan tersebut menjaga jaminan tetap presisi. Eksekusi berbasis descriptor adalah primitive identitas pada batas process image. Mekanisme ini menghapus satu kelas substitusi namespace dari batas tersebut, tetapi tidak mengubah file mutable menjadi executable immutable.