0
size
returns positive valuesavg
head
and tail
are Safeaverage
map
init
init'
insert
List
s ElementsList
keys
Map
eval
create
: Allocate and Fill a ByteString
unsafeTake
: Extract prefix of size n
unpack
: Convert ByteString
into List of Char
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
API
Strategy: 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 ()
Arithmetic
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
How to prevent overflows e.g. writing 5 or 50 zeros into 4-byte block ?
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 -- pointer
-> (PtrN a (fplen fp) -> IO b) -- action
-> IO b -- use
Arithmetic
Refine type to track remaining buffer size
plusPtr :: p:Ptr a
-> o:{Nat | o <= plen p} -- offset 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
Specification
Implementation
pack
: Convert List of Char
into ByteString
Specification
Implementation
unsafeTake
: Extract prefix of size n
Specification
Implementation
unpack
: Convert ByteString
into List of Char
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
"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
.