0
size
returns positive valuesavg
head
and tail
are Safefoldr1
average
map
init
init'
insert
List
s ElementsList
keys
Map
eval
create
unsafeTake
unpack
chop
head
and tail
are Safefoldr1
average
map
init
init'
Modern languages are built on top of C
Implementation errors could open up vulnerabilities
A String Truncation Function
import Data.ByteString.Char8 (pack, unpack)
import Data.ByteString.Unsafe (unsafeTake)
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
Works if you use the valid prefix size
λ> let ex = "Ranjit Loves Burritos"
λ> heartBleed ex 10
"Ranjit Lov"
Leaks overflow buffer if invalid prefix size!
λ> let ex = "Ranjit Loves Burritos"
λ> heartBleed ex 30
"Ranjit Loves Burritos\NUL\201\&1j\DC3\SOH\NUL"
Strategy: Specify and Verify Types for
Pointer
APIByteString
APIApplication
APIStrategy: Specify and Verify Types for
Pointer
APIByteString
APIApplication
APIErrors at each level are prevented by types at lower levels
Low-level Pointers
data Ptr a
Foreign Pointers
data ForeignPtr a
ForeignPtr
wraps around Ptr
; can be exported to/from C.
Read
peek :: Ptr a -> IO a
Write
poke :: Ptr a -> a -> IO ()
plusPtr :: Ptr a -> Int -> Ptr b
Create
malloc :: Int -> ForeignPtr a
Unwrap and Use
withForeignPtr :: ForeignPtr a -- pointer
-> (Ptr a -> IO b) -- action
-> IO b -- result
Allocate a block and write 4 zeros into it
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
1. Refine pointers with allocated size
measure plen :: Ptr a -> Int
measure fplen :: ForeignPtr a -> Int
Abbreviations for pointers of size N
type PtrN a N = {v:_ | plen v = N}
type ForeignPtrN a N = {v:_ | fplen v = N}
Create
malloc :: n:Nat -> ForeignPtrN a n
Unwrap and Use
withForeignPtr :: fp:ForeignPtr a
-> (PtrN a (fplen fp) -> IO b)
-> IO b
Arithmetic
Refine type to track remaining buffer size
plusPtr :: p:Ptr a
-> o:{Nat|o <= plen p} -- in bounds
-> PtrN b (plen b - o) -- remainder
Read & Write require non-empty remaining buffer
Read
peek :: {v:Ptr a | 0 < plen v} -> IO a
Write
poke :: {v:Ptr a | 0 < plen v} -> a -> IO ()
How to prevent overflows e.g. writing 5 or 50 zeros?
Strategy: Specify and Verify Types for
Pointer
APIByteString
APIApplication
APIErrors at each level are prevented by types at lower levels
A Useful Abbreviation
type ByteStringN N = {v:ByteString| bLen v = N}
Note: length of good2
is 3
which is less than allocated size 5
Claimed length exceeds allocation ... rejected at compile time
create
Allocate and fill a ByteString
Implementation
pack
Specification
Implementation
unsafeTake
Extract prefix string of size n
Specification
Implementation
unpack
Specification
unpack
:: b:ByteString -> StringN (bLen b)
Implementation
unpack b = you . get . the . idea -- see source
Strategy: Specify and Verify Types for
Pointer
APIByteString
APIApplication
APIErrors at each level are prevented by types at lower levels
Lets revisit our potentially "bleeding" chop
chop
{-@ chop :: s:String
-> n:{Nat | n <= len s}
-> {v:String | len v = 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 CUT -->
"Bleeding" chop ex 30
rejected by compiler
Strategy: Specify and Verify Types for
Pointer
APIByteString
APIApplication
APIErrors at each level are prevented by types at lower levels
For a more in depth example, let's take a look at group
, which transforms strings like
"foobaaar"
into lists of strings like
["f","oo", "b", "aaa", "r"]
.
The specification is that group
should produce a list of ByteStrings
We use the type alias
to specify (safety) and introduce a new measure
to specify (precision). The full type-specification looks like this:
As you can probably tell, spanByte
appears to be doing a lot of the work here, so let's take a closer look at it to see why the post-condition holds.
LiquidHaskell infers that 0 <= i <= l
and therefore that all of the memory accesses are safe. Furthermore, due to the precise specifications given to unsafeTake
and unsafeDrop
, it is able to prove that spanByte
has the type
where ByteStringPair b
describes a pair of ByteString
s whose lengths sum to the length of b
.