
I have the idea that we ought to be able to implement the equivalent of a unix chroot command, but all within the haskell IO monad. So the idea would be that if I want to do some operation that only requires access to, say /var/cache/test, but involves user-provided input which I don't trust (and if I don't want to trust the security of my parsing of the possibly malicious input), I could run: chroot "/var/cache/test" $ complicated_function "/" scary_user_input which would be the equivalent of complicated_function "/var/cache/test" scary_user_input except that every file and directory access (read or write or stat or whatever) would be forced to be in a subdirectory of /var/cache/test. Moreover, calls such as system would not be allowed (that is, they'd throw an isChrootError exception), since such calls of course could not be verified to touch no file outside of /var/cache/test (at least not using only haskell). Actually, there are two ways this could be done. One is the one mentioned above, which is roughly equivalent to a unix chroot. Another (now that I think about it, probably more elegant) would be to implement a restrict_access type of function, which wouldn't change the meaning of any file paths as a chroot does, but would simply cause any access outside of the specified directory to fail. This would have the advantage of being transparent to the writer of complicated_function--and might be extended to allow access to more than one directory (e.g. for a webserver of only static pages maybe /var/www and /etc/myserver/). As I see it, implementing such a function would require support in the IO monad (some of which may already be there?), since it would need to know for each primitive operation which arguments are file paths, plus whether that operation is even legal in a chroot environment. Or perhaps it's the other way around, and every primitive operation would need to support chroot... I know I would find such a function exceedingly comforting, since I wonder from time to time what would happen if users stick weird filenames in the wrong places, or make symlinks to things they should not have access to. And the cool thing is that with haskell's monadic IO, I think it could be done in a very elegant manner, and in such a way that it is natural (and simple) to wrap a chroot around individual operations, giving finer grained control than I can imagine doing any other way. Any ideas whether such a feat would be possible, and if so how hard it would be? -- David Roundy http://www.abridgegame.org

I have the idea that we ought to be able to implement the equivalent of a unix chroot command, but all within the haskell IO monad.
[...]
As I see it, implementing such a function would require support in the IO monad (some of which may already be there?), since it would need to know for each primitive operation which arguments are file paths, plus whether that operation is even legal in a chroot environment. Or perhaps it's the other way around, and every primitive operation would need to support chroot...
Implementing barriers of this kind requires that your code has a "narrow waist". That is, if you look at a callgraph for your system (including libraries, runtime system, system calls and anything else you have the option of changing), you need to find a small number of edges in the callgraph which all relevant calls pass through. If the waist is not narrow, it is a huge amount of work to insert and maintain all the tests and your chances of getting them all are pretty slim. Unfortunately, the foreign function interface is specifically designed to make the IO monad be a very, very broad waist. Every library is free to add further IO operations. Worse, functions like 'System.system' allow arbitrary strings to be passed out of Haskell and interpreted there. Another problem is the unsafePerformIO function which could easily result in code that is supposed to be protected by your chroot function to be executed after the chroot call has finished. I think your best bet is to catch problems at the system call interface (either using the Unix chroot system call itself or by interposing on calls to 'open'). Or, tackle the same problem in a different way by extending Haskell with a datasource-tracking feature along the lines of taint perl. -- Alastair Reid
participants (2)
-
Alastair Reid
-
David Roundy