41

“普通人不想自由。他只是想安全。” - HL 门肯

我正在尝试编写非常安全的 C。下面我列出了我使用的一些技术,并询问它们是否像我认为的那样安全。请不要犹豫,将我的代码/成见撕成碎片。任何能找到最微不足道的漏洞或教给我新想法的答案都将受到高度重视

从流中读取:

根据GNU C Programming Tutorial getline:

getline 函数将通过 realloc 函数根据需要自动扩大内存块,因此永远不会出现空间短缺——这是 getline 如此安全的原因之一。[..] 请注意,无论输入多长,getline 都可以安全地处理您的输入行。

我假设 getline在所有输入下都应该防止从流中读取时发生缓冲区溢出。

  • 我的假设正确吗?是否存在可能导致漏洞利用的输入和/或分配方案?例如,如果流中的第一个字符是一些奇怪的控制字符,可能是 0x08 BACKSPACE (ctl-H)。
  • 有没有做任何工作来从数学上证明 getline 是安全的?

Malloc 在失败时返回 Null:

如果 malloc 遇到错误 malloc 返回一个 NULL 指针。这带来了安全风险,因为仍然可以将指针算术应用于 NULL (0x0) 指针,因此维基百科建议

/* Allocate space for an array with ten elements of type int. */
int *ptr = (int*)malloc(10 * sizeof (int));
if (ptr == NULL) {
    /* Memory could not be allocated, the program should handle 
       the error here as appropriate. */
} 

安全 sscanf:

使用sscanf时,我养成了将要提取的字符串大小分配给输入字符串的大小的习惯,希望避免出现溢出的可能性。例如:

const char *inputStr = "a01234b4567c";
const char *formatStr = "a%[0-9]b%[0-9]c":
char *str1[strlen(inputStr)];
char *str2[strlen(inputStr)];

sscanf(inputStr, formatStr, str1, str2);

因为 str1 和 str2 是 inputStr 的大小,并且不能从 inputStr 读取超过 strlen(inputStr) 的字符,所以考虑到 inputStr 的所有可能值导致缓冲区溢出,这似乎是不可能的?

  • 我对么?有没有我没有想到的奇怪的角落案例?
  • 有没有更好的方法来写这个?已经解决的库?

一般的问题:

虽然我已经发布了大量问题,但我不希望任何人都能回答所有问题。这些问题更像是我正在寻找的各种答案的指南。我真的很想学习安全的 C 思维方式。

  • 还有哪些其他安全的 C 习惯用法?
  • 我需要经常检查哪些极端情况?
  • 如何编写单元测试来强制执行这些规则?
  • 如何以可测试性或可证明正确的方式强制执行约束?
  • 任何推荐的 C 静态/动态分析技术或工具?
  • 您遵循哪些安全的 C 实践,您如何向自己和他人证明它们的合理性?

资源:

许多资源都是从答案中借来的。

4

7 回答 7

7

我认为您的 sscanf 示例是错误的。以这种方式使用时仍然会溢出。

试试这个,它指定要读取的最大字节数:

void main(int argc, char **argv)
{
  char buf[256];
  sscanf(argv[0], "%255s", &buf);
}

查看这篇关于防止缓冲区溢出的 IBM 开发文章。

在测试方面,我会编写一个程序来生成随机长度的随机字符串并将它们提供给你的程序,并确保它们得到适当的处理。

于 2010-01-05T18:35:45.253 回答
4

David Wheeler 的优秀安全编码网站是一个很好的起点。

他的免费在线书籍“ Secure Programming for Linux and Unix HOWTO ”是一个定期更新的优秀资源。

您可能还想看看他出色的静态分析器FlawFinder以获得更多提示。但请记住,没有任何自动化工具可以替代一双经验丰富的好眼睛,或者正如大卫所说的那样。

任何静态分析工具,例如 Flawfinder,都只是一个工具。没有任何工具可以代替人类的思想!简而言之,“有工具的傻瓜还是傻瓜”。认为分析工具(如漏洞查找器)可以替代安全培训和知识是错误的

我个人使用大卫的资源已有好几年了,发现它们非常棒。

于 2010-01-05T18:41:33.073 回答
4
  1. 从流中读取

    “将根据需要自动扩大内存块”这一事实getline()意味着这可以用作拒绝服务攻击,因为生成一个太长的输入将耗尽可用内存是微不足道的进程(或更糟的是,系统!)。一旦出现内存不足的情况,其他漏洞也可能会发挥作用。低/无内存中的代码行为很少很好,而且很难预测。恕我直言,为所有内容设置合理的上限更安全,尤其是在对安全敏感的应用程序中。

    此外(正如您通过提到特殊字符所预期的那样),getline()只给您一个缓冲区;它不对缓冲区的内容做任何保证(因为安全性完全取决于应用程序)。因此,清理输入仍然是处理和验证用户数据的重要部分。

  2. 扫描仪

    我倾向于使用正则表达式库,并且为用户数据定义了非常狭窄的正则表达式,而不是使用sscanf. 这样,您可以在输入时执行大量验证。

  3. 普通的留言

    • 可以使用模糊工具生成随机输入(有效和无效),可用于测试您的输入处理
    • 缓冲区管理至关重要:缓冲区溢出、下溢、内存不足
    • 竞争条件可以在其他安全代码中被利用
    • 可以操纵二进制文件以将无效值或过大的值注入标头中,因此文件格式代码必须坚如磐石,并且不假定二进制数据是有效的
    • 临时文件通常是安全问题的根源,必须小心管理
    • 代码注入可用于将系统或运行时库调用替换为恶意版本
    • 插件为攻击提供了巨大的载体
    • 作为一般原则,我建议有明确定义的接口,其中用户数据(或来自应用程序外部的任何数据)在被处理、清理和验证之前被假定为无效和敌对,并且是用户数据进入应用程序的唯一方式
于 2010-01-09T02:01:14.823 回答
1

示例
博客的不安全编程以及一些答案

于 2010-01-05T18:46:56.537 回答
1

Yannick Moy 在博士期间为 C 开发了一个 Hoare/Floyd 最弱前置条件系统,并将其应用于 CERT 管理的字符串库。他发现了许多错误(参见他的回忆录第 197 页)。好消息是图书馆现在对他的工作来说更安全了。

于 2010-01-05T18:49:08.343 回答
1

您还可以在此处查看 Les Hatton 的网站和他的书Safer C,您可以从亚马逊获得。

于 2010-01-06T09:54:02.387 回答
0

不要gets()用于输入,使用fgets(). 要使用fgets(),如果您的缓冲区是自动分配的(即“在堆栈上”),请使用以下成语:

char buf[N];
...
if (fgets(buf, sizeof buf, fp) != NULL)

如果您决定更改buf. 我更喜欢这种形式:

#define N whatever
char buf[N];
if (fgets(buf, N, fp) != NULL)

因为第一种形式用于buf确定第二个参数,并且更清晰。


检查 的返回值fclose()


于 2010-01-06T10:04:32.803 回答