diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,39 @@
 # Changelog
 
+## 0.2.0.0 -- 2026-09-08
+
+Corrects four FFI signatures that did not match the s2n-tls C API. These were
+found by auditing all 245 wrapped functions against the s2n-tls 1.6.4 header.
+`cbits/s2n_wrapper.c` does not include `s2n.h`, so none of them were caught at
+compile time.
+
+### Breaking changes
+
+* `s2n_client_hello_has_extension` and `s2n_cert_get_x509_extension_value` take
+  `Ptr CBool` for their `bool *` out-parameters, previously `Ptr CInt`. s2n
+  writes a single byte, so callers that allocated a `CInt` read three bytes of
+  uninitialized memory and could see a nonzero result for an absent extension.
+  Callers must switch to `alloca @CBool`.
+* `S2nCertTiebreakCallback` is now
+  `Ptr S2nCertChainAndKey -> Ptr S2nCertChainAndKey -> Ptr Word8 -> Word32 -> IO (Ptr S2nCertChainAndKey)`.
+  The previous type took the certificate name by value instead of by pointer,
+  took the name length by pointer instead of by value, and returned `CInt`
+  where s2n expects a certificate chain pointer. Installing a tiebreak callback
+  built against the old type would have crashed.
+* `S2nMemMallocCallback` takes `Ptr Word32` for its `allocated` out-parameter,
+  previously `Word32`. A custom allocator had no way to report its allocation
+  size back to s2n.
+
+### Fixed
+
+* Missing optional symbols are recorded in `missingSymbols` again. `dlsym`
+  throws an `IOError` for an absent symbol rather than returning `nullFunPtr`,
+  which made the null checks in the loader dead code -- a missing optional
+  symbol escaped `withS2nTlsFfi` as an opaque `user error` and `missingSymbols`
+  could never report anything but `[]`.
+* A missing error-reporting symbol now raises the typed `RequiredSymbolNotFound`
+  instead of an untyped `IOError`.
+
 ## 0.1.0.0 -- 2026-04-28
 
 * Initial release
diff --git a/cbits/s2n_wrapper.c b/cbits/s2n_wrapper.c
--- a/cbits/s2n_wrapper.c
+++ b/cbits/s2n_wrapper.c
@@ -1393,8 +1393,8 @@
 }
 
 int s2n_wrap_client_hello_has_extension(
-    int (*fn)(void *, uint16_t, int *),
-    void *client_hello, uint16_t extension_iana, int *exists,
+    int (*fn)(void *, uint16_t, bool *),
+    void *client_hello, uint16_t extension_iana, bool *exists,
     const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
 {
     int result = fn(client_hello, extension_iana, exists);
@@ -2394,8 +2394,8 @@
 }
 
 int s2n_wrap_cert_get_x509_extension_value(
-    int (*fn)(void *, const uint8_t *, uint8_t *, uint32_t *, int *),
-    void *cert, const uint8_t *oid, uint8_t *value, uint32_t *length, int *critical,
+    int (*fn)(void *, const uint8_t *, uint8_t *, uint32_t *, bool *),
+    void *cert, const uint8_t *oid, uint8_t *value, uint32_t *length, bool *critical,
     const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out)
 {
     int result = fn(cert, oid, value, length, critical);
diff --git a/cbits/s2n_wrapper.h b/cbits/s2n_wrapper.h
--- a/cbits/s2n_wrapper.h
+++ b/cbits/s2n_wrapper.h
@@ -602,8 +602,8 @@
     const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
 
 int s2n_wrap_client_hello_has_extension(
-    int (*fn)(void *, uint16_t, int *),
-    void *client_hello, uint16_t extension_iana, int *exists,
+    int (*fn)(void *, uint16_t, bool *),
+    void *client_hello, uint16_t extension_iana, bool *exists,
     const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
 
 int s2n_wrap_client_hello_get_session_id_length(
@@ -985,8 +985,8 @@
     const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
 
 int s2n_wrap_cert_get_x509_extension_value(
-    int (*fn)(void *, const uint8_t *, uint8_t *, uint32_t *, int *),
-    void *cert, const uint8_t *oid, uint8_t *value, uint32_t *length, int *critical,
+    int (*fn)(void *, const uint8_t *, uint8_t *, uint32_t *, bool *),
+    void *cert, const uint8_t *oid, uint8_t *value, uint32_t *length, bool *critical,
     const S2nErrorFuncs *err_funcs, S2nErrorInfo *err_out);
 
 int s2n_wrap_cert_get_utf8_string_from_extension_data_length(
diff --git a/s2n-tls-ffi.cabal b/s2n-tls-ffi.cabal
--- a/s2n-tls-ffi.cabal
+++ b/s2n-tls-ffi.cabal
@@ -1,6 +1,6 @@
 cabal-version:      3.0
 name:               s2n-tls-ffi
-version:            0.1.0.0
+version:            0.2.0.0
 synopsis:           Low-level FFI bindings to the s2n-tls library
 description:
     Low-level FFI bindings to the s2n-tls library.
@@ -12,7 +12,7 @@
 copyright:          2026 Daniel Goertzen
 category:           Cryptography, FFI, Network
 build-type:         Simple
-tested-with:        GHC == 9.8.2
+tested-with:        GHC == 9.12.2
 extra-doc-files:
     CHANGELOG.md
     README.md
@@ -27,7 +27,7 @@
 source-repository this
     type:     git
     location: https://github.com/goertzenator/s2n-tls-ffi.git
-    tag:      v0.1.0.0
+    tag:      v0.2.0.0
 
 library
     exposed-modules:
diff --git a/src/S2nTls/Ffi.hs b/src/S2nTls/Ffi.hs
--- a/src/S2nTls/Ffi.hs
+++ b/src/S2nTls/Ffi.hs
@@ -112,7 +112,7 @@
     S2nErrorFuncs (..),
 ) where
 
-import Control.Exception (Exception, bracket, throwIO)
+import Control.Exception (Exception, IOException, bracket, catch, throwIO)
 import Control.Monad (when)
 import Data.IORef (modifyIORef', newIORef, readIORef)
 import Data.Word (Word16, Word32, Word64, Word8)
@@ -235,13 +235,33 @@
 openLib :: FilePath -> IO DL
 openLib path = dlopen path [RTLD_LAZY, RTLD_LOCAL]
 
--- | Load the error functions (required for meaningful error reporting)
+{- | Look up a symbol, yielding 'nullFunPtr' when it is absent.
+
+'dlsym' signals a missing symbol by throwing an untyped 'IOError' rather than
+returning 'nullFunPtr', so every caller that wants to tolerate a missing symbol
+has to recover it explicitly.
+-}
+dlsymMaybe :: DL -> String -> IO (FunPtr a)
+dlsymMaybe dl name = dlsym dl name `catch` \(_ :: IOException) -> pure nullFunPtr
+
+{- | Load the error functions (required for meaningful error reporting).
+
+Every wrapper calls through these unconditionally -- 's2n_send' and 's2n_recv'
+do so even on the success path -- so a null here would be an immediate segfault
+inside C. Fail at load time with a typed 'RequiredSymbolNotFound' instead.
+-}
 loadErrorFuncs :: DL -> IO S2nErrorFuncs
-loadErrorFuncs dl = do
-    el <- dlsym dl "s2n_errno_location"
-    sd <- dlsym dl "s2n_strerror_debug"
-    et <- dlsym dl "s2n_error_get_type"
-    pure $ S2nErrorFuncs el sd et
+loadErrorFuncs dl =
+    S2nErrorFuncs
+        <$> loadRequired "s2n_errno_location"
+        <*> loadRequired "s2n_strerror_debug"
+        <*> loadRequired "s2n_error_get_type"
+  where
+    loadRequired :: String -> IO (FunPtr a)
+    loadRequired name = do
+        ptr <- dlsymMaybe dl name
+        when (ptr == nullFunPtr) $ throwIO (RequiredSymbolNotFound name)
+        pure ptr
 
 -- | Create a closure that throws MissingSymbol
 throwMissing :: String -> IO a
@@ -262,7 +282,7 @@
         -- Helper to load a symbol with forgiving behavior
         load :: String -> MethodRequirement -> IO (FunPtr a)
         load name req = do
-            ptr <- dlsym dl name
+            ptr <- dlsymMaybe dl name
             when (ptr == nullFunPtr) $ do
                 modifyIORef' missingRef (name :)
             if ptr == nullFunPtr && req == Mandatory
@@ -784,7 +804,7 @@
 foreign import ccall safe "s2n_wrap_client_hello_get_extensions" c_wrap_client_hello_get_extensions :: FunPtr () -> Ptr S2nClientHello -> Ptr Word8 -> Word32 -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CSsize
 foreign import ccall safe "s2n_wrap_client_hello_get_extension_length" c_wrap_client_hello_get_extension_length :: FunPtr () -> Ptr S2nClientHello -> S2nTlsExtensionType -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CSsize
 foreign import ccall safe "s2n_wrap_client_hello_get_extension_by_id" c_wrap_client_hello_get_extension_by_id :: FunPtr () -> Ptr S2nClientHello -> S2nTlsExtensionType -> Ptr Word8 -> Word32 -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CSsize
-foreign import ccall safe "s2n_wrap_client_hello_has_extension" c_wrap_client_hello_has_extension :: FunPtr () -> Ptr S2nClientHello -> Word16 -> Ptr CInt -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_client_hello_has_extension" c_wrap_client_hello_has_extension :: FunPtr () -> Ptr S2nClientHello -> Word16 -> Ptr CBool -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
 foreign import ccall safe "s2n_wrap_client_hello_get_session_id_length" c_wrap_client_hello_get_session_id_length :: FunPtr () -> Ptr S2nClientHello -> Ptr Word32 -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
 foreign import ccall safe "s2n_wrap_client_hello_get_session_id" c_wrap_client_hello_get_session_id :: FunPtr () -> Ptr S2nClientHello -> Ptr Word8 -> Ptr Word32 -> Word32 -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
 foreign import ccall safe "s2n_wrap_client_hello_get_compression_methods_length" c_wrap_client_hello_get_compression_methods_length :: FunPtr () -> Ptr S2nClientHello -> Ptr Word32 -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
@@ -857,7 +877,7 @@
 foreign import ccall safe "s2n_wrap_cert_get_der" c_wrap_cert_get_der :: FunPtr () -> Ptr S2nCert -> Ptr (Ptr Word8) -> Ptr Word32 -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
 foreign import ccall safe "s2n_wrap_connection_get_peer_cert_chain" c_wrap_connection_get_peer_cert_chain :: FunPtr () -> Ptr S2nConnection -> Ptr S2nCertChainAndKey -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
 foreign import ccall safe "s2n_wrap_cert_get_x509_extension_value_length" c_wrap_cert_get_x509_extension_value_length :: FunPtr () -> Ptr S2nCert -> Ptr Word8 -> Ptr Word32 -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
-foreign import ccall safe "s2n_wrap_cert_get_x509_extension_value" c_wrap_cert_get_x509_extension_value :: FunPtr () -> Ptr S2nCert -> Ptr Word8 -> Ptr Word8 -> Ptr Word32 -> Ptr CInt -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
+foreign import ccall safe "s2n_wrap_cert_get_x509_extension_value" c_wrap_cert_get_x509_extension_value :: FunPtr () -> Ptr S2nCert -> Ptr Word8 -> Ptr Word8 -> Ptr Word32 -> Ptr CBool -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
 foreign import ccall safe "s2n_wrap_cert_get_utf8_string_from_extension_data_length" c_wrap_cert_get_utf8_string_from_extension_data_length :: FunPtr () -> Ptr Word8 -> Word32 -> Ptr Word32 -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
 foreign import ccall safe "s2n_wrap_cert_get_utf8_string_from_extension_data" c_wrap_cert_get_utf8_string_from_extension_data :: FunPtr () -> Ptr Word8 -> Word32 -> Ptr Word8 -> Ptr Word32 -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO CInt
 foreign import ccall safe "s2n_wrap_external_psk_new" c_wrap_external_psk_new :: FunPtr () -> Ptr S2nErrorFuncs -> Ptr S2nError -> IO (Ptr S2nPsk)
diff --git a/src/S2nTls/Ffi/Types.hs b/src/S2nTls/Ffi/Types.hs
--- a/src/S2nTls/Ffi/Types.hs
+++ b/src/S2nTls/Ffi/Types.hs
@@ -790,7 +790,7 @@
 type S2nMemCleanupCallback = FunPtr (IO CInt)
 
 -- | Memory allocation callback.
-type S2nMemMallocCallback = FunPtr (Ptr (Ptr ()) -> Word32 -> Word32 -> IO CInt)
+type S2nMemMallocCallback = FunPtr (Ptr (Ptr ()) -> Word32 -> Ptr Word32 -> IO CInt)
 
 -- | Memory free callback.
 type S2nMemFreeCallback = FunPtr (Ptr () -> Word32 -> IO CInt)
@@ -811,7 +811,7 @@
 type S2nClientHelloFn = FunPtr (Ptr S2nConnection -> Ptr () -> IO CInt)
 
 -- | Certificate tiebreak callback.
-type S2nCertTiebreakCallback = FunPtr (Ptr S2nCertChainAndKey -> Ptr S2nCertChainAndKey -> Word8 -> Ptr (Ptr S2nCertChainAndKey) -> IO CInt)
+type S2nCertTiebreakCallback = FunPtr (Ptr S2nCertChainAndKey -> Ptr S2nCertChainAndKey -> Ptr Word8 -> Word32 -> IO (Ptr S2nCertChainAndKey))
 
 -- | Verify host callback.
 type S2nVerifyHostFn = FunPtr (CString -> CSize -> Ptr () -> IO Word8)
@@ -972,7 +972,7 @@
   , s2n_client_hello_get_extensions :: Ptr S2nClientHello -> Ptr Word8 -> Word32 -> IO (Either S2nError CSsize)
   , s2n_client_hello_get_extension_length :: Ptr S2nClientHello -> S2nTlsExtensionType -> IO (Either S2nError CSsize)
   , s2n_client_hello_get_extension_by_id :: Ptr S2nClientHello -> S2nTlsExtensionType -> Ptr Word8 -> Word32 -> IO (Either S2nError CSsize)
-  , s2n_client_hello_has_extension :: Ptr S2nClientHello -> Word16 -> Ptr CInt -> IO (Either S2nError CInt)
+  , s2n_client_hello_has_extension :: Ptr S2nClientHello -> Word16 -> Ptr CBool -> IO (Either S2nError CInt)
   , s2n_client_hello_get_session_id_length :: Ptr S2nClientHello -> Ptr Word32 -> IO (Either S2nError CInt)
   , s2n_client_hello_get_session_id :: Ptr S2nClientHello -> Ptr Word8 -> Ptr Word32 -> Word32 -> IO (Either S2nError CInt)
   , s2n_client_hello_get_compression_methods_length :: Ptr S2nClientHello -> Ptr Word32 -> IO (Either S2nError CInt)
@@ -1062,7 +1062,7 @@
   , s2n_cert_get_der :: Ptr S2nCert -> Ptr (Ptr Word8) -> Ptr Word32 -> IO (Either S2nError CInt)
   , s2n_connection_get_peer_cert_chain :: Ptr S2nConnection -> Ptr S2nCertChainAndKey -> IO (Either S2nError CInt)
   , s2n_cert_get_x509_extension_value_length :: Ptr S2nCert -> Ptr Word8 -> Ptr Word32 -> IO (Either S2nError CInt)
-  , s2n_cert_get_x509_extension_value :: Ptr S2nCert -> Ptr Word8 -> Ptr Word8 -> Ptr Word32 -> Ptr CInt -> IO (Either S2nError CInt)
+  , s2n_cert_get_x509_extension_value :: Ptr S2nCert -> Ptr Word8 -> Ptr Word8 -> Ptr Word32 -> Ptr CBool -> IO (Either S2nError CInt)
   , s2n_cert_get_utf8_string_from_extension_data_length :: Ptr Word8 -> Word32 -> Ptr Word32 -> IO (Either S2nError CInt)
   , s2n_cert_get_utf8_string_from_extension_data :: Ptr Word8 -> Word32 -> Ptr Word8 -> Ptr Word32 -> IO (Either S2nError CInt)
   , s2n_external_psk_new :: IO (Either S2nError (Ptr S2nPsk))
