Mac解决方案:“安全”替代“不安全”的C/C++标准库函数?

14

在Mac上,什么是最好的一站式“安全”C库解决方案? 我在“安全”/“不安全”上使用引号,因为对于某些标准库函数或其被认为改进的替代品的益处存在很多争议。

许多传统的标准C库函数(例如vfprintf)由于可能导致缓冲区溢出或其他安全问题而被认为是不安全的。

在Windows上,Microsoft C/C++编译器提供“_s”函数(例如vfprintf_s)作为标准库调用的更安全的替代方法。 这些函数不是即插即用的替代品,因为它们具有提供额外安全信息所需的不同签名(例如缓冲区长度)。 它们还提供其他功能,如无效格式字符串检测,不同的文件安全性等。 据我所知,这个实现在Mac上不可用。

苹果(或第三方)是否提供类似于OSX上GCC的东西?

特别是,我正在寻找至少以下函数的“安全”实现:fopen vfprintf vsprintf sprintf strncpy strcpy strcat

请注意:这个问题涉及到Mac。我不是在询问您对于微软实现的意见(除非它在Mac上可用)。虽然有些功能可能很容易自己编写,但并不是所有功能都是如此。我不是在问如何自己编写这些功能。我也不是在寻求如何使用STL类来完成这些功能的提示。我也不是在问如何关闭警告。我的特定需求非常具体。我正在尝试找到一个最佳实践的Mac API,它与传统的C库调用尽可能相似,并增加了安全性。当然,一个能够在Mac、Windows(和其他操作系统)上工作的可移植实现会更好。

10
使用这些所谓的“安全”函数并不是最佳实践。 - anon
5
为什么您不能使用安全的C++标准库等价函数?(或者为什么标题会说C/C++标准库函数,但您拒绝使用C++标准库?) - jalf
3
GCC已经对常规C标准库函数进行格式字符串验证。它使用特殊关键字实现,所以您甚至可以对自己的采用标准格式字符串的函数执行此操作。 - Stéphan Kochen
2
@Neil Butterworth: 为什么“安全”函数不是最佳实践呢?也许你可以提供一个解释的链接吗?在Mac上,最佳实践是什么呢? - jwfearn
2
@Shteef:非常感谢您的评论。我想知道启用GCC格式字符串验证的秘密关键字是什么?有链接吗? - jwfearn
显示剩余6条评论
9个回答

16
首先,打印有关 MSDN 中“安全/不安全”函数的文档并将其销毁!
fopen
如果你不傻到认为返回的指针不是NULL,或者提供NULL作为输入参数,那么它与 fopen_s 一样安全。
vfprintf vsprintf sprintf 

微软不支持C99,使用snprintf系列函数。

 strncpy

只要你阅读了说明书,就是完全安全的。

strcpy strcat

使用strncpystrncat函数并阅读规范。(例如,strncpy函数可能没有以null结尾)

所以...再一次:

打印有关MSDN中“安全/不安全”函数的文档并将其销毁!


4
strncpy / strncat 是安全的,但使用起来很麻烦。strncpy 要求调用者添加0终止符。strncat 需要用户计算要复制的字符数,而不是目标缓冲区的大小。 - R Samuel Klatchko
请注意,Microsoft的vsnprintf()函数无法符合标准(C99),因为C99定义要求vsnprintf()在缓冲区长度不为零时添加终止空字符(这具有特殊含义)。我认为微软知道当它说它的vsnprintf()函数并不总是添加终止空字符时意味着什么,但这只意味着微软(可能)在标准规范正确行为之前实现了它,然后感到无法冒险更改错误的定义(尽管很难看到任何工作代码会因此而破坏)。 - Jonathan Leffler
1
@Artyom,感谢您的回答。您可以安全地假设我是个白痴。特别是,您可以打赌我最终会在任何“如果我阅读手册就安全”的函数中出错。 - jwfearn
4
@jwfearn -- 重点在于微软创建了自己的“安全”函数,而不是采用C99的安全函数。例如,fopen_s完全是“垃圾”,sprintf_s也是如此 - 应使用snprintf。等等。是的,strncpy和strncat有些棘手,但仍然更好地遵循标准...不要相信关于“不安全功能”的所有内容。 - Artyom
1
实际上,TR24731是晚于微软等价函数的 - 微软先实现了一些东西,然后将其系统提交给标准C委员会。被“标准化”的内容(在目前的技术报告中)与微软的提案不同。 - Jonathan Leffler
strncpy不安全。它不能保证字符串以空字符结尾。正确使用它容易出错。 - George

12

概要:在Mac上,有几个API和编译器选项提供比C标准库函数更安全的替代方案。以下是其中一些与Microsoft的"safe" API进行比较:

   C        MSVC      提供者    Mac解决方案
---------------------------------------------------------------------------------
fopen     fopen_s     C          没有,假设fopen是安全的
vfprintf  vfprintf_s  GCC        GCC_WARN_TYPECHECK_CALLS_TO_PRINTF(1)
vsprintf  vsprintf_s  GCC,C99   GCC_WARN_TYPECHECK_CALLS_TO_PRINTF,vsnprintf(2)
sprintf   sprintf_s   GCC,C99   GCC_WARN_TYPECHECK_CALLS_TO_PRINTF,snprintf(3)
strncpy   strncpy_s   BSD        strlcpy(4)
strcpy    strcpy_s    BSD        strlcpy
strcat    strcat_s    BSD        strlcat(5)

(1)GCC_WARN_TYPECHECK_CALLS_TO_PRINTF是一个XCode配置选项,对应于GCC命令行选项-Wformat。此选项会产生编译器警告,指出参数类型与静态格式字符串之间的不一致。有多种其他选项可控制GCC对格式字符串的处理。您甚至可以使用GCC的format函数属性来启用自己函数的格式字符串检查。

(2) vsnprintf和(3) snprintf是C99版本的C标准库函数(在Mac上的GCC可用,但在Windows上的MSVC不可用)。

(4) strlcpy和(5) strlcat是BSD库函数,在Mac上可用。


6

不要使用sprintf和vsprintf,而是使用:

snprintf(buffer, buffer_size, fmt_string, args, ...);
vsnprintf(buffer, buffer_size, fmt_string, valist);

你不想使用strcpy、strncpy、strcat和strncat,而是要使用:

strlcpy(dest, src, dest_size);
strlcat(dest, src, dest_size);

有一种情况下,strn函数无法被strl函数替代。如果您想要复制非0结尾的字符串,通过将长度设置为副本数量和目标缓冲区大小中较小的值,strn函数允许您这样做。而strl函数则不能这样做,只有在源字符串为0结尾时才能使用。

不确定fopen或vfprintf为何被认为是不安全的。


1
vfprintf()是不安全的,因为它支持'%n'指令(在处理该指令时报告写入的字节数)。这是C89标准化过程中有趣(糟糕?)的一步 - 它要求在printf()等函数中使用输出参数(指向int的指针)。这部分是不安全的 - 但其余部分则没有问题。 - Jonathan Leffler

5

另请参见:SO 327980

标准C委员会创建了一份技术报告TR 24731-1,部分原因是受到微软的鼓励(我认为)。它标准化了各种函数的接口,如vsnprintf_s()。然而,遗憾的是,标准定义的接口与微软定义的接口不兼容,因此使得该标准在很大程度上无关紧要。

例如,TR 24731-1指出vsnprintf_s()的接口:

#define _ _STDC_WANT_LIB_EXT1_ _ 1
#include <stdarg.h>
#include <stdio.h>
int vsnprintf_s(char * restrict s, rsize_t n,
                const char * restrict format, va_list arg);

不幸的是,MSDN 上对 vsnprintf_s() 接口的说明如下:

int vsnprintf_s(
   char *buffer,
   size_t sizeOfBuffer,
   size_t count,
   const char *format,
   va_list argptr 
);

参数

  • buffer - 输出的存储位置。
  • sizeOfBuffer - 输出缓冲区的大小。
  • count - 写入的最大字符数(不包括终止的空字符),或者使用_TRUNCATE。
  • format - 格式说明。
  • argptr - 指向参数列表的指针。

请注意,这不仅仅是类型映射的问题:固定参数的数量不同,因此无法协调。对于为什么同时具有“sizeOfBuffer”和“count”的好处,我也不清楚(可能标准委员会也一样)。看起来这是相同的信息两次(或者至少,常规代码将为两个参数使用相同的值)。


@Jonathan Leffler:你知道这些API在Mac上是否可用吗? - jwfearn
它们不在主系统库 - /usr/lib/libSystem.B.dylib中。我没有检查机器上的每个dylib,但我怀疑它们在这里和大多数基于Unix的机器上都是缺失的。大约6个月前,有人为字符串例程创建了安全库;如果您找不到它,请问我。 - Jonathan Leffler
我进行了彻底的搜索(find / -name '*.dylib' -print0 | xargs -0 nm -og | grep '_s$'),但没有找到任何 TR 24731 安全函数。有一些以“_s”结尾的函数,但都不相关。 - Jonathan Leffler

4
特别是,我正在寻找至少以下函数的“安全”实现: fopen vfprintf vsprintf sprintf strncpy strcpy strcat ...
我试图找到一个最佳实践的 Mac API,它尽可能地类似于传统的 C 库调用,同时增加安全性。
那很简单。请查阅 Apple Secure Coding Guide。苹果恰好使用了 BSD“更安全”的函数。

enter image description here


相关:虽然苹果和微软提供了更安全的功能,但Linux没有。GNU C没有包括“边界检查接口”(ISO的TR24731),因为像Ulrich Drepper(GNU libc门卫)这样的人反对。他反对是因为只指定了目标缓冲区。他称之为“更安全”的函数是BSD Crap。有关Drepper的引用,请参见Sourceware邮件列表上的Re: PATCH:safe string copy and concetation
遵循Drepper的建议将导致惊人的失败。CVE-2012-5958 CVE-2012-5959 CVE-2012-5960 CVE-2012-5961 CVE-2012-5962 CVE-2012-5963 CVE-2012-5964 CVE-2012-5965(也称为libupnp中的多个缓冲区溢出)为胜利!可惜libupnp遵循Drepper并忽略最佳实践,并且放弃了“更安全”的功能。我不知道有多少百万台路由器和网关至今仍未修补...

@jww:你图片中列出的那些实际上更安全的函数早在 Linux 上就提供了。只有无用且有害的附录 k 被忽略了。 - Deduplicator
@Deduplicator - 出于好奇,您为什么认为Annex K是失灵的?(顺便说一句,它现在已经成为标准的一部分,不再是附录)。 - jww
再次看一下这个问题:https://dev59.com/73RC5IYBdhLWcg3wOOP1。截至C11(仍然是当前标准),它们只成为了一个可选的附录(这并不意味着MS实现将符合)。在此之前,它是一个TR。 - Deduplicator
@Deduplicator - 对不起,你是正确的。它们仍然在附录中,但现在它们是规范性的。显然这种情况自2011年以来一直存在(ISO/IEC 9899:2011)。但我仍然对其功能失调感到好奇。 - jww

3

C标准已经有了一组这些函数的“安全”版本。(对于“安全”一词的特定定义)

snprintf()(以及其它相关函数)提供了您需要的安全功能。它可以检查缓冲区溢出。此外,gcc编译器还进行格式字符串验证(但比微软更好,因为验证是在编译时完成的)。

fopen()             Not sure how you make that safer?
vfprintf            --  These are low level functions
vsprintf            --  These are low level functions
sprintf             snprintf
strncpy             Already the safe version
strcpy              strncpy
strcat              strncat

我对fopen的理解是,“标准”版本被指定使用默认安全规则,这可能与主机操作系统的“安全”默认值不匹配……或者类似于此。并非缓冲区溢出问题。我可能是错的。我将其列入我的清单中,因为MSVC提供了一个fopen_s函数。 - jwfearn
strncpy()必须非常小心地使用才能保证安全 - 当源字符串太长无法适应目标字符串时,它不会添加空字符终止符。它的填充属性(零填充到完整长度)有时会成为障碍,特别是当您有一个20 KB的缓冲区但大多数情况下只传输50字节的字符串时。 - Jonathan Leffler
@jwfearn:在TR24731中,标准的fopen()和fopen_s()之间的主要区别是添加了一个标志'u'。引用:只要底层系统支持这些概念,为写入而打开的文件应该使用独占(也称为非共享)访问方式打开。如果正在创建文件,并且模式字符串的第一个字符不是'u',只要底层系统支持,文件应该具有防止系统上其他用户访问文件的文件权限。[...] - Jonathan Leffler

3

1

0

我的safeclib是一个便携式解决方案。微软的实现存在漏洞和不安全性,其他实现方式虽然存在,但不具备可移植性或过于天真。

https://github.com/rurban/safeclib


当链接到您自己的网站或内容(或与您有关联的内容)时,您必须在答案中披露您的关联,以便不被视为垃圾邮件。根据Stack Exchange政策,在您的用户名中具有相同文本作为URL或在您的个人资料中提及它不被视为充分的披露。您还在多个答案中链接到了您的存储库。您需要在所有答案中披露关联。 - cigien

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接