A Haskell Binding for OpenBSD pledge
Overview
EuroBSDCon 2023, Coimbra
- OpenBSD Pledge
- Haskell
- Design
- Code examples
Pledge
Restrict kernel API
#include <unistd.h>
#include <stdio.h>
int main(int argc, char **argv){
("stdio","");
pledge ("hello\n");
printf ("","");
pledge ("hello again\n"); // process gets terminated
printf }
Haskell
Haskell Language
first defined in the 1990s
functional
-> x + x) (\x
length . revert) [1,2,3] (
lazily evaluated
-> x * x) (2 + 2) (\x
pure
with a good type system
GHC
The Glasgow Haskell Compiler
- de facto standard compiler, REPL and RTS
- various backends and platforms
- m:n multithreading
- software transactional memory
- garbage collector
Haskell Ecosystem
- Hackage: central package repo
- Hoogle: search engine
- Cabal: package manager and build system
- Haskell Language Server
Imperative Haskell and Side Effects
Hello World
module Main where
import System.IO (appendFile)
formGreeting :: String -> String
= "hello " <> n <> "!"
formGreeting n
main :: IO ()
= do
main putStrLn "what is your name?"
<- getLine
name appendFile "names.txt" $ name <> "\n"
putStrLn $ formGreeting name
With Promises
module Main where
import System.IO (appendFile)
formGreeting :: String -> String
= "hello " <> n <> "!"
formGreeting n
main :: IO ()
= do
main putStrLn "what is your name?" -- stdio
<- getLine -- stdio
name appendFile "names.txt" $ name <> "\n" -- wpath, stdio
putStrLn $ formGreeting name -- stdio
Introducing Pledge
FFI Binding for Pledge
-- System.OpenBSD.Pledge.Internal
pledge :: Set Promise -> IO ()
-- System.OpenBSD.Pledge.Promise.Type
data Promise =
Stdio | Rpath | Wpath | Cpath | Dpath | Tmppath | Inet | Mcast |
Fattr | Chown | Flock | Unix | Dns | Getpw | Sendfd | Recvfd | Tape
| Tty | Proc | Exec | Prot_exec| Settime | Ps | Vminfo | Id | Route
| Wroute | Audio | Video | Bpf| Unveil | Error
deriving (Show, Eq, Enum, Ord)
c.f. pledge(2)
What to Pledge
module Main where
import System.IO (appendFile)
import System.OpenBSD.Pledge.Promise.Type
import System.OpenBSD.Pledge.Internal
formGreeting :: String -> String
= "hello " <> n <> "!"
formGreeting n
main :: IO ()
= do
main
pledge _putStrLn "what is your name?" -- stdio
pledge _<- getLine -- stdio
name
pledge _appendFile "names.txt" $ name <> "\n" -- wpath, stdio
pledge _putStrLn $ formGreeting name -- stdio
$ fromList [] pledge
Solution
module Main where
import System.IO (appendFile)
import System.OpenBSD.Pledge.Promise.Type
import System.OpenBSD.Pledge.Internal
formGreeting :: String -> String
= "hello " <> n <> "!"
formGreeting n
main :: IO ()
= do
main $ fromList [Stdio, Wpath]
pledge putStrLn "what is your name?"
$ fromList [Stdio, Wpath]
pledge <- getLine
name $ fromList [Stdio, Wpath]
pledge appendFile "names.txt" $ name <> "\n"
$ fromList [Stdio]
pledge putStrLn $ formGreeting name
$ fromList [] pledge
Imperative but Functional
A Puzzle
do
<- getLine -- IO String
s putStrLn s -- String -> IO ()
How is this functional?
With Sugar
do
<- getLine -- IO String
s putStrLn s -- String -> IO ()
is actually
getLine >>= (\s -> putStrLn s)
Bind
getLine >>= (\s -> putStrLn s)
where
(>>=) :: IO a -> (a -> IO b) -> IO b
Make Bind Work for Us
Label Actions
with promoted types
-- System.OpenBSD.MultiPledge
newtype Pledge (zs :: [Promise]) (ps :: [Promise]) a
= Pledge { getAction :: IO a }
requires some explanation
The Explanation
DataKinds
promotes data constructors to types
Stdio :: Promise
Promise :: *
'Stdio :: Promise
Stdio, Inet] :: [Promise]
[Promise] :: *
['Stdio, 'Inet] :: [Promise] '[
For example
annotate base functions
-- System.Directory
getDirectoryContents :: FilePath -> IO [FilePath]
import qualified System.Directory as D (getDirectoryContents)
getDirectoryContents :: FilePath -> Pledge zs '[ 'Rpath] [FilePath]
= Pledge . D.getDirectoryContents getDirectoryContents
A New Bind Operator
-- System.OpenBSD.MultiPledge
(>>=) :: forall zs ps m qs a b.
MonadIO m, SingI zs, SingI ps, SingI qs
(
)=> Pledge (zs `Union` ps) qs m a
-> (a -> Pledge zs ps m b)
-> Pledge zs (ps `Union` qs) m b
best explained with a picture and some code
Closing
Caveats
- no multithreading
- no exec promises
- redundant pledge calls
- not portable (so far)
Resources
Thanks
Björn Gohla