Partager via


Microsoft Visual C++ 中的安全功能

Microsoft Visual C++ 中的安全功能

发表时间:2017年6月28日,作者:Andrew Pardoe [MSFT],原文链接:Security Features in Microsoft Visual C++

每一个开发者都会犯一些错误。无论编写代码时多么小心,您将总是会出现一些错误。并且当运行在连接环境中的软件或者早于其最初计划的寿命使用的软件时,任何错误都可能成为安全漏洞。 不正确的代码是不安全的代码。

       Microsoft Visual C++ 工具集提供了大量的可以帮助你写出安全的、正确的代码的功能,在您开始输入代码之前编写安全到你发布代码给用户。

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

 

在编写代码之前

     在您编写第一行代码,代码安全就开始了。编译器工具集不能显示可能导致安全漏洞的设计缺陷,但打印和在线可以使用许多资源,以帮助您思考潜在漏洞以及如何安全地设计代码。 例如,几乎每个曾经在微软的人都读过Michael Howard和David LeBlanc的“写作安全代码”。

它是非常重要的,在您编写代码时使用现代的C++构建去管理和访问资源。C++可用的资源利用是C++ 核心指南,一套针对C++ 编写的经过验证的准则,规则还有最好的实践。C++核心指南中推荐的编码实践可以帮助您编写出简单的、更现代化的软件。这样做的话,您将避免常见的陷阱,例如整数溢出或者缓冲区溢出,从而使您的代码更加安全。许多C++核心指南可以使用VisualC++附带的静态分析工具执行。

编写代码时

   当您编写代码的时候你可以做些什么来帮助你自己呢?首先,通过设置正确的警告级别,从内置的编译器诊断中获取到所有可用的值。在构建代码之后运行代码分析,让编译器工具集深入分析您的代码。并且不要忘记和您的团队定期进行代码审查

编译器警告

最常用的功能就是编译器警告。MSVC 编译器提供很多开关,允许您控制在代码中看到的警告,以及它们作为信息性消息还是导致您编译失败。

一些编译器警告默认是保持在默认状态,因为它们在您的旧代码中频繁的出现,但大部分被用户都不想看见它们。但是这些警告有许多显示的是您程序中的真实错误。例如,你的代码可能有一个合法的理由比较一个无符号值和一个负数,但它可能是一个错误。通过启用默认值以外的警告,您可以捕获到很多潜在的错误。

要了解有关如何调整构建设置以允许编译器尽可能多地在代码中查找错误的更多信息,您可以参照编译器警告级别选项的文档

静态代码分析安全功能

我们总是写关于C++ 代码分析的博客,同时我们也让您更新有关CPPCoreCheck扩展的更新,该扩展检查您的代码是否从C++核心准则导出的规则。但是您是否知道,微软长期以来一直将PREfast作为我们的代码分析核心的引擎,一个安全工具? 该工具最初由一个专注于软件卓越的团队开发,后来由Secure Development Lifecycle团队拥有,之后才能使C ++团队进入所有版本的Visual Studio。

我们现在有一些基于PREfast引擎的代码分析工具,包括我们的基础/分析规则,ESPC并发检查器(pdf)和CppCoreCheckers。 我们也在寻找方法来帮助您将代码分析更深入地整合到您的日常开发中

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

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

1 void one() 2 { 3    int a[4]; 4    a[4] = 1; // Buffer overrun, stack overflow5 } 67 void two(int *p) 8 { 9   bool isnull = false ; 10  if (p == nullptr ) 11      isnull = true ; 12   *p = 1;    // Null pointer dereference13 } 1415 int three(bool b)   16 {   17  int i;   18if (b)   19      i = 0;   20return i; // i is unintialized if b is false21 }

 

1 C:\tmp>cl /c example.cpp /Wall /analyze2 Microsoft (R) C/C++ Optimizing Compiler Version 19.10.25019 for x643 Copyright (C) Microsoft Corporation.  All rights reserved. 45 example.cpp6   c:\tmp\example.cpp(4) : warning C6201: Index '4' is out of valid index range '0' to '3' for possibly stack allocated buffer 'a' .7   c:\tmp\example.cpp(4) : warning C6386: Buffer overrun while writing to 'a':  the writable size is '16' bytes, but '20' bytes might be written.: Lines: 3, 48c:\tmp\example.cpp(12) : warning C6011: Dereferencing NULL pointer 'p'. : Lines: 9, 10, 11, 129c:\tmp\example.cpp(22) : warning C6001: Using uninitialized memory 'i'.: Lines: 17, 18, 2210 c:\tmp\example.cpp(4) : warning C4789: buffer 'a' of size 16 bytes will be overrun; 4 bytes will be written starting at offset 1611 c:\tmp\example.cpp(22) : warning C4701: potentially uninitialized local variable 'i' used

以上来源的每个功能都有错误。编译器错过了这三个错误之一,间接地归因于另一个错误。随着编译器可用的解析信息的改进,编译器中的分析将会得到改善,您将看到更多可以在工具之间复制诊断的实例。

1.函数中的一个代码分析告诉我们,我们使用数组的边界作为索引,导致C6201。这两个代码分析和编译器拾起内存的腐败,前者发布了C6386,后者是C4789

2.在函数二中,我们在一个代码路径上引用一个空指针,导致C6011。编译器完全错过了这个错误。

3.编译器和代码分析两个功能都接收到错误。编译器发出违规警告C4701;代码分析,C6001

代码分析也擅长寻找不能做你可能认为的代码。例如,警告C6268查找包含不正确的操作顺序的代码,并建议使用括号来清除订单。文档中给出的示例包含潜在的缓冲区溢出。

您可以在VS和Microsoft Docs站点的命令行中阅读更多有关使用C ++代码分析的信息

代码审查

代码审查看起来似乎会有一些开销但从长远来看它节省了时间。一些团队进行一对一的代码审查,其他人讲所有更改发送到一组评论,以查看一周所有的更改。

您怎么做代码审查无关紧要。它们将成为您可以用来提高代码质量的最有价值的技术之一。 找到适合您的团队并使用它的流程。

附加的安全检查 / sdl编译器开关启用了针对Microsoft Secure Development Lifecycle过程定义的安全问题的其他警告。 / sdl开关在许多方面是扩展了默认情况下的默认警告C4701,因此默认情况下关闭。

CRT安全功能负载

对于C 的库来说,安全不是一个主要设计的点-通常代码在组织内部被编写并。而不是暴露在全球的计算机网球计算机网络。C的“String”没有记录其长度的元数据。例如,处理字符串中的字符串拷贝功能必须假定作为参数提供的缓冲区是所请求操作的适当大小。 好多内存操作也有类似的限制。

十年之前微软给这些功能引入了一系列工作负载,用于验证参数安全。如果您仍然在使用C风格函数,您应该考虑迁移到C++,它提供了更安全的对象和抽象类。如果您没有使用C++,至少使用C运行时功能安全的版本

(注意:当我们介绍这个功能时,我们错误地将不安全的C函数称为“不赞成使用”,这仅仅意味着Microsoft不建议使用不安全的功能,而是建议您使用安全重载,我们知道术语“已弃用” “使用不正确)

当测试代码时

编译器工具集提供您更多的选项去帮助您测试自己的代码。大多数这些交换机并不打算与您的程序的最终零售版本一起运送。默认情况下,它们已被启用以进行调试生成,或者选择加入,以便您可以在测试期间找到更多错误。

CRT调试堆

调试堆被执行在您编译您还没有发布的代码的时候。它可以发现普遍的堆内存错误,包括缓冲区溢出或者泄露。在您测试代码时 CRT 调试堆将在遇到任何堆内存错误时断言。各种例行程序被执行当您定义 _DEBUG标志。

运行时检查

CRT通过使用/ RTC开关提供运行时检查功能。这些检查在您的程序中发现真正的逻辑错误,例如数据丢失,初始化错误还有堆栈帧检查。运行时检查仅适用于当您正在测试调试版本并且与优化不兼容时。由于这些限制他们自动关闭。

检查迭代器

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

编译代码之后

Windows团队提供帮助验证您编译的二进制文件是否安全的工具。您可以在WindowsWindows SDK的调试工具中找到这些工具。

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

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

发布的代码运行时保护

MSVC代码生成器和链接器提供了几个安全功能,可以在构建和部署代码后继续提供保护。 因为代码生成器可以一次看到所有的代码 - 而不是一次只能使用一个源文件 - 它通常可以检测到在单个源文件中找不到的错误和漏洞。 并且代码生成器和链接器与OS加载程序和运行时配合工作,以便在Windows中加载和执行二进制文件时提供更高的安全性。

缓冲区安全检查

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

安全异常处理 安全异常处理是另一个长期的安全功能。这个功能是默认的仅应用于生成在x86平台上的代码。当启用它时,链接器只会产生一个图像,如果它可以创建图像的安全异常处理程序的静态表。 这样可以防止攻击者覆盖异常处理控制流的目标。

动态基地址和地址空间布局随机化 地址空间布局随机化(ASLR)是一种技术,使攻击者难以预测攻击目标地址。 当您的二进制映像启用ASLR时,OS加载程序将以难以预测的基址加载映像。 默认情况下,/ DYNAMICBASE切换到链接器,使图像能够使用ASLR。

数据执行预防

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

控制流程保护

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

即将推出的安全功能

我们继续通过从代码生成器程序分析中受益的新安全功能进行创新。安全需要“深度防御”,因为攻击者总是会找到一种解决现有保护措施的方法。我们经常需要找到新的方法来帮助保护您的代码在各个层面。

您的代码是否安全?

良好的开发人员工具可以做很多工作来帮助您编写稳固和安全的代码,但不幸的是,他们无法为您做任何事情。您需要从一个良好的设计开始,包括适合我们的代码将运行的环境的安全性,无论是在部署时还是在未来许多年,可能期望您的代码被重写,替换后,或只是过时。您的代码可以在连接的环境中运行吗?您需要规划攻击,包括那些简单的拒绝服务。您的代码会处理敏感的用户信息吗?您需要规划您的代码将如何站起来攻击谁想要获取您处理的数据。

安全性不是一个功能,可以被螺栓连接到成品上。但是很好的工具(如Visual C ++工具集中提供的工具)可以帮助您编写稳固,安全的代码。

谢谢!

感谢您阅读在开发过程中不同点提供的这长串安全功能。感谢数百人提供反馈并帮助我们改进Visual Studio中的C ++体验。

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