open(2)
manページの説明は、最初に手掛かりを与えます:
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.
ときどき、ファイルやディレクトリを開きたくないことがあります。代わりに、特定の操作を実行するために、そのファイルシステムオブジェクトへの参照が必要です(たとえば、をfchdir()
使用して開いたファイル記述子によって参照されるディレクトリへの参照O_PATH
)。つまり、これは私たちの目的であればO_PATH
、ファイル自体は実際には開かれないので、with with を少し安くする必要があります。
そして、それほど重要ではない点:が存在する前O_PATH
に、ファイルシステムオブジェクトへのそのような参照を取得する方法は、でオブジェクトを開くことでしたO_RDONLY
。ただし、を使用するにO_RDONLY
は、オブジェクトに対する読み取り権限が必要です。ただし、実際にオブジェクトを読み取る必要がないさまざまなユースケースがあります。たとえば、バイナリを実行したり、ディレクトリにアクセスしfchdir()
たり()、ディレクトリを介してディレクトリ内のオブジェクトにアクセスしたりします。
「* at()」システムコールでの使用
一般的には、だけでなく、の使用はO_PATH
のようなシステムコール、「時*」で使用するために、そのディレクトリを参照するようにするために、ディレクトリを開くことでopenat()
、fstatat()
、fchownat()
、などを。私たちは大体似た名前を持つ古いシステムコールに近代的な後継者として考えることができ、システムコール、この家族(open()
、fstat()
、fchown()
など)、目的のカップル、あなたは「頼むときあなたが触れるうち最初のサーブなぜディレクトリのパスの代わりにファイル記述子を使用したいのですか?」open(2)
マニュアルページをさらに見ると、次のテキストが見つかります( "* 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().
これをより具体的にするには...現在の作業ディレクトリ以外のディレクトリで複数の操作を実行するプログラムがあるとします。つまり、使用するファイル名の一部としてディレクトリプレフィックスを指定する必要があります。たとえば、パス名が次の/dir1/dir2/file
2つの操作を実行するとします。
- いくつかのチェックを実行します
/dir1/dir2/file
(たとえば、ファイルの所有者、または最終変更日時)。
- そのチェックの結果に満足したら、おそらく同じディレクトリで他のファイルシステム操作を実行したいとします
/dir1/dir2/file.new
。たとえば、というファイルを作成します。
ここで、まず、従来のパス名ベースのシステムコールを使用してすべてを実行したとします。
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 */
}
さらに、ディレクトリプレフィックス/dir1/dir2
で、コンポーネントの1つ(たとえばdir2
)が実際にはシンボリックリンク(ディレクトリを参照)であり、悪意のある人物への呼び出しと悪意のある人物への呼び出しの間stat()
open()
で、dir2
別のディレクトリを指すシンボリックリンク。これは、従来のチェック時間と使用時間の競合状態です。私たちのプログラムは、あるディレクトリのファイルをチェックしましたが、その後、だまされて別のディレクトリ(おそらくセキュリティの影響を受けやすいディレクトリ)にファイルを作成しました。ここで重要なのは、パス名/dir/dir2
は同じに見えましたが、パス名が完全に変更されたことです。
「* at」呼び出しを使用すると、この種の問題を回避できます。まず、作業を行うディレクトリを参照するハンドルを取得します。
dirfd = open("/dir/dir2", O_PATH);
ここで重要な点は、それがdirfd
ある安定したパスによって参照されたディレクトリへの参照/dir1/dir2
時にopen()
呼び出し。シンボリックリンクのターゲットdir2
が後で変更された場合、これは参照先に影響しませんdirfd
。これで、上記のstat()
and open()
呼び出しと同等の「* at」呼び出しを使用して、check +操作を実行できます。
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 */
}
これらの手順の間、パス名のシンボリックリンクを操作し/dir/dir2
ても影響はありません。チェック(fstatat()
)と操作(openat()
)は同じディレクトリで実行されることが保証されています。
マルチスレッドプログラムでの「スレッドごとの現在の作業ディレクトリ」の考え方に関連する「* at()」呼び出しを使用する別の目的があります(ここでもを使用してディレクトリを開くことができますO_PATH
)が、おそらくこの使用法は質問との関連性は低くなりますopen(2)
。詳しく知りたい場合は、manページをお読みください。
通常のファイルのファイル記述子での使用
O_PATH
通常のファイルでの1つの使用法は、実行権限を持つバイナリを開くことです(ただし、必ずしも読み取り権限ではないため、でファイルを開くことができませんO_RDONLY
)。次に、そのファイル記述子を渡してfexecve(3)
プログラムを実行できます。fexecve(fd, argv, envp)
そのfd
議論で行われていることはすべて本質的には次のとおりです。
snprintf(buf, "/proc/self/fd/%d", fd);
execve(buf, argv, envp);
(ただし、glibc 2.27以降では、execveat(2)
システムコールを提供するカーネルで、実装は代わりにシステムコールを使用します。)