UNIX环境编程学习笔记(22)——进程管理之system 函数执行命令行字符串

摘要:
Lienhua342014-10-15ISOC定义了在程序中执行命令字符串的系统函数。系统函数调用fork函数来创建子进程,然后子进程调用“/bin/sh-ccmdstring”来执行命令行参数cmdstring。执行此命令后,它将返回到调用进程。在调用系统函数期间,SIGCHLD信号将暂时挂起,SIGINT和SIGQUIT信号将被忽略。此功能可用于确定操作系统是否支持系统功能。UNIX系统始终支持系统功能。以下程序使用系统函数执行第一个命令行参数。这是系统功能的安全漏洞。

lienhua34
2014-10-15

ISO C 定义了 system 函数,用于在程序中执行一个命令字符串。其声明如下,

#include <stdlib.h>

int system(const char *cmdstring);

system 函数在其实现中调用了 fork、exec 和 waitpid 函数。system 函数调用 fork 函数创建子进程,然后由子进程调用’/bin/sh -c cmdstring’ 来执行命令行参数 cmdstring,此命令执行完后便返回调用的进程。在调用system 函数期间 SIGCHLD 信号会被暂时搁置,SIGINT 和 SIGQUIT 信号则会被忽略。

system 函数的返回值有点复杂,有下面几种情况,

1. 如果 cmdstring 是空指针,则仅当命令处理程序处理程序可用时,返回非 0 值。这一特征可用于确定一个操作系统是否支持 system 函数。UNIX 系统总是支持 system 函数的。

2. 如果 fork 失败或者 waitpid 返回除 EINTR 之外的出错,则 system返回 -1,而且 errno 中设置错误类型值。

3. 如果 exec 失败(表示不能执行 shell),则其返回值相当于 shell 执行了 exit(127) 后的终止状态。

4. 如果所有三个函数(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函数进行了所需的各种出错处理,以及各种信号处理。但是,system 函数也有其致命弱点:在设置用户 ID 程序中使用 system 函数会存在安全性漏洞。下面,我们来看个例子。


下面程序使用 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);
}

下面提供一个打印进程实际用户 ID 和有效用户 ID 的程序,将该程序编译为可执行文件 printuids。

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
int
main(void)
{
    printf("real uid=%d, effective uid=%d
", getuid(), geteuid());
    exit(0);
}

运用这两个可执行文件,我们执行下面的操作,

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)。而调用 system 函数执行 printuids 文件的子进程继承了这个有效用户 ID,于是子进程就拥有了很大的权限,可以执行任何可能会造成致命伤害的程序命令。这就是 system 函数存在的安全性漏洞。

如果一个进程以特殊权限(设置用户 ID 或设置组 ID)运行,它又想生成另一个进程执行另一个程序,则应当直接使用 fork 和 exec,而且在fork 之后、exec 之前要改回到普通权限。设置用户 ID 或设置组 ID 程序决不应调用 system 函数。

(done)

免责声明:文章转载自《UNIX环境编程学习笔记(22)——进程管理之system 函数执行命令行字符串》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇AssetPostprocessor.OnPreprocessModelVim 命令整理下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

vim recording功能介绍

使用vim时无意间触碰到q键,左下角出现“recording”这个标识,觉得好奇,遂在网上查了一下,然后这是vim的一个强大功能。他可以录制一个宏(Macro),在开始记录后,会记录你所有的键盘输入,包括在insert模式下的输入、正常模式下使用的各种命令等。 具体使用: 第一步:在正常模式下(非insert模式、非visual模式)按下q键盘 第二步:选...

[201020] Manjaro(KDE桌面环境)小白向完全安装教程(附Linux简要介绍)

本文并非网上常见的安装试错文,这一点还请读者大可放心。 本文力求做到手把手教学,尽量为系统安装过程中每个步骤给出截图并做出必要的说明。不过即便如此也难免有疏漏之处,到时还请读者自行咨询搜索引擎。在Linux的使用过程中一定会遇到各种各样的问题,搜索引擎永远是你最好的伴侣。 基础知识 不需这些知识,想要直接按步骤安装系统的读者可以直接跳过本节。 开始教程前首...

用c51命令行编译HEX单片机文件

刚开始学STC51单片机的开发,用Keil uVision4进行编辑和编译。Keil的IDE是使用工程方式组织,对于小弟初学并不是很方便。 每次做个试验都要建立一个工程,而且会出现一堆文件。 虽然电脑还可以,感觉启动IED比较慢 Keil的编辑器对中文的支持并不太好 由于改动并不一定在已打开IED的情况比较多,每次都必须打开Keil编译,郁闷的很。 最最...

UNIX环境高级编程 第一章

代码笔记,仅供自己学习使用。 下面是通过调用调用dirent的系统库实现,查看目录下内容的模块 #include "apue.h"#include <dirent.h> int main(int argc, char *argv[]){ DIR *dp; struct dirent *dirp;...

adb remount 失败:remount failed: Operation not permitted

adb remount 失败:remount failed: Operation not permitted     关于ADB的使用,这里再说明下:经常使用命令 adb shell - 登录设备shell,后面也可直接跟执行命令。如:adb shell rm -r /system/sd/app adb pull - 从手机中下载文件到电脑上。如:ad...

Java与flash的TCP通讯

flash代码: var mySocket:XMLSocket = new XMLSocket(); //本地地址,端口5000 mySocket.connect(”localhost”, 5000); //事件 mySocket.onConnect = function(myStatus) { if (myStatus) { trace(”连接成功!”...