/GS(缓冲区安全检查)

检测覆盖函数返回地址、异常处理程序地址或特定类型参数的某些缓冲区溢出。 导致缓冲区溢出是黑客用于攻击代码的技术,该技术不强制实施缓冲区大小限制。

语法

/GS[-]

备注

默认情况下,/GS 处于启用状态。 如果希望应用程序没有安全风险,请使用 /GS-。 有关取消缓冲区溢出检测的详细信息,请参阅安全缓冲区

安全检查

对于编译器识别为受缓冲区溢出问题影响的函数,编译器会在返回地址之前在堆栈上分配空间。 调用该函数时,分配的空间中会加载一个安全 Cookie,在模块加载期间会对该 Cookie 进行一次计算。 退出调用该函数时,在 64 位操作系统上展开帧的过程中,会调用帮助程序函数来确保 Cookie 的值依然相同。 如果值不同,则指示可能已覆盖堆栈。 如果检测到不同的值,将终止进程。

GS 缓冲区

缓冲区溢出安全检查对 GS 缓冲区执行。 GS 缓冲区可以是以下对象之一:

  • 大于 4 个字节、具有两个以上元素且元素类型不是指针类型的数组。

  • 大小超过 8 个字节且不包含指针的数据结构。

  • 使用 _alloca 函数分配的缓冲区。

  • 包含 GS 缓冲区的任何类或结构。

例如,以下语句声明是 GS 缓冲区。

char buffer[20];
int buffer[20];
struct { int a; int b; int c; int d; } myStruct;
struct { int a; char buf[20]; };

而以下语句声明不是 GS 缓冲区。 前两个声明包含指针类型的元素。 第三个和第四个语句声明的数组大小太小。 第五个语句声明的结构在 x86 平台上的大小不超过 8 个字节。

char *pBuf[20];
void *pv[20];
char buf[4];
int buf[2];
struct { int a; int b; };

/GS 编译器选项要求初始化安全 Cookie 之后再运行使用该 Cookie 的任何函数。 进入 EXE 或 DLL 时必须立即初始化安全 Cookie。 如果使用默认 VCRuntime 入口点 mainCRTStartup、wmainCRTStartup、WinMainCRTStartup、wWinMainCRTStartup 或 _DllMainCRTStartup,则会自动执行此操作。 如果使用备用入口点,则必须通过调用 __security_init_cookie 手动初始化安全 Cookie。

保护内容

/GS 编译器选项保护以下项:

  • 函数调用的返回地址。

  • 函数的异常处理程序地址。

  • 易受攻击的函数参数。

在所有平台上,/GS 会尝试检测有关返回地址的缓冲区溢出。 x86 和 x64 等平台上更容易遭受缓冲区溢出攻击,这些平台所使用的调用约定会将函数调用的返回地址存储在堆栈上。

在 x86 上,如果函数使用异常处理程序,编译器会注入安全 Cookie 来保护异常处理程序的地址。 展开帧期间会检查 Cookie。

/GS 可保护传入函数的易受攻击的参数。 易受攻击的参数是包含指针或 GS 缓冲区的指针、C++ 引用、C 结构(C++ POD 类型)。

易受攻击的参数是在 Cookie 和局部变量之前分配的。 缓冲区溢出可能会覆盖这些参数。 在使用这些参数的函数返回并在安全检查执行之前,该函数中的代码可能会导致攻击。 为了最大程度降低危险,编译器会在函数 prolog 期间创建易受攻击参数的副本,并将其置于任何缓冲区的存储区域下。

在以下情况下,编译器不会创建易受攻击参数的副本:

  • 不包含 GS 缓冲区的函数。

  • 未启用优化(/O 选项)。

  • 具有变量参数列表 (...) 的函数。

  • 标记为 naked 的函数。

  • 在第一个语句中包含内联程序集代码的函数。

  • 仅采用不太易受缓冲区溢出攻击的方式使用参数。

非保护内容

/GS 编译器选项不会防范所有缓冲区溢出安全攻击。 例如,如果对象中包含缓冲区和 VTable,则缓冲区溢出可能会损坏 vTable。

即使使用 /GS,也务必尝试写入无缓冲区溢出的安全代码。

在 Visual Studio 中设置此编译器选项

  1. 打开项目的“属性页” 对话框。 有关详细信息,请参阅在 Visual Studio 中设置 C++ 编译器和生成属性

  2. 选择“配置属性”>“C/C++”>“代码生成”属性页面

  3. 修改“缓冲区安全检查”属性。

以编程方式设置此编译器选项

示例

此示例将发生缓冲区溢出。 这会导致应用程序在运行时失败。

// compile with: /c /W1
#include <cstring>
#include <stdlib.h>
#pragma warning(disable : 4996)   // for strcpy use

// Vulnerable function
void vulnerable(const char *str) {
   char buffer[10];
   strcpy(buffer, str); // overrun buffer !!!

   // use a secure CRT function to help prevent buffer overruns
   // truncate string to fit a 10 byte buffer
   // strncpy_s(buffer, _countof(buffer), str, _TRUNCATE);
}

int main() {
   // declare buffer that is bigger than expected
   char large_buffer[] = "This string is longer than 10 characters!!";
   vulnerable(large_buffer);
}

另请参阅

MSVC 编译器选项
MSVC 编译器命令行语法