aboutsummaryrefslogtreecommitdiff
path: root/src/Cleanse.hsc
blob: bb6094541c72871ca8d6f01dfe6dce528d930e7c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
{-# LANGUAGE ScopedTypeVariables #-}

-- | This module wraps BoringSSL's @OPENSSL_cleanse@, which securely overwrites
-- memory. ("Securely" here means that BoringSSL uses some assembly magic to
-- prevent the compiler from optimizing out the write.) However, the module
-- doesn't actually expose @OPENSSL_cleanse@ directly; instead, it lets you
-- allocate 'ForeignPtr's with cleansing registered as a finalizer. GHC runs all
-- 'ForeignPtr' finalizers prior to program termination, which gives the
-- 'ForeignPtr's allocated this way the approximately same security guarantees
-- as memory allocated through BoringSSL's allocator interface. In particular,
-- unless you exit your program through GHC's foreign function interface, all
-- memory allocated through 'mallocCleansablePtr' will be forcibly cleared prior
-- to program exit.
module Cleanse
  ( mallocCleansablePtr
  ) where

import Foreign
       (FinalizerPtr, ForeignPtr, Storable(poke, sizeOf),
        addForeignPtrFinalizer, mallocForeignPtrBytes, withForeignPtr)
import Foreign.C.Types
import Foreign.ForeignPtr.Compat (plusForeignPtr)

#include <stddef.h>

#include <openssl/mem.h>

-- We implement 'mallocCleansablePtr' using the standard allocator technique of
-- saving the allocated region size immediately before the allocated region.

#def struct __attribute__((__packed__)) Buffer {
  size_t size;
  char data[];
};

bufferSize :: Int
bufferSize = #size struct Buffer

dataOffset :: Int
dataOffset = #offset struct Buffer, data

mallocCleansablePtr :: forall a. Storable a => IO (ForeignPtr a)
mallocCleansablePtr = do
  -- Allocate the buffer.
  let dataSize = sizeOf (undefined :: a)
  fp <- mallocForeignPtrBytes (bufferSize + dataSize)
  -- Save the data size.
  withForeignPtr fp $ \p -> poke p (fromIntegral dataSize :: CSize)
  -- Now that the size is saved, we can register the cleansing finalizer. This
  -- will look at the size and wipe the buffer.
  addForeignPtrFinalizer btlsCleansePtr fp
  -- Return a pointer to the data region.
  return (fp `plusForeignPtr` dataOffset :: ForeignPtr a)

-- The cleansing finalizer itself is totally straightforward.

#def void btlsCleanse(struct Buffer* const p) {
  OPENSSL_cleanse(p->data, p->size);
}

foreign import ccall "&btlsCleanse"
  btlsCleansePtr :: FinalizerPtr a