Case Study: Low Level Memory ============================ {#mem} ------- "HeartBleed" in Haskell -----------------------
**Modern languages are built on top of C**
Implementation errors could open up vulnerabilities
"HeartBleed" in Haskell (1/3) ----------------------------- **A String Truncation Function**
\begin{spec} chop :: String -> Int -> String chop s n = s' where b = pack s -- down to low-level b' = unsafeTake n b -- grab n chars s' = unpack b' -- up to high-level \end{spec} "HeartBleed" in Haskell (2/3) ----------------------------- Works if you use the **valid prefix** size
\begin{spec} λ> let ex = "Ranjit Loves Burritos" λ> heartBleed ex 10 "Ranjit Lov" \end{spec} "HeartBleed" in Haskell (3/3) ----------------------------- Leaks *overflow buffer* if **invalid prefix** size!
\begin{spec} λ> let ex = "Ranjit Loves Burritos" λ> heartBleed ex 30 "Ranjit Loves Burritos\NUL\201\&1j\DC3\SOH\NUL" \end{spec} Types Against Overflows -----------------------
**Strategy: Specify and Verify Types for**
1.
Low-level `Pointer` API
2.
Lib-level `ByteString` API
3.
User-level `Application` API

Errors at *each* level are prevented by types at *lower* levels
{#ptr} ======= 1. Low-level Pointer API ------------------------
Strategy: Specify and Verify Types for
1. **Low-level `Pointer` API** 2. Lib-level `ByteString` API 3. User-level `Application` API
Errors at *each* level are prevented by types at *lower* levels 1. Low-Level Pointer API ======================== API: Types ----------
**Low-level Pointers** \begin{spec} data Ptr a \end{spec}
**Foreign Pointers** \begin{spec} data ForeignPtr a \end{spec}
`ForeignPtr` wraps around `Ptr`; can be exported to/from C.
API: Operations (1/2) ---------------------
**Read** \begin{spec} peek :: Ptr a -> IO a \end{spec}

**Write** \begin{spec} poke :: Ptr a -> a -> IO () \end{spec}

**Arithmetic** \begin{spec} plusPtr :: Ptr a -> Int -> Ptr b \end{spec}
API: Operations (2/2) ---------------------
**Create** \begin{spec} malloc :: Int -> ForeignPtr a \end{spec}

**Unwrap and Use** \begin{spec} withForeignPtr :: ForeignPtr a -- pointer -> (Ptr a -> IO b) -- action -> IO b -- result \end{spec}
Example ------- **Allocate a block and write 4 zeros into it**
\begin{code} zero4 = do fp <- malloc 4 withForeignPtr fp $ \p -> do poke (p `plusPtr` 0) zero poke (p `plusPtr` 1) zero poke (p `plusPtr` 2) zero poke (p `plusPtr` 3) zero return fp where zero = 0 :: Word8 \end{code}
Example ------- **Allocate a block and write 4 zeros into it** How to *prevent overflows* e.g. writing 5 or 50 zeros?
**Step 1** *Refine pointers* with allocated size

**Step 2** *Track sizes* in pointer operations
Refined API: Types ------------------
**1. Refine pointers with allocated size** \begin{spec} measure plen :: Ptr a -> Int measure fplen :: ForeignPtr a -> Int \end{spec}
Abbreviations for pointers of size `N` \begin{spec} type PtrN a N = {v:_ | plen v = N} type ForeignPtrN a N = {v:_ | fplen v = N} \end{spec}
Refined API: Ops (1/3) ----------------------
**Create** \begin{spec} malloc :: n:Nat -> ForeignPtrN a n \end{spec}

**Unwrap and Use** \begin{spec} withForeignPtr :: fp:ForeignPtr a -> (PtrN a (fplen fp) -> IO b) -> IO b \end{spec}
Refined API: Ops (2/3) ----------------------
**Arithmetic** Refine type to track *remaining* buffer size
\begin{spec} plusPtr :: p:Ptr a -> o:{Nat|o <= plen p} -- in bounds -> PtrN b (plen b - o) -- remainder \end{spec}
Refined API: Ops (3/3) ---------------------- **Read & Write require non-empty remaining buffer**
**Read** \begin{spec} peek :: {v:Ptr a | 0 < plen v} -> IO a \end{spec}

**Write** \begin{spec} poke :: {v:Ptr a | 0 < plen v} -> a -> IO () \end{spec}
Example: Overflow Prevented --------------------------- How to *prevent overflows* e.g. writing 5 or 50 zeros?
\begin{code} exBad = do fp <- malloc 4 withForeignPtr fp $ \p -> do poke (p `plusPtr` 0) zero poke (p `plusPtr` 1) zero poke (p `plusPtr` 2) zero poke (p `plusPtr` 5) zero return fp where zero = 0 :: Word8 \end{code}
{#bs} ====== 2. ByteString API -----------------
Strategy: Specify and Verify Types for
1. Low-level `Pointer` API 2. **Lib-level `ByteString` API** 3. User-level `Application` API
Errors at *each* level are prevented by types at *lower* levels 2. ByteString API ================= Type ------- \begin{code} data ByteString = PS { bPtr :: ForeignPtr Word8 , bOff :: !Int , bLen :: !Int } \end{code} Refined Type ------------ \begin{code} {-@ data ByteString = PS { bPtr :: ForeignPtr Word8 , bOff :: {v:Nat| v <= fplen bPtr} , bLen :: {v:Nat| v + bOff <= fplen bPtr} } @-} \end{code} Refined Type ------------
**A Useful Abbreviation** \begin{spec} type ByteStringN N = {v:ByteString| bLen v = N} \end{spec} Legal Bytestrings -----------------
\begin{code} {-@ good1 :: IO (ByteStringN 5) @-} good1 = do fp <- malloc 5 return (PS fp 0 5) {-@ good2 :: IO (ByteStringN 3) @-} good2 = do fp <- malloc 5 return (PS fp 2 3) \end{code}
**Note:** *length* of `good2` is `3` which is *less than* allocated size `5`
Illegal Bytestrings -----------------
\begin{code} bad1 = do fp <- malloc 3 return (PS fp 0 10) bad2 = do fp <- malloc 3 return (PS fp 2 2) \end{code}
Claimed length *exceeds* allocation ... **rejected** at compile time
API: `create` ------------- *Allocate* and *fill* a `ByteString`
**Specification** \begin{code} {-@ create :: n:Nat -> (PtrN Word8 n -> IO ()) -> ByteStringN n @-} \end{code}
**Implementation** \begin{code} create n fill = unsafePerformIO $ do fp <- malloc n withForeignPtr fp fill return (PS fp 0 n) \end{code}
API: `pack` ------------ **Specification** \begin{code} {-@ pack :: s:String -> ByteStringN (len s) @-} \end{code}
**Implementation** \begin{code} pack str = create n $ \p -> go p xs where n = length str xs = map c2w str go p (x:xs) = poke p x >> go (plusPtr p 1) xs go _ [] = return () \end{code}
API: `unsafeTake` ----------------- Extract *prefix* string of size `n`
**Specification** \begin{code} {-@ unsafeTake :: n:Nat -> b:{ByteString | n <= bLen b} -> ByteStringN n @-} \end{code}

**Implementation** \begin{code} unsafeTake n (PS x s l) = PS x s n \end{code}
API: `unpack` ------------- **Specification** \begin{spec} unpack :: b:ByteString -> StringN (bLen b) \end{spec}
**Implementation** \begin{spec} unpack b = you . get . the . idea -- see source \end{spec}
{#heartbleedredux} ================== 3. Application API -------------------
Strategy: Specify and Verify Types for
1. Low-level `Pointer` API 2. Lib-level `ByteString` API 3. **User-level `Application` API**
Errors at *each* level are prevented by types at *lower* levels 3. Application API ================== Revisit "HeartBleed" -------------------- Lets revisit our potentially "bleeding" `chop`
\begin{code} {-@ chop :: s:String -> n:{Nat | n <= len s} -> StringN n @-} chop s n = s' where b = pack s -- down to low-level b' = unsafeTake n b -- grab n chars s' = unpack b' -- up to high-level \end{code}
"HeartBleed" no more --------------------
\begin{code} demo = [ex6, ex30] where ex = ['N','o','r','m','a','n'] ex6 = chop ex 6 -- ok ex30 = chop ex 30 -- out of bounds \end{code}
"Bleeding" `chop ex 30` *rejected* by compiler Recap: Types vs Overflows -------------------------
**Strategy: Specify and Verify Types for**
1. Low-level `Pointer` API 2. Lib-level `ByteString` API 3. User-level `Application` API
**Errors at *each* level are prevented by types at *lower* levels**