C中的字符串处理实践

tyl*_*erl 22 c security string robustness

我正在开始一个普通C(c99)的新项目,该项目主要用于文本.由于外部项目的限制,这段代码必须非常简单和紧凑,由一个没有外部依赖的源代码文件或除libc和类似的无处不在的系统库之外的库组成.

根据这种理解,哪些最佳实践,陷阱,技巧或其他技术可以帮助使项目的字符串处理更加健壮和安全?

R..*_*R.. 33

如果没有关于代码正在做什么的任何其他信息,我建议您设计所有的接口,如下所示:

size_t foobar(char *dest, size_t buf_size, /* operands here */)
Run Code Online (Sandbox Code Playgroud)

语义如下snprintf:

  • dest指向至少一个大小的缓冲区buf_size.
  • 如果buf_size为零,则可以接受null/invalid指针,dest并且不会写入任何内容.
  • 如果buf_size非零,dest始终以 null结尾.
  • 每个函数foobar返回完整的非截断输出的长度; 如果buf_size小于或等于返回值,则截断输出.

这样,当调用者可以容易地知道所需的目标缓冲区大小时,可以预先获得足够大的缓冲区.如果调用者不能轻易知道,它可以使用零参数调用该函数buf_size,或者使用"可能足够大"的缓冲区调用该函数,并且仅在空间不足时重试.

您也可以创建类似于GNU asprintf函数的此类调用的包装版本,但如果您希望代码尽可能灵活,我将避免在实际的字符串函数中进行任何分配.在调用者级别处理失败的可能性总是更容易,并且许多调用者可以通过使用在程序中更早获得的本地缓冲区或缓冲区来确保失败是不可能的,以便更大操作的成功或失败是原子的(这极大地简化了错误处理).


Ada*_*iss 10

来自长期嵌入式开发人员的一些想法,其中大部分内容都阐述了您对简单性的要求,而不是特定于C的:

  • 确定您需要哪些字符串处理功能,并尽可能减小该设置以最小化故障点.

  • 按照R.的建议定义一个在所有字符串处理程序中保持一致的清晰界面.严格,小但详细的规则允许您使用模式匹配作为调试工具:您可能会怀疑任何看起来与其他代码不同的代码.

  • 正如Bart van Ingen Schenau所说,跟踪缓冲区长度与字符串长度无关.如果您将始终使用文本,则可以安全地使用标准空字符来指示字符串结尾,但是您可以确保text + null适合缓冲区.

  • 确保所有字符串处理程序的一致行为,特别是缺少标准函数的情况:截断,空输入,空终止,填充.

  • 如果您绝对需要违反任何规则,请为此目的创建单独的功能并对其进行适当命名.换句话说,给每个函数一个明确的行为.所以你可能会使用str_copy_and_pad()一个总是用空值填充其目标的函数.

  • 尽可能使用安全的内置功能(例如 memmove()每个Jonathan Leffler)来完成繁重的工作.但要测试它们以确保它们正在做你认为他们正在做的事情!

  • 尽快检查错误.未检测到的缓冲区溢出可能导致"跳弹"错误,这些错误很难找到.

  • 每个功能编写测试以确保其满足合同.一定要覆盖边缘情况(关闭1,空/空字符串,源/目标重叠)这听起来很明显,但请确保您了解如何创建和检测缓冲区欠载/溢出,然后编写测试明确生成并检查这些问题.(我的质量保证人员可能因为听到我的指示而感到厌倦"不要只是测试以确保它有效;测试以确保它不会破坏.")

以下是一些对我有用的技巧:

  • 为内存管理例程创建包装器,在分配期间在缓冲区的任一端分配"fence bytes",并在取消分配时检查它们.您也可以在字符串处理程序中验证它们,可能是在设置了STR_DEBUG宏时. 警告:您需要彻底测试您的诊断程序,以免它们产生额外的故障点.

  • 创建一个封装缓冲区及其长度的数据结构.(如果你使用它们,它也可以包含fence字节.) 警告:你现在有一个非标准的数据结构,你的整个代码库必须管理,这可能意味着重大的重写(以及附加的故障点).

  • 让字符串处理程序验证其输入.如果函数禁止空指针,请明确检查它们.如果它需要有效的字符串(如strlen()应该)并且您知道缓冲区长度,请检查缓冲区是否包含空字符.换句话说,验证您可能对代码或数据做出的任何假设.

  • 先写下你的测试.这将有助于您理解每个函数的契约 - 正是它对调用者的期望,以及调用者对它的期望.你会发现自己正在考虑使用它的方式,它可能会破坏的方式,以及它必须处理的边缘情况.

非常感谢你提出这个问题!我希望更多的开发人员会考虑这些问题 - 特别是他们开始编码之前.祝您好运,并祝愿您拥有一款强大而成功的产品!

  • 上半场和最后一段+1.他们足够好,我在下半场拒绝判断.:-)测试空指针是我考虑的有害之一,虽然花哨的结构可以帮助你调试,但它们也使你的代码更难以使用和与其他代码集成.我会努力严格测试你的函数以满足他们的合同,然后你不需要任何进一步的运行时检查混乱. (2认同)

ism*_*ail 7

看看,strlcpystrlcat查看original paper详细信息.

  • 连接几乎总是一个不好的习惯用语(在安全性,性能和复杂性方面). (5认同)