EXECVE(2) | Linux Programmer's Manual | EXECVE(2) |
execve - プログラムを実行する
#include <unistd.h>
int execve(const char *filename, char *const
argv[],
char *const envp[]);
execve() は、filename によって指定されたプログラムを実行する。 filename は、バイナリ実行形式か、 以下の形式の行で始まるスクリプトでなければならない。
#! interpreter [optional-arg]
後者の詳細は、後ろの「インタープリタースクリプト」の節を参照のこと。
argv は新しいプログラムに渡される引き数文字列の配列である。 慣例では、引き数文字列の最初の要素には実行されたファイルに関連付けられた ファイル名を含めることになっている。 envp は文字列の配列であり、伝統的に key=value の形式をしており、 新しいプログラムの環境変数として渡される。 argv と envp はいずれものヌルポインターで終わっている必要がある。 引き数配列と環境変数は、呼び出されたプログラムの main 関数を 以下のように定義することによってアクセス可能になる。
int main(int argc, char *argv[], char *envp[])
成功した場合、 execve() は返らない。 そして、呼び出し元のプロセスの text, data, bss, スタックは、 読み込まれたプログラムによって上書きされる。
元のプログラムが ptrace されている場合、 execve() が成功した後に そのプログラムに SIGTRAP が送られる。
filename で指定されたプログラムファイルに set-user-ID ビットが設定されており、 ファイルが存在するファイルシステムが nosuid (mount(2) の MS_NOSUID フラグ) でマウントされておらず、 呼び出したプロセスが ptrace されていない場合、 呼び出したプロセスの実効 (effective) ユーザー ID は プログラムファイルの所有者 (owner) に変更される。 同様に、プログラムファイルに set-group-ID ビットが設定されていた場合、 呼び出したプロセスの有効グループ ID は プログラムファイルのグループに変更される。
プロセスの実効ユーザー ID は保存 (saved) set-user-ID にコピーされる。 同様に、実効グループ ID は保存 set-group-ID にコピーされる。 このコピーは、set-user-ID / set-group-ID 許可ビットにより発生する 実効 ID の変更後に行われる。
実行ファイルが動的リンクされた a.out 実行形式で、共有ライブラリの スタブを含むものだった場合、実行の開始時に Linux の ダイナミックリンカー ld.so(8) が呼び出され、必要な共有ライブラリをメモリーに読み込んでリンクを行う。
実行ファイルがダイナミックリンクされた ELF 実行形式だった場合、 PT_INTERP セグメントに指定されたインタープリターが必要な 共有ライブラリ (shared library) を読み込むのに使用される。 通常、インタープリターは glibc をリンクしたバイナリでは /lib/ld-linux.so.2 である。
以下に示す以外のすべてのプロセス属性は execve() の前後で保持される。
上記のリストのプロセス属性はいずれも POSIX.1-2001 で規定されている。 以下に示す Linux 固有のプロセス属性も execve() の前後で保持されない。
以下の点についても注意すること:
インタープリタースクリプトとは、実行許可が有効になっていて、 最初の行が以下の形になっているテキストファイルのことである。
#! interpreter [optional-arg]
interpreter は有効な実行ファイルのパス名でなければならず、 それ自身がスクリプトであってはならない。 execve() の filename 引き数がインタープリタースクリプトを指定している場合、 interpreter は以下の引き数で起動される。
interpreter [optional-arg] filename arg...
arg... は、 execve() の argv 引き数が指すワード列である。 argv[1] から始まる。
移植性を持たすには、 optional-arg は空か 1ワードだけにすべきである (つまり、ホワイトスペースを含めるべきではない)。 下記の「注意」の節を参照。
ほとんどの UNIX の実装は、新しいプログラムに渡すことができる コマンドライン引き数 (argv) と環境変数 (envp) の文字列群の合計サイズに何らかの上限を設けている。 POSIX.1 は、 ARG_MAX 定数を使ってこの上限を決める実装を認めている (ARG_MAX は <limits.h> で定義されるか、実行時に sysconf(_SC_ARG_MAX) の呼び出しで入手できるかのいずれかである)。
カーネル 2.6.23 より前の Linux では、環境変数と引き数の文字列群を 格納するのに使用されるメモリーは 32 ページに制限されていた (32 ページというのはカーネル定数 MAX_ARG_PAGES で定義される)。したがって、 ページサイズが 4 kB のアーキテクチャーでは、 最大サイズは 128 kB ということになる。
カーネル 2.6.23 以降では、ほとんどのアーキテクチャーにおいて、 execve() が呼び出された時点で適用されているリソースのソフト上限 RLIMIT_STACK に基づいたサイズ上限が使われる (メモリー管理ユニット (MMU) を持たないアーキテクチャーは上記の変更の 例外であり、これらのアーキテクチャーではカーネル 2.6.23 より前と 同じ上限がそのまま使用される)。 これらのアーキテクチャーでは、合計サイズは許可されたスタックサイズの 1/4 に制限されている (1/4 の上限を設けているのは、新しいプログラムが必ずある程度の スタック空間を持てることを保証するためである)。 Linux 2.6.25 以降では、カーネルはこのサイズ上限に 32 ページの下限を 設けている。これにより、 RLIMIT_STACK が非常に小さく設定された場合でも、アプリケーションが少なくとも Linux 2.6.23 以前で提供されていたのと同じ大きさの引き数と環境変数の空間 と同じだけは確保できることが保証されている (この最低限の保証は Linux 2.6.23 と 2.6.24 では提供されていない)。 また、各文字列の上限は 32 ページ (カーネル定数 MAX_ARG_STRLEN) で、文字列数の最大値は 0x7FFFFFFF である。
成功すると execve() は返らない。エラーの場合は -1 を返し、 errno を適切に設定する。
SVr4, 4.3BSD, POSIX.1-2001. POSIX.1-2001 には #! 動作についての記述はないが、 他は互換性がある。
set-user-id プロセスと set-group-ID プロセスは ptrace(2) できない。
ファイルシステムを nosuid でマウントした場合に set-user-ID/set-group-ID の実行ファイルを どの様に扱うかは、Linux カーネルのバージョンによって異なる: あるバージョンでは、すでに必要な権限を持っている場合を除いて、 その実行を拒否する (そして EPERM を返す)。別のあるバージョンでは set-user-ID/set-group-ID ビットのみを無視し exec() は成功する。 Linux では、 argv と envp に NULL を指定することができる。 どちらに NULL を指定した場合も、 これらの引き数にヌルポインター 1 個だけを含むリストへのポインターを指定したのと同じ効果を持つ。 「この間違った機能を利用しないこと」。 これは非標準で、移植性もない。 他のほとんどの UNIX システムでは、これを行うとエラー (EFAULT) になる。
POSIX.1-2001 は、 sysconf(3) が返す値はプロセスの生存中は変化しないべきだとしている。 しかしながら、Linux 2.6.23 以降では、リソース上限 RLIMIT_STACK が変化した場合、 コマンドライン引き数と環境変数を保持するための空間に対する上限が 変化したことを反映して、 _SC_ARG_MAX が返す値も変化する。
execve() が失敗するほとんどの場合、 制御は元の実行可能イメージに戻り、 execve() の呼び出し元がエラーを処理することができる。 しかしながら、 (リソース枯渇が原因となった場合など、まれに) 呼び出し元に制御が戻る時点を過ぎてからエラーが発生する場合がある。 元の実行可能イメージはすでに破棄されているが、 新しいイメージが完全には構築されていないという状況である。 このような場合、カーネルはそのプロセスをシグナル SIGKILL で停止 (kill) する。
インタープリタースクリプトの 1行目に許されている文字数は、 最大 127 文字である。
インタープリタースクリプトの optional-arg 引き数の解釈方法は実装により異なる。 Linux では、インタープリター名 interpreter に続く文字列全体がインタープリターに 1個の引き数として渡される。 しかし、動作が異なるシステムもある。 あるシステムでは、 optional-arg のうち最初のホワイトスペースまでが 引き数として渡される。 また、別のシステムでは インタープリタースクリプトは複数の引き数を持つことができ、 optional-arg 内のホワイトスペースが引き数の区切りとなる。
Linux はスクリプトの set-user-ID と set-group-ID ビットを無視する。
execve() を呼び出した際に (Linux 3.1 以降で) 起こり得る EAGAIN エラーの詳細な説明を以下で行う。
直前の setuid(2), setreuid(2), setresuid(2) の呼び出しで、 そのプロセスの実ユーザー ID が変更され、 その変更によりそのプロセスが RLIMIT_NPROC リソース上限を超過してしまった場合 (すなわち、新しい実ユーザー ID に属するプロセス数が RLIMIT_NPROC リソース上限を超過した場合) に、 EAGAIN エラーが発生する。 Linux 2.6.0 以上 3.0 以下では、これにより set*uid() の呼び出しが失敗していた。 (Linux 2.6 より前では、このリソース上限はユーザー ID を変更したプロセスには適用されていなかった。)
Linux 3.1 以降では、上で説明したシナリオでは set*uid() の呼び出しは失敗しない。 なぜなら、 返されたステータスの確認を行わず「呼び出し元が特権を持っている場合には」呼び出しは必ず成功するとみなしているバグがあるアプリケーションでは、セキュリティホールにつながることが非常によくあるからだ。 その代わり、 set*uid() の呼び出しによる実 UID の変更は成功するが、 カーネルは PF_NPROC_EXCEEDED という名前の内部フラグをセットする。 このフラグは RLIMIT_NPROC リソース上限が超過したことを示す。 PF_NPROC_EXCEEDED フラグがセットされていて、その後で execve() が呼ばれた際にリソース上限がまだ超過していれば、 その execve() の呼び出しは EAGAIN エラーで失敗する。 このカーネルのロジックにより、 特権デーモンでよく行われる処理フロー、 すなわち fork(2) + set*uid() + execve() に対して、前と変わらず RLIMIT_NPROC リソース上限を適用できることが保証される。
(set*uid() と execve() の呼び出しの間に、この実 UID に属する他のプロセスが終了して) 次に execve() が呼び出された際にこのリソース上限が超過してなければ、 execve() の呼び出しは成功し、カーネルは PF_NPROC_EXCEEDED プロセスフラグをクリアする。 同じプロセスによって fork(2) の呼び出しが後で行われた場合にも、このフラグはクリアされる。
UNIX V6 では exec() コールの引き数リストは 0 で終端され、 main の引き数リストは -1 で終端されていた。 そのため、 main の引き数リストは、その後の exec() コールには直接使用できなかった。 UNIX V7 以降では、ともに NULL で終端される。
このプログラムは、以下の二つ目のプログラムから実行するためのものである。 コマンドライン引き数を 1行に 1個ずつ表示するだけのプログラムである。
/* myecho.c */ #include <stdio.h> #include <stdlib.h> int main(int argc, char *argv[]) {
int j;
for (j = 0; j < argc; j++)
printf("argv[%d]: %s\n", j, argv[j]);
exit(EXIT_SUCCESS); }
以下のプログラムは、コマンドライン引き数で指定した名前のプログラムを
実行するのに使う。
/* execve.c */ #include <stdio.h> #include <stdlib.h> #include <unistd.h> int main(int argc, char *argv[]) {
char *newargv[] = { NULL, "hello", "world", NULL };
char *newenviron[] = { NULL };
if (argc != 2) {
fprintf(stderr, "Usage: %s <file-to-exec>\n", argv[0]);
exit(EXIT_FAILURE);
}
newargv[0] = argv[1];
execve(argv[1], newargv, newenviron);
perror("execve"); /* execve() returns only on error */
exit(EXIT_FAILURE); }
二つ目のプログラムを使って一つ目のプログラムを実行するには 以下のようにする。
$ cc myecho.c -o myecho $ cc execve.c -o execve $ ./execve ./myecho argv[0]: ./myecho argv[1]: hello argv[2]: world
さらに、これらのプログラムを使って、スクリプトインタープリターの例を示す。 このために、「インタープリター」として先ほど作成したプログラム myecho を使うスクリプトを作成する。
$ cat > script #!./myecho script-arg ^D $ chmod +x script
作成しておいたプログラムを使ってスクリプトを実行する。
$ ./execve ./script argv[0]: ./myecho argv[1]: script-arg argv[2]: ./script argv[3]: hello argv[4]: world
chmod(2), execveat(2), fork(2), ptrace(2), execl(3), fexecve(3), getopt(3), credentials(7), environ(7), path_resolution(7), ld.so(8)
この man ページは Linux man-pages プロジェクトのリリース 3.79 の一部 である。プロジェクトの説明とバグ報告に関する情報は http://www.kernel.org/doc/man-pages/ に書かれている。
2015-01-22 | Linux |