面向开发者的 Oracle® Solaris 11 安全性指南

退出打印视图

更新时间: 2014 年 7 月
 
 

使用 C 函数时的安全注意事项

下表中列出了使用 C 库函数时必要的安全注意事项。每个函数可分为以下类别之一:

无限制

所有函数的缺省情况。

谨慎使用

为确保安全使用,需要特别小心。

避免

避免使用这些函数。

不安全

请勿使用这些函数。

表 G-1  使用 C 函数时的安全注意事项
函数
格式
类别
注释
替代方法
access()
int access(const char *path, in mode)
避免
收到此函数提供的信息时,这些信息便已经过时。使用 access() 函数时,如果后跟 open() 函数,则会导致无法解决的争用情况。
使用目标用户的权限打开文件。
bcopy()
void bcopy(const void *s1, void *s2, size_t n) void *memcpy(void *s1, const void *s2, size_t n)
谨慎使用
即使长度已知,也不应当用于复制字符串。 应改用 strlcpy() 函数。
NA
catopen()
nl_catd catopen(const char *name, int oflag)
谨慎使用
库和程序不应当针对用户提供的路径名调用 catopen() 函数。可以利用用户提供的消息目录轻松中断特权代码。
NA
cftime()
int cftime(char *s, char *format, const time_t *clock)
int ascftime(char *s, const char *format, const struct tm *timeptr)
不安全
这些函数不检查输出缓冲区是否有边界,而且可以通过 CFTIME 环境变量导入用户数据。
strftime(buf, sizeof (buf), fmt, &tm)
chdir()
int chdir(const char *path)
谨慎使用
易于产生路径名争用情况。请勿在多线程程序中使用。
要避免争用情况,请在目录打开且已使用 fstat() 函数检查过属性后,再使用 fchdir() 函数。Oracle Solaris 11 已添加了针对 openat()linkat()mkdirat()mkfifoat()readlinkat()symlinkat() 等文件运行的 POSIX 2008 *at() 版本的系统调用。这些调用采用目录的文件描述符作为第一个参数,该参数要用作工作目录的相对路径。在一个线程调用 chdir(),另一个线程正在调用 open()unlink() 等函数时,这些方法可避免产生争用情况。
chmod()
int chmod(const char *path, mode_t mode)
int fchmodat(int fd, const char *path, mode_t mode, int flag)
int chown(const char *path, uid_t owner, gid_t group)
int lchown(const char *path, uid_t owner, gid_t group)
避免
这些函数在路径名上运行,易于产生争用情况。通常,程序不需要调用 chown()chmod(),但接受当前的 UID(在打开文件之前转换回去)和 umask。请注意,chmod() 始终会打开符号链接。
如果必须更改文件的属性,请安全地打开文件并对生成的文件描述符使用 fchown()fchmod() 函数。
chroot()
int chroot(const char *path)
谨慎使用
调用 chroot() 函数后,该函数的调用环境提供的保护较少。程序可以很容易转义。不要在这样的环境中运行特权程序,而且在运行 chroot() 函数后将目录更改到新的根下面的某个点。
在非全局区域中运行。
copylist()
char *copylist(const char *filenm, off_t *szptr)
DBM *dbm_open(const char *file, int open_flags, mode_t file_mode)
int dbminit(char *file)
谨慎使用
用于打开文件,而且仅应当用于打开已知安全的路径名。
NA
dlopen()
void *dlopen(const char *pathname, int mode)
谨慎使用
传递到 dlopen() 函数的参数只应当是非限定路径名,这些路径名随后可以使用运行时链接程序的路径或并非从用户输入(包括从 argv[0])派生的完整路径名找到。无法安全打开用户提供的共享对象。对象的 _init() 函数在 dlopen() 返回之前执行。
NA
drand48()
double drand48(void)
double erand48(unsigned short xi[3])
long lrand48(void)long mrand48(void)
long jrand48(unsigned short xi[3])
long nrand48(unsigned short xi[3])
void srand48(long seedval)
int rand(void)
int rand_r(unsigned int *seed)
void srand(unsigned int seed)
long random(void)
避免
这些弱随机数生成器无助于提高安全性。要生成随机数以实现安全性,请使用从 Solaris 9 开始提供的 /dev/urandom
NA
dup()
int dup(int fildes)
int dup2(int fildes, int fildes2)
谨慎使用
dup()dup2() 函数可返回清除了 FD_CLOEXEC 的文件描述符,因此在程序调用 exec() 时,这两个函数可能会泄漏。这些函数返回以设置该标志后,旧代码很快进行 fcntl() 调用。但在多线程代码中(包括了一些程序,它们本身仅运行一个线程但可能与运行其他线程的库链接),此时会出现一个窗口供其他线程争用。对 fcntl 的 F_DUPFD_CLOEXECF_DUP2FD_CLOEXE 调用(在 Oracle Solaris 11 和更高版本中提供)将重复和标志设置组合为一个原子操作,因此不存在争用情况。
fcntl(fildes, F_DUPFD_CLOEXEC, 0)
fcntl(fildes, F_DUP2FD_CLOEXEC, fildes2)
execl()
int execl(const char *path, const char *arg0, ..., const char *argn, NULL)
int execv(const char *path, char *const argv[])
int execve(const char *path, char *const argv[], char *const envp[])
谨慎使用
请在执行新程序之前确保环境无危险而且已关闭不必要的文件描述符。
NA
execvp()
int execvp(const char *file, const char *argv[])
int execlp(const char *file, const char *arg0, ..., const char *argn, NULL)
避免
十分危险,不能在库或特权命令和守护进程中使用,因为它们通过在 PATH 环境变量中搜索目录来查找可执行文件,而这完全由用户控制。大多数其他程序都应避免这种情况。
使用 execl()execv()execve() 函数。
fattach()
int fattach(int filedes, const char *path)
谨慎使用
检查 open() 函数后面的文件描述符(使用 fstat()),而不是 open() 函数前面的路径名。
NA
fchmod()
int fchmod(int filedes, mode_t mode)
int fchown(int filedes, uid_t owner, gid_t group)
无限制
chmod()chown() 函数的首选替代方法。
NA
fdopen()
FILE *fdopen(int filedes, const char *mode)
无限制
fopen() 的替代方法
NA
fopen()
FILE *fopen(const char *path, const char *mode)
FILE *freopen(const char *path, const char *mode, FILE *stream)
谨慎使用
使用 fopen() 无法安全地创建文件。但是,确认存在路径名之后(即调用 mkstemp() 函数之后),可以用 fopen 函数来打开路径名。在另外一些情况下,应使用后跟 fdopen()open() 安全调用。
使用后跟 fdopen()open(),例如:
FILE *fp; int fd;
fd = open(path,
O_CREAT|O_EXCL|O_WRONLY,
0600); 
if (fd < 0){
......  }
fp = fdopen(fd, "w");
fstat()
int fstat(int filedes, struct stat *buf)
无限制
用于检查所打开的文件是否为预计要打开的文件。
NA
ftw()
int ftw(const char *path, int (*fn)(), int depth)
int nftw(const char *path, int (*fn)(), int depth, int flags)
谨慎使用
打开符号链接并跨过挂载点。
使用设置了相应标志(FTW_PHYSFTW_MOUNT 的组合)的 nftw。
getenv()
char *getenv(const char *name)
谨慎使用
环境完全是由用户指定的。如果可能,避免在库中使用 getenv()getenv() 返回的字符串最长可以为 NCARGS 个字节(当前,32 位环境是 1MB)。不应信任从环境变量派生的路径名。不应将其用作任何 *open() 函数(包括 catopen()dlopen())的输入。
NA
getlogin()
char *getlogin(void)
避免
getlogin() 返回的值不可靠。这只是用户名的提示。
NA
getpass()
char *getpass(const char *prompt)
避免
仅使用输入的前 8 个字节。避免在新代码中使用。
使用 getpassphrase() 函数。
gets()
char *gets(char *s)
不安全
在存储输入时,此函数不检查是否有范围。无法安全地使用此函数。
使用 fgets(buf, sizeof (buf), stdin)getline(buf, bufsize, stdin)
getline(buf, bufsize, stdin) 是 Oracle Solaris 11 中提供的新函数。
kvm_open()
kvm_t *kvm_open(char *namelist, char *corefile, char *swapfile, int flag, char *errstr)
int nlist(const char *filename, struct nlist *nl)
避免
如果需要内核信息,请写入适合的 kstat 或其他接口。如果接受用户指定的 namelist 参数,请确保在使用之前撤销特权。否则,专门构造的 namelist 可用于读取内核中的随机部分,而这可能会泄漏敏感数据。
NA
lstat()
int lstat(const char *path, struct stat *buf)
int stat(const char *path, struct stat *buf)
int fstatat(int fildes, const char *path, struct stat *buf, int flag)
谨慎使用
不要使用这些函数检查文件是否存在。后跟 open()lstat()stat()fstatat() 函数存在固有的争用情况。
如果目的是创建不存在的文件,请使用
open(file, O_CREAT|O_EXCL, mode)
如果目的是读取文件,则打开进行读取。如果目的是在读取之前确保文件属性正确,则使用
fd = open(file, O_RDONLY); fstat(fd, &statbuf);
如果无法信任路径名,则向打开标志添加 O_NONBLOCK。这将避免在打开设备时挂起应用程序。
mkdir()
int mkdir(const char *path, mode_t mode)
int mkdirat(int fd, const char *path, mode_t mode)
int mknod(const char *path, mode_t mode, dev_t dev)
int mknodat(int fd, const char *path, mode_t mode, dev_t dev)
谨慎使用
请务必留意所用的路径。这些函数不会打开最后一个组成部分的符号链接,因此它们相对安全。
NA
mkstemp()
int mkstemp(char *template)
无限制
安全的临时文件创建函数。
NA
mktemp()
char *mktemp(char *template)
避免
生成一个临时文件名,但并不能保证生成的路径名能安全地使用,因为在 mktemp() 中的检查和应用程序对 open() 的后续调用之间存在争用情况。
使用 mkstemp() 创建文件,使用 mkdtemp() 创建目录。
open()
int open(const char *path, int oflag, /* mode_t mode */...)
int creat(const char *path, mode_t mode)
谨慎使用
为从特权程序读取而打开时,请放弃特权或将有效的 UID 设置为真实的 UID,从而确保以用户身份打开文件。在任何情况下,程序都不应根据文件的所有权和模式实现自身的访问控制。同样,创建文件时,不要先打开文件,再对文件使用 chown()
为进行写入而打开时,程序可能会被诱使通过打开符号链接或硬链接来打开错误的文件。要避免此问题,要么使用 O_NOFOLLOWO_NOLINKS 标志,要么使用 O_CREAT|O_EXCL 来确保创建新文件而不是打开现有文件。
打开文件时,考虑 exec() 调用期间是否应使文件描述符一直保持打开状态。在 Oracle Solaris 11 中,可以在打开的标志中指定 O_CLOEXEC,以便以原子方式将文件描述符标记为将由可执行的系统调用关闭。在较旧版本中,必须使用带 FD_CLOEXEC 标志的 fcntl() 函数,这就使得当其他线程在 open()fcntl() 调用之间派生和执行时,多线程程序中会存在争用情况。
NA
popen()
FILE *popen(const char *command, const char *mode)
int p2open(const char *cmd, FILE *fp[2])
int system(const char *string)
避免
这三个库调用始终涉及到 shell,该 shell 又会涉及到 PATH、IFS 和其他环境变量以及对特殊字符的解释。有关更多详细信息,请参阅《CERT C Coding Recommendation ENV04-C》。
使用 posix_spawn() 执行其他程序,必要时使用 with waitpid()pipe()
printf()
int printf(const char *format, ...)
int vprintf(const char *format, va_list ap)
int fprintf(FILE *stream, const char *format, ...)
int vfprintf(FILE *stream, const char *format, va_list ap)
int snprintf(char *s, size_t n, const char *format, ...)
int vsnprintf(char *s, size_t n, const char *format, va_list ap)
int wprintf(const wchar_t *format, ...)
int vwprintf(const wchar_t format, va_list arg)
int fwprintf(FILE *stream, const wchar_t *format, ...)
int vfwprintf(FILE *stream, const wchar_t *format, va_list arg)
int swprintf(wchar_t *s, size_t n, const wchar_t *format, ...)
int vswprintf(wchar_t *s, size_t n, const wchar_t *format, va_list arg)
int asprintf(char **ret, const char *format, ...)
谨慎使用
用户指定的格式字符串有风险。如果格式字符串来自消息目录,请验证 NLSPATH 处理和 catopen()catget() 的使用。C 库试图通过忽略 set-uid 和 set-gid 应用程序的 NLSPATH 设置来确保安全。
snprintf()vsnprintf() 函数会返回已写入缓冲区的字符数(如果缓冲区足够大)。无法在 p += snprintf(p, lenp, "...") 等结构中使用此值,因为 p 可能随后指向 p+lenp 以外的位置。
scanf()
int scanf(const char *format, ...)
int vscanf(const char *format, va_list arg)
int fscanf(FILE *stream, const char *format, ...)
int vfscanf(FILE *stream, const char *format, va_list arg)
int sscanf(const char *s, const char *format, ...)
int vsscanf(const char *s, const char *format, va_list arg)
谨慎使用
扫描字符串时,请确保指定的格式包括最长缓冲区长度。使用 scanf("%10s", p) 来限制 scanf() 最多读取 10 个字符。请注意,相应缓冲区必须至少为 11 个字节,以便为终止 NULL 字符留出空间。
NA
sprintf()
int sprintf(char *s, const char *fmt, ...)
int vsprintf(char *s, const char *fmt, va_list ap)
避免
通常导致缓冲区溢出。如果必须使用这些函数,请确保 fmt 参数不能由用户控制,而且可以确信这些参数不会溢出目标缓冲区。
使用 snprintf()vsnprintf()asprintf()asprintf() 是 Oracle Solaris 11 中提供的新函数。
strcat()
char *strcat(char *s1, const char *s2)
char *strcpy(char *s1, const char *s2)
避免
无法将这些函数限制为最大缓冲区大小。但是,可以在调用 strcat 或 strcpy 之前计算所需的空间大小。使用这些函数时,始终会强行要求审核者遵守逻辑,而且会防止自动扫描源代码中有无漏洞。
strlcat(dst, src, dstsize)
strlcpy(dst, src, dstsize)
strccpy()
char *strccpy(char *output, const char *input)
char *strcadd(char *output, const char *input)
char *streadd(char *output, const char *input)
char *strecpy(char *output, const char *input, const char *exceptions)
char *strtrns(const char *string, const char *old, const char *new, char *result)
谨慎使用
strcpy() 的问题类似。有关正确的使用方法,请参见 strcpy 和 strccpy 手册页。
NA
strlcpy()
size_t strlcpy(char *dst, const char *src, size_t dstsize)
size_t strlcat(char *dst, const char *src, size_t dstsize)
无限制
strcpy()strcat() 函数的首选替代方法。在 Solaris 8 及更高版本中提供。为了方便审核代码,应当与常数(而非计算的大小参数)配合使用。
NA
strncat()
char *strncat(char *s1, const char *s2, size_t n)
char *strncpy(char *s1, const char *s2, size_t n)
谨慎使用
strncpy() 函数不保证目标缓冲区以空字符结尾。strncat() 函数很难使用,因为它需要计算目标缓冲区的合适大小。
事实上,空间不足时,strncpy() 函数不以空字符结尾,还有个副作用就是,如果还有空间,该函数将添加 NULL 个字节,使其成为有用的函数,使其成为用于更新磁盘上结构的函数。例如,wtmpx 文件常常使用 write(fd, w, sizeof (*w)) 来生成;
strlcpy(dst, src, dstsize)
strlcat(dst, src, dstsize)
syslog()
void syslog(int priority, const char *message, ...)
void vsyslog(int priority, const char *message, va_list ap)
谨慎使用
用户指定的格式字符串有风险。检验 NLSPAT 的处理和 catopen()catget() 的使用。
NA
tempnam()
char *tempnam(const char *dir, const char *pfx)
char *tmpnam(char *s)
char *tmpnam_r(char *s)
避免
这些函数不适用于生成不可预测的文件名。在文件名的生成和使用(例如 open())之间存在争用情况。
mkstem()
tmpfile()
FILE *tmpfile(void)
谨慎使用
使用 mkstemp(),因此可安全使用。但是,由于此函数会改变 umask,因此不具有多线程安全性。
NA
truncate()
int truncate(const char *path, off_t length)
避免
此函数易于产生路径名争用情况。
在安全的 open() 后面使用 ftruncate()
umask()
mode_t umask(mode_t cmask)
谨慎使用
不应在库或应用程序中使用;应使用用户的 umask。此外,它也不具有多线程安全性。
NA
utmpname()
int utmpname(const char *file) int utmpxname(const char *file)
避免
使用缺省的 utmputmpx 文件。
NA