安全编程: 防止缓冲区溢出

1988 年 11 月,许多团伙不得不以“Morris
蠕虫”而切断 Internet 连接,“Morris 蠕虫”是 23 年份之程序员 Robert Tappan
Morris 编写的用于攻击 VAX 和 Sun 机器的先后。
据有关方估算,这个程序大约使得所有 Internet 的 10% 崩溃。 2001 年 7
月,另一个名叫吧“Code Red”的蠕虫病毒最终导致了全球运作微软的 IIS Web
Server 的 300,000 多台计算机受到攻击。2003 年 1
月,“Slammer”(也称之为“Sapphire”)蠕虫利用 Microsoft SQL Server 2000
中的一个弱点,使得南韩以及日本之有些 Internet
崩溃,中断了芬兰底电话机服务,并且令美国飞行订票系统、信用卡网络以及自行出纳机运行缓慢。所有这些攻击
―― 以及任何多抨击,都应用了一个称做吧 缓冲区溢出 的次序瑕疵。

1999 年
Bugtraq(一个议论安全缺陷的邮件列表)进行的一模一样赖非正式调查发现,三分之二底参与者认为首先哀号的老毛病就是缓冲区溢起(要询问有关背景,请参考本文后面 参考资料一对列有底“Buffer
Overflows: Attacks and Defenses for the Vulnerability of the
Decade”一轻柔)。从 1997 年交 2002 年 3 月,CERT/CC
发出之一半有惊无险警报都根据缓冲区缺陷。

如若想自己的顺序是安全之,您需知道什么是缓冲区溢起,如何预防它们,可以采取什么样最新的自动化工具来预防她(以及为何这些家伙还欠缺够),还有哪些以公自己的顺序中防止她。

嘿是缓冲区溢起?

缓冲区以前或许受定义也“包含相同数据类型的实例的一个连续计算机内存块”。在
C 和 C++ 中,缓冲区通常是采用数组和如malloc()和 new 这样的内存分配例程来实现的。极其普遍的缓冲区种类是简简单单的字符数组。 溢出 是靠多少被补加到分配给该缓冲区的外存块之外。

如若攻击者能够造成缓冲区溢起,那么其就会操纵程序中之其它价值。虽然是不少下缓冲区溢起的不二法门,不过最好广的不二法门还是“stack-smashing”攻击。Elias
Levy (又叫做也 Aleph One)的均等篇经典文章“Smashing the Stack for Fun and
Profit”解释了 stack-smashing 攻击,Elias Levy 是 Bugtraq
邮件列表(请参阅 参考资料 以获取有关链接)的先驱者主席。

为知道 stack-smashing
攻击(或另任何缓冲区攻击)是怎样进行的,您需了解有有关电脑以机器语言级实际如何行事的知。在类
UNIX 系统及,每个过程都好划分为老三单至关重要区域:文本、数据与库房。 文件区域席卷代码和特念数据,通常不可知针对它实施写副操作。 多少区域还要概括静态分配的内存(比如全局和静态数据)和动态分配的内存(通常号称 )。 库房区域用来允许函数/方法调用;它用来记录函数得后的归来位置,存储函数中采用的本土变量,向函数传递参数,以及由函数返回值。每当调用一个函数,就会动一个初的 堆栈帧来支撑该调用。了解这些下,让咱们来考察一个简易的次序。

清单 1. 一个简便的次第

void function1(int a, int b, int c) {
   char buffer1[5];
   gets(buffer1); /* DON'T DO THIS */
}
void main() {
  function(1,2,3);
}

 

倘以 gcc 来编译清单 1
中之概括程序,在 X86 上的 Linux 中运行,并且紧跟以对 gets() 的调用之后中止。此时的内存内容看起像啊法呢?答案是其看起好像图
1,其中显示了打左的低位地址及右边的要职地址排序的内存布局。

图 1. 堆栈视图

内存的底部             内存的顶部
  buffer1 sfp ret a b c  
<— 增长 — [ ] [ ] [ ] [ ] [ ] [ ]
堆栈的顶部             堆栈的底部

重重电脑处理器,包括有 x86
处理器,都支持于高位地址为没有地址“倒”增长堆栈。因此,每当一个函数调用另一个函数,更多的数码将吃上加到左手(低位地址),直至系统的库空间耗尽。在斯事例中,当 main() 调用 function1() 时,它将 c 的值压入堆栈,然后压入
b 的价值,最后压入 a 的价值。之后其杀入 return (ret) 值,这个价当 function1() 完成时告诉 function1() 返回到main() 中的哪里。它还拿所谓的“已保存的帧指针(saved
frame
pointer,sfp)”记录及库房上;这并无是必须保留的情,此处我们无待理解她。在另外动静下, function1() 在开行以后,它会否 buffer1() 预留空间,这当图 1
面临显示也具备一个不及地址位置。

现行要是攻击者发送了超 buffer1() 所能够处理的多少。接下来会时有发生啊情形吧?当然,C
和 C++
程序员不见面活动检查是题目,因此只有程序员明确地拦住它们,否则下一个值将进入外存中的“下一个”位置。那表示攻击者能够改变写 sfp (即现已保存的帧指针),然后改变写 ret (返回地址)。之后,当 function1() 完成时,它以“返回”――
不了不是回到 main() ,而是回到到攻击者想使运行的别样代码。

便攻击者会动她想使运行的恶意代码来而缓冲区溢起,然后攻击者会再次改返回值以对她都发送的恶意代码。这表示攻击者本质上能当一个操作中做到整个攻击!Aleph
On 的稿子(请参阅 参考资料)详细介绍了如此的攻击代码是什么创造的。例如,将一个
ASCII 0
字符压入缓冲区通常是生困难的,而该文介绍了攻击者一般怎样能化解之题目。

除去 smashing-stack
和转移返回地址外,还留存用缓冲区溢起缺点的任何路线。与改写返回地址不同,攻击者可
smashing-stack(使堆栈上之休养冲区溢起),然后转写有变量以使缓冲区溢起缺点。缓冲区根本就无需在仓库上
――
它可以是积着动态分配的内存(也称“malloc”或“new”区域),或者在某些静态分配的外存中(比如“global”或“static”内存)。基本上,如果攻击者能够溢起缓冲区的边际,麻烦或就是见面寻找上您了。
然而,最凶险的苏冲区溢起攻击就是 stack-smashing
攻击,因为要程序对攻击者很薄弱,攻击者获得任何机器的控制权就专门爱。

怎么缓冲区溢起这么广阔?

以几拥有电脑语言中,不管是新的语言或老的言语,使缓冲区溢起之外尝试通常还见面给拖欠语言本身自动检测并挡(比如通过吸引一个非常或者根据需要给缓冲区添加更多空间)。但是发生个别栽语言不是这般:C
和 C++ 语言。C 和 C++
语言通常仅是让额外的数额乱刻画及外内存的别样位置,而这种气象也许给运从而致使人心惶惶之结果。更糟糕的凡,用
C 和 C++
编写正确的代码来始终如一地拍卖缓冲区溢起则进一步艰难;很轻就会意外地促成缓冲区溢出。除了
C 和 C++ 使用得 非常周边外,上述这些也许还是匪系的实情;例如,Red
Hat Linux 7.1 中 86% 的代码行都是为此 C 或 C ++
编写的。因此,大量底代码对是问题且是软弱的,因为实现语言无法保障代码避免这题目。

在 C 和 C++
语言本身吃,这个问题是免爱解决之。该问题因 C
语言的常有设计决定(特别是 C 语言中指针和反复组的处理方式)。由于 C++
是极度匹配的 C 语言超集,它吧存有同等之问题。存在一些能够防这题材的
C/C++ 兼容版本,但是她在不过严重的特性问题。而且若改变 C
语言来防护此题材,它就是不再是 C 语言了。许多言语(比如 Java 和
C#)在语法上看似 C,但它其实是差之言语,将现有 C 或 C++
程序改吧运用那些语言是同一项艰巨的任务。

然,其他语言的用户也未应该沾沾自喜。有些语言有允许缓冲区溢起有的“转义”子句。Ada
一般会检测与预防缓冲区溢起(即针对这样的尝尝引发一个坏),但是差的次序可能会见禁用这个特点。C#
一般会检测与防缓冲区溢起,但是其同意程序员将某些例程定义为“不安全之”,而这般的代码 可能 会导致缓冲区溢出。因此要你运那些转义机制,就得以
C/C++ 程序所要采取的均等档次之保护体制。许多语言都是故 C
语言来兑现之(至少部分是用 C 语言来落实之
),并且因此外语言编写的具有程序本质上且依赖用 C 或 C++
编写的库房。因此,所有程序都见面持续那些问题,所以了解这些问题是老大重点之。

回页首

招缓冲区溢起之常见 C 和 C++
错误

从根本上讲,在程序用数据读入或复制到缓冲区中之别样时候,它要在复制 之前反省是不是发足的上空。能够容易看出来的坏就未可能会见生出
―― 但是程序通常会随时间而改变,从而使不可能变成可能。

不满的凡,C 和 C++
附带的大气生死攸关函数(或周边使用的仓库)甚至并这点(指检查空间)也无法完成。程序对这些函数的其它利用都是一个警告信号,因为只有慎重地行使她,否则她就会见化程序瑕疵。您不待牢记这些函数的列表;我的真正目的是印证这题目是多大。这些函数包括 strcpy(3)、strcat(3)、sprintf(3) (及其同类 vsprintf(3) )和 gets(3) 。 scanf() 函数集(scanf(3)、fscanf(3)、sscanf(3)、vscanf(3)、vsscanf(3) 和 vfscanf(3) )可能会见招致问题,因为用一个不曾定义最特别长的格式是老轻之(当读取不吃信赖的输入时,使用格式“%s”总是一个错)。

其它危险的函数包括 realpath(3)、getopt(3)、getpass(3)、streadd(3)、strecpy(3) 和 strtrns(3) 。 从理论及说道, snprintf()应当是相对安全之 ――
在现代 GNU/Linux 系统中审是这样。但是那个老的 UNIX 和 Linux
系统尚未落实 snprintf() 所应该实现的维护机制。

Microsoft
的库中还有在对应平台达成造成同类问题之旁函数(这些函数包括 wcscpy()、_tcscpy()、_mbscpy()、wcscat()、_tcscat()、_mbscat() 和 CopyMemory() )。注意,如果用 Microsoft
的 MultiByteToWideChar() 函数,还存一个大的危殆错误 ――
该函数需要一个绝充分尺寸作为字符数目,但是程序员经常用拖欠尺寸为字节计(更宽广的消),结果造成缓冲区溢起缺点。

其余一个题材是 C 和 C++
对整数具有特别弱的品种检查,一般不会见检测操作这些整数的题材。由于其要求程序员手工做有所的题目检测工作,因此为某种可吃应用的章程不得法地操作那些整数是特别轻之。特别是,当你需要跟缓冲区长要读取某个内容之长时,通常就是这种气象。但是若以一个起号的价值来储存这个长度值会发生啊动静呢
――
攻击者会使她“成为负值”,然后把欠数据说明也一个实在很怪的在吗?当数字值当不同之尺寸中变时,攻击者会用这操作也?数值溢起而吃运呢?
有时处理整数的章程会招程序瑕疵。

回页首

严防缓冲区溢起的初技巧

自然,要吃程序员 犯常见错是杀为难的,而被程序(以及程序员)改呢使用其他一样种语言通常更困难。那么为什么未为脚系统自动保护程序避免这些题材呢?最起码,避免
stack-smashing 攻击是如出一辙起善事,因为 stack-smashing
攻击是特地容易好的。

一般的话,更改底层系统为避免大的安全题材是一个最好好之想法,我们当本文后面呢会见赶上是主题。事实证明存在多可用的防御措施,而有些顶让欢迎的办法而分组为以下项目:

  • 据悉探测方法(canary)的防守。这包
    StackGuard(由 Immunix 所使用)、ProPolice(由 OpenBSD 所使用)和
    Microsoft 的 /GS 选项。
  • 勿执行之堆栈防御。这包括 Solar
    Designer 的 non-exec 补丁(由 OpenWall 所使用)和 exec shield(由
    Red Hat/Fedora 所使用)。
  • 其他措施。这包 libsafe(由 Mandrake
    所使用)和仓库分割方法。

不满的凡,迄今所见之兼具方都负有短,因此它不是万能药,但是她会提供有助。

冲探测方法的守

研讨人口 Crispen Cowan 创建了一个称为
StackGuard 的幽默方法。Stackguard 修改 C
编译器(gcc),以便将一个“探测”值插入到回地址的前。“探测仪”就像煤矿中的探测仪:它在某个地方来故障时起警示。在旁函数返回之前,它执行检查为保探测值没有改观。如果攻击者改写返回地址(作为
stack-smashing
攻击的等同有些),探测仪的价值可能就是见面转移,系统内虽会见相应地中止。这是一模一样栽有效的方式,不过如果留心这种方法无法预防缓冲区溢起改变写其他价值(攻击者仍然会采取这些价值来攻击系统)。人们呢就扩展这种方式来保安其他价值(比如堆上的值)。Stackguard(以及其它防御措施)由
Immunix 所使用。

IBM 的 stack-smashing
保护程序(ssp,起初名也 ProPolice)是 StackGuard
的主意的同样种转移形式。像 StackGuard 一样,ssp
使用一个改过的编译器在函数调用中插入一个探测仪以检测堆栈溢出。然而,它吃这种基本的思路上加了有些诙谐的变型。
它对存储局部变量的位置展开双重排序,并复制函数参数中的指针,以便其为于其它数组之前。这样增强了ssp
的维护力量;它意味着缓冲区溢起不见面改指针值(否则会控制指针的攻击者就能用指针来支配程序保存数据的职务)。默认情况下,它不见面检测所有函数,而就是检测确实需要保护之函数(主要是行使字符数组的函数)。从理论及讲话,这样见面稍微削弱保护能力,但是这种默认行为改进了性,同时还会防大多数问题。考虑到实用的素,它们为独立为系统布局的方式采取
gcc 来促成其的方法,从而使其再便于使。从 2003 年 5
月的揭示版开始,广被夸赞之
OpenBSD(它最主要关注安全性)在他们的整整发行套件中行使了 ssp(也叫做
ProPolice)。

Microsoft 基于 StackGuard
的果实,添加了一个编译器标记(/GS)来促成该 C 编译器中的探测仪。

未执行之库防御

其它一样种方式首先让以库房上实施代码变得不容许。
遗憾的是,x86
处理器(最常见的计算机)的内存保护机制无法容易地支撑这点;通常,如果一个外存页是不过读的,它便是可尽之。一个名叫
Solar Designer 的开发人员想发生了平等种基础和电脑机制的灵性组合,为 Linux
内核创建了一个“非执行的库补丁”;有了是补丁,堆栈上之次第就算不再会像平常的那样在
x86 上运行。 事实证明在有点情况下,可执行程序 需要每当库上;这包信号处理和跳板代码(trampoline)处理。trampoline
是偶尔是因为编译器(比如 GNAT Ada
编译器)生成的怪结构,用以支持像嵌套子例程之类的构造。Solar Designer
还解决了怎样当戒备攻击的而使这些特殊情况不吃影响的题目。

Linux 中落实此目的的首补丁在 1998
年给 Linus Torvalds
拒绝,这是盖一个有趣之缘由。即使不可知以代码放到堆栈上,攻击者也得使缓冲区溢出来要程序“返回”某个现有的子例程(比如
C
库中的某子例程),从而进行抨击。简而言之,仅只是是有着不可实行之堆栈是不足够的。

一段时间之后,人们又想有了千篇一律种预防拖欠问题之初思路:将有可实行代码转移到一个叫“ASCII
保护(ASCII
armor)”区域的内存区。要懂得当下是怎工作之,就亟须掌握攻击者通常不克应用相似的休养生息冲区溢起攻击来插入
ASCII NUL 字符(0)这个谜底。 这意味着攻击者会意识,要如一个序返回包含
0 的地点是死窘迫的。由于这谜底,将兼具可尽代码转移到含有 0
的地点便会见使攻击该次困难多了。

具备此特性之极度要命连续内存范围是打 0 到
0x01010100 的同一组内存地址,因此其就是叫命名为 ASCII
保护区域(还有装有此属性的其他地点,但它是散落的)。与非可尽的库房相结合,这种艺术就一定有价了:非可实施的仓库阻止攻击者发送可尽代码,而
ASCII
保护内存使得攻击者难于通过运用现有代码来绕了无可实施堆栈。这样将维护程序代码避免堆栈、缓冲区和函数指针溢起,而且都不欲重新编译。

只是,ASCII
保护内存并无适用于有程序;大程序也许无法装入 ASCII
保护内存区域(因此这种保护是免周全的),而且有时攻击者 能够用 0 插入目的地址。
此外,有些实现无支持跳板代码,因此恐怕要对急需这种保护之主次禁用该特性。Red
Hat 的 Ingo Molnar 在他的“exec-shield”补丁中贯彻了这种思考,该补丁由
Fedora 核心(可自从 Red Hat 获得其的免费版本)所运用。最新版本的 OpenWall
GNU/Linux (OWL)使用了 Solar Designer 提供的这种艺术的兑现(请参阅 参考资料 以获取对这些本子的链接)。

其余方法

尚出另过多艺术。一栽方式就是是如果标准库对攻击更具有抵抗力。Lucent
Technologies 开发了 Libsafe,这是差不多单专业 C 库函数的包装,也不怕是比如说 strcpy() 这样就领略之针对性 stack-smashing
攻击老软的函数。Libsafe 是当 LGPL
下给予许可证之绽开源代码软件。那些函数的 libsafe
版本执行有关的检查,确保数组改写不见面超越堆栈桢。然而,这种方式才保护那些特定的函数,而不是由总体达成防范堆栈溢出缺点,并且其仅仅保护仓,而无维护仓中之局部变量。它们的最初实现以了 LD_PRELOAD ,而当时或许与其它程序来冲突。Linux
的 Mandrake 发行套件(从 7.1 版开始)包括了 libsafe。

外一样种植办法称为“分割控制与数据堆栈”――
基本的思绪是将堆积栈分割为简单个仓库,一个用于存储控制信息(比如“返回”地址),另一个用来控制其他具备数据。Xu
et al. 在 gcc 中实现了这种方式,StackShield
在汇编程序中贯彻了这种方法。这样令操纵返回地址困难多了,但其不见面拦改变调用函数的数码的复苏冲区溢起攻击。

骨子里还出任何方式,包括随机化可执行程序的岗位;Crispen
的“PointGuard”将这种探测仪思想引申到了堆积如山着,等等。如何保障当今底计算机现在已经改为了同一宗活跃的研讨任务。

回页首

相似保护是不足够的

然多不同的措施表示什么啊?对用户来说,好的一派在大量翻新之方式在试验之中;长期看来,这种“竞争”会重复易于看哪种办法极其好。而且,这种多样性还令攻击者躲避所有这些点子更加不便。然而,这种多样性也代表开发人员需要 避免编排会惊动其中任何一样种方式的代码。这在实践上是很容易之;只要非修对堆放栈桢执行低级操作还是针对仓的布局作而的代码就行了。即使不有这些艺术,这为是一个要命好之建议。

操作系统供应商需要与进去就一定强烈了:至少挑选一种植方法,并使用她。缓冲区漫起是第一声泪俱下的问题,这些方式吃最为好之艺术一般能够减轻发行套件中几乎拦腰早就知道缺陷的震慑。可以证明,不管是根据探测仪的法子重复好,还是冲不可尽堆栈的办法更好,它们都有所各自的独到之处。可以拿其组成起来用,但是个别方式无支持这样使,因为附加的性质损失让这样做不值得。我并无其余意思,至少就这些主意本身而言是这般;libsafe
和撤并控制和数量堆栈的计在它所提供的维护地方都富有局限性。当然,最不好之解决办法就是一向无对准之第一号的缺陷提供保护。还从未实现同种艺术的软件供应商需要立即计划这样做。从
2004
年初始,用户应开始免采取这样的操作系统,即其至少没有针对缓冲区溢起提供某种活动保护机制。

只是,没有呀种方法允许开发人员忽略缓冲区溢出。所有这些艺术都能让攻击者破坏。
攻击者也许能由此反函数中其他数据的价值来运缓冲区溢起;没有啊种方法会防这点。如果能够插某些难于创建的价值(比如
NUL
字符),那么这之中的群计还能够于攻击者绕开;随着多媒体和减少数量易得更加广泛,攻击者绕开这些方法就再度便于了。从根本上讲,所有这些点子还能够减轻从程序接管攻击到拒绝服务攻击的苏冲区溢起攻击所带动的损坏。遗憾之是,随着计算机体系于又多要场合的运,即使拒绝服务通常为是不可接受的。因而,尽管发行套件应该至少包括同栽适于的守措施,并且开发人员应该使(而非是不以为然)那些方法,但是开发人员仍然需要最初就编写无缺陷的软件。

回页首

C/C++ 解决方案

针对缓冲区溢起之一样种简单解决办法就是转为采取能够防缓冲区溢起的语言。毕竟,除了
C 和 C++
外,几乎每种高级语言都享有实用预防缓冲区溢起的置机制。但是不少开发人员因为种种原因还是选择使用
C 和 C++。那么您会举行什么吗?

事实证明存在很多戒缓冲区溢起之不比技术,但它还不过分割也以下简单栽办法:静态分配的缓冲区和动态分配的缓冲区。首先,我们将叙这简单栽方法分别是呀。然后,我们用讨论静态方法的鲜独例子(标准
strncpy/strncat
OpenBSD 的strlcpy/strlcat ),接着讨论动态方法的有数单例(SafeStr
和 C++ 的 std::string )。

回页首

主要选择:静态和动态分配的缓冲区

缓冲区有有限的上空。因此实际在处理缓冲区空间欠缺之一定量种植或方式。

  • “静态分配的缓冲区”方法:也就是当缓冲区用完经常,您抱怨并拒绝也缓冲区增加其它空间。
  • “动态分配的缓冲区”方法:也不怕是当缓冲区用完经常,动态地将缓冲区大小调整到再次特别的尺码,直至用了所有内存。

静态方法具有部分欠缺。事实上,静态方法有时可能会见带来不同之瑕疵。静态方法基本上就是是抛弃“过多之”数据。如果程序无论如何要采用了结果数据,那么攻击者会尝试填满缓冲区,以便在多少被截断时用外想之另内容来填充缓冲区。如果使用静态方法,应该保证攻击者能够做的极致糟糕的工作不见面令预先的假设无效,而且检查最终结果为是一个好主意。

动态方法有众多亮点:它们能提高适用于更可怜的问题(而未是带来任意的界定),而且其从不招安全题材之字符数组截断问题。但它们啊不无自己的题材:在承受任意大小的数常常,可能会见碰到内存不足的情形
――
而立当输入时或许不会见生出。任何内存分配还可能会见破产,而编制真正特别好地拍卖该问题之
C 或 C++
程序是可怜拮据的。甚至在内存真正用了之前,也恐怕致计算机变得最好忙碌而无可用。简而言之,动态方法一般使得攻击者发起拒绝服务攻击变得尤为爱。因此还需要限制输入。此外,必须小心设计程序来处理任意位置的内存耗尽问题,而就不是一律项好之事体。

标准 C 库方法

不过简单易行的措施有是简约地以那些设计用来防止缓冲区溢起之业内
C 库函数(即使以采取 C ++,这也是实用的),特别是strncpy(3) 和 strncat(3) 。这些标准 C
库函数一般支持静态分配方式,也即是当数量无法装入缓冲区时丢弃它。这种措施的最酷亮点在于,您可以一定这些函数在其它机器上都可用,并且其他
C/C++
开发人员都见面了解它们。许许多多的顺序还是以这种艺术编写的,并且真可行。

遗憾之是,要对地完成及时点倒是是令人吃惊的不方便。下面是里面的一对题材:

  • strncpy(3) 和 strncat(3) 都务求您吃出 剩余的空间,而未是为出缓冲区的总大小。这之所以会成为问题是为,虽然缓冲区的轻重要分配就未会见扭转,但是缓冲区中剩下的空间量会于历次添加或去数据经常发生变化。这意味程序员必须尽盯住或又计算剩余的上空。这种跟踪或重计算好易失误,而另外不当还或于缓冲区攻击打开方便之门。 
  • 当来了溢起(和多少丢失)时,两个函数都未见面被起简约的喻,因此如果假定检测缓冲区溢起,程序员就务须召开更多的劳作。
  • 使源字符串至少与对象一致长,那么函数 strncpy(3) 还不见面利用 NUL
    来了却字符串;这也许会见在后导致严重破坏。因而,在运作 strncpy(3) 之后,您便用再次结束目标字符串。 
  • 函数 strncpy(3) 还得据此来才将源字符串的 一部分复制到目标中。
    在履此操作时,要复制的字符的数据通常是冲源字符串的系信息来计量的。
    这样的危的处当让,如果忘记了考虑可用的缓冲区空间,那么 便在行使 strncpy(3) 时也说不定会见留缓冲区攻击隐患。这个函数也非会见复制
    NUL 字符,这或许为是一个问题。 
  • 可以通过平等种植预防缓冲区溢起之法子使 sprintf() ,但是殊不知地留下缓冲区溢起攻击隐患是非常容易的。 sprintf() 函数使用一个控制字符串来指定输出格式,该控制字符串通常包括“ %s ”(字符串输出)。如果指定字符串输出的标准指定符(比如%.10s ),那么您便会由此点名输出的最为充分尺寸来防护缓冲区溢出。甚至可以运用“ * ”作为纯粹指定符(比如“ %.*s ”),这样您便得流传一个极酷长值,而未是于控制字符串中置放太充分长值。这样的问题在,很轻就见面不正确地行使sprintf() 。一个“字段宽度”(比如“ %10s ”)仅指定了最为小长 ――
    而未是最最可怜尺寸。“字段宽度”指定符会留下缓冲区溢起隐患,而字段宽度和精确宽度指定符看起来几完全相同
    ――
    唯一的界别在安之版本有一个点号。另一个题材在,精确字段仅指定一个参数的极其可怜长,但是缓冲区需要对组合起来的数据的极度深尺寸调整大小。 
  • scanf() 多重函数具有一个无限要命幅面值,至少
    IEEE Standard 1003-2001
    清楚地确定这些函数一定不克读取超过最深开间之数据。遗憾之凡,并非有正规都知道地确定了立即或多或少,我们无懂得是否具有实现还不错地促成了这些限制(这当现今之
    GNU/Linux 系统上虽 不能毋庸置疑地工作)。如果您依赖它,那么当设置或初始化期间运行小测试来管她能对工作,这样做用凡明智的。

strncpy(3) 还在一个讨厌的性质问题。从理论及言语, strncpy(3) 是 strcpy(3) 的安取代者,但是 strncpy(3) 还会于源字符串结束时行使 NUL
来填充整个目标上空。
这是可怜奇怪之,因为实际并无存在这样做的充分好理由,但是其于同开始就是如此,并且小程序还凭借之特点。这意味着从 strcpy(3) 切换到 strncpy(3) 会降低性能 ――
这在现底电脑达常见不是一个重的题目,但它还是伤的。

那可以规范 C
库的例程来防护缓冲区溢起吧?是的,不过并无便于。如果计划沿着这长长的途径走,您得掌握上述的有中心。或者,您可以应用下几乎节约用设描述的同样栽替代方式。

OpenBSD 的 strlcpy/strlcat

OpenBSD
开发人员开发了一样种不同的静态方法,这种办法基于他们付出之新函数 strlcpy(3) 和 strlcat(3) 。这些函数执行字符串复制与东拼西凑,不过再无便于错。这些函数的原型如下:

size_t strlcpy (char *dst, const char *src, size_t size); 
size_t strlcat (char *dst, const char *src, size_t size);

 

strlcpy() 函数把以 NUL 结尾的字符串从“ src ”复制到“ dst ”(最多 size-1 个字符)。 strlcat() 函数把为 NUL 结尾的字符串 src附加到 dst 的末梢(但是对象被的字符数目将无超过
size-1)。

乍看起,它们的原型和标准 C
库函数并不曾多大分别。但是事实上,它们之间是部分斐然区别。这些函数都领受目标的究竟大小(而无是剩下空间)作为参数。这代表你不要总是地再计算空间尺寸,而就是相同项好出错的任务。此外,只要目标的深浅至少为
1,两个函数都管目标以以 NUL
结尾(您不可知用另内容放入零长度的缓冲区)。如果没有发生缓冲区溢起,返回值始终是整合字符串的长短;这令检测缓冲区溢起真正转移得易了。

不满的凡, strlcpy(3) 和 strlcat(3) 并无是于类 UNIX
系统的标准库中广泛可用。OpenBSD 和 Solaris 将它们坐在 <string.h>
中,但是 GNU/Linux
系统也不是这么。这并无是同一桩那么窘迫的事情;因为当脚系统绝非供其经常,您还可以以部分小函数直接包括于大团结之程序源代码中。

SafeStr

Messier 与 Viega
开发了“SafeStr”库,这是千篇一律栽用于 C
的动态方法,它自动根据需要调动字符串的轻重。使用 malloc() 实现所采用的同技巧,Safestr
字符串很易变为正规的 C“ char * ”字符串:safestr
在传递指针“之前”的地方处存储重要信息。这种技术的优点在于,在存活程序中采用
SafeStr 将会晤好容易。SafeStr
还支持“只念”和“受信赖”的字符串,这为恐怕是立竿见影之。这种措施的一个问题在它需要
XXL(这是一个被 C
添加十分处理以及资源管理支持的库房),因此你实际上如果独自为处理字符串而引入一个重要的仓库。Safestr
是于放源代码的 BSD 风格的许可证下发布的。

C++ std::string

对 C++
用户的外一样栽缓解方案是正规的 std::string 类,这是相同种植动态的法子(缓冲区依据需要而提高)。它几乎是无欲伤脑筋的,因为
C++
语言直接支持该类,因此不需要开特殊之干活便可使她,并且其他库也恐怕会见动它们。就该自身而言,std::string 通常会防止缓冲区溢起,但是若经过其取一个寻常
C 字符串(比如使 data() 或 c_str() ),那么地方讨论的兼具问题都见面又出现。还要记住 data() 并无总是回到以 NUL
结尾的字符串。

由种种历史原因,许多 C++
库和预先在的先后都创了它自己的字符串类。这也许使 std::string 更艰难使用,并且在运那些库或者改动那些程序时效率非常没有,因为不同之字符串类型将不得不连续地往返换。并非任何有那些字符串类都见面防止缓冲区溢起,并且只要其对准
C 不深受保障的 char* 类型执行机关转换,那么缓冲区溢起缺点很容易引入那些看似吃。

回页首

工具

产生过多器得以在缓冲区漫起缺点造成问题之前帮助检测她。
例如,像本人的 Flawfinder 和 Viega 的 RATS
这样的家伙能够搜索源代码,识别出或被免正确地动的函数(基于它的参数来分类)。这些家伙的一个败笔在于,它们不是两全的
――
它们会挂一漏万一些缓冲区溢起缺点,并且其会识别出有实际上不是问题之“问题”。但是下它还是是值得的,因为同手工查找相比,它们以救助你在短缺得差不多之年华外识别出代码中的私问题。

回页首

结束语

凭知识、谨慎和工具,C 和 C++
中的缓冲区溢起缺点是好预防的。不过开起来连没那容易,特别是在 C
中。如果下 C 和 C++
来修安全的次序,您需真正懂缓冲区溢起和怎样预防她。

一如既往种替代方式是下外一样种植编程语言,因为现在的几任何有语言都能防备缓冲区溢出。但是用其它一样种植语言并无会见免去所有题目。许多语言依赖
C
库,并且多言语还具备关闭该保安特色的编制(为快要牺牲安全性)。但是即便如此,不管您运啊种语言,开发人员都或会见犯其他很多谬误,从而带动引入缺陷。

不论是你做什么,开发尚未错的主次都是无比艰苦的,即使最细心的复查通常也会遗留漏其中一部分不当。
开发安全程序的最好要紧措施之一是 极端小化特权。那表示程序的相继组成部分应享有其要的唯一特权,一点乎非克多。这样,即使程序有所短(谁会无过?),也说不定会见避免用该缺陷转化为安全事故。但是在实践中如何做到就点也?下同样首文章以研究什么实际地太小化
Linux/UNIX
系统受的特权,以便你会防备投机不可避免的荒谬所带动平安隐患。

 

参考资料

  • 乃可参考本文在 developerWorks
    全球站点上的 英文原稿. 

  • 阅读 developerWorks 上 David 的 安然编程 专栏系列中的有所文章连载。 

  • David 的书 Secure Programming for Linux
    and Unix HOWTO
     翔介绍了什么开发安全的软件。 
  • “ The What, Why, and How of the
    1988 Internet
    Worm”更详实地介绍了
    1988 年的 Morris 蠕虫事件。 
  • C. Ian Kyer、Warren J. Sheffer 和
    Bruce Salvatore、Fasken Martineau DuMoulin LLP 所著的 New IT Concerns in the Age of
    Anti-Terrorism: How the Canadian Government has Reacted and How
    Business Should
    React 指出,Morris
    蠕虫使得这大致有 88,000 大计算机的 Internet 中之 10%
    的计算机崩溃。 
  • Steve Burnett and Stephen Paine
    所著的 RSA Security’s
    Official Guide to
    Cryptography 
    (McGraw-Hill,2001 年) 在第
    11 章(“Doing it Wrong: The break-ins”)中指出,Morris 蠕虫使得大约
    10% 的 Internet 崩溃,该书还针对安康故障提出了别样有趣的评说。 
  • CERT(R) Advisory CA-2001-19 “Code
    Red” Worm Exploiting Buffer Overflow In IIS Indexing Service
    DLL复详实地介绍了
    Code Red 病毒。 
  • “ Frontline: Cyber War!: The
    Warnings?”总结了各种攻击及其已了解的震慑,包括
    Code Red 和 Slammer。 
  • Aleph One (Elias Levy) 撰写的 Smashing The Stack For Fun
    And Profit一文( Phrack Magazine,1996 年 11
    月 8 日,第 49 期第 14 首文章)阐述了 stack-smashing
    攻击是怎么进展的。在该文刊出之前多年尽管已经以来 stack-smashing
    攻击,但是该文很好地讲述了这些攻击。 
  • David 的文章“ More than a Gigabuck:
    Estimating GNU/Linux’s Size”研究了
    Red Hat Linux 7.1 的源代码。 结果发现此发行套件包括 3
    千差不多万个实际源代码行(source lines of code,SLOC),其中 86% 都是因此
    C 或者 C++ 编写的。 该文还发现,如果采取美国底民俗专有手段,开发之
    Linux 发行套件将待 10 亿美元与 8,000 个人年的基金(以 2000
    年的美元币值计)。 
  • Crispin Cowan、Perry Wagle、Calton
    Pu、Steve Beattie 和 Jonathan Walpole 撰写的 Buffer Overflows: Attacks and
    Defenses for the Vulnerability of the Decade 相同文讨论了防护
    stack-smashing 攻击的 Stackguard 方法;该 Web 站点还蕴含 Cowan
    用于防止攻击的外艺术的参考资料。该文包括 Bugtraq 1999
    年非正式调查 的摘要。 
  • IBM 的 stack-smashing protector
    (ssp,又名为
    ProPolice)Web
    站点提供了关于 ssp 的更多信息。ssp 由 OpenBSD 所使用。 
  • “ Linux kernel patch from the
    Openwall Project”讨论了
    Solar Designer 的当下 Linux
    内核补丁(包括无可实施的仓库组件)。 
  • “ Linux: Exec Shield Overflow
    Protection” 讨论了 Ingo Molnar
    的 exec-shield 方法。 
  • OpenWall GNU/Linux
    (OWL)行使了 Solar Designer
    的免可实行堆栈补丁版本,而 Red Hat
    Fedora虽用了 exec shield ――
    两栽选择都得兑现不可实施的库房(大多数时节)。 
  • Libsafe 是一个用于保护某些正式 C
    函数避免 stack-smashing 攻击的库房。Libsafe 已组成及较新本子的 Mandrake
    Linux批发套件中。 
  • Messier 和 Viega 编写的 安全 C 字符串(Safe C
    String,SafeStr)库举凡一个有意思之库,它提供简单和安的
    C 字符串处理。 
  • XXL
    库大凡一个用以 C
    的线程安全之好处理以及及资源管理库。它当 BSD 许可证下可用。 
  • Flawfinder
    项目主页提供
    Flawfinder,这是一个以 GPL 下与许可证的工具,用于查找 C 和 C++
    程序中之题材。 
  • John Viega 的 简单易行安全审查工具(或称为 RATS)是一个免费之盛开源代码工具,用于对代码和应用程序。 
  • O’Reilly & Associates 正在以 安编程技巧 为题,出版
    Gene Spafford、Simson Garfinkel 和 Alan Schwartz 所著的Practical
    UNIX & Internet Security, 3rd Edition
    遇的多如牛毛文章摘选。 
  • “ 自我管理数据缓冲区内存”( developerWorks,2004 年 1
    月)讲述了如何在 C 代码中不过当实际多少易得可用时才分配内存 ――
    在恰当地使用时,这种办法极其老限度地落了缓冲区溢起之恐怕。 
  • 在 developerWorks Linux
    专区足找到为
    Linux 开发人员准备的再多参考资料。 
  • 在 Developer Bookstore 的 Linux
    专区得找到各种有关
    Linux 的图书。

相关文章