views:

607

answers:

4

A few days ago, there were a couple questions on buffer overflow vulnerabilities (such as Does Java have buffer overflows?, Secure C and the universities - trained for buffer overflow, to name a couple) which can happen in imperative programming languages such as C.

In functional programming, (from the very limited exposure I've had from trying out Haskell), I can see how vulnerabilities such as buffer overflow wouldn't occur because those problems are a result of changing the state of a program or an area of memory. (Please correct me if I am wrong.)

Without accounting for the possiblity of vulnerabilities present in the compiler, interpreter or execution environment, are there any kind of security vulnerabilities that exist in the functional programming paradigm? Are there any specific types of vulnerabilities that exist in functional programming but not in imperative programming?

+1  A: 

It may be possible to more easily cause unbounded recursion (and the resulting memory use) in a functional implementation than in the corresponding imperative implementation. While an imperative program might just go into an infinite loop when processing malformed input data, a functional program may instead gobble up all the memory it can while doing the same processing. Generally, it would be the responsibility of the VM in which the program is running to limit the amount of memory a particular process can consume. (Even for native Unix processes, ulimit can provide this protection.)

Greg Hewgill
In my experience, the stack is limited by default and you just get a stack overflow and a DoS. ulimit can bound the "standard" stack, but not the total used memory (it pretends it can, but limit on RSS is not implemented); and e.g. the Glasgow Haskell Compiler uses two stacks (one of which is probably not OS-visible), so if you can't bound the total memory you are in trouble. Another compilation method, Continuation-Passing Style, uses the heap instead of the stack (mostly), so you're again in trouble.
Blaisorblade
+4  A: 

I don't think so.

As I see it, instead of programming paradigm, vulnerabilities like buffer overflows has more to do with the compiler/interpreter architecture/virtual machine.

For eg, if you are using functional programming in .NET environment (C#, VB etc), as long as you are dealing with managed objects, buffer overflows will be taken care of.

amazedsaint
> functional programming in .NET environment (C#, VB etc)?? Maybe you mean imperative programming.Anyway, it is true that C vulnerabilities are specific to unsafe languages (having, for instance, no bounds-checking and typecasts) rather than imperative languages in general. But that's really a language matter rather than a VM matter - there's no way to have a safe useful C implementation (compiler/interp./VM).The JVM and the safe subset of CIL only support safe languages, but it's still the semantics of a language which make it safe and easily implementable on such platforms.
Blaisorblade
+9  A: 

If the programmer doesn't anticipate that [some input] could cause [program] to consume more-than-available resources, that's a vulnerability in the form of a possible DoS. This is a weakness of all Turing-complete languages I've seen, but Haskell's laziness makes it harder to reason about what a computation involves.

As a (rather contrived) example,

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"

The naïve programmer may think, "Haskell is lazy, so it won't open and read the files until it needs to", and "Haskell is garbage collected, so once it's done with a file, it can close the file handle". Unfortunately, this program actually will just open lots of files all at once (implementation-specific), and only the empty files will get their filehandles closed (side-effect of implementation's liveliness rules):

$ 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
$

You might not be expecting a -EMFILE "Too many open files" error to ever occur.

Like I said, this is a contrived example, and can happen in other languages too, but it's just easier to miss certain resource usages in Haskell.

ephemient
I upvoted your answer, but please note that it is specific to lazy languages, not to all functional ones.
Blaisorblade
@Blaisorblade Yes, and this isn't even very specific to functional languages, as the same can happen if the programmer expects file handles to be GC'ed when leaving scope in Java. But I suppose you could argue that GC means lazy clean-up :-)
ephemient
Laziness is famous for creating space leaks, as you correctly point out at the beginning. You just happened to choose a somewhat bad example (involving file handles) which is not laziness-specific. The canonical example involves some lazy thunk referencing a huge array.GC is not about cleanup of external resources, it is just about RAM. Finalizers ought not to exist, full stop. Laziness is special because it creates many more memory leaks than usual - and because reasoning about space requires analizing the whole program:http://lambda-the-ultimate.org/node/2273#comment-40161
Blaisorblade
+1  A: 

Functional languages have an under-appreciated "security through obscurity" advantage due to their execution models. If you look at security exploits in C programs, they take advantage of the weak type system, pointer manipulation, and the lack of bounds checking, but more importantly they take advantage of a well-understood, straight-forward execution model. For example, you can reliably smash the stack in C, because it's relatively easy to know where the stack is, just by taking the address of local variables. Many other exploits rely on a similar low-level understanding of the execution model.

In contrast, it's not nearly so obvious how functional code will be compiled down to a binary, so it's not nearly so easy to devise a recipe for executing injected code or accessing privileged data. Ironically, the obscurity of execution models is usually considered a weakness of functional languages, since programmers don't always have a good intuition of how their code will perform.

Chris Conway
First, your point is moot for bounds-checked languages, including functional ones, at least for your examples (smashing the stack?).Then, security through obscurity is never an advantage. Security vulnerabilities on closed-source programs get discovered with debuggers, and at that level you have just assembler. I know the differences in the execution model which are still visible, and I'm not alone. Most crackers don't know such differences, but that's not due to the paradigm, it's just because there's almost no functional program worth an exploit (a webserver, a setuid program...).
Blaisorblade
Additionally, most people don't know the Java execution model, either, and that's even harder to understand, because you don't have a compiled binary but must ask the JVM through some obscure option to dump the JITted code. While at least Ocaml and GHC have a standard binary compiler. Scala and F# are compiled to Java/.NET bytecode formats. I have no idea for Scheme or Common Lisp, even because there are many radically different implementations.But of course, again, this is purely theoretical, given that also Java is bound-checked, or rather a safe language (like functional ones).
Blaisorblade