Untuk apa saya seharusnya menggunakan `O_PATH`, dan bagaimana?

8

Saya menggunakan distribusi berbasis Linux 4.x, dan saya baru-baru ini memperhatikan open()system call kernel mendukung O_PATHflag terbuka.

Sementara manhalaman untuk itu memang memiliki daftar panggilan sistem yang secara teori dapat digunakan, saya tidak begitu mengerti apa idenya. Apakah saya open(O_PATH)hanya direktori, bukan file? Dan jika saya melakukannya, mengapa saya ingin menggunakan deskriptor file alih-alih jalur direktori? Juga, sebagian besar panggilan sistem yang terdaftar di sana tampaknya tidak khusus untuk direktori; jadi, apakah saya juga membuka file biasa O_PATHuntuk mendapatkan direktori mereka sebagai deskriptor file? Atau untuk mendapatkan deskriptor file untuk mereka tetapi dengan fungsi terbatas?

Bisakah seseorang memberikan penjelasan yang meyakinkan tentang apa O_PATHitu dan bagaimana, dan untuk apa, kita seharusnya menggunakannya?

Catatan:

  • Tidak perlu menggambarkan sejarah bagaimana ini berevolusi (halaman manual yang relevan menyebutkan perubahan di Linux 2.6.x, 3.5 dan 3.6) kecuali perlu - saya hanya peduli bagaimana keadaannya sekarang.
  • Tolong jangan katakan padaku untuk hanya menggunakan libc atau fasilitas tingkat tinggi lainnya, saya tahu itu.
einpoklum
sumber
@sebasth: Memang terkait, tetapi: 1. Ini sudah agak tua sekarang dan hal-hal mungkin telah berubah. 2. Sejujurnya, saya tidak mengerti intinya.
einpoklum
1
Anda dapat memposting komentar di pertanyaan itu menanyakan apakah ada yang berubah.
Barmar

Jawaban:

8

Deskripsi di open(2)halaman manual memberikan beberapa petunjuk untuk memulai dengan:

   O_PATH (since Linux 2.6.39)
          Obtain a file descriptor that can be used for two purposes:
          to  indicate  a location in the filesystem tree and to per‐
          form operations that act  purely  at  the  file  descriptor
          level.  The file itself is not opened, and other file oper‐
          ations  (e.g.,  read(2),  write(2),  fchmod(2),  fchown(2),
          fgetxattr(2), ioctl(2), mmap(2)) fail with the error EBADF.

Terkadang, kami tidak ingin membuka file atau direktori. Sebagai gantinya, kami hanya ingin referensi ke objek sistem file itu untuk melakukan operasi tertentu (misalnya, ke fchdir()direktori yang dirujuk oleh deskriptor file yang kami buka menggunakan O_PATH). Jadi, titik sepele: jika ini adalah tujuan kita, maka pembukaan dengan O_PATHharus sedikit lebih murah, karena file itu sendiri sebenarnya tidak dibuka.

Dan poin yang kurang sepele: sebelum keberadaan O_PATH, cara mendapatkan referensi seperti itu ke objek sistem file adalah dengan membuka objek O_RDONLY. Tetapi penggunaan O_RDONLYmengharuskan kita telah membaca izin pada objek. Namun, ada berbagai kasus penggunaan di mana kita tidak perlu membaca objek: misalnya, menjalankan biner atau mengakses direktori ( fchdir()) atau menjangkau melalui direktori untuk menyentuh objek di dalam direktori.

Penggunaan dengan panggilan sistem "* at ()"

Umum, tetapi bukan satu-satunya, penggunaan O_PATHadalah untuk membuka direktori, untuk memiliki referensi ke direktori untuk digunakan dengan "* di" panggilan sistem, seperti openat(), fstatat(), fchownat(), dan sebagainya. Ini keluarga panggilan sistem, yang kita kira-kira bisa anggap sebagai penerus modern untuk panggilan sistem yang lebih tua dengan nama yang mirip ( open(), fstat(), fchown(), dan sebagainya), melayani beberapa tujuan, yang pertama Anda menyentuh ketika Anda meminta " mengapa saya ingin menggunakan deskriptor file alih-alih jalur direktori? " Jika kita melihat lebih jauh ke bawah di open(2)halaman manual, kita menemukan teks ini (di bawah judul dengan alasan untuk panggilan sistem "* at"):

   First,  openat()  allows  an  application to avoid race conditions
   that could occur when using open() to open  files  in  directories
   other  than  the current working directory.  These race conditions
   result from the fact that some component of the  directory  prefix
   given  to  open()  could  be  changed in parallel with the call to
   open().  Suppose, for example, that we wish  to  create  the  file
   path/to/xxx.dep  if  the  file path/to/xxx exists.  The problem is
   that between the existence check and the file creation step,  path
   or  to  (which might be symbolic links) could be modified to point
   to a different location.  Such races can be avoided by  opening  a
   file descriptor for the target directory, and then specifying that
   file descriptor as the dirfd argument of (say) fstatat(2) and ope‐
   nat().

Untuk membuat ini lebih konkret ... Misalkan kita memiliki program yang ingin melakukan beberapa operasi di direktori selain direktori kerjanya saat ini, artinya kita harus menentukan beberapa awalan direktori sebagai bagian dari nama file yang kita gunakan. Misalkan, misalnya, bahwa nama pathnya /dir1/dir2/filedan kami ingin melakukan dua operasi:

  1. Lakukan beberapa pemeriksaan /dir1/dir2/file(misalnya, siapa yang memiliki file, atau jam berapa terakhir kali diubah).
  2. Jika kita puas dengan hasil pemeriksaan itu, mungkin kita kemudian ingin melakukan beberapa operasi sistem file lain di direktori yang sama, misalnya, membuat file bernama /dir1/dir2/file.new.

Sekarang, misalkan kita melakukan semuanya menggunakan panggilan sistem berbasis pathname tradisional:

struct stat stabuf;
stat("/dir1/dir2/file", &statbuf);
if ( /* Info returned in statbuf is to our liking */ ) {
    fd = open("/dir1/dir2/file.new", O_CREAT | O_RDWR, 0600);
    /* And then populate file referred to by fd */
}

Sekarang, lebih jauh lagi anggaplah bahwa dalam awalan direktori /dir1/dir2salah satu komponen (katakanlah dir2) sebenarnya adalah tautan simbolis (yang merujuk ke direktori), dan bahwa antara panggilan ke stat()dan panggilan keopen() orang jahat dapat mengubah target dari tautan simbolis dir2untuk menunjuk ke direktori yang berbeda. Ini adalah kondisi balapan waktu-of-check-of-use klasik. Program kami memeriksa file di satu direktori tetapi kemudian tertipu untuk membuat file di direktori yang berbeda - mungkin direktori yang sensitif terhadap keamanan. Poin kunci di sini adalah bahwa nama path /dir/dir2tampak sama, tetapi apa yang dirujuknya berubah sepenuhnya.

Kita dapat menghindari masalah semacam ini menggunakan panggilan "* at". Pertama-tama, kami memperoleh pegangan yang merujuk ke direktori tempat kami akan melakukan pekerjaan kami:

dirfd = open("/dir/dir2", O_PATH);

Titik penting di sini adalah bahwa dirfdadalah stabil referensi ke direktori yang disebut oleh jalan /dir1/dir2pada saat open()panggilan. Jika target tautan simbolik dir2kemudian diubah, ini tidak akan memengaruhi apa yang dirfddimaksud. Sekarang, kita dapat melakukan operasi pemeriksaan + menggunakan panggilan "* at" yang setara dengan stat()dan open()panggilan di atas:

fstatat(dirfd, ""file", &statbuf)
struct stat stabuf;
fstatat(dirfd, "file", &statbuf);
if ( /* Info returned in statbuf is to our liking */ ) {
    fd = openat(dirfd, "file.new", O_CREAT | O_RDWR, 0600);
    /* And then populate file referred to by fd */
}

Selama langkah-langkah ini setiap manipulasi tautan simbolik dalam pathname tidak /dir/dir2akan berdampak: check ( fstatat()) dan operasi ( openat()) dijamin akan terjadi di direktori yang sama.

Ada tujuan lain untuk menggunakan panggilan "* at ()", yang berhubungan dengan gagasan "direktori kerja saat ini per-thread" dalam program multithreaded (dan sekali lagi kita bisa membuka direktori menggunakan O_PATH), tapi saya pikir penggunaan ini mungkin kurang relevan dengan pertanyaan Anda, dan saya meninggalkan Anda untuk membaca open(2)halaman manual jika Anda ingin tahu lebih banyak.

Penggunaan dengan deskriptor file untuk file biasa

Salah satu penggunaan O_PATHdengan file biasa adalah membuka biner yang izinnya kami jalankan (tetapi tidak harus membaca izin, sehingga kami tidak dapat membuka file itu dengan O_RDONLY). Deskriptor file itu kemudian dapat diteruskan ke fexecve(3)untuk menjalankan program. Semua yang fexecve(fd, argv, envp)dilakukan dengan fdargumennya pada dasarnya adalah:

snprintf(buf, "/proc/self/fd/%d", fd);
execve(buf, argv, envp);

(Meskipun, dimulai dengan glibc 2.27, implementasi akan menggunakan execveat(2)system call, pada kernel yang menyediakan system call itu.)

mtk
sumber
The problem is that between the existence check and the file creation step, path or to ... could be modified - tidak dapat menguraikan kalimat ini. Tapi saya mengerti, saya pikir. Jadi ini berfungsi sebagai semacam mekanisme penguncian pada direktori. Tetapi mengapa menggunakan open()hasilnya daripada kunci yang sebenarnya?
einpoklum
@einpoklum masalahnya adalah 'path' dan 'to' tidak memiliki format yang ditunjukkan di halaman manual asli. Ini adalah komponen dari pathname hipotetis "/ path / to / xxx". Dan, ini tidak seperti kunci: ini adalah referensi stabil ke objek sistem file; beberapa program mungkin memiliki referensi untuk objek yang sama.
mtk