One project I created at MariaDB is libMariaS3 [source, documentation]. It is a lightweight simple LGPL licensed API to access Amazon S3 and compatible object storage implementations. We created it so that GPL v2.0 licensed projects could use S3 without the license incompatibility of Amazon’s Apache 2.0 licensed SDK.

MariaDB itself uses this for the new S3 storage engine coming in 10.5 and several other upcoming projects. I designed it to be easy to use for anyone outside of MariaDB just in case some other project would find it useful.

libMariaS3 uses libcurl’s easy API underneath. Curl is an awesome project which helps to, amongst other things, make HTTP requests. libcurl is the library underneath which makes it easy for any application to make these requests. It is thanks to libcurl that I was able to rapidly develop libMariaS3.

There was a recent report of libMariaS3 crashing due to a race condition. The stack traces sent to me were very clearly triggered inside OpenSSL. A quick bit of research showed me that OpenSSL <= 1.0.2 has some thread safety issues, this version is used in some OS builds of Curl. This isn’t an issue with Curl itself as other libraries and applications can hit this, and it is great that this is documented including a code workaround.

The workaround requires the end application to link with OpenSSL and implement a couple of callbacks. The problem for libMariaS3 is when compiling on a given platform it needed to know at compile time if libcurl provided by the OS used OpenSSL <= 1.0.2. libcurl can work with many SSL implementations and every OS can compile against a different one. It created a little bit of a chicken and egg challenge.

To solve this I took a dig into the excellent libcurl documentation to see if there was a way of finding which SSL library and version it was compiled against. One potential way of solving this would be to probe the curl-config tool to find out what version it was compiled against. Unfortunately older versions of libcurl that are still in current Debian and CentOS releases do not give the OpenSSL version in this output.

So, digging back into the documentation I noticed curl_version_info() contains a structure which gives the required information. Time to dig out my (possibly rusty) m4 skills to make the build system detect this. I created the following quick script which at configure time, compiles some C code to test if the SSL version is OpenSSL <= 1.0 (since the bug is fixed in 1.1). It creates a flag defined as HAVE_CURL_OPENSSL_UNSAFE which can be used with #define in libMariaS3 to determine whether or not to compile the workaround.

The end result looks like this, it may be useful for anyone else who has this issue with libcurl and older versions of OpenSSL:

# SYNOPSIS
#
#   AX_CURL_OPENSSL_UNSAFE
#
# DESCRIPTION
#
#   Checks to see if Curl is using a thread safe version of OpenSSL
#
# LICENSE
#
#   Copyright (c) 2019 Andrew Hutchings <andrew.hutchings@mariadb.com>
#
#   Copying and distribution of this file, with or without modification, are
#   permitted in any medium without royalty provided the copyright notice
#   and this notice are preserved. This file is offered as-is, without any
#   warranty.

#serial 1

AC_DEFUN([AX_CURL_OPENSSL_UNSAFE],
    [AC_PREREQ([2.63])dnl
    AC_CACHE_CHECK([whether curl uses an OpenSSL which is not thread safe],
      [ax_cv_curl_openssl_unsafe],
      [AX_SAVE_FLAGS
      LIBS="-lcurl $LIBS"
      AC_LANG_PUSH([C])
      AC_RUN_IFELSE([AC_LANG_PROGRAM([[#include <curl/curl.h>
#include <stdlib.h>
#include <string.h>]],
[[curl_version_info_data *data = curl_version_info(CURLVERSION_NOW);

  if (data->ssl_version)
  {
    if (strncmp(data->ssl_version, "OpenSSL", 7) != 0)
    {
      return EXIT_FAILURE;
    }
    if (data->ssl_version[8] == '0')
    {
      return EXIT_SUCCESS;
    }
    if ((data->ssl_version[8] == '1') && (data->ssl_version[10] == '0'))
    {
      return EXIT_SUCCESS;
    }
    return EXIT_FAILURE;
  }
  else
  {
    return EXIT_FAILURE;
  }
  return EXIT_SUCCESS;]])],
      [ax_cv_curl_openssl_unsafe=yes],
      [ax_cv_curl_openssl_unsafe=no],
      [ax_cv_curl_openssl_unsafe=no])
    AC_LANG_POP
    AX_RESTORE_FLAGS])
    AC_MSG_CHECKING([checking for curl_openssl_unsafe])
  AC_MSG_RESULT(["$ax_cv_curl_openssl_unsafe"])
  AS_IF([test "x$ax_cv_curl_openssl_unsafe" = xyes],
      [AC_DEFINE([HAVE_CURL_OPENSSL_UNSAFE],[1],[define if curl's OpenSSL is not thread safe])])
  ])

Hopefully this is somewhat self-explanatory to people who have used C and m4 before. But basically AC_RUN_IFELSE executes code and checks the return result. The version response for OpenSSL in libcurl is always in the form OpenSSL/x.y.z so we can figure out which bytes of the response are the version number. EXIT_SUCCESS is when an unsafe OpenSSL is found, EXIT_FAILURE is when it is safe or not OpenSSL.

If you would like me to break this m4 script down further please let me know in the comments and I’ll do a follow-up. Otherwise there is an excellent book call Autotools 2nd edition that is available on pre-order right now.

Also thanks to my friends at wolfSSL for sponsoring the continued development of Curl and libcurl.

Featured image is Cloud curl used under a CC 4.0 license.

Leave a Reply

Your email address will not be published. Required fields are marked *