Assertions

From Crypto++ Wiki
Jump to navigation Jump to search
CRYPTOPP_ASSERT
Documentation
#include <cryptopp/trap.h>

Posix and ISO C assertions are an excellent debugging aide during development. However, they are usually a problem in production for high integrity and security sensitive software because asserts lead to a call to abort(). Some folks feel it is OK to call assert in production software to crash a program, while other folks do not. This page will explain the library's use of assert, when it is active and how it behaves. Also see Asserts considered harmful (or GMP spills its sensitive information) on OSS Security mailing list and GMP and assert() on LWN.

The library does not use Posix or C assert. Instead the library uses a safer variant called CRYPTOPP_ASSERT. CRYPTOPP_ASSERT does not call abort() or raise SIGABRT. In Release builds CRYPTOPP_ASSERT is defined to nothing, just like a C assert. The library will either fail a function call or throw an exception.

In Debug builds, CRYPTOPP_ASSERT raises a trap rather than an abort. The trap will snap the debugger (if present), and allow the developer to debug the code path. If there's bad data, the data will be available through its respective variables. After the trap is raised, the program will continue by failing the function call or throwing an exception.

CRYPTOPP_ASSERT was added after CVE-2016-7420. The postmortem analysis of the CVE revealed it was too easy to configure the library in an unsafe manner when using unsupported build systems. Users will now have to make an effort to get into a less secure configuration, regardless of the build system.

The pedigree of CRYPTOPP_ASSERT can be traced back to John Robbins' 2000 book Debugging Applications (ISBN 0735608865). Robbins is a legendary bug slayer on the Windows platform. Robbins shows readers how to effectively use assertions during the development process and provides a SUPERASSERT that even sends emails to developers with function parameters and stack traces.

Library Position

The Crypto++ library never asserts in production for five reasons. First, it is the application's authors decision to crash their app. The library does not make policy decisions for the application author.

Second, some platforms, like Apple iOS, forbid applications from crashing because it degrades the UI experience. In this case, the App Store has set the policy for the application author. The library will not cause an author's app to be rejected from an App Store.

Third, the library handles sensitive information like private keys, shared secrets and passwords. When an assert fires a core file could be written that includes the sensitive information. A core file means the sensitive information has been egressed outside the application's security boundary to the filesystem. Folks with access to the mobile device, desktop computer or a computer paired/sync'd with the mobile device will be able to recover the secrets from the filesystem.

Fourth, the core file, if present, may be shipped to an Error Reporting Service. Now Apple, Fedora, Google, Red Hat, Ubuntu or Microsoft have the user's private keys, shared secrets and passwords. The information is then passed on to the developer, who has the user's private keys, shared secrets and passwords, too.

Fifth, asserts destroy most of Confidentiality-Availability-Integrity (CIA). When an assert crashes a program, it destroys (1) Confidentiality of the data and (2) Availability of the program or service.

Some folks claim they crash to preserve Integrity. If an author wishes to preserve Integrity, he/she/it only needs to return false in the offending function or call exit(1) without the loss of Confidentiality or Availability.

Build Configurations

The library recognizes two types of build configurations. The first is a Debug build, and the second is a Release build. Windows developers are familiar with the two configuration options. Many Unix and Linux developers don't recognize the difference. A related page is config.h.

The major differences between the two builds for the library are listed below.

Release build

  • Used in production
  • CRYPTOPP_ASSERT are removed
  • NDEBUG may be defined
  • modest symbols using -g2
  • optimizations using -O2 or -O3

Debug build

  • Used during development
  • CRYPTOPP_ASSERT are in effect
  • DEBUG may be defined
  • maximum symbols using -g3
  • minimum optimizations using -O0, -Og or -O1

Release builds

Asserts are not in effect during Release builds. The library's assert, CRYPTOPP_ASSERT, is defined to (void)0. See below for more information on CRYPTOPP_ASSERT.

You do not need to define NDEBUG or take other actions to remove assertions due to CVE-2016-7420. If you do nothing then you will be in a Release configuration.

Debug builds

Asserts are in effect during Debug builds. The library's assert, CRYPTOPP_ASSERT, prints a message to std::cerr and raises a SIG_TRAP on Unix and Linux or calls DebugBreak() on Windows. See below for more information on CRYPTOPP_ASSERT.

To enable a debug build you must define DEBUG or CRYPTOPP_DEBUG. If you do nothing then you will be in a Release configuration.

CRYPTOPP_ASSERT

The library's assert is CRYPTOPP_ASSERT. CRYPTOPP_ASSERT is a macro activated when DEBUG or CRYPTOPP_DEBUG are defined. The code for the assert is located in trap.h.

When CRYPTOPP_ASSERT is activated the code for the library's assert is shown below.

#if defined(CRYPTOPP_DEBUG)
# if defined(UNIX_SIGNALS_AVAILABLE) || defined(__CYGWIN__)
#  define CRYPTOPP_ASSERT(exp) {                                  \
    if (!(exp)) {                                                 \
      std::ostringstream oss;                                     \
      oss << "Assertion failed: " << __FILE__ << "("              \
          << __LINE__ << "): " << __func__                        \
          << std::endl;                                           \
      std::cerr << oss.str();                                     \
      raise(SIGTRAP);                                             \
    }                                                             \
  }
#elif defined(CRYPTOPP_WIN32_AVAILABLE)
#  define CRYPTOPP_ASSERT(exp) {                                  \
    if (!(exp)) {                                                 \
      std::ostringstream oss;                                     \
      oss << "Assertion failed: " << __FILE__ << "("              \
          << __LINE__ << "): " << __FUNCTION__                    \
          << std::endl;                                           \
      std::cerr << oss.str();                                     \
      if (IsDebuggerPresent()) {DebugBreak();}                    \
    }                                                             \
  }
# endif // Unix or Windows
#endif  // CRYPTOPP_DEBUG

#ifndef CRYPTOPP_ASSERT
# define CRYPTOPP_ASSERT(exp) (void)0
#endif

Typical Usage

Below is an example of the library's use of CRYPTOPP_ASSERT. The source file is rsa.cpp, and the function is GenerateRandom. The function is used to generate a private key with optional parameters supplied by the user.

The asserts are in effect during debug builds. The assert will snap the debugger so the developer can examine the state of the program and arguments supplied by the user if the arguments fail to validate. In both debug and release builds the library will throw an exception if needed.

void InvertibleRSAFunction::GenerateRandom(RandomNumberGenerator &rng, const NameValuePairs &alg)
{
    int modulusSize = 2048;
    alg.GetIntValue(Name::ModulusSize(), modulusSize) || alg.GetIntValue(Name::KeySize(), modulusSize);

    CRYPTOPP_ASSERT(modulusSize >= 16);
    if (modulusSize < 16)
        throw InvalidArgument("InvertibleRSAFunction: specified modulus size is too small");

    m_e = alg.GetValueWithDefault(Name::PublicExponent(), Integer(17));

    CRYPTOPP_ASSERT(m_e >= 3); CRYPTOPP_ASSERT(!m_e.IsEven());
    if (m_e < 3 || m_e.IsEven())
        throw InvalidArgument("InvertibleRSAFunction: invalid public exponent");

    ...
}