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

  随后将展示 C/C 中防止缓冲区溢出各种思路方法同时包括静态调整大小思路方法(比如标准 C 库和 OpenBSD/strlcpy 解决方案)和动态调整大小解决方案以及些将为您提供帮助工具最后本文以些有关缓冲区溢出缺陷未来发展形势预测来结束全文讨论

  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(个讨论安全缺陷邮件列表)进行次非正式调查发现 3分的 2参和者认为第缺陷就是缓冲区溢出(要了解相关背景请参阅本文后面 参考资料部分列出“Buffer Overflows: Attacks and Defenses for the Vulnerability of the Decade”文)从 1997 年到 2002 年 3 月CERT/CC 发出半数安全警报都基于缓冲区缺陷

  如果希望自己是安全您需要知道什么是缓冲区溢出如何防止它们可以采用哪些最新自动化工具来防止它们(以及为什么这些工具还不足够)还有如何在您自己中防止它们

  什么是缓冲区溢出?

  缓冲区以前可能被定义为“包含相同数据类型例子个连续计算机内存块”在 C 和 C缓冲区通常是使用和诸如 malloc 这样内存分配例程来实现极其常见缓冲区种类是简单 溢出 是指数据被添加到分配给该缓冲区内存块的外

  如果攻击者能够导致缓冲区溢出那么它就能控制其他值虽然存在许多利用缓冲区溢出思路方法不过最常见思路方法还是“stack-smashing”攻击Elias Levy (又名为 Aleph One)篇经典文章“Smashing the Stack for Fun and Profit”解释了 stack-smashing 攻击Elias Levy 是 Bugtraq 邮件列表(请参阅 参考资料 以获得相关链接)前任主持人

  为了理解 stack-smashing 攻击(或其他任何缓冲区攻击)是如何进行您需要了解些有关计算机在机器语言级实际如何工作知识在类 UNIX 系统上每个进程都可以划分为 3个主要区域:文本、数据和堆栈 文本区域包括代码和只读数据通常不能对它执行写入操作 数据区域同时包括静态分配内存(比如全局和静态数据)和动态分配内存(通常称为 堆) 堆栈区域用于允许/思路方法;它用于记录完成的后返回位置存储中使用本地变量传递参数以及从返回值每当就会使用个新 堆栈帧来支持该了解这些的后让我们来考察个简单

  清单 1. 个简单

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


  假设使用 gcc 来编译清单 1 中简单在 X86 上 Linux 中运行并且紧跟在对 gets 的后中止此时内存内容看起来像什么样子呢?答案是它看起来类似图 1其中展示了从左边低位地址到右边高位地址排序内存布局

  图 1. 堆栈视图

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



  许多计算机处理器包括所有 x86 处理器都支持从高位地址向低位地址“倒”增长堆栈因此每当更多数据将被添加到左边(低位地址)直至系统堆栈空间耗尽在这个例子中 function1它将 c 值压入堆栈然后压入 b 最后压入 a 的后它压入 (ret) 值这个值在 function1 完成时告诉 function1 返回到 何处它还把所谓“已保存帧指针(saved frame poersfp)”记录到堆栈上;这并不是必须保存内容此处我们不需要理解它在任何情况下 function1 在启动以后它会为 buffer1 预留空间这在图 1 中显示为具有个低地址位置

  现在假设攻击者发送了超过 buffer1 所能处理数据接下来会发生什么情况呢?当然C 和 C 员不会自动检查这个问题因此除非员明确地阻止它否则下个值将进入内存中“下个”位置那意味着攻击者能够改写 sfp (即已保存帧指针)然后改写 ret (返回地址)的后当 function1 完成时它将“返回”―― 不过不是返回到 而是返回到攻击者想要运行任何代码

  通常攻击者会使用它想要运行恶意代码来使缓冲区溢出然后攻击者会更改返回值以指向它们已发送恶意代码这意味着攻击者本质上能够在个操作中完成整个攻击!Aleph On 文章(请参阅 参考资料)详细介绍了这样攻击代码是如何创建例如个 ASCII 0 压入缓冲区通常是很困难而该文介绍了攻击者般如何能够解决这个问题

  除了 smashing-stack 和更改返回地址外还存在利用缓冲区溢出缺陷其他途径和改写返回地址区别攻击者可以 smashing-stack(使堆栈上缓冲区溢出)然后改写局部变量以利用缓冲区溢出缺陷缓冲区根本就不必在堆栈上 ―― 它可以是堆中动态分配内存(也称为“malloc”或“”区域)或者在某些静态分配内存中(比如“global”或“”内存)基本上如果攻击者能够溢出缓冲区边界麻烦或许就会找上你了 然而最危险缓冲区溢出攻击就是 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 改为使用那些语言是项艰巨任务

  然而其他语言用户也不应该沾沾自喜有些语言存在允许缓冲区溢出发生&ldquo;转义&rdquo;子句Ada 般会检测和防止缓冲区溢出(即针对这样尝试引发个异常)但是区别可能会禁用这个特性C# 般会检测和防止缓冲区溢出但是它允许员将某些例程定义为“不安全而这样代码 可能 会导致缓冲区溢出因此如果您使用那些转义机制就需要使用 C/C 所必须使用相同种类保护机制许多语言都是用 C 语言来实现(至少部分是用 C 语言来实现 )并且用任何语言编写所有本质上都依赖用 C 或 C 编写因此所有都会继承那些问题所以了解这些问题是很重要

  导致缓冲区溢出常见 C 和 C

  从根本上讲将数据读入或复制到缓冲区中任何时候它需要在复制 的前检查是否有足够空间能够容易看出来异常就不可能会发生 ―― 但是通常会随时间而变更从而使得不可能成为可能

  遗憾C 和 C 附带大量危险(或普遍使用库)甚至连这点(指检查空间)也无法做到对这些任何使用都是个警告信号除非慎重地使用它们否则它们就会成为缺陷您不需要记住这些列表;我真正目是介绍说明这个问题是多么普遍这些包括 strcpy(3)、strcat(3)、sprf(3) (及其同类 vsprf(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) 从理论上讲 snprf 应该是相对安全 ―― 在现代 GNU/Linux 系统中确是这样但是非常老 UNIX 和 Linux 系统没有实现 snprf 所应该实现保护机制

  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 下授予许可证开放源代码软件Software那些 libsafe 版本执行相关检查确保改写不会超出堆栈桢然而这种思路方法仅保护那些特定而不是从总体上防止堆栈溢出缺陷并且它仅保护堆栈而不保护堆栈中局部变量它们最初实现使用了 LD_PRELOAD 而这可能和其他产生冲突Linux Mandrake 发行套件(从 7.1 版开始)包括了 libsafe

  另种思路方法称为“分割控制和数据堆栈”―― 基本思路是将堆栈分割为两个堆栈个用于存储控制信息(比如“返回”地址)个用于控制其他所有数据Xu et al. 在 gcc 中实现了这种思路方法StackShield 在汇编中实现了这种思路方法这样使得操纵返回地址困难多了但它不会阻止改变数据缓冲区溢出攻击

  事实上还有其他思路方法包括随机化可执行位置;Crispen “PoGuard”将这种探测仪思想引申到了堆中等等如何保护当今计算机现在已成了项活跃研究任务

  般保护是不足够

  如此多区别思路方法意味着什么呢?对用户来说面在于大量创新思路方法正在试验的中;长期看来这种“竞争”会更容易看出哪种思路方法最好而且这种多样性还使得攻击者躲避所有这些思路方法更加困难然而这种多样性也意味着开发人员需要 避免编写会干扰其中任何种思路方法代码这在实战上是很容易;只要不编写对堆栈桢执行低级操作或对堆栈布局作假设代码就行了即使不存在这些思路方法这也是个很好建议

  操作系统供应商需要参和进来就相当明显了:至少挑选种思路方法并使用它缓冲区溢出是第问题这些思路方法中最好思路方法通常能够减轻发行套件中几乎半数已知缺陷影响可以证明不管是基于探测仪思路方法更好还是基于非可执行堆栈思路方法更好它们都具有各自优点可以将它们结合起来使用但是少数思路方法不支持这样使用附加性能损失使得这样做不值得我并没有其他意思至少就这些思路方法本身而言是这样;libsafe 和分割控制及数据堆栈思路方法在它们所提供保护方面都具有局限性当然最糟糕解决办法就是根本不对这个第缺陷提供保护还没有实现种思路方法软件Software供应商需要立即计划这样做从 2004 年开始用户应该开始避免使用这样操作系统即它们至少没有对缓冲区溢出提供某种自动保护机制

  然而没有哪种思路方法允许开发人员忽略缓冲区溢出所有这些思路方法都能够被攻击者破坏 攻击者也许能够通过改变中其他数据值来利用缓冲区溢出;没有哪种思路方法能够防止这点如果能够插入某些难于创建值(比如 NUL )那么这其中许多思路方法都能被攻击者绕开;随着多媒体和压缩数据变得更加普遍攻击者绕开这些思路方法就更容易了从根本上讲所有这些思路方法都能减轻从接管攻击到拒绝服务攻击缓冲区溢出攻击所带来破坏遗憾随着计算机系统在更多关键场合使用即使拒绝服务通常也是不可接受因而尽管发行套件应该至少包括种适当防御思路方法并且开发人员应该使用(而不是反对)那些思路方法但是开发人员仍然需要最初就编写无缺陷软件Software

  C/C 解决方案

  针对缓冲区溢出种简单解决办法就是转为使用能够防止缓冲区溢出语言毕竟除了 C 和 C几乎每种高级语言都具有有效防止缓冲区溢出内置机制但是许多开发人员种种原因还是选择使用 C 和 C那么您能做什么呢?

  事实证明存在许多防止缓冲区溢出区别技术但它们都可划分为以下两种思路方法:静态分配缓冲区和动态分配缓冲区首先我们将讲述这两种思路方法分别是什么然后我们将讨论静态思路方法两个例子(标准 C strncpy/strncat 和 OpenBSD strlcpy/strlcat )接着讨论动态思路方法两个例子(SafeStr 和 C std:: )

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

  缓冲区具有有限空间因此实际上存在处理缓冲区空间不足两种可能方式

  “静态分配缓冲区”思路方法:也就是当缓冲区用完时您抱怨并拒绝为缓冲区增加任何空间

  “动态分配缓冲区”思路方法:也就是当缓冲区用完时动态地将缓冲区大小调整到更大尺寸直至用完所有内存

  静态思路方法具有些缺点事实上静态思路方法有时可能会带来区别缺陷静态思路方法基本上就是丢弃&ldquo;过多&rdquo;数据如果无论如何还是使用了结果数据那么攻击者会尝试填满缓冲区以便在数据被截断时使用他希望任何内容来填充缓冲区如果使用静态思路方法应该确保攻击者能够做最糟糕事情不会使得预先假设无效而且检查最终结果也是个好主意

  动态思路方法具有许多优点:它们能够向上适用于更大问题(而不是带来任意限制)而且它们没有导致安全问题截断问题但它们也具有自身问题:在接受任意大小数据时可能会遇到内存不足情况 ―― 而这在输入时也许不会发生任何内存分配都可能会失败而编写真正很好地处理该问题 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 这可能也是个问题

  可以通过种防止缓冲区溢出方式使用 sprf 但是意外地留下缓冲区溢出攻击隐患是非常容易 sprf 使用个控制串来指定输出格式该控制串通常包括“ %s ”(串输出)如果指定串输出精确指定符(比如 %.10s )那么您就能够通过指定输出最大长度来防止缓冲区溢出甚至可以使用“ * ”作为精确指定符(比如“ %.*s ”)这样您就可以传入个最大长度值而不是在控制串中嵌入最大长度值这样问题在于很容易就会不正确地使用 sprf 个“字段宽度”(比如“ %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 将它们内置在 <.h> 中但是 GNU/Linux 系统却不是这样这并不是件那么困难事情;当底层系统没有提供它们时您甚至可以将些小直接包括在自己源代码中

  SafeStr

  Messier 和 Viega 开发了“SafeStr”库这是种用于 C 动态思路方法它自动根据需要调整大小使用 malloc 实现所使用相同窍门技巧Safestr 串很容易转换为常规 C“ char * ”串:safestr 在传递指针“的前”地址处存储重要信息这种技术优点在于在现有中使用 SafeStr 将会很容易SafeStr 还支持“只读”和“受信任”这也可能是有用这种思路方法个问题在于它需要 XXL(这是个给 C 添加异常处理和资源管理支持库)因此您实际上要仅为了处理串而引入个重要Safestr 是在开放源代码 BSD 风格许可证下发布

  C std::

  针对 C 用户种解决方案是标准 std::这是种动态思路方法(缓冲区根据需要而增长)它几乎是不需要伤脑筋 C 语言直接支持该类因此不需要做特殊工作就可使用它并且其他库也可能会使用它就其本身而言 std:: 通常会防止缓冲区溢出但是如果通过它提取个普通 C 串(比如使用 data 或 c_str )那么上面讨论所有问题都会重新出现还要记住 data 并不总是返回以 NUL 结尾

  由于种种历史原因许多 C 库和预先存在都创建了它们自己串类这可能使得 std:: 更难于使用并且在使用那些库或修改那些时效率很低区别串类型将不得不连续地来回转换并非其他所有那些串类都会防止缓冲区溢出并且如果它们对 C 不受保护 char* 类型执行自动转换那么缓冲区溢出缺陷很容易引入那些类中

  工具

  有许多工具可以在缓冲区溢出缺陷导致问题的前帮助检测它们 例如像我 Flawfinder 和 Viega RATS 这样工具能够搜索源代码识别出可能被不正确地使用(基于它们参数来归类)这些工具个缺点在于它们不是完美 ―― 它们会遗漏些缓冲区溢出缺陷并且它们会识别出些实际上不是问题“问题”但是使用它们仍然是值得和手工查找相比它们将帮助您在短得多时间内识别出代码中潜在问题

  结束语

  借助知识、谨慎和工具C 和 C缓冲区溢出缺陷是可以防止不过做起来并没有那么容易特别是在 C 中如果使用 C 和 C 来编写安全您需要真正理解缓冲区溢出和如何防止它们

  种替代思路方法是使用另种编程语言如今几乎其他所有语言都能防止缓冲区溢出但是使用另种语言并不会消除所有问题许多语言依赖 C 库并且许多语言还具有关闭该保护特性机制(为速度而牺牲安全性)但是即便如此不管您使用哪种语言开发人员都可能会犯其他许多从而带来引入缺陷

  不管您做什么开发没有都是极其困难即使最仔细复查通常也会遗漏其中 开发安全最重要思路方法的是 最小化特权那意味着各个部分应该具有它们需要特权点也不能多这样即使具有缺陷(谁能无过?)也可能会避免将该缺陷转化为安全事故但是在实战中如何做到这点呢?下篇文章将研究如何实际地最小化 Linux/UNIX 系统中特权以便您能防止自己不可避免所带来安全隐患

Tags:  缓冲区溢出保护 缓冲区溢出漏洞 缓冲区溢出教程 缓冲区溢出

延伸阅读

最新评论

发表评论