UNIX環境プログラミング学習ノート(22)――プロセス管理のシステム関数実行命令行文字列

11044 ワード

lienhua342014-10-15
ISO Cは、プログラム内でコマンド文字列を実行するためのシステム関数を定義する.その声明は以下の通りです.
#include
int system(const char *cmdstring);
System関数は、その実装においてfork、exec、waitpid関数を呼び出す.System関数はfork関数を呼び出してサブプロセスを作成し、サブプロセスから'/bin/sh-c cmdstring'を呼び出してコマンドラインパラメータcmdstringを実行し、このコマンドが実行されると呼び出されたプロセスに戻ります.システム関数が呼び出される間、SIGCHLD信号は一時的に保留され、SIGINT信号とSIGQUIT信号は無視されます.
System関数の戻り値は少し複雑で、次のような場合があります.
1.cmdstringが空のポインタの場合、コマンドハンドラハンドラが使用可能な場合にのみ0以外の値が返されます.この特徴は、オペレーティングシステムがsystem関数をサポートするかどうかを決定するために使用することができる.UNIXシステムは常にシステム関数をサポートしています.
2.forkが失敗した場合、またはwaitpidがEINTR以外のエラーを返した場合、systemは-1を返し、errnoでエラータイプ値を設定します.
3.execが失敗した場合(shellを実行できないことを示す)、その戻り値はshellがexit(127)を実行した後の終了状態に相当する.
4.すべての3つの関数(fork、exec、waitpid)が正常に実行された場合、systemの戻り値はshellがコマンドパラメータcmdstringを実行した後の終了状態である.
上記の戻り値の説明から,コマンドラインパラメータcmdstringが空ポインタでない場合,戻り値が−1であるか否かを判断することによってsystem関数が成功したか否かを判定できることが分かった.その他の場合、ドキュメント「プロセス終了状態のwaitとwaitpid関数を取得」で説明した処理終了状態のようにsystem関数の戻り値を処理できます.例を見てみましょう
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/wait.h>

extern void print_exit(int status);

int
main(void)
{
  int status;

  if ((status = system("date")) < 0) {
    printf("system() error: %s
", strerror(errno)); exit(-1); } print_exit(status); if ((status = system("nosuchcommand")) < 0) { printf("system() error: %s
", strerror(errno)); exit(-1); } print_exit(status); if ((status = system("who; exit 44")) < 0) { printf("system() error: %s
", strerror(errno)); exit(-1); } print_exit(status); exit(0); } void print_exit(int status) { if (WIFEXITED(status)) { printf("normal termination, exit status = %d
", WEXITSTATUS(status)); } else if (WIFSIGNALED(status)) { printf("abnormal termination, signal number =%d
", WTERMSIG(status)); } }

上記の手順で説明したように、system関数の戻り値を処理プロセス終了状態のように処理することができます.このプログラムをコンパイルし、ファイルsystemdemoを生成して実行し、
lienhua34:demo$ gcc -o systemdemo systemdemo.c
lienhua34:demo$ ./systemdemo
2014  10  15      23:15:28 CST
normal termination, exit status = 0
sh: 1: nosuchcommand: not found
normal termination, exit status = 127
lienhua34 tty7 2014-10-15 22:28
lienhua34 pts/0 2014-10-15 22:37 (:0.0)
lienhua34 pts/3 2014-10-15 23:10 (:0.0)
normal termination, exit status = 44
lienhua34:demo$

System関数作成プロセスのforkとexec関数の組合せに対する利点は、system関数が必要な各種エラー処理と各種信号処理を行うことである.しかし、システム関数には、ユーザID設定プログラムでシステム関数を使用するとセキュリティホールが存在するという致命的な弱点もある.次に、例を見てみましょう.
次のプログラムはsystem関数を使用して最初のコマンドラインパラメータを実行します.このプログラムを実行可能ファイルtsysにコンパイルします.
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>

int
main(int argc, char *argv[])
{
  int status;
  if (argc < 2) {
    printf("command-line argument required.
"); exit(-1); } if ((status = system(argv[1])) < 0) { printf("system error: %s
", strerror(errno)); exit(-1); } exit(0); }

次に、実行可能ファイルprintuidsにコンパイルされた印刷プロセスの実際のユーザIDと有効なユーザIDのプログラムを提供する.
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
int
main(void)
{
    printf("real uid=%d, effective uid=%d
", getuid(), geteuid()); exit(0); }

この2つの実行可能なファイルを使用して、次の操作を行います.
lienhua34:demo$ ./tsys ./printuids
real uid=1000, effective uid=1000
lienhua34:demo$ su
# chown root tsys
# chmod u+s tsys
# ls -l tsys
-rwsrwxr-x 1 root lienhua34 7358 10  15 23:37 tsys
# exit
exit
lienhua34:demo$ ./tsys ./printuids
real uid=1000, effective uid=0

上記の実行中、tsysファイルの所有者をスーパーユーザーrootに設定し、ユーザーIDビットを設定しました.そこで,tsysファイルを実行すると,プロセスの有効ユーザIDは0(すなわちroot)となる.システム関数を呼び出してprintuidsファイルを実行するサブプロセスは、この有効なユーザーIDを継承し、サブプロセスは致命的な傷害をもたらす可能性のあるプログラムコマンドを実行する権限を持っています.これがシステム関数に存在するセキュリティ・ホールです.
あるプロセスが特殊な権限(ユーザーIDの設定またはグループIDの設定)で実行され、別のプロセスを生成して別のプログラムを実行したい場合は、forkとexecを直接使用し、forkの後、execの前に通常の権限に戻る必要があります.ユーザIDまたはセットグループIDプログラムを設定してsystem関数を呼び出すべきではありません.
(done)