C 在脚本路径前面加上`/usr/bin/env-i`是否会确保system()和popen()的使用安全?
我现在正在做一个代码审查,我被这个家伙编写的代码量吓坏了,他只是为了执行一个脚本(使用硬编码路径,没有输入参数)并从中读取输出。(顺便说一句,他有很多错误。) 我以前遇到过类似的问题,有人建议我手动执行pipe/fork/exec“更安全”。我意识到两个潜在问题:C 在脚本路径前面加上`/usr/bin/env-i`是否会确保system()和popen()的使用安全?,c,security,posix,popen,C,Security,Posix,Popen,我现在正在做一个代码审查,我被这个家伙编写的代码量吓坏了,他只是为了执行一个脚本(使用硬编码路径,没有输入参数)并从中读取输出。(顺便说一句,他有很多错误。) 我以前遇到过类似的问题,有人建议我手动执行pipe/fork/exec“更安全”。我意识到两个潜在问题: 当system()和popen()执行shell命令时,可能会将潜在有害的环境变量值滑入以这种方式执行的程序 另一个问题是,当命令是由用户输入构造的时候。我可以想象潜艇会做各种有害的事情等等 我想知道在这种情况下,建议使用popen(
system()
和popen()
执行shell命令时,可能会将潜在有害的环境变量值滑入以这种方式执行的程序popen()
是否合适。这将大大简化代码。第二点不是问题,因为没有用户输入。通过在执行脚本之前使用env-i
清理环境,应消除第一个问题:
FILE *fp = popen("/usr/bin/env -i /path/to/some/fancy/script.sh", "r");
/* ... */
我是否还遗漏了任何其他潜在问题,或者“手动”执行脚本是否仍然值得付出努力?从技术上讲,这不是您对如何安全调用
popen()
的问题的回答,而是对您应该问到的问题的回答:“如何制作更好的popen()
”
函数child\u spawn(argv、env、flags)
将设置与子进程通信的管道,并生成子进程。它将返回一个
struct child
,它保存用于通信的子pid和文件描述符
argv
是命令和参数的NULL
终止字符串数组,而env
是环境变量的NULL
终止字符串数组。如果env
为空,则子级将从父级继承环境
所以argv
应该有
const char* argv[] = {"/bin/ls", "-l", NULL};
const char **env = NULL;
or
const char *env[] =
{
"PATH=/bin:/usr/bin",
"HOME=/tmp",
"SHELL=/bin/sh",
NULL
};
和env
应具有以下格式
const char* argv[] = {"/bin/ls", "-l", NULL};
const char **env = NULL;
or
const char *env[] =
{
"PATH=/bin:/usr/bin",
"HOME=/tmp",
"SHELL=/bin/sh",
NULL
};
完成子进程后,child\u wait()
将关闭与子进程关联的文件描述符,并等待它退出
要使用child\u spawn()
替代popen()
,您可以这样称呼它:
struct child c = child_spawn(argv, NULL, CHILD_PIPE_STDOUT);
您现在可以从c->fd_out
中读取以获取child标准输出的内容
常量CHILD\u PIPE\u STDIN
、CHILD\u PIPE\u STDOUT
和CHILD\u PIPE\u STDERR
可以“或”-组合在一起,以便在c->fd\u in
、c->fd\u out
、c->fd err中具有有效的文件描述符
请注意,如果您使用child_PIPE_STDIN | child_PIPE_STDOUT
生成子对象,则在读写时存在死锁风险,除非您执行非阻塞io
函数my\u system()
是一个关于如何使用child\u spawn()实现更安全的system()
的示例
/*
我们必须定义GNU源才能访问“char**environ”`
*/
#定义GNU源
#包括
#包括
#包括
#包括
#包括
#包括
#包括
#包括
#包括
结构子对象
{
pid_t pid;
int fd_in;
int fd_out;
int fd_err;
};
静态空隙
如果有效,则关闭(int fd)
{
如果(fd!=-1)关闭(fd);
}
/*
关闭子通信的所有文件描述符
并等待子进程退出
从waitpid()返回状态值。
有关如何解释该值,请参见“man waitpid”
*/
int child_wait(结构child*c)
{
如果有效,则关闭(c->fd\U in);
如果有效,则关闭(c->fd\U out);
如果有效,则关闭(c->fd\U err);
智力状态;
pid\u t p=waitpid(c->pid,&status,0);
如果(p==0)
错误(1,errno,“waitpid()失败”);
返回状态;
}
int
dup_如果有效(int fd1,int fd2)
{
如果(fd1!=-1&&fd1!=fd2)
返回dup2(fd1,fd2);
返回fd2;
}
皮杜
子项生成fd(常量字符*常量argv[],常量字符*常量环境[],
整数输入,整数输出,整数错误)
{
fflush(stdout);
pid_t p=fork();
如果(p)
返回p;
/***********************
我们现在处于儿童时期
***********************/
/*
将文件描述符设置为预期值,
-1表示从父代继承
*/
if(dup_if_有效(in,0)=-1)
后进先出;
if(dup_if_有效(out,1)=-1)
后进先出;
如果(dup_如果_有效(错误,2)=-1)
后进先出;
/*
关闭所有不需要的文件描述符
这将释放资源并保持文件和套接字属于
父项打开的时间比需要的时间长
在*BSD上,我们可以称之为“closefrom(3)”,但这可能不存在
所以我们循环所有可能的文件描述符编号。
更好的解决方案是查看`/proc/self/fs`
*/
int max\u fd=sysconf(\u SC\u OPEN\u max);
对于(int-fd=3;fd-Big-topic)。您的建议在某种程度上会有所帮助,但您需要提供一些环境变量(如路径),因此您突然陷入了困境。system()
和popen()
应该被烧掉,作为对过去编程罪恶之神的祭品。试图让它们安全是很困难的。不管你怎么努力,你都会失败。创建自己的安全版本的popen()要容易得多
从头开始在管道/fork/exec.Oh上,当你走fork/exec路线时,千万不要把你的代码和fork/exec混在一起。总是用通用的实用程序函数来包装它们。env-i/path/to/script.sh
比/path/to/script.sh更安全。但是这家伙写的代码可能更安全。还有“安全程度如何?”取决于脚本的功能及其所需的环境。例如,如果脚本没有收到来自命令行的输入,但确实收到来自计算机上某个文件的用户输入,或者如果脚本不可信,则您仍有顾虑。