11

在编写代码时,您是否有意识地进行防御性编程以确保高程序质量并避免您的代码被恶意利用的可能性,例如通过缓冲区溢出利用或代码注入?

您将始终应用于代码的“最低”质量水平是多少?

4

14 回答 14

12

在我的工作中,我们的代码必须是最高质量的。
所以,我们主要关注两点:

  1. 测试
  2. 代码审查

那些把钱带回家。

于 2008-08-09T18:00:26.907 回答
4

与 abyx 类似,在我所在的团队中,开发人员总是使用单元测试和代码审查。除此之外,我还旨在确保我不包含人们可能使用的代码——我倾向于只为手头的对象按照规范运行所需的基本方法集编写代码. 我发现合并可能永远不会使用但提供功能的方法可能会无意中在系统中引入“后门”或意外/意外使用。

稍后返回并介绍被询问的方法、属性和属性要比预测可能永远不会发生的事情要容易得多。

于 2008-08-09T18:05:09.363 回答
1

我建议对进入“组件”或框架的数据采取防御措施。在“组件”或框架内,人们应该认为数据是“正确的”。

这样想。由调用者提供正确的参数,否则所有函数和方法都必须检查每个传入参数。但是,如果只为调用者进行检查,则只需要检查一次。因此,参数应该是“正确的”,因此可以传递到较低的级别。

  1. 始终检查来自外部来源、用户等的数据
  2. “组件”或框架应始终检查传入调用。

如果有错误并且在调用中使用了错误的值。什么是真正正确的事情?只有一个迹象表明程序正在处理的“数据”是错误的,有些像 ASSERTS 但其他人希望使用高级错误报告和可能的错误恢复。在任何情况下,都会发现数据有问题,在少数情况下,最好继续处理它。(请注意,如果服务器至少不死,那很好)

从卫星发送的图像可能是尝试高级错误恢复的案例...从互联网下载的图像以放置错误图标...

于 2008-08-10T09:26:45.617 回答
1

我建议人们编写在开发环境中是法西斯主义而在生产环境中仁慈的代码。

在开发过程中,您希望尽早捕获错误的数据/逻辑/代码,以防止问题被忽视或导致难以追踪根本原因的后期问题。

在生产中尽可能优雅地处理问题。如果某事确实是不可恢复的错误,则处理它并将该信息呈现给用户。

作为一个例子,这里是我们规范化向量的代码。如果你在开发中给它输入错误的数据,它会尖叫,在生产中它会返回一个安全值。

inline const Vector3 Normalize( Vector3arg vec )
{
    const float len = Length(vec);
    ASSERTMSG(len > 0.0f "Invalid Normalization");
    return len == 0.0f ? vec : vec / len;
}
于 2008-08-13T18:15:11.803 回答
0

我总是努力防止诸如注入攻击之类的事情。但是,当您在内部 Intranet 站点上工作时,大多数安全功能都感觉像是白费力气。我仍然做他们,也许不是那么好。

于 2008-08-09T17:09:25.853 回答
0

嗯,有一套安全的最佳实践。至少,对于数据库应用程序,您需要注意 SQL 注入。

散列密码、加密连接字符串等其他内容也是标准。

从这里开始,这取决于实际应用。

幸运的是,如果您使用 .Net 等框架,则内置了很多安全保护。

于 2008-08-09T17:47:10.223 回答
0

即使对于内部应用程序,您也必须始终进行防御性编程,这仅仅是因为用户可能只是靠运气编写的东西会破坏您的应用程序。当然,您可能不必担心试图骗取您的钱,但仍然如此。始终进行防御性编程并假设应用程序将失败。

于 2008-08-09T17:54:50.303 回答
0

使用测试驱动开发当然有帮助。您一次编写一个组件,然后在编写代码之前枚举所有潜在的输入案例(通过测试)。这可以确保您已经涵盖了所有基础并且没有编写任何没有人会使用但可能会破坏的酷代码。

虽然我没有做任何正式的事情,但我通常会花一些时间查看每节课并确保:

  1. 如果它们处于有效状态,则它们保持在有效状态
  2. 无法在无效状态下构造它们
  3. 在特殊情况下,它们会尽可能优雅地失败(通常这是清理和抛出)
于 2008-08-10T00:53:35.183 回答
0

这取决于。

如果我真的想为自己使用一些东西,那么我将编写我不必考虑的最好的代码。让编译器成为我警告等的朋友,但我不会自动为它创建类型。

代码被使用的可能性越大,即使是偶尔,我也会提高检查级别。

  • 最小幻数
  • 更好的变量名
  • 完全检查和定义的数组/字符串长度
  • 通过合约断言编程
  • 空值检查
  • 异常(取决于代码的上下文)
  • 基本解释性意见
  • 可访问的使用文档(如果 perl 等)
于 2008-08-10T08:13:50.303 回答
0

我将对防御性编程采用不同的定义,正如Josh Bloch的Effective Java所倡导的那样。在书中,他谈到了如何处理调用者传递给你的代码的可变对象(例如,在 setter 中),以及你传递给调用者的可变对象(例如,在 getter 中)。

  • 对于 setter,请确保克隆任何可变对象,并存储克隆。这样,调用者就不能在事后更改传入的对象以破坏程序的不变量。
  • 对于 getter,如果接口允许,则返回内部数据的不可变视图;否则返回内部数据的克隆。
  • 当使用内部数据调用用户提供的回调时,酌情发送不可变视图或克隆,除非您打算通过回调更改数据,在这种情况下,您必须在事后验证它。

带回家的信息是确保没有外部代码可以为您在内部使用的任何可变对象保存别名,以便您可以维护您的不变量。

于 2008-08-10T08:26:46.703 回答
0

我非常认为正确的编程可以防止这些风险。诸如避免已弃用的函数(至少在 Microsoft C++ 库中)由于安全漏洞而通常被弃用,以及验证跨越外部边界的所有内容。

仅从您的代码调用的函数不应该需要过多的参数验证,因为您控制调用者,即不跨越外部边界。其他人的代码调用的函数应该假设传入的参数在某些时候是无效的和/或恶意的。

我处理暴露函数的方法是简单地崩溃,如果可能的话,提供有用的信息。如果调用者无法正确获取参数,那么问题出在他们的代码中,他们应该修复它,而不是你。(显然你已经为你的函数提供了文档,因为它是公开的。)

只有当您的应用程序能够提升当前用户时,代码注入才是一个问题。如果一个进程可以将代码注入您的应用程序,那么它可以轻松地将代码写入内存并执行它。如果无法完全访问系统代码注入攻击是没有意义的。(这就是为什么管理员使用的应用程序不应该被较小的用户写入。)

于 2008-08-10T11:27:30.960 回答
0

根据我的经验,积极采用防御性编程并不一定意味着您最终会提高代码质量。不要误会我的意思,你需要防御性地编程来捕捉用户会遇到的各种问题——当你的程序崩溃时用户不喜欢它——但这不太可能使代码更容易维护,测试等

几年前,我们制定了在我们软件的所有级别使用断言的政策,这与单元测试、代码审查等以及我们现有的应用程序测试套件一起对我们的代码质量产生了显着的积极影响。

于 2008-08-13T17:36:12.633 回答
0

Java、签名的 JAR 和 JAAS。

Java 防止缓冲区溢出和指针/堆栈攻击。

不要使用 JNI。(Java Native Interface) 它向您展示 DLL/共享库。

签署 JAR 以阻止类加载成为安全问题。

JAAS 可以让您的应用程序不信任任何人,甚至不信任它自己。

J2EE 具有(诚然有限的)对基于角色的安全性的内置支持。

其中一些有一些开销,但安全漏洞消失了。

于 2008-09-05T05:31:09.540 回答
0

简单的回答:这取决于. 过多的防御性编码导致严重的性能问题。

于 2009-09-07T11:28:38.657 回答