Scroll to navigation

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 の形式をしており、 新しいプログラムの環境変数として渡される。 argvenvp はいずれものヌルポインターで終わっている必要がある。 引き数配列と環境変数は、呼び出されたプログラムの 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() の前後で保持される。

  • 捕捉されたシグナルの処理方法 (disposition) は デフォルト動作にリセットされる (signal(7))。
  • 代替シグナルスタックはどれも保持されない (sigaltstack(2))。
  • メモリーマッピングは保持されない (mmap(2))。
  • 付加された (attached) System V 共有メモリーセグメントは分離される (shmat(2))。
  • POSIX 共有メモリー領域はマッピングを解除される (shm_open(3))。
  • オープンされた POSIX メッセージキューディスクリプターはクローズされる (mq_overview(7))。
  • オープンされた POSIX 名前付きセマフォはいずれもクローズされる (sem_overview(7))。
  • POSIX タイマーは保持されない (timer_create(2))。
  • オープンされたディレクトリストリームはいずれもクローズされる (opendir(3))。
  • メモリーロックは保持されない (mlock(2), mlockall(2))。
  • 終了 (exit) ハンドラーは保持されない (atexit(3), on_exit(3))。
  • 浮動小数点関連の環境はデフォルトにリセットされる (fenv(3) 参照)。

上記のリストのプロセス属性はいずれも POSIX.1-2001 で規定されている。 以下に示す Linux 固有のプロセス属性も execve() の前後で保持されない。

  • set-user-ID か set-group-ID されたプログラムが実行されている場合、 prctl(2)PR_SET_DUMPABLE フラグはクリアされる。それ以外の場合、このフラグはセットされる。
  • prctl(2)PR_SET_KEEPCAPS フラグはクリアされる。
  • (Linux 2.4.36 以降 / 2.6.23 以降) set-user-ID や set-group-ID されたプログラムが実行された場合、 prctl(2)PR_SET_PDEATHSIG フラグで設定された parent death シグナルはクリアされる。
  • プロセス名は新しい実行ファイルの名前にリセットされる。 プロセス名は prctl(2)PR_SET_NAME で設定でき、 ps -o comm で表示できる。
  • SECBIT_KEEP_CAPSsecurebits フラグはクリアされる。 capabilities(7) 参照。
  • 終了シグナル (termination signal) は SIGCHLD にリセットされる (clone(2) 参照)。

以下の点についても注意すること:

  • 呼び出し元スレッド以外の全てのスレッドは execve() 中に破棄される。 mutex、条件変数、その他の pthread オブジェクトは保持されない。
  • setlocale(LC_ALL, "C") 相当の処理がプログラム開始時に実行される。
  • POSIX.1-2001 は、動作が無視かデフォルトに設定されている全てのシグナル の処理方法は変更せずそのままにする、と規定している。 但し、POSIX.1-2001 には一つ例外があり、 SIGCHLD が無視になっている場合、 その処理方法を変更せずにそのままにするか、デフォルト動作にリセットするかは 実装依存となっている。 Linux では前者 (変更しない) となっている。
  • 完了していない非同期 I/O 操作はキャンセルされる (aio_read(3), aio_write(3))。
  • execve(2) 時のケーパビリティの扱いについては、 capabilities(7) を参照。
  • デフォルトでは、ファイルディスクリプターは execve() を行った後でもオープンされたままである。 close-on-exec の印が付いているファイルディスクリプターはクローズされる。 fcntl(2)FD_CLOEXEC の説明を参照。 (ファイルディスクリプターがクローズされると、このプロセスが ファイルディスクリプターに対応するファイルに対して獲得していた レコードのロックが全て解放されることになる。) POSIX.1-2001 では、 ファイルディスクリプター 0, 1, 2 が execve() 成功後にどこかでクローズされ、かつ 実行されるファイルに set-user_ID か set-group_ID の許可ビットが セットされていてプロセスが特権を獲得した場合、 システムは何らかのファイルをオープンする際に これらの番号のディスクリプターのどれかを使うことがある、 とされている。 原則として、移植性が必要なプログラムでは、 特権の有無に関わらず、 execve() の前後でこれら 3つのファイルディスクリプターがクローズされたままで あることを前提にすることはできない。

インタープリタースクリプト

インタープリタースクリプトとは、実行許可が有効になっていて、 最初の行が以下の形になっているテキストファイルのことである。


#! 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 を適切に設定する。

エラー

環境変数 (envp) と引き数リスト (argv) の合計バイト数が大き過ぎる。
filename やスクリプトインタープリター名の構成要素に検索許可 (search permission) が与えられていない (path_resolution(7) も参照すること)。
ファイルもしくはスクリプトのインタープリターが通常ファイル (regular file) でない。
ファイルやスクリプトや ELF インタープリターに 実行許可 (execute permission) が与えられていない。
ファイルシステムが noexec でマウントされている。
set*uid() のいずれかの呼び出しでプロセスの実 UID が変更されたとすると、呼び出し元の RLIMIT_NPROC リソース上限 (setrlimit(2) 参照) を超えてしまう、 現在もまだ超えている。 このエラーの詳細な説明については「注意」の節を参照。
filename または配列 argvenvp のポインターの一つがアクセス可能なアドレス空間の外を指している。
ELF 実行形式で複数の PT_INTERP セグメントが存在する。 (すなわち複数のインタープリターを指定した。)
I/O エラーが発生した。
ELF インタープリターがディレクトリだった。
ELF インタープリターが理解できるフォーマットでなかった。
filename やスクリプトや ELF のインタープリターを解決する際に遭遇した シンボリックリンクが多過ぎる。
そのプロセスがオープンできるファイル数の上限まで既にオープンしている。
filename が長過ぎる。
オープンされたファイルの総数がシステム全体の上限に達していた。
ファイル filename かスクリプトや ELF のインタープリターが存在しない。
実行ファイルが理解できない形式であるか、違うアーキテクチャーのものか、 その他のフォーマットエラーにより実行ができなかった。
カーネルに十分なメモリーがない。
filename やスクリプトや ELF のインタープリターの構成要素がディレクトリでない。
ファイルシステムが nosuid でマウントされ、ユーザーがスーパーユーザーでなく、 ファイルに set-user-ID あるいは set-group-ID ビットが設定されている。
プロセスがトレースされ、ユーザーがスーパーユーザーでなく、 ファイルに set-user-ID あるいは set-group-ID ビットが設定されている。
実行ファイルを書き込み用にオープンしているプロセスがある。

準拠

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 では、 argvenvp に 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() と EAGAIN

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