C语言中危险函数
在 C 语言中,有一些函数因为存在缓冲区溢出、格式字符串漏洞等问题,被认为是“危险函数”。这些函数通常不能有效地检查输入的长度或数据,容易导致程序崩溃、内存泄漏或安全漏洞。常见的危险函数有:
1. gets()
gets()
函数用于从标准输入读取字符串,但它没有限制输入的长度。这就导致了缓冲区溢出问题。如果用户输入的字符串超出了缓冲区的大小,程序会覆盖其他内存区域,可能导致未定义行为或攻击者执行恶意代码。
替代函数:fgets()
,它允许指定读取的最大字符数,从而防止溢出。
2. scanf()
scanf()
在读取用户输入时,如果没有正确指定格式控制符的长度限制,也有可能导致缓冲区溢出。例如,scanf("%s", buffer)
会继续读取直到遇到空白字符,而不检查 buffer
的大小。
替代方法:使用限定输入大小的格式,如 scanf("%99s", buffer)
,或者使用 fgets()
等安全输入函数。
3. strcpy()
和 strcat()
strcpy()
和 strcat()
分别用于字符串拷贝和连接操作,但它们不会检查目标缓冲区的大小,容易造成缓冲区溢出,尤其是当输入的字符串长度超过目标数组的容量时。
替代函数:
strncpy()
:限制拷贝的字符数(但仍需小心,确保目标数组有足够的空间)。strncat()
:限制连接的字符数。- 推荐使用
snprintf()
或strlcpy()
(某些库提供)。
4. sprintf()
和 vsprintf()
printf()
系列的函数在输出到字符数组时,如果没有检查目标数组的大小,也可能导致缓冲区溢出,特别是当格式字符串非常复杂或者输入数据太大时。
替代函数:
snprintf()
:可以指定最大输出长度,避免溢出。vsnprintf()
:用于格式化可变参数的版本。
5. memcpy()
和 memmove()
这两个函数用于内存拷贝操作,但它们不会检查目标缓冲区的大小,使用时很容易引发缓冲区溢出问题。尤其在拷贝的长度不确定时,使用不当会导致内存破坏。
替代方法:如果长度已知,确保不会超过目标缓冲区大小;否则使用带有边界检查的函数,如 memcpy_s()
(在某些平台上可用)。
6. strtok()
strtok()
用于字符串分割,但它会直接修改原始字符串,并返回指向分割后的子字符串的指针。这种操作可能破坏原始数据,也容易出现内存泄漏或访问非法内存。
替代方法:使用 strtok_r()
,它是线程安全的。
7. alloca()
alloca()
函数分配内存,并且分配的内存是在栈上,而不是堆上。虽然它的作用和 malloc()
类似,但因为栈空间有限,使用不当可能会导致栈溢出。此外,它没有 free()
函数释放内存,导致内存泄漏。
替代方法:尽量避免使用 alloca()
,可以使用 malloc()
配合 free()
来管理堆内存。
8. system()
system()
函数用于执行系统命令,它会把字符串传递给操作系统的命令行解释器。在接受用户输入时,恶意用户可能通过构造特定的命令,执行系统命令,从而导致安全漏洞。
替代方法:避免直接使用 system()
,可以使用更安全的替代方法,如 exec()
系列函数,或者通过更安全的方式调用外部程序。
9. vprintf()
和 vfprintf()
这些函数用于格式化输出,但如果格式化字符串不可靠(比如由用户提供),可能会导致格式字符串攻击。例如,攻击者通过控制格式字符串,可以导致程序泄露内存内容或执行任意代码。
替代方法:使用参数校验,确保格式字符串是固定的,或者使用 snprintf()
等安全函数。
10. longjmp()
和 setjmp()
setjmp()
和 longjmp()
用于在程序中实现非局部跳转(类似异常处理机制)。虽然它们本身并不直接导致缓冲区溢出或内存泄漏,但它们的使用可能会绕过正常的资源清理流程(如栈上的变量销毁),因此,如果使用不当,可能会导致资源泄露或程序状态不一致。
建议:尽量避免在代码中滥用 setjmp()
和 longjmp()
,并确保它们仅用于明确的控制流,避免跳过栈帧中重要的资源释放操作。
11. rand()
和 srand()
虽然 rand()
和 srand()
本身不会引发安全漏洞,但它们的伪随机性较差,生成的随机数序列很容易被预测。在密码学相关应用中,使用 rand()
生成的随机数不适合用于加密或安全相关的操作,因为它们不够随机,容易被攻击者预测。
替代方法:对于安全敏感的应用,应该使用更为安全的随机数生成方法,例如 random()
或 arc4random()
,或者在现代系统中使用 openssl
库提供的高质量随机数生成函数。
12. tmpnam()
和 tempnam()
tmpnam()
和 tempnam()
用于生成临时文件名,但它们有可能导致竞争条件(TOCTOU,时间一环条件)漏洞。尤其是在多线程环境或多个进程并发执行时,可能会导致文件被其他进程提前创建,从而被攻击者利用。
替代方法:使用更安全的 mkstemp()
或 tmpfile()
,这些函数会创建并返回一个临时文件,并且具备更好的安全性。
13. strchr()
和 strrchr()
strchr()
和 strrchr()
分别用于查找字符串中第一次和最后一次出现指定字符的位置。它们本身不是不安全的,但如果传入一个没有 \0
结尾的字符串,可能会导致越界访问。由于 C 语言字符串是基于空字符终止的,错误的字符串输入可能导致无法预料的行为。
建议:确保传入给 strchr()
和 strrchr()
的字符串是合法的以避免访问非法内存。
14. setvbuf()
和 setbuf()
setvbuf()
和 setbuf()
用于设置文件流的缓冲区大小和类型,但如果不小心使用,可能会导致缓冲区溢出或错误的缓冲区处理。例如,如果你没有足够的内存或缓冲区空间,可能会出现崩溃或资源泄漏。
建议:使用时小心指定合适的缓冲区大小,确保程序稳定。
15. sscanf()
sscanf()
是一种从字符串中读取格式化数据的函数,虽然它功能强大,但也很容易出错。没有正确限制输入的长度或格式时,sscanf()
可能会导致缓冲区溢出、内存泄漏或其他问题。
建议:使用时确保格式字符串正确,且传入的缓冲区有足够的空间。
16. vfork()
vfork()
用于创建新进程,与 fork()
类似,但它的行为不同,通常会导致父进程被暂停,直到子进程执行完毕。因为 vfork()
可能改变进程的状态,容易引发竞态条件或不一致的资源状态,从而导致难以调试的错误。
建议:在不需要特殊优化的情况下,尽量避免使用 vfork()
,改用 fork()
,并且确保正确管理资源。
17. strtol()
、strtoul()
虽然这些函数用于将字符串转换为长整型或无符号长整型,但如果输入字符串不符合预期格式,可能会导致未定义的行为,尤其是在字符串超出整数范围或包含非数字字符时。
建议:使用时确保输入字符串的格式正确,并检查转换后的返回值和错误码。
18. gethostbyname()
和 gethostbyaddr()
这些函数用于 DNS 查询,但它们已经被标记为过时(deprecated),并且存在潜在的安全风险,例如缓冲区溢出,尤其是在一些老旧的代码库中。
替代方法:使用 getaddrinfo()
,这是一个更现代、更安全的替代方案。
19. fseek()
和 ftell()
虽然 fseek()
和 ftell()
本身不是危险的函数,但在某些情况下,它们可能会导致对文件指针的错误操作,导致程序崩溃或不一致的文件状态。例如,在文件打开模式不正确时,可能会导致未定义的行为。
总结
以上这些“危险函数”通常是因为缺乏足够的输入验证或边界检查,在处理数据时容易引发缓冲区溢出、格式字符串漏洞等问题。现代编程推荐使用更安全的替代函数,如 fgets()
、snprintf()
、strncpy()
等,来确保程序的健壮性和安全性。此外,始终注意进行输入验证,避免直接信任用户输入,减少安全漏洞的风险。