3.写安全的C程序 
一般有两方面的安全问题,在写程序时必须考虑: 
(1)确保自己建立的任何临时文件不含有机密数据,如果有机密数据,设置 
临时文件仅对自己可读/写.确保建立临时文件的目录仅对自己可写. 
(2)确保自己要运行的任何命令(通过system(),popen(),execlp(), 
execvp()运行的命令)的确是自己要运行的命令,而不是其它什么命 
令,尤其是自己的程序为SUID或SGID许可时要小心. 
第一方面比较简单,在程序开始前调用umask(077).若要使文件对其他人可 
读,可再调chmod(),也可用下述语名建立一个""不可见""的临时文件. 
creat(""/tmp/xxx"",0); 
file=open(""/tmp/xxx"",O_RDWR); 
unlink(""/tmp/xxx""); 
文件/tmp/xxx建立后,打开,然后断开链,但是分配给该文件的存储器并未删 
除,直到最终指向该文件的文件通道被关闭时才被删除.打开该文件的进程 
和它的任何子进程都可存取这个临时文件,而其它进程不能存取该文件,因 
为它在/tmp中的目录项已被unlink()删除. 
第二方面比较复杂而微妙,由于system(),popen(),execlp(),execvp()执行 
时,若不给出执行命令的全路径,就能""骗""用户的程序去执行不同的命令.因 
为系统子程序是根据PATH变量确定哪种顺序搜索哪些目录,以寻找指定的命 
令,这称为SUID陷井.最安全的办法是在调用system()前将有效UID改变成实 
际UID,另一种比较好的方法是以全路径名命令作为参数.execl(),execv(), 
execle(),execve()都要求全路径名作为参数.有关SUID陷井的另一方式是 
在程序中设置PATH,由于system()和popen()都启动shell,故可使用shell句 
法.如: 
system(""PATH=/bin:/usr/bin cd""); 
这样允许用户运行系统命令而不必知道要执行的命令在哪个目录中,但这种 
方法不能用于execlp(),execvp()中,因为它们不能启动shell执行调用序列 
传递的命令字符串. 
关于shell解释传递给system()和popen()的命令行的方式,有两个其它的问 
题: 
*shell使用IFS shell变量中的字符,将命令行分解成单词(通常这个 
shell变量中是空格,tab,换行),如IFS中是/,字符串/bin/ed被解释成单词 
bin,接下来是单词ed,从而引起命令行的曲解. 
再强调一次:在通过自己的程序运行另一个程序前,应将有效UID改为实际的 
UID,等另一个程序退出后,再将有效UID改回原来的有效UID. 
SUID/SGID程序指导准则 
(1)不要写SUID/SGID程序,大多数时候无此必要. 
(2)设置SGID许可,不要设置SUID许可.应独自建立一个新的小组. 
(3)不要用exec()执行任何程序.记住exec()也被system()和popen()调用. 
. 若要调用exec()(或system(),popen()),应事先用setgid(getgid()) 
将有效GID置加实际GID. 
. 若不能用setgid(),则调用system()或popen()时,应设置IFS: 
popen(""IFS=	
;export IFS;/bin/ls"",""r""); 
. 使用要执行的命令的全路径名. 
. 若不能使用全路径名,则应在命令前先设置PATH: 
popen(""IFS=	
;export IFS;PATH=/bin:/usr/bin;/bin/ls"",""r""); 
. 不要将用户规定的参数传给system()或popen();若无法避免则应检查 
变元字符串中是否有特殊的shell字符. 
. 若用户有个大程序,调用exec()执行许多其它程序,这种情况下不要将 
大程序设置为SGID许可.可以写一个(或多个)更小,更简单的SGID程序 
执行必须具有SGID许可的任务,然后由大程序执行这些小SGID程序. 
(4)若用户必须使用SUID而不是SGID,以相同的顺序记住(2),(3)项内容,并 
相应调整.不要设置root的SUID许可.选一个其它户头. 
(5)若用户想给予其他人执行自己的shell程序的许可,但又不想让他们能 
读该程序,可将程序设置为仅执行许可,并只能通过自己的shell程序来 
运行. 
编译,安装SUID/SGID程序时应按下面的方法 
(1)确保所有的SUID(SGID)程序是对于小组和其他用户都是不可写的,存取 
权限的限制低于4755(2755)将带来麻烦.只能更严格.4111(2111)将使 
其他人无法寻找程序中的安全漏洞. 
(2)警惕外来的编码和make/install方法 
. 某些make/install方法不加选择地建立SUID/SGID程序. 
. 检查违背上述指导原则的SUID/SGID许可的编码. 
. 检查makefile文件中可能建立SUID/SGID文件的命令. 
4.root程序的设计 
有若干个子程序可以从有效UID为0的进程中调用.许多前面提到的子程序, 
当从root进程中调用时,将完成和原来不同的处理.主要是忽略了许可权限的检 
查. 
由root用户运行的程序当然是root进程(SUID除外),因有效UID用于确定文 
件的存取权限,所以从具有root的程序中,调用fork()产生的进程,也是root进程. 
(1)setuid():从root进程调用setuid()时,其处理有所不同,setuid()将把有 
效的和实际的UID都置为指定的值.这个值可以是任何整型数.而对非root 
进程则仅能以实际UID或本进程原来有效的UID为变量值调用setuid(). 
(2)setgid():在系统进程中调用setgid()时,与setuid()类似,将实际和有效 
的GID都改变成其参数指定的值. 
* 调用以上两个子程序时,应当注意下面几点: 
. 调用一次setuid()(setgid())将同时设置有效和实际UID(GID),独立分 
别设置有效或实际UID(GID)固然很好,但无法做到这点. 
. setuid()(setgid())可将有效和实际UID(GID)设置成任何整型数,其数 
值不必一定与/etc/passwd(/etc/group)中用户(小组)相关联. 
. 一旦程序以一个用户的UID了setuid(),该程序就不再做为root运行,也 
不可能再获root特权. 
(3)chown():当root进程运行chown()时,chown()将不删除文件的SUID和/或 
SGID许可,但当非root进程运行chown()时,chown()将取消文件的SUID和/ 
或SGID许可. 
(4)chroot():改变进程对根目录的概念,调用chroot()后,进程就不能把当前 
工作目录改变到新的根目录以上的任一目录,所有以/开始的路径搜索,都 
从新的根目录开始. 
(5)mknod():用于建立一个文件,类似于creat(),差别是mknod()不返回所打开 
文件的文件描述符,并且能建立任何类型的文件(普通文件,特殊文件,目录 
文件).若从非root进程调用mknod()将执行失败,只有建立FIFO特别文件 
(有名管道文件)时例外,其它任何情况下,必须从root进程调用mknod().由 
于creat()仅能建立普通文件,mknod()是建立目录文件的唯一途径,因而仅 
有root能建立目录,这就是为什么mkdir命令具有SUID许可并属root所有. 
一般不从程序中调用mknod().通常用/etc/mknod命令建立特别设备文件而 
这些文件一般不能在使用着时建立和删除,mkdir命令用于建立目录.当用 
mknod()建立特别文件时,应当注意确从所建的特别文件不允许存取内存, 
磁盘,终端和其它设备. 
(6)unlink():用于删除文件.参数是要删除文件的路径名指针.当指定了目录 
时,必须从root进程调用unlink(),这是必须从root进程调用unlink()的唯 
一情况,这就是为什么rmdir命令具有root的SGID许可的原因. 
(7)mount(),umount():由root进程调用,分别用于安装和拆卸文件系统.这两 
个子程序也被mount和umount命令调用,其参数基本和命令的参数相同.调 
用mount(),需要给出一个特别文件和一个目录的指针,特别文件上的文件 
系统就将安装在该目录下,调用时还要给出一个标识选项,指定被安装的文 
件系统要被读/写(0)还是仅读(1).umount()的参数是要一个要拆卸的特别 
文件的指针.