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){
pledge ("stdio","");
printf ("hello\n");
pledge ("","");
printf ("hello again\n"); // process gets terminated
}Haskell
Haskell Language
first defined in the 1990s
functional
(\x -> x + x)(length . revert) [1,2,3]lazily evaluated
(\x -> x * x) (2 + 2)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
formGreeting n = "hello " <> n <> "!"
main :: IO ()
main = do
putStrLn "what is your name?"
name <- getLine
appendFile "names.txt" $ name <> "\n"
putStrLn $ formGreeting nameWith Promises
module Main where
import System.IO (appendFile)
formGreeting :: String -> String
formGreeting n = "hello " <> n <> "!"
main :: IO ()
main = do
putStrLn "what is your name?" -- stdio
name <- getLine -- stdio
appendFile "names.txt" $ name <> "\n" -- wpath, stdio
putStrLn $ formGreeting name -- stdioIntroducing 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
formGreeting n = "hello " <> n <> "!"
main :: IO ()
main = do
pledge _
putStrLn "what is your name?" -- stdio
pledge _
name <- getLine -- stdio
pledge _
appendFile "names.txt" $ name <> "\n" -- wpath, stdio
pledge _
putStrLn $ formGreeting name -- stdio
pledge $ fromList []Solution
module Main where
import System.IO (appendFile)
import System.OpenBSD.Pledge.Promise.Type
import System.OpenBSD.Pledge.Internal
formGreeting :: String -> String
formGreeting n = "hello " <> n <> "!"
main :: IO ()
main = do
pledge $ fromList [Stdio, Wpath]
putStrLn "what is your name?"
pledge $ fromList [Stdio, Wpath]
name <- getLine
pledge $ fromList [Stdio, Wpath]
appendFile "names.txt" $ name <> "\n"
pledge $ fromList [Stdio]
putStrLn $ formGreeting name
pledge $ fromList []Imperative but Functional
A Puzzle
do
s <- getLine -- IO String
putStrLn s -- String -> IO ()How is this functional?
With Sugar
do
s <- getLine -- IO String
putStrLn s -- String -> IO ()is actually
getLine >>= (\s -> putStrLn s)Bind
getLine >>= (\s -> putStrLn s)where
(>>=) :: IO a -> (a -> IO b) -> IO bMake 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]
getDirectoryContents = Pledge . D.getDirectoryContentsA 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 bbest 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