Repository: randombit/botan
Pull request #2769 (2021-06-30 22:07:19)

Capture the FFI exception string in a thread_local variable
Discussion in https://github.com/randombit/botan-rs/issues/16


Patch:
diff --git a/src/lib/ffi/ffi.cpp b/src/lib/ffi/ffi.cpp
index 50c08bfe9..eb7342aa8 100644
--- a/src/lib/ffi/ffi.cpp
+++ b/src/lib/ffi/ffi.cpp
@@ -16,8 +16,12 @@
 
 namespace Botan_FFI {
 
+thread_local std::string g_last_exception_what;
+
 int ffi_error_exception_thrown(const char* func_name, const char* exn, int rc)
    {
+   g_last_exception_what.assign(exn);
+
    std::string val;
    if(Botan::OS::read_env_variable(val, "BOTAN_FFI_PRINT_EXCEPTIONS") == true && val != "")
       {
@@ -88,6 +92,8 @@ int ffi_map_error_type(Botan::ErrorType err)
 
 int ffi_guard_thunk(const char* func_name, std::function<int ()> thunk)
    {
+   g_last_exception_what.clear();
+
    try
       {
       return thunk();
@@ -122,6 +128,11 @@ extern "C" {
 
 using namespace Botan_FFI;
 
+const char* botan_error_last_exception_message()
+   {
+   return g_last_exception_what.c_str();
+   }
+
 const char* botan_error_description(int err)
    {
    switch(err)
@@ -200,7 +211,11 @@ uint32_t botan_ffi_api_version()
 
 int botan_ffi_supports_api(uint32_t api_version)
    {
-   // This is the API introduced in 2.18 and 3.0
+   // This is the API introduced in 3.0
+   if(api_version == 20210628)
+      return BOTAN_FFI_SUCCESS;
+
+   // This is the API introduced in 2.18
    if(api_version == 20210220)
       return BOTAN_FFI_SUCCESS;
 
diff --git a/src/lib/ffi/ffi.h b/src/lib/ffi/ffi.h
index dc5d1b21b..25a30e057 100644
--- a/src/lib/ffi/ffi.h
+++ b/src/lib/ffi/ffi.h
@@ -96,6 +96,15 @@ enum BOTAN_FFI_ERROR {
 */
 BOTAN_PUBLIC_API(2,8) const char* botan_error_description(int err);
 
+/**
+* Return the message of the last exception caught in this thread.
+*
+* This pointer can/will be reallocated or overwritten the next time
+* this thread calls any other Botan FFI function and must be copied
+* to persistent storage first.
+*/
+BOTAN_PUBLIC_API(3,0) const char* botan_error_last_exception_message();
+
 /**
 * Return the version of the currently supported FFI API. This is
 * expressed in the form YYYYMMDD of the release date of this version
diff --git a/src/lib/ffi/ffi_util.h b/src/lib/ffi/ffi_util.h
index 7501938c5..be35cb045 100644
--- a/src/lib/ffi/ffi_util.h
+++ b/src/lib/ffi/ffi_util.h
@@ -109,25 +109,18 @@ int apply_fn(botan_struct<T, M>* o, const char* func_name, F func)
 template<typename T, uint32_t M>
 int ffi_delete_object(botan_struct<T, M>* obj, const char* func_name)
    {
-   try
+   return ffi_guard_thunk(func_name, [=]() -> int
       {
+      // ignore delete of null objects
       if(obj == nullptr)
-         return BOTAN_FFI_SUCCESS; // ignore delete of null objects
+         return BOTAN_FFI_SUCCESS;
 
       if(obj->magic_ok() == false)
          return BOTAN_FFI_ERROR_INVALID_OBJECT;
 
       delete obj;
       return BOTAN_FFI_SUCCESS;
-      }
-   catch(std::exception& e)
-      {
-      return ffi_error_exception_thrown(func_name, e.what());
-      }
-   catch(...)
-      {
-      return ffi_error_exception_thrown(func_name, "unknown exception");
-      }
+      });
    }
 
 #define BOTAN_FFI_CHECKED_DELETE(o) ffi_delete_object(o, __func__)
diff --git a/src/lib/ffi/info.txt b/src/lib/ffi/info.txt
index 865beb61e..414b41997 100644
--- a/src/lib/ffi/info.txt
+++ b/src/lib/ffi/info.txt
@@ -1,5 +1,5 @@
 <defines>
-FFI -> 20210220
+FFI -> 20210628
 </defines>
 
 <header:internal>
diff --git a/src/python/botan2.py b/src/python/botan2.py
index 24247ceb5..003c98f21 100755
--- a/src/python/botan2.py
+++ b/src/python/botan2.py
@@ -40,8 +40,14 @@ class BotanException(Exception):
         if rc == 0:
             super(BotanException, self).__init__(message)
         else:
-            descr = _DLL.botan_error_description(rc).decode('ascii')
-            super(BotanException, self).__init__("%s: %d (%s)" % (message, rc, descr))
+            exn_msg = _DLL.botan_error_last_exception_message().decode('ascii')
+            err_descr = _DLL.botan_error_description(rc).decode('ascii')
+
+            formatted_msg = "%s: %d (%s)" % (message, rc, err_descr)
+            if exn_msg != "":
+                formatted_msg += ': ' + exn_msg
+
+            super(BotanException, self).__init__(formatted_msg)
 
     def error_code(self):
         return self.__rc
@@ -119,6 +125,9 @@ def _set_prototypes(dll):
     dll.botan_error_description.argtypes = [c_int]
     dll.botan_error_description.restype = c_char_p
 
+    dll.botan_error_last_exception_message.argtypes = []
+    dll.botan_error_last_exception_message.restype = c_char_p
+
     # These are generated using src/scripts/ffi_decls.py:
     ffi_api(dll.botan_constant_time_compare, [c_void_p, c_void_p, c_size_t], [-1])
     ffi_api(dll.botan_scrub_mem, [c_void_p, c_size_t])


Test patch:
diff --git a/src/scripts/test_python.py b/src/scripts/test_python.py
index 1735f26d5..6a2b3b1b8 100644
--- a/src/scripts/test_python.py
+++ b/src/scripts/test_python.py
@@ -278,7 +278,7 @@ ofvkP1EDmpx50fHLawIDAQAB
         try:
             rsapub = botan2.PublicKey.load_rsa(n - 1, e)
         except botan2.BotanException as e:
-            self.assertEqual(str(e), "botan_pubkey_load_rsa failed: -1 (Invalid input)")
+            self.assertEqual(str(e), "botan_pubkey_load_rsa failed: -1 (Invalid input): Invalid RSA public key parameters")
 
     def test_rsa(self):
         # pylint: disable=too-many-locals