MSVC中的安全特性

可共享链接: https://aka.ms/msvcsecurity 点这里看中文版

null

每个开发者都会犯错。无论编写代码时多么小心,都会引入bug。当软件在连接的环境中运行或使用时间超过最初计划的寿命时,任何bug都可能成为安全漏洞。不正确的代码是不安全的代码。

微软VisualC++工具集提供了许多功能,帮助您在开始键入代码之前编写安全、正确的代码,直到您将代码发送到用户之后。

有关MSVC工具集中特定安全功能的更多信息,请确保查看 C++的安全最佳实践 .

在你写任何代码之前

安全代码在编写第一行代码之前启动。编译器工具集不能显示可能导致安全漏洞的设计缺陷,但有许多印刷和在线资源可以帮助您考虑潜在的漏洞以及如何安全地设计代码。例如,几乎所有在微软工作过一段时间的人都读过迈克尔·霍华德和大卫·勒布朗的著作 编写安全代码 .

当您开始编写代码时,使用现代C++结构来管理和访问资源非常重要。可用的最佳资源之一是 C++核心指南 一组关于C++中的编码准则、规则和最佳实践。C++核心指南中推荐的编码实践帮助您编写更简单、更现代的软件。这样做,您将避免常见的陷阱,如整数溢出或缓冲区溢出,使您的代码更安全。而且许多C++核心指南都是可以执行的。 用Visual C++编写的静态分析代码工具 .

当你写代码的时候

在编写代码时,您能做些什么来帮助自己?首先,通过正确设置警告级别,从内置编译器诊断中获取所有可以获得的值。在构建之后运行代码分析,让编译器工具集深入分析代码。别忘了 与您的团队定期进行代码评审 !

编译器警告

最常用的安全特性之一是编译器警告。MSVC编译器提供了许多开关,允许您控制在代码中看到哪些警告,以及这些警告是作为信息消息保留还是导致编译失败。

默认情况下,某些编译器警告处于关闭状态 因为它们在遗留代码中发出的频率太高,大多数用户不想看到它们。但这些警告中的许多都表明程序中存在真正的错误。例如,您的代码可能有一个有效的理由 比较无符号值和负数 但也有可能是个虫子。通过启用默认关闭警告,您可以捕获潜在的错误。

要了解有关如何调整生成设置以允许编译器在代码中找到尽可能多的bug的更多信息,请参阅 关于编译器选项警告级别的文档 .

静态代码分析安全特性

我们经常在这个博客上写C++代码分析。我们还将更新CppCoreCheck扩展,它检查C++代码中的规则。但是你知道吗,微软早就考虑过PREfast,我们代码分析的核心引擎,一个安全工具?该工具最初是由一个专注于软件卓越的团队开发的,后来被安全开发生命周期团队所拥有,之后才进入C++团队,以与所有版本的VisualStudio一起。

我们现在有许多基于PREfast引擎构建的代码分析工具,包括 基本/分析规则集 ,的 并发检查器 (pdf)和 CppCoreCheckers公司 . 我们也在寻找帮助你的方法 将代码分析更深入地集成到您的日常开发例程中 .

顾名思义,代码分析对代码进行更深入的分析,以发现可能的错误。当编译器检测到代码中的许多潜在错误时,代码分析会检查整个函数,以确定是否存在可能导致错误的某些代码路径。我们称这种分析为“路径敏感”分析。

虽然编译器可以做很多路径敏感的分析,但是有很多情况它无法识别。例如,在打开所有编译器警告的情况下编译此代码( /Wall )分析( /analyze )显示编译器只能找到三个潜在错误中的一个:

void one(){    int a[4];    a[4] = 1; // Buffer overrun, stack overflow}void two(int *p){   bool isnull = false;   if (p == nullptr)      isnull = true;   *p = 1;   // Null pointer dereference}int three(bool b)  {     int i;     if (b)        i = 0;     return i; // i is unintialized if b is false  }
C:	mp>cl /c example.cpp /Wall /analyzeMicrosoft (R) C/C++ Optimizing Compiler Version 19.10.25019 for x64Copyright (C) Microsoft Corporation.  All rights reserved.example.cppc:	mpexample.cpp(4) : warning C6201: Index '4' is out of valid index range '0' to '3' for possibly stack allocated buffer 'a'.c:	mpexample.cpp(4) : warning C6386: Buffer overrun while writing to 'a':  the writable size is '16' bytes, but '20' bytes might be written.: Lines: 3, 4c:	mpexample.cpp(12) : warning C6011: Dereferencing NULL pointer 'p'. : Lines: 9, 10, 11, 12c:	mpexample.cpp(22) : warning C6001: Using uninitialized memory 'i'.: Lines: 17, 18, 22c:	mpexample.cpp(4) : warning C4789: buffer 'a' of size 16 bytes will be overrun; 4 bytes will be written starting at offset 16c:	mpexample.cpp(22) : warning C4701: potentially uninitialized local variable 'i' used

上面源代码中的每个函数都有一个错误。编译器会忽略这三个错误中的一个,并间接地为另一个错误指定属性。作为 编译器可用的分析信息 ,编译器中的分析将得到改进,您将看到更多的实例,在这些实例中,可以在工具之间复制诊断。

  1. 在功能上 one 代码分析告诉我们使用数组的边界作为索引,导致 C6201型 . 代码分析和编译器都会发现内存损坏,前者 C6386型 ,后者, C4789型 .
  2. 在功能上 two 我们在一个代码路径上取消对空指针的引用,导致 C6011型 . 编译器完全忽略了这个错误。
  3. 编译器和代码分析都能发现函数中的错误 three . 编译器默认发出关闭警告 C4701型 ; 代码分析, C6001型 .

代码分析也擅长于发现那些你可能认为做不到的代码。例如,警告C6268会发现包含错误操作顺序的代码,并建议使用括号来明确顺序。这个 文件中给出的示例 包含潜在的缓冲区溢出。

你可以阅读更多关于 使用C++代码分析内部VS和命令行 在Microsoft文档网站上。

代码评审

代码评审看起来开销很大,但从长远来看,它们可以节省时间。有些团队进行一对一的代码评审,有些团队将所有更改发送给一组评审,有些团队每周五召集团队查看一周的所有更改。你怎么做代码评审并不重要。它们将成为您可以用来提高代码质量的最有价值的技术之一。找到一个适用于您的团队的流程并加以使用。

附加安全检查

这个 /sdl编译器开关 启用关注由定义的安全问题的其他警告 Microsoft安全开发生命周期 过程。默认情况下,/sdl开关在许多方面都是off警告的扩展 C4701型 因此默认情况下是关闭的。

CRT安全函数重载

对于C库来说,安全性并不是一个重要的设计点,通常代码是在一个组织内部编写和运行的,而不是暴露在世界范围的计算机网络中。C“string”没有与之相关联的元数据来记录其长度。例如,处理字符串的函数,例如 strcpy ,必须假定作为参数提供的缓冲区大小适合请求的操作。一些内存操作也有类似的限制。

十多年前,微软为这些函数引入了一组重载 验证其安全参数 . 如果您仍然使用C样式函数,则应该考虑移动到C++,这在其对象和抽象中提供了更多的安全性。如果你不能使用C++,至少 使用C运行时函数的安全版本 .

(注意:当我们引入这个特性时,我们错误地称不安全的C函数为“deprecated”。这只意味着Microsoft不建议使用不安全的函数,而是建议您使用安全重载。我们知道“弃用”一词用得不正确。)

在测试代码时

编译器工具集提供了许多选项,可以帮助您测试代码。这些开关中的大多数并不打算随程序的最终零售版本一起提供。默认情况下,它们会为调试构建打开,或者选择加入,这样您就可以在测试期间发现更多的bug。

CRT调试堆

在调试(非发布)模式下编译程序时,CRT调试堆将启用。它发现常见的堆内存错误,包括缓冲区溢出和泄漏。CRT调试堆将在测试代码时遇到堆内存错误时进行断言。 各种调试例程 在定义 _DEBUG 旗帜。

运行时检查

CRT提供 运行时检查 通过使用 /RTC 开关。这些检查可以发现程序中的实际逻辑错误,如数据丢失、初始化问题和堆栈帧检查。运行时检查仅适用于正在测试调试生成并且与优化不兼容的情况。由于这些限制,它们在默认情况下处于禁用状态。

检查迭代器

检查迭代器有助于确保代码不会意外地覆盖代码中iterable容器的边界。它们可以同时用于 除错码 (作为调试迭代器)和 发布代码 (作为检查迭代器。)

编译代码之后

Windows团队提供了一些工具来帮助验证编译的二进制文件是否安全。你可以在图书馆找到这些工具 Windows调试工具 以及 Windows SDK .

GFlags和PageHeap

这个 GFlags和PageHeap工具 为Windows启用堆分配监视。当使用这些工具时,Windows将在每次分配的边界上保留内存,以允许它检测分配内存之外的内存访问。

应用程序验证器

应用程序验证器 是一种动态验证工具,在您执行代码并生成潜在漏洞报告时,它会使您的二进制文件承受许多压力和测试。

已发布代码的运行时保护

MSVC代码生成器和链接器提供了几个安全特性,在构建和部署代码之后,这些特性将继续提供保护。因为代码生成器可以一次查看您的所有代码,而不是一次只查看一个源文件,所以它通常可以检测单个源文件中找不到的错误和漏洞。代码生成器和链接器与操作系统加载程序和运行时一起工作,以便在Windows中加载和执行二进制文件时提供更高的安全性。

缓冲器安全检查

代码生成器中最古老的安全特性之一是 缓冲器安全检查 ,由 /GS 切换到编译器。默认情况下,此功能处于启用状态,因为它可以防止最常见的安全漏洞攻击。它在编译器检测到易受缓冲区溢出攻击的函数中创建“安全cookie”。如果攻击者通过返回地址、异常处理程序的地址或易受攻击的函数参数写入超过缓冲区结尾的内容,则攻击者将覆盖安全cookie。运行时将在允许执行跳转到此地址或返回这些参数之前检查cookie的完整性。

安全异常处理程序

安全异常处理程序 是另一个长期存在的安全特性。默认情况下,此功能处于启用状态,但仅适用于为x86平台生成的代码。当它被启用时,链接器只会生成一个映像,如果它可以创建映像的安全异常处理程序的静态表的话。这可防止攻击者覆盖异常处理控制流的目标。

动态基和地址空间布局随机化

随机地址空间分配 (ASLR)是一种使攻击者更难预测其攻击的目标地址的技术。当在二进制映像上启用ASLR时,OS加载程序将以难以预测的基址加载映像。这个 /DYNAMICBASE 转换 到链接器(默认情况下处于启用状态)使图像能够使用ASLR。

数据执行预防

攻击者的一种常见技术是将数据用作可执行代码。在实时(JIT)编译器中,许多语言(如.NET语言或JavaScript)都使用了一种强大的技术,即执行经过特殊格式化为机器代码的数据。但是C++程序通常不需要执行数据。Windows允许使用一种称为 数据执行保护(DEP) . 这个 /NXCOMPAT 默认情况下,打开链接器开关可指定图像是否与DEP兼容。

控制流护罩

2014年,我们宣布了一项令人兴奋的新安全功能,名为 控制流护罩 . 这个 /guard:cf 选项指示编译器在编译时分析任何间接调用的控制流,并将分析结果记录在已编译的二进制文件中。它还会在运行代码时,在Windows检查的每个间接调用之前检查二进制文件。Windows将调用 RaiseFastFailException 如果这些检查在运行时失败。

即将推出的安全功能

我们不断创新新的安全特性,这些特性得益于代码生成器的程序分析。安全要求“ 纵深防御 “因为攻击者总会找到一种方法来绕过你现在的保护措施。我们必须不断地寻找新的方法来帮助在各个级别保护您的代码。

你的密码安全吗?

好的开发人员工具可以帮助您编写可靠和安全的代码,但不幸的是,它们不能为您做所有事情。您需要从一个良好的设计开始,该设计包含适合于我们的代码在部署时运行的环境的安全性,并且可能在未来的许多年中,在您可能期望您的代码被重写、替换或过时很久之后。您的代码是否可以在连接的环境中运行?您需要计划攻击,包括像拒绝服务这样简单的攻击。你的代码会处理敏感的用户信息吗?你需要计划你的代码将如何抵抗那些想要获取你处理的数据的攻击者。

安全性不是一个可以固定在成品上的特性。但是良好的工具(如VisualC++工具集中提供的工具)可以帮助您编写坚实、安全的代码。

谢谢您!

感谢您阅读了在开发过程中不同阶段提供的安全特性的长列表。感谢数以百计的人提供反馈,帮助我们改善VisualStudio中的C++体验。

如果您对我们有任何反馈或建议,请联系我们。我们可以通过以下评论和电子邮件联系到您( visualcpp@microsoft.com )你可以通过 帮助>报告产品中的问题 ,或通过 开发者社区 . 你也可以在Twitter上找到我们( @视觉 )还有Facebook( msftvisualcpp软件 ).

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享