私はLinux 4.xベースのディストリビューションを使用しており、最近カーネルのopen()
システムコールが公開O_PATH
フラグをサポートしていることを発見しました。
そのページには理論的に利用可能なシステムコールのリストがありますが、man
そのアイデアが何であるかわかりません。open(O_PATH)
ファイルではなくディレクトリのみを使用しますか?これがディレクトリパスの代わりにファイル記述子を使用するのはなぜですか?また、そこにリストされているほとんどのシステムコールはディレクトリごとに変わらないようです。では、O_PATH
そのディレクトリをファイル記述子として使用して通常のファイルも開くことができますか?それともファイル記述子を入手しましたが、機能が制限されていますか?
O_PATH
誰かが私たちがそれをどのように使用し、どのように使用するべきかについて説得力のある説明を与えることができますか?
メモ:
- 必要がない限り、それがどのように進化したかについての履歴を説明する必要はありません(関連マニュアルページにLinux 2.6.x、3.5、および3.6の変更点が記載されています)。ただ現在の位置が気になるだけです。
- libcや他の上位機能だけを使用するように言わないでください。私も知っています。
答え1
説明open(2)
マニュアルページには、始めるためのいくつかの手がかりがあります。
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
ファイル自体が実際には開かれていないので、開く方が安いはずです。
そしてそれほど重要ではありません:存在する前に、O_PATH
ファイルシステムオブジェクトへの参照を取得する方法は、を使用してオブジェクトを開くことでしたO_RDONLY
。ただし、これを使用するにはO_RDONLY
オブジェクトに対する読み取り権限が必要です。しかし、実際にオブジェクトを読む必要がないユースケースもたくさんあります。たとえば、バイナリを実行したり、ディレクトリ(fchdir()
)にアクセスしたり、ディレクトリを介してディレクトリ内のオブジェクトにアクセスしたりします。
"*at()" システムコールで使用されます。
の一般的な(唯一のものではない)用途は、などのようなO_PATH
"*at"システムコールで使用するために参照できるようにディレクトリを開くことです。似た名前( ,, とおおよそ考えられるこのシステムコールファミリーは、いくつかの目的に使用されます。 ディレクトリパスの代わりにファイル記述子を使用するには?" "。 at"システムコールの根拠があるサブタイトルの下)。 openat()
fstatat()
fchownat()
open()
fstat()
fchown()
open(2)
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呼び出しに対応する「* at」呼び出しを使用してチェック+操作を実行できますopen()
。
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)
もっと知りたい場合は、マニュアルページを読んでみましょう。
通常ファイルのファイル記述子とともに使用されます。
通常のファイルの1つの目的O_PATH
は、実行権限を持つバイナリファイルを開くことです(ただし、必ずしも読み取り権限ではないため、ファイルを開くために使用することはできません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)
システムコールを提供するカーネルのシステムコールです。 )