安全开发生命周期(SDL)禁止的函数调用(翻译)

原文名

正文

迈克尔霍华德
首席网络安全顾问
微软公司
2011年6月

本文源于Michael Howard和Steve Lipner撰写的“安全开发生命周期”一书,微软出版社,2006年。
禁止使用禁用函数是从C和C ++代码中移除大量潜在代码漏洞的好方法。这种做法反映在“安全开发生命周期”一书的第11章,以及SDL实施阶段的SDL过程指南和Microsoft SDL白皮书的简化实施中。
三十年前首次创造C运行时库(CRT)时,计算机的威胁是不同的;计算机并不像今天那样互联,而且攻击并不普遍。考虑到这一点,必须为新代码弃用C运行时库的子集,并且随着时间的推移,从早期代码中删除。使用这些过时的函数导致代码错误太容易了,这些类型的错误会导致漏洞。甚至一些经典的替换功能也容易出错。
此列表是已知错误函数的已编译库,应将其移除以减少漏洞,这是SDL实践的一部分。它源于对现实世界安全漏洞的经验,主要关注可导致缓冲区溢出的函数(软件开发的24个致命错误; Howard,LeBlanc和Viega 2009)。现有代码必须用更安全的版本替换禁用的函数,或者重新构建,以便不使用禁用的函数。对于标记为“推荐”的函数,请将此视为强烈推荐,并根据您自己的安全要求评估功能,并根据需要将其提升为“必需”。在任何情况下,都不应在新代码中使用列出的禁用函数。
笔记:
禁用的功能是基于以下要求或建议:
1> 考虑到以正确和“安全”方式调用函数的总体难度,评估功能的风险。
2> 安全更换的可用性以及实施替换的复杂性。
3> 某些函数名称可能略有不同,具体取决于函数是使用ASCII,Unicode,_T(ASCII或Unicode)还是多字节字符。某些函数名称可能在名称末尾包含A或W.例如,StrSafe StringCbCatEx函数也可用StringCbCatExW(Unicode)和StringCbCatExA(ASCII)。
4> 显然,你不能用另一个禁用函数替换禁止的函数。例如,用strncpy替换strcpy无效,因为strncpy也被禁止。

表1. 禁止的字符串复制函数和它的替代函数

禁用函数 StrSafe 替代函数 safe CRT 替代函数
strcpy, strcpyA, strcpyW, wcscpy, _tcscpy, _mbscpy, StrCpy, StrCpyA, StrCpyW, lstrcpy, lstrcpyA, lstrcpyW, _tccpy, _mbccpy, _ftcscpy, strncpy, wcsncpy, _tcsncpy, _mbsncpy, _mbsnbcpy, StrCpyN, StrCpyNA, StrCpyNW, StrNCpy, strcpynA, StrNCpyA, StrNCpyW, lstrcpyn, lstrcpynA, lstrcpynW String*Copy 或者 String*CopyEx strcpy_s
  • 对于StrSafe,*应替换为Cch(字符数)或Cb(字节数)

表2. 禁止的字符串连接函数和它的替代函数

禁用函数 StrSafe 替代函数 safe CRT 替代函数
strcat, strcatA, strcatW, wcscat, _tcscat, _mbscat, StrCat, StrCatA, StrCatW, lstrcat, lstrcatA, lstrcatW, StrCatBuff, StrCatBuffA, StrCatBuffW, StrCatChainW, _tccat, _mbccat, _ftcscat, strncat, wcsncat, _tcsncat, _mbsncat, _mbsnbcat, StrCatN, StrCatNA, StrCatNW, StrNCat, StrNCatA, StrNCatW, lstrncat, lstrcatnA, lstrcatnW, lstrcatn String*Cat 或者 String*CatEx strcat_s

表3.禁止的sprintf函数和替代函数

禁用函数 StrSafe 替代函数 safe CRT 替代函数
sprintfW, sprintfA, wsprintf, wsprintfW, wsprintfA, sprintf, swprintf, _stprintf, wvsprintf, wvsprintfA, wvsprintfW, vsprintf, _vstprintf, vswprintf
推荐: wnsprintf, wnsprintfA, wnsprintfW, _snwprintf, snprintf, sntprintf _vsnprintf, vsnprintf, _vsnwprintf, _vsntprintf, wvnsprintf, wvnsprintfA, wvnsprintfW
String*Printf 或者 String*PrintfEx sprintf_s

表4.禁止的“n”sprintf函数和替换

禁用函数 StrSafe 替代函数 safe CRT 替代函数
推荐: _snwprintf, _snprintf, _sntprintf, nsprintf String*Printf 或者 String*PrintfEx _snprintf_s 或者_snwprintf_s

表5.禁止变量参数sprintf函数和替换

禁用函数 StrSafe 替代函数 safe CRT 替代函数
wvsprintf, wvsprintfA, wvsprintfW, vsprintf, _vstprintf, vswprintf String*VPrintf 或者String*VPrintfEx _vstprintf_s

表6.禁止变量参数“n”sprintf函数和替换

禁用函数 StrSafe 替代函数 safe CRT 替代函数
推荐: _vsnprintf, _vsnwprintf, _vsntprintf, wvnsprintf, wvnsprintfA, wvnsprintfW String*VPrintf 或者String*VPrintfEx vsntprintf_s

表7.禁止的“n”字符串复制功能和替换

禁用函数 StrSafe 替代函数 safe CRT 替代函数
strncpy, wcsncpy, _tcsncpy, _mbsncpy, _mbsnbcpy, StrCpyN, StrCpyNA, StrCpyNW, StrNCpy, strcpynA, StrNCpyA, StrNCpyW, lstrcpyn, lstrcpynA, lstrcpynW, _fstrncpy String*CopyN 或者String*CopyNEx strncpy_s

表8.禁止的“n”字符串连接函数和替换

禁用函数 StrSafe 替代函数 safe CRT 替代函数
strncat, wcsncat, _tcsncat, _mbsncat, _mbsnbcat, StrCatN, StrCatNA, StrCatNW, StrNCat, StrNCatA, StrNCatW, lstrncat, lstrcatnA, lstrcatnW, lstrcatn, _fstrncat String*CatN 或者String*CatNEx strncat_s
  • 开发人员经常将strcpy等函数替换为计数的“n”版本,例如strncpy。 不推荐这种做法。 根据我们的经验,“n”函数也难以保证(Howard 2004),因此我们禁止在新代码中使用它们。

表9.禁止的字符串标记化功能和替换

禁用函数 StrSafe 替代函数 safe CRT 替代函数
推荐: strtok, _tcstok, wcstok, _mbstok none strtok_s

表10.禁止的Makepath功能和替换

禁用函数 StrSafe 替代函数 safe CRT 替代函数
推荐: makepath, _tmakepath, _makepath, _wmakepath none _makepath_s

表11.禁止的Splitpath函数和替换

禁用函数 StrSafe 替代函数 safe CRT 替代函数
推荐: _splitpath, _tsplitpath, _wsplitpath none _splitpath_s

表12.禁止的scanf函数和替换

禁用函数 StrSafe 替代函数 safe CRT 替代函数
推荐: scanf, wscanf, _tscanf, sscanf, swscanf, _stscanf none sscanf_s

表13.禁止的“n”scanf函数和替换

禁用函数 StrSafe 替代函数 safe CRT 替代函数
推荐:snscanf, snwscanf, _sntscanf, _stscanf none _snscanf_s

表14.禁止的数字转换函数和替换

禁用函数 StrSafe 替代函数 safe CRT 替代函数
推荐:_itoa, _itow, _i64toa, _i64tow, _ui64toa, _ui64tot, _ui64tow, _ultoa, _ultot, _ultow none _itoa_s, _itow_s

表15.禁止gets()函数和替换

禁用函数 StrSafe 替代函数 safe CRT 替代函数
gets, _getts, _gettws String*Gets gets_s

表16.禁止的IsBad *函数和替换

禁用函数 替换
IsBadWritePtr, IsBadHugeWritePtr, IsBadReadPtr, IsBadHugeReadPtr, IsBadCodePtr, IsBadStringPtr 这些功能可以掩盖错误,并且没有替换功能。 您应该重写代码以避免使用这些函数。 如果您需要避免崩溃,请使用__try / __除外包装指针的用法。 这样做可以轻松隐藏错误; 您应该只在避免崩溃绝对关键的区域(例如崩溃恢复代码)以及您对所查看数据可能无效的原因有合理解释。 您也不应该捕获所有异常,但只能捕获您知道的类型。 捕获所有异常与使用IsBad * Ptr一样糟糕。
对于IsBadWritePtr,使用memset填充目标缓冲区是验证输出缓冲区是否有效且大到足以容纳调用者声称它们提供的空间量的首选方法。

表17.禁止的OEM转换功能和替换

禁用 Windows Replacement
Recommended: CharToOem, CharToOemA, CharToOemW, OemToChar, OemToCharA, OemToCharW, CharToOemBuffA, CharToOemBuffW WideCharToMultiByte

表18.禁止堆栈动态内存分配函数和替换

禁用 Windows Replacement
Recommended: alloca, _alloca SafeAllocA

表19.禁止的字符串长度函数和替换

  • 对于重要应用程序,例如接受匿名Internet连接的应用程序,必须替换strlen
禁用函数 StrSafe 替代函数 safe CRT 替代函数
推荐: strlen, wcslen, _mbslen, _mbstrlen, StrLen, lstrlen String*Length strnlen_s

表20.禁止的内存复制功能和替换

禁用函数 StrSafe 替代函数 safe CRT 替代函数
memcpy, RtlCopyMemory, CopyMemory, wmemcpy none memcpy_s, wmemcpy_s

表21.禁止的窗口消息传递功能和替换

禁用
推荐: ChangeWindowMessageFilter 建议不要使用此功能,因为它具有进程范围。 您可以使用ChangeWindowMessageFilterEx来控制特定窗口的访问,但要仔细考虑任何邮件过滤更改。

禁止使用“n”函数的原因

经典的C运行时,“n”函数(例如strncpy和strncat)被禁止,因为它们很难正确调用。 作者已经看到许多错误调用这些函数以试图使代码更安全。 请注意,我们并不是说替换是完美的,但是当前“n”函数的问题包括溢出缓冲区的非空终止以及溢出时没有错误返回。
较新的StrSafe和Safe CRT功能在失败时更加一致。

重要的警告

简单地用更好的替换替换禁止的函数调用并不能保证代码是安全的。 可能会滥用替换函数,最常见的方法是使目标缓冲区大小错误。
确保函数调用安全的一种方法是仔细检查安全CRT函数调用中的numberOfElements参数(例如strcpy_s)或StrSafe函数中的cchDest参数(例如StringCchCopy)是否不大于目标缓冲区大小。 一个好方法是使用_countof(缓冲区)调用。 示例将在本文档的后面部分中显示。

选择StrSafe与Safe CRT

这两组替换C运行时函数之间存在重叠。 你选择哪个取决于你的具体情况; 下表可以帮助您做出决定。 在某些情况下,你可能别无选择,只能使用一个而不是另一个; 例如,如果您的代码调用itoa,则StrSafe中没有替换,但安全CRT中存在替换。 您需要围绕itoa呼叫编码或使用安全CRT。

StrSafe Safe CRT
Distribution Method Web (http://msdn.microsoft.com) Microsoft Visual Studio 2005 or later
# Headers One (StrSafe.h) Numerous (various C runtime headers)
Library Version Available Yes Yes
Inline Version Available Yes No
Industry Standard No Not Yet (Secure C Lib Functions)
Kernel Mode Yes No
Return Type HRESULT (user) or NTSTATUS (kernel) Varies by function (errno_t)
Requires Code Changes Yes Yes
Main Focus Buffer overrun issues Various, including buffer overruns

Using StrSafe

  • 要在C或C ++代码中使用StrSafe,只需添加以下标头:
#include "strsafe.h"
  • 这将使函数内联。 如果要使用库版本Strsafe.lib,请在代码中添加以下内容:
#define STRSAFE_LIB
#include "strsafe.h"
  • 请注意,所有StrSafe函数都包含用于内核使用的Rtl版本。
StrSafe Example

The following code

void Function(char *s1, char *s2) {
    char temp[32];
    strcpy(temp,s1);
    strcat(temp,s2);
}

转换为StrSafe时可能如下所示:

HRESULT Function(char *s1, char *s2) {
    char temp[32];
    HRESULT hr = StringCchCopy(temp,_countof(temp), s1);
    if (FAILED(hr)) return hr;
    return StringCchCat(temp,_countof(temp), s2);
}

Using Safe CRT

从Visual Studio 2005开始包含Safe CRT。使用此编译器编译代码时,它将自动警告您代码中已弃用的函数。 此外,在某些情况下,如果在编译时已知目标缓冲区大小且代码中的CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES为#defined,则编译器会将某些函数调用更改为安全函数调用。
例如,以下代码:

int main(int argc, char* argv[]) {
    char t[10];
       ...
    if (2==argc) 
        strcpy(t,argv[1]);

       ...
    return 0;

}

由编译器更改为:

int main(int argc, char* argv[]) {
    char t[10];
   ...
   if (2==argc) 
       strcpy_s(t,_countof(t),argv[1]);

   ...
   return 0;
}
Safe CRT Example

以下代码:

void Function(char *s1, char *s2) {
    char temp[32];
    strcpy(temp,s1);
    strcat(temp,s2);
}

转换为安全CRT时可能如下所示:

errno_t Function(char *s1, char *s2) {
    char temp[32];
    errno_t err = strcpy_s(temp,_countof(temp), s1);
    if (!err) return err;
    return strcat_s(temp,_countof(temp), s2);
}

彼得替代

如果您使用的是C ++,则应该认真考虑使用std :: string模板类而不是直接操作缓冲区。
许多* nix变体,包括OpenBSD和一些Linux操作系统,都包括对字符串拷贝替换strlcpy和strlcat的支持(Miller和de Raadt 1999)。

支持工具

Visual Studio 2005(及更高版本)编译器具有这些函数的内置弃用; 应调查所有C4996编译器警告,以确保有问题的功能不在前面的禁止列表中。 另外,请注意禁用此警告的代码,例如#pragma warning(disable:4996)。

投资回报率和成本影响

如前所述,移除禁用的功能是一种通过非常少的工程工作来减少潜在安全漏洞的方法,如本文档开头所示。 如果未使用禁用功能,则不需要某些Microsoft安全公告,因此组织的潜在成本节约可能非常大。

指标和目标

要跟踪的度量标准是前代码和新代码中禁用函数的数量。 对于新代码,数量应该为零,并且对于前代码,应该随着时间的推移遵循下滑路径。

参考文献

略,这里就不写了

翻译总结

这是本人的第一篇翻译文章,先用Google翻译,然后再人为校验,有什么错误或意见,欢迎大家的批评指正!
终于完成了,激动~

猜你喜欢

转载自blog.csdn.net/liunan199481/article/details/82838638
今日推荐