system 函数

    system 函数可用来方便地执行一个命令字符串。
#include <stdlib.h>
int system(const char *cmdstring);         /* 返回值:(见下)*/

    如果 cmdstring 是一个空指针,则仅当命令处理程序可用时,system 返回非 0 值。可利用这一特征来确定某个系统是否支持 system 函数,UNIX 系统上 system 总是可用的。
    因为 system 在其实现中调用了 fork、exec 和 waitpid,因此有 3 种返回值。
    1、fork 失败或者 waitpid 返回除 EINTR 之外的出错,则 system 返回 -1,并设置 errno 以指示错误类型。
    2、如果 exec 失败(表示不能执行 shell),则其返回值如同 shell 执行了 exit(127)。
    3、3 个函数都成功,则 system 的返回值是 shell 的终止状态,此时取决于 waitpid。
    下面是 system 函数的 POSIX.1 实现,并使用了 wait 和 waitpid 函数中编写的 pr_exit 函数对其进行测试。注意,为了完整性这里添加了信号处理部分,可参考本人后面与信号相关的博文笔记来了解具体的信号函数的用法。
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <errno.h>
#include <unistd.h>
#include <signal.h>

extern void pr_exit(int status);

/* with appropriate signal handling */
int mySystem(const char *cmdstring){
	pid_t	pid;
	int		status;
	struct sigaction	ignore, saveintr, savequit;
	sigset_t	chldmask, savemask;

	if(cmdstring == NULL)
		return 1;		// always a command processor with UNIX

	ignore.sa_handler = SIG_IGN;	// ignore SIGINT and SIGQUIT
	sigemptyset(&ignore.sa_mask);
	ignore.sa_flags = 0;
	if(sigaction(SIGINT, &ignore, &saveintr) < 0)
		return -1;
	if(sigaction(SIGQUIT, &ignore, &savequit) < 0)
		return -1;
	sigemptyset(&chldmask);		// now block SIGCHLD
	sigaddset(&chldmask, SIGCHLD);
	if(sigprocmask(SIG_BLOCK, &chldmask, &savemask) < 0)
		return -1;

	if((pid=fork()) < 0){
		status = -1;	// probably out of processes
	}else if(pid == 0){
		/* restore previous signal action & reset signal mask */
		sigaction(SIGINT, &saveintr, NULL);
		sigaction(SIGQUIT, &savequit, NULL);

		execl("/bin/sh", "sh", "-c", cmdstring, (char *)0);
		_exit(127);		// execl error
	}else{
		while(waitpid(pid, &status, 0) < 0){
			if(errno != EINTR){
				status = -1;	// error other than EINTR from waitpid
				break;
			}
		}
	}
	/* restore previous signal action & reset signal mask */
	if(sigaction(SIGINT, &saveintr, NULL) < 0)
		return -1;
	if(sigaction(SIGQUIT, &savequit, NULL) < 0)
		return -1;
	if(sigprocmask(SIG_SETMASK, &savemask, NULL) < 0)
		return -1;

	return status;
}

int main(void){
	int	status;

	if((status=mySystem("date")) < 0)
		printf("mySystem 1 error\n");
	pr_exit(status);

	if((status=mySystem("nosuchcommand")) < 0)
		printf("mySystem 2 error\n");
	pr_exit(status);

	if((status=mySystem("who; exit 44")) < 0)
		printf("mySystem 3 error\n");
	pr_exit(status);

	exit(0);
}

    注意,这里调用 _exit 而不是 exit 主要是为了防止任一标准 I/O 缓冲(这些缓冲会在 fork 中由父进程复制到子进程)在子进程中被冲洗。
    执行结果:
$ ./mySystem.out 
2017年 09月 11日 星期一 23:25:21 CST
normal terminaltion, exit status = 0              # 对于 date
sh: nosuchcommand: command not found
normal terminaltion, exit status = 127            # 对于无此种命令
lei      tty1         2017-04-27 04:07 (:0)
lei      pts/1        2017-07-12 23:10 (:0.0)
lei      pts/2        2017-08-19 08:02 (:0.0)
normal terminaltion, exit status = 44             # 对于 exit

    在一个设置用户 ID 程序中调用 system 将是一个安全性方面的漏洞。下面这个 tsys 程序说明了这一点。
#include <stdio.h>
#include <stdlib.h>

extern void pr_exit(int status);

int main(int argc, char *argv[]){
	int	status;
	if((status=system(argv[1])) < 0)
		printf("system error\n");
	pr_exit(status);
	exit(0);
}

    接下来是另一个程序 printuids,它打印实际用户 ID 和有效用户 ID。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main(void){
	printf("real uid = %d, effective uid = %d\n", getuid(), geteuid());
	exit(0);
}

    运行 tsys 程序,得到:
$ ./tsys.out ./printuids.out
real uid = 500, effective uid = 500            # 正常执行,无特权
normal terminaltion, exit status = 0
$
$ su                                           # 成为超级用户
密码:
# chown root tsys.out                          # 更改所有者
# chmod u+s tsys.out                           # 增加设置用户 ID
# ls -l tsys.out                               # 检验文件权限和所有者
-rwsr-xr-x. 1 root root 7412 9月  12 00:07 tsys.out
# 
# exit                                         # 退出超级用户 shell
$
$ ./tsys.out ./printuids.out 
real uid = 500, effective uid = 0              # 权限提升了(跟系统实现有关)!
normal terminaltion, exit status = 0

    由此可见,给予 tsys 程序的超级用户权限在 system 中执行了 fork 和 exec 之后仍被保持了下来(注意,有些实现通过更改 /bin/sh,当有效用户 ID 与实际用户 ID 不匹配时,会将有效用户 ID 设置为实际用户 ID,这样就避免了该安全漏洞)。
    如果一个进程正以特殊的权限(设置用户 ID 或设置组 ID)运行,它又想生成另一个进程执行另一个程序,则它应当直接使用 fork 和 exec,而且在 fork 之后、exec 之前要更改回普通权限。设置用户 ID 或设置组 ID 程序决不应调用 system 函数(这种警告的一个理由是:system 调用 shell 对命令字符串进行语法分析,而 shell 使用 IFS 变量作为其输入字段分隔符。早期的 shell 版本在被调用时不将此变量重置为普通字符集。这就允许一个恶意的用户在调用 system 前设置 IFS,造成 system 执行一个不同的程序)。

猜你喜欢

转载自aisxyz.iteye.com/blog/2392897