用Visual C++ 2005 Express Edition构建安全代码

本文内容:
C运行时库的新安全特性
使用标准C++库
标准C++库的边界检查
编译器安全特性
新的C++编程语言

引言
      对编程爱好者来说,想要简单快速地生成安全可靠的程序,如今又多了一个新的选择,那就是微软公司刚刚发布不久的Visual C++ 2005 Express Edition,它是Microsoft Visual C++ 2005系列中最初级的版本,我们可以把它看作是个人版,可以从微软的官方网站免费下载使用。在Visual C++ 2005中,新的语言和新的库特性,可使编写安全可靠的程序变得比以往更加容易,同时,它也提供了标准C++语言强大的功能和高度的可伸缩性,而且,对 .NET Framework编程而言,它可能是最强大的语言了。
 
 
C运行时库的安全特性
      如果正在使用C运行时库构建应用程序,那么应感到欣慰的是,Visual C++现在已经包含了大多数库函数更安全的版本。那些参数中有缓冲区的函数,现在已加入了长度参数,以确保数据不会超出缓冲区,同时也有更多的函数现在可以检查参数的有效性了,以保证在必要时,可调用非法参数处理程序。
      在C运行时库中,最靠不住的函数就是gets了,它用于从标准输入中读取一行,请看下列代码: 

      第一行声明了缓冲区变量,并把缓冲区内的字符初始化为零;在声明变量时就把变量初始化为一个确定的值,这是一个非常好的习惯。接着,gets函数从标准输入流中读取一行,并存入到缓冲区中。那么错在哪里呢?C风格的数组在传递给函数时不是传值,而是传递了指向第一个数组元素的指针。因此,函数把char[]当成了char*,从而指针中就不再包含用于确认缓冲区大小的信息。那么gets将怎样做呢?它假定缓冲区为无穷大(实际上精确到UINT_MAX),只是简单地从输入流中一直把读取的数值复制到缓冲区中。攻击者很容易利用这一点,现在,这种类型的错误已经是一个“臭名昭著”的缓冲区溢出问题了。

      大多数的最初C运行时库函数都受类似问题的影响,原因是缺乏对参数的验证。而现在是一个“安全第一”的世界,所有此类的函数都应该被提供了更好安全性的相同函数所取代,当然,这要基于现有的代码在多大程度上使用了老式的库函数,可能要花上一点时间来移植到新的安全版本。这些新的函数有一个 _s 后缀,例如,gets函数被gets_s取代,而strcpy被strcpy_s取代,如下例:

      gets_s函数新增了一个参数,用于指示最大可写入的字符数(包含了一个空值结束符)。这儿使用的sizeof操作符,可使编译器在编译时确定数组的长度,注意,sizeof返回的是操作数的字节数,必须要除以数组中的第一个元素,从而得到数组中的元素个数。如果将来要移植到Unicode上,可使用以字符计量缓冲区大小的_getws_s函数。

      前面还提到,另一个需要安全检查的常用函数是非常类似的strcpy,像gets函数一样,它不能确定可用的缓冲区大小,只能假定有足够大的空间可容纳源字符串,这将在运行时导致不可预知的行为,下面是使用安全的strcpy_s函数的例子:

      以上是调用新的strcpy_s函数时的大体样子,最明显的不同之处是新增的参数,它以字符计量来确定目的缓冲区的大小。这允许strcpy_s函数进行运行时检查,以保证字符不会超出目的缓冲区。

      另外,还有其他类型的检查可用于保证函数参数的有效性,在以debug构建程序时,有断言(assertion)检查,当条件未满足时,它会显示调试报告。而在debug和release构建时,如果一个特定的条件未满足,将会调用非法参数处理程序,默认情况下,会产生一个访问违例并终止当前程序。这样可保证程序不会有不可预知的行为,当然,只要确保strcpy_s之类的函数在调用时没有非法函数,就可防止此类问题的发生。

      通过使用新的_countof宏,可简化前一个例子,还能防止sizeof操作符的误用。宏_countof返回C风格数组中的元素数,但如果传递给它一个原始指针,将不能通过编译。

使用标准C++库 
      看过C运行时库的新增安全部分之后,让我们来看一下标准C++库是怎样有助于减少代码中错误产生的可能性的。 
      从C运行时库转到标准C++库时,其中最有效的一个方法是使用库的vector类。Vector是标准C++库中的一个容器类,模拟了一个一维的T数组,而T可能是任何类型。大多数代码中使用缓冲区的地方,都能由vector类取而代之。让我们再看一下前一节两个例子,在第一个例子中,使用了gets_s函数来从标准输入中读取一行,请看下面的替代方法:

      最大的不同之处是缓冲区变量现已是一个vector对象,vector对象初始大小为10个字符,并由其构造函数初始化为零;表达式&buffer[0]取vector对象中第一个元素的地址,这是传递vector给一个有缓冲区参数的C函数的正确方法。与sizeof操作符不同的是,所有的容器类计量都采用的是元素,而不是字节。例如,vector的size方法就返回容器类中的元素个数。 
      前一节的第二个例子中,使用了strcpy_s函数把字符从源地址复制到目的地址,而vextor类也能被用于取代其中的C风格数组,在演示之前,先来看一下另一个非常有用的标准C++库容器类。 
      Basic_string类可在C++中像使用普通类型那样来使用字符串,它也提供了多种重载操作符,以便为C++程序员提供一种更自然的编程模式。相对strcpy_s和其他字符串操作函数而言,更应该使用basic_string类basic_string是一种类型T的字符容器类,只不过此处的T是字符类型。标准C++库为常用的字符类型都提供了类型定义,string和wstring是分别对应于char和wchar_t元素类型定义,下面的例子演示了basic_string类是多么的简单和安全:

      basic_string同时还提供了可用于其他常用字符串操作的方法和操作符,如字符串拼接和在字符串中进行字链查找。 
      最后,标准C++库也提供了一个非常强大的I/O库,可用于与标准输入、标准输出和文件流的交互,虽然简单但是安全。尽管gets_s函数使用一个vector比使用C风格数组更好,但通过使用对类basic_istream和basic_ostream的类型定义,甚至能更进一步简化,这样写出的代码,不但简单,而且类型安全;不但能读取字符串,还能从流中读取任何类型的数据。

      cin被定义为basic_istream,能从标准输入中读取键入的字符,而wcin是为wchar_t类型准备的。另一方面,cout被定义为basic_ostream,可用来写入到标准输出中。正如你所想的,这种模式可以无限扩展,而不只限于gets_s和puts函数,但在此的真正价值是,如今程序中想要有一丁点安全缺陷都很难喔。

标准C++库的边界检查
       部分标准C++库的容器类与iterator在默认状态下并不提供边界检查,例如,vector的下标操作符在传统意义上来说,是一种快速、但有潜在不安全因素的访问个体元素的方法,如果想检查访问的安全性,可使用at方法。也许新增的安全性检查可能会导致性能降低,但在大多数时候,性能的损失是可以忽略不计的。请看如下的示例函数:

PrintAll函数使用下标操作符,而且函数控制的index在已知状态下是安全的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值