函数式编程范例中的漏洞?

时间:2009-01-31 04:24:29

标签: security functional-programming

几天前,有几个关于缓冲区溢出漏洞的问题(例如Does Java have buffer overflows?Secure C and the universities - trained for buffer overflow,以命名一对),这可能发生在C等命令式编程语言中。

在函数式编程中,(从我尝试Haskell的非常有限的曝光),我可以看到缓冲区溢出等漏洞不会发生,因为这些问题是由于改变状态而导致的一个程序或一个记忆区域。 (如果我错了,请纠正我。)

如果不考虑编译器,解释器或执行环境中存在的漏洞的可能性,功能编程范例中是否存在任何类型的安全漏洞?功能编程中是否存在任何特定类型的漏洞,而不是命令式编程中存在这些漏洞?

4 个答案:

答案 0 :(得分:15)

如果程序员没有预料到 [某些输入] 会导致 [program] 消耗超过可用资源,那么这就是一个漏洞可能的DoS。这是我见过的所有图灵完备语言的弱点,但是Haskell的懒惰使得更难以推断出计算涉及的内容。

作为一个(相当做作的)例子,

import Control.Monad (when)
import System (getArgs)
main = do
    files <- getArgs
    contents <- mapM readFile files
    flip mapM_ (zip files contents) $ \(file, content) ->
        when (null content) $ putStrLn $ file ++ " is empty"

天真的程序员可能会认为,“Haskell是懒惰的,所以它不会打开并读取文件,直到它需要”,并且“Haskell被垃圾收集,所以一旦完成文件,就可以关闭文件处理”。不幸的是,这个程序实际上只会一次打开大量文件(特定于实现),只有空文件才会关闭文件句柄(实现的生动性规则的副作用):

$ ghc --make -O2 Test
[1 of 1] Compiling Main             ( Test.hs, Test.o )
Linking Test ...
$ strace -etrace=open,close ./Test dir/* /dev/null
...
open("dir/1", O_RDONLY|O_NOCTTY|O_NONBLOCK|O_LARGEFILE) = 3
open("dir/2", O_RDONLY|O_NOCTTY|O_NONBLOCK|O_LARGEFILE) = 4
open("dir/3", O_RDONLY|O_NOCTTY|O_NONBLOCK|O_LARGEFILE) = 5
open("dir/4", O_RDONLY|O_NOCTTY|O_NONBLOCK|O_LARGEFILE) = 6
open("dir/5", O_RDONLY|O_NOCTTY|O_NONBLOCK|O_LARGEFILE) = 7
...
open("/dev/null", O_RDONLY|O_NOCTTY|O_NONBLOCK|O_LARGEFILE) = 255
close(255)
/dev/null is empty
$

您可能不会期望-EMFILE“打开文件太多”错误。

就像我说的,这是一个人为的例子,也可能在其他语言中发生,但是更容易错过Haskell中的某些资源使用。

答案 1 :(得分:5)

由于其执行模式,功能语言具有不受重视的“通过默默无闻的安全性”优势。如果你看一下C程序中的安全漏洞,它们会利用弱类型系统,指针操作和缺少边界检查,但更重要的是,它们利用了一个易于理解的直接执行模型。例如,您可以在C中可靠smash the stack,因为只需获取局部变量的地址,就可以相对轻松地知道堆栈的位置。许多其他漏洞依赖于对执行模型的类似的低级理解。

相比之下,如何将功能代码编译成二进制文件并不是那么明显,因此设计执行注入代码或访问特权数据的方法并不是那么容易。具有讽刺意味的是,执行模型的模糊性通常被认为是函数式语言的弱点,因为程序员并不总是对代码的执行方式有很好的直觉。

答案 2 :(得分:4)

我不这么认为。

正如我所看到的,缓冲区溢出等漏洞与编程/解释器架构/虚拟机无关,而不是编程范式。

例如,如果您在.NET环境(C#,VB等)中使用函数式编程,只要您处理托管对象,就会处理缓冲区溢出。

答案 3 :(得分:1)

在功能实现中比在相应的命令式实现中更容易引起无限递归(以及由此产生的内存使用)。虽然命令式程序在处理格式错误的输入数据时可能会进入无限循环,但功能程序可能会在执行相同处理时吞噬所有可能的内存。通常,运行程序的VM负责限制特定进程可以使用的内存量。 (即使对于本机Unix进程,ulimit也可以提供这种保护。)