Merge pull request #194 from Exiv2/integer_overflow_checks
Add Integer addition overflow checks
This commit is contained in:
commit
eec3f9a0c5
@ -140,6 +140,7 @@ set( LIBEXIV2_PRIVATE_HDR canonmn_int.hpp
|
||||
tifffwd_int.hpp
|
||||
tiffimage_int.hpp
|
||||
tiffvisitor_int.hpp
|
||||
safe_op.hpp
|
||||
)
|
||||
|
||||
|
||||
|
||||
310
src/safe_op.hpp
Normal file
310
src/safe_op.hpp
Normal file
@ -0,0 +1,310 @@
|
||||
// ********************************************************* -*- C++ -*-
|
||||
/*
|
||||
* Copyright (C) 2004-2017 Exiv2 maintainers
|
||||
*
|
||||
* This program is part of the Exiv2 distribution.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, 5th Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
/*!
|
||||
@file safe_op.hpp
|
||||
@brief Overflow checks for integers
|
||||
@author Dan Čermák (D4N)
|
||||
<a href="mailto:dan.cermak@cgc-instruments.com">dan.cermak@cgc-instruments.com</a>
|
||||
@date 14-Dec-17, D4N: created
|
||||
*/
|
||||
|
||||
#ifndef SAFE_OP_HPP_
|
||||
#define SAFE_OP_HPP_
|
||||
|
||||
#include <limits>
|
||||
#include <stdexcept>
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#include <Intsafe.h>
|
||||
#endif
|
||||
|
||||
/*!
|
||||
* @brief Arithmetic operations with overflow checks
|
||||
*/
|
||||
namespace Safe
|
||||
{
|
||||
/*!
|
||||
* @brief Helper structs for providing integer overflow checks.
|
||||
*
|
||||
* This namespace contains the internal helper structs fallback_add_overflow
|
||||
* and builtin_add_overflow. Both have a public static member function add
|
||||
* with the following interface:
|
||||
*
|
||||
* bool add(T summand_1, T summand_2, T& result)
|
||||
*
|
||||
* where T is the type over which the struct is templated.
|
||||
*
|
||||
* The function performs a check whether the addition summand_1 + summand_2
|
||||
* can be performed without an overflow. If the operation would overflow,
|
||||
* true is returned and the addition is not performed if it would result in
|
||||
* undefined behavior. If no overflow occurs, the sum is saved in result and
|
||||
* false is returned.
|
||||
*
|
||||
* fallback_add_overflow implements a portable but slower overflow check.
|
||||
* builtin_add_overflow uses compiler builtins (when available) and should
|
||||
* be considerably faster. As builtins are not available for all types,
|
||||
* builtin_add_overflow falls back to fallback_add_overflow when no builtin
|
||||
* is available.
|
||||
*/
|
||||
namespace Internal
|
||||
{
|
||||
/*!
|
||||
* @brief Helper struct to determine whether a type is signed or unsigned
|
||||
|
||||
* This struct is a backport of std::is_signed from C++11. It has a public
|
||||
* enum with the property VALUE which is true when the type is signed or
|
||||
* false if it is unsigned.
|
||||
*/
|
||||
template <typename T>
|
||||
struct is_signed
|
||||
{
|
||||
enum
|
||||
{
|
||||
VALUE = T(-1) < T(0)
|
||||
};
|
||||
};
|
||||
|
||||
/*!
|
||||
* @brief Helper struct for SFINAE, from C++11
|
||||
|
||||
* This struct has a public typedef called type typedef'd to T if B is
|
||||
* true. Otherwise there is no typedef.
|
||||
*/
|
||||
template <bool B, class T = void>
|
||||
struct enable_if
|
||||
{
|
||||
};
|
||||
|
||||
/*!
|
||||
* @brief Specialization of enable_if for the case B == true
|
||||
*/
|
||||
template <class T>
|
||||
struct enable_if<true, T>
|
||||
{
|
||||
typedef T type;
|
||||
};
|
||||
|
||||
/*!
|
||||
* @brief Fallback overflow checker, specialized via SFINAE
|
||||
*
|
||||
* This struct implements a 'fallback' addition with an overflow check,
|
||||
* i.e. it does not rely on compiler intrinsics. It is specialized via
|
||||
* SFINAE for signed and unsigned integer types and provides a public
|
||||
* static member function add.
|
||||
*/
|
||||
template <typename T, typename = void>
|
||||
struct fallback_add_overflow;
|
||||
|
||||
/*!
|
||||
* @brief Overload of fallback_add_overflow for signed integers
|
||||
*/
|
||||
template <typename T>
|
||||
struct fallback_add_overflow<T, typename enable_if<is_signed<T>::VALUE>::type>
|
||||
{
|
||||
/*!
|
||||
* @brief Adds the two summands only if no overflow occurs
|
||||
*
|
||||
* This function performs a check if summand_1 + summand_2 would
|
||||
* overflow and returns true in that case. If no overflow occurs,
|
||||
* the sum is saved in result and false is returned.
|
||||
*
|
||||
* @return true on overflow, false on no overflow
|
||||
*
|
||||
* The check for an overflow is performed before the addition to
|
||||
* ensure that no undefined behavior occurs. The value in result is
|
||||
* only valid when the function returns false.
|
||||
*
|
||||
* Further information:
|
||||
* https://wiki.sei.cmu.edu/confluence/display/c/INT32-C.+Ensure+that+operations+on+signed+integers+do+not+result+in+overflow
|
||||
*/
|
||||
static bool add(T summand_1, T summand_2, T& result)
|
||||
{
|
||||
if (((summand_2 >= 0) && (summand_1 > std::numeric_limits<T>::max() - summand_2)) ||
|
||||
((summand_2 < 0) && (summand_1 < std::numeric_limits<T>::min() - summand_2))) {
|
||||
return true;
|
||||
} else {
|
||||
result = summand_1 + summand_2;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/*!
|
||||
* @brief Overload of fallback_add_overflow for unsigned integers
|
||||
*/
|
||||
template <typename T>
|
||||
struct fallback_add_overflow<T, typename enable_if<!is_signed<T>::VALUE>::type>
|
||||
{
|
||||
/*!
|
||||
* @brief Adds the two summands only if no overflow occurs
|
||||
*
|
||||
* This function performs a check if summand_1 + summand_2 would
|
||||
* overflow and returns true in that case. If no overflow occurs,
|
||||
* the sum is saved in result and false is returned.
|
||||
*
|
||||
* @return true on overflow, false on no overflow
|
||||
*
|
||||
* Further information:
|
||||
* https://wiki.sei.cmu.edu/confluence/display/c/INT30-C.+Ensure+that+unsigned+integer+operations+do+not+wrap
|
||||
*/
|
||||
static bool add(T summand_1, T summand_2, T& result)
|
||||
{
|
||||
if (summand_1 > std::numeric_limits<T>::max() - summand_2) {
|
||||
return true;
|
||||
} else {
|
||||
result = summand_1 + summand_2;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/*!
|
||||
* @brief Overflow checker using compiler intrinsics
|
||||
*
|
||||
* This struct provides an add function with the same interface &
|
||||
* behavior as fallback_add_overload::add but it relies on compiler
|
||||
* intrinsics instead. This version should be considerably faster than
|
||||
* the fallback version as it can fully utilize available CPU
|
||||
* instructions & the compiler's diagnostic.
|
||||
*
|
||||
* However, as some compilers don't provide intrinsics for certain
|
||||
* types, the default implementation of add is the version from falback.
|
||||
*
|
||||
* The struct is explicitly specialized for each type via #ifdefs for
|
||||
* each compiler.
|
||||
*/
|
||||
template <typename T>
|
||||
struct builtin_add_overflow
|
||||
{
|
||||
/*!
|
||||
* @brief Add summand_1 and summand_2 and check for overflows.
|
||||
*
|
||||
* This is the default add() function that uses
|
||||
* fallback_add_overflow<T>::add(). All specializations must have
|
||||
* exactly the same interface and behave the same way.
|
||||
*/
|
||||
static inline bool add(T summand_1, T summand_2, T& result)
|
||||
{
|
||||
return fallback_add_overflow<T>::add(summand_1, summand_2, result);
|
||||
}
|
||||
};
|
||||
|
||||
#if defined(__GNUC__) || defined(__clang__)
|
||||
#if __GNUC__ >= 5
|
||||
|
||||
/*!
|
||||
* This macro pastes a specialization of builtin_add_overflow using gcc's &
|
||||
* clang's __builtin_(s/u)add(l)(l)_overlow()
|
||||
*
|
||||
* The add function is implemented by forwarding the parameters to the intrinsic
|
||||
* and returning its value.
|
||||
*
|
||||
* The intrinsics are documented here:
|
||||
* https://gcc.gnu.org/onlinedocs/gcc/Integer-Overflow-Builtins.html#Integer-Overflow-Builtins
|
||||
*/
|
||||
#define SPECIALIZE_builtin_add_overflow(type, builtin_name) \
|
||||
template <> \
|
||||
struct builtin_add_overflow<type> \
|
||||
{ \
|
||||
static inline bool add(type summand_1, type summand_2, type& result) \
|
||||
{ \
|
||||
return builtin_name(summand_1, summand_2, &result); \
|
||||
} \
|
||||
}
|
||||
|
||||
SPECIALIZE_builtin_add_overflow(int, __builtin_sadd_overflow);
|
||||
SPECIALIZE_builtin_add_overflow(long, __builtin_saddl_overflow);
|
||||
SPECIALIZE_builtin_add_overflow(long long, __builtin_saddll_overflow);
|
||||
|
||||
SPECIALIZE_builtin_add_overflow(unsigned int, __builtin_uadd_overflow);
|
||||
SPECIALIZE_builtin_add_overflow(unsigned long, __builtin_uaddl_overflow);
|
||||
SPECIALIZE_builtin_add_overflow(unsigned long long, __builtin_uaddll_overflow);
|
||||
|
||||
#undef SPECIALIZE_builtin_add_overflow
|
||||
#endif
|
||||
|
||||
#elif defined(_MSC_VER)
|
||||
|
||||
/*!
|
||||
* This macro pastes a specialization of builtin_add_overflow using MSVC's
|
||||
* U(Int/Long/LongLong)Add.
|
||||
*
|
||||
* The add function is implemented by forwarding the parameters to the
|
||||
* intrinsic. As MSVC's intrinsics return S_OK on success, this specialization
|
||||
* returns whether the intrinsics return value does not equal S_OK. This ensures
|
||||
* a uniform interface of the add function (false is returned when no overflow
|
||||
* occurs, true on overflow).
|
||||
*
|
||||
* The intrinsics are documented here:
|
||||
* https://msdn.microsoft.com/en-us/library/windows/desktop/ff516460(v=vs.85).aspx
|
||||
*/
|
||||
#define SPECIALIZE_builtin_add_overflow_WIN(type, builtin_name) \
|
||||
template <> \
|
||||
struct builtin_add_overflow<type> \
|
||||
{ \
|
||||
static inline bool add(type summand_1, type summand_2, type& result) \
|
||||
{ \
|
||||
return builtin_name(summand_1, summand_2, &result) != S_OK; \
|
||||
} \
|
||||
}
|
||||
|
||||
SPECIALIZE_builtin_add_overflow_WIN(unsigned int, UIntAdd);
|
||||
SPECIALIZE_builtin_add_overflow_WIN(unsigned long, ULongAdd);
|
||||
SPECIALIZE_builtin_add_overflow_WIN(unsigned long long, ULongLongAdd);
|
||||
|
||||
#undef SPECIALIZE_builtin_add_overflow_WIN
|
||||
|
||||
#endif
|
||||
|
||||
} // namespace Internal
|
||||
|
||||
/*!
|
||||
* @brief Safe addition, throws an exception on overflow.
|
||||
*
|
||||
* This function returns the result of summand_1 and summand_2 only when the
|
||||
* operation would not overflow, otherwise an exception of type
|
||||
* std::overflow_error is thrown.
|
||||
*
|
||||
* @param[in] summand_1, summand_2 summands to be summed up
|
||||
* @return the sum of summand_1 and summand_2
|
||||
* @throws std::overflow_error if the addition would overflow
|
||||
*
|
||||
* This function utilizes compiler builtins when available and should have a
|
||||
* very small performance hit then. When builtins are unavailable, a more
|
||||
* extensive check is required.
|
||||
*
|
||||
* Builtins are available for the following configurations:
|
||||
* - GCC/Clang for signed and unsigned int, long and long long (not char & short)
|
||||
* - MSVC for unsigned int, long and long long
|
||||
*/
|
||||
template <typename T>
|
||||
T add(T summand_1, T summand_2)
|
||||
{
|
||||
T res = 0;
|
||||
if (Internal::builtin_add_overflow<T>::add(summand_1, summand_2, res)) {
|
||||
throw std::overflow_error("Overflow in addition");
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
} // namespace Safe
|
||||
|
||||
#endif // SAFE_OP_HPP_
|
||||
@ -3,6 +3,7 @@ add_executable(unit_tests mainTestRunner.cpp
|
||||
test_types.cpp
|
||||
test_tiffheader.cpp
|
||||
test_futils.cpp
|
||||
test_safe_op.cpp
|
||||
)
|
||||
|
||||
#TODO Use GTest::GTest once we upgrade the minimum CMake version required
|
||||
|
||||
183
unitTests/test_safe_op.cpp
Normal file
183
unitTests/test_safe_op.cpp
Normal file
@ -0,0 +1,183 @@
|
||||
#include <safe_op.hpp>
|
||||
|
||||
#include "gtestwrapper.h"
|
||||
|
||||
namespace si = Safe::Internal;
|
||||
|
||||
/*!
|
||||
* struct that contains test numbers for Safe::add. Since different test values
|
||||
* are required for signed and unsigned types, this struct is specialized via
|
||||
* SFINAE.
|
||||
*
|
||||
* Each overload has two arrays:
|
||||
* T summand[] - the test numbers
|
||||
* bool overflow[][] - overflow[i][j] indicates whether summand[i] + summand[j]
|
||||
* is expected to overflow
|
||||
*
|
||||
* The numbers in summand are chosen accordingly to test all "interesting" cases
|
||||
* and cause some overflows. The actual test should simply check all possible
|
||||
* sums of summand against the value in overflow[][]
|
||||
*/
|
||||
template <typename T, typename = void>
|
||||
struct AdditionTestValues;
|
||||
|
||||
/*!
|
||||
* Overload for unsigned types.
|
||||
*/
|
||||
template <typename T>
|
||||
struct AdditionTestValues<T, typename si::enable_if<!si::is_signed<T>::VALUE>::type>
|
||||
{
|
||||
static const size_t case_count = 5;
|
||||
static const T summand[case_count];
|
||||
static const bool overflow[case_count][case_count];
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
const T AdditionTestValues<T, typename si::enable_if<!si::is_signed<T>::VALUE>::type>::summand[] = {
|
||||
0, 1, 2, static_cast<T>(std::numeric_limits<T>::max() - 1), std::numeric_limits<T>::max()};
|
||||
|
||||
template <typename T>
|
||||
const bool
|
||||
AdditionTestValues<T, typename si::enable_if<!si::is_signed<T>::VALUE>::type>::overflow[case_count][case_count] = {
|
||||
// 0
|
||||
{false, false, false, false, false},
|
||||
// 1
|
||||
{false, false, false, false, true},
|
||||
// 2
|
||||
{false, false, false, true, true},
|
||||
// max - 1
|
||||
{false, false, true, true, true},
|
||||
// max
|
||||
{false, true, true, true, true}};
|
||||
|
||||
/*!
|
||||
* Overload for signed integers
|
||||
*/
|
||||
template <typename T>
|
||||
struct AdditionTestValues<T, typename si::enable_if<si::is_signed<T>::VALUE>::type>
|
||||
{
|
||||
static const size_t case_count = 8;
|
||||
static const T summand[case_count];
|
||||
static const bool overflow[case_count][case_count];
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
const T AdditionTestValues<T, typename si::enable_if<si::is_signed<T>::VALUE>::type>::summand[] = {
|
||||
std::numeric_limits<T>::min(),
|
||||
static_cast<T>(std::numeric_limits<T>::min() + 1),
|
||||
-1,
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
static_cast<T>(std::numeric_limits<T>::max() - 1),
|
||||
std::numeric_limits<T>::max()};
|
||||
|
||||
template <typename T>
|
||||
const bool
|
||||
AdditionTestValues<T, typename si::enable_if<si::is_signed<T>::VALUE>::type>::overflow[case_count][case_count] = {
|
||||
// min
|
||||
{true, true, true, false, false, false, false, false},
|
||||
// min + 1
|
||||
{true, true, false, false, false, false, false, false},
|
||||
// -1
|
||||
{true, false, false, false, false, false, false, false},
|
||||
// 0
|
||||
{false, false, false, false, false, false, false, false},
|
||||
// 1
|
||||
{false, false, false, false, false, false, false, true},
|
||||
// 2
|
||||
{false, false, false, false, false, false, true, true},
|
||||
// max - 1
|
||||
{false, false, false, false, false, true, true, true},
|
||||
// max
|
||||
{false, false, false, false, true, true, true, true}};
|
||||
|
||||
/*!
|
||||
* Test the addition of all combinations of AdditionTestValues<T>::summand[i],
|
||||
* AdditionTestValues<T>::summand[j] using fallback_add_overflow::add and
|
||||
* builtin_add_overflow::add. The return value is checked against
|
||||
* AdditionTestValues<T>::overflow[i][j]. If no overflow occurs, then the result
|
||||
* is checked too.
|
||||
*/
|
||||
template <typename T>
|
||||
void test_add()
|
||||
{
|
||||
typedef AdditionTestValues<T> TestValues;
|
||||
|
||||
#define TEST_ADD(func) \
|
||||
for (size_t i = 0; i < TestValues::case_count; ++i) { \
|
||||
for (size_t j = 0; j < TestValues::case_count; ++j) { \
|
||||
T res = 0; \
|
||||
ASSERT_EQ(func(TestValues::summand[i], TestValues::summand[j], res), TestValues::overflow[i][j]); \
|
||||
if (!TestValues::overflow[i][j]) { \
|
||||
ASSERT_EQ(res, TestValues::summand[i] + TestValues::summand[j]); \
|
||||
} \
|
||||
} \
|
||||
}
|
||||
|
||||
TEST_ADD(si::fallback_add_overflow<T>::add)
|
||||
TEST_ADD(si::builtin_add_overflow<T>::add)
|
||||
|
||||
#undef TEST_ADD
|
||||
}
|
||||
|
||||
/*!
|
||||
* Test the addition of all combinations of AdditionTestValues<T>::summand[i],
|
||||
* AdditionTestValues<T>::summand[j] using Safe::add.
|
||||
*
|
||||
* It is checked whether an exception is thrown when
|
||||
* AdditionTestValues<T>::overflow[i][j] is true, otherwise the result is
|
||||
* checked.
|
||||
*/
|
||||
template <typename T>
|
||||
void test_safe_add()
|
||||
{
|
||||
typedef AdditionTestValues<T> TestValues;
|
||||
|
||||
for (size_t i = 0; i < TestValues::case_count; ++i) {
|
||||
for (size_t j = 0; j < TestValues::case_count; ++j) {
|
||||
if (TestValues::overflow[i][j]) {
|
||||
ASSERT_THROW(Safe::add(TestValues::summand[i], TestValues::summand[j]), std::overflow_error);
|
||||
} else {
|
||||
ASSERT_EQ(Safe::add(TestValues::summand[i], TestValues::summand[j]),
|
||||
TestValues::summand[i] + TestValues::summand[j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST(lowLevelAddOverflow, checkUnsignedOverflow)
|
||||
{
|
||||
test_add<unsigned char>();
|
||||
test_add<unsigned short>();
|
||||
test_add<unsigned int>();
|
||||
test_add<unsigned long>();
|
||||
test_add<unsigned long long>();
|
||||
}
|
||||
|
||||
TEST(lowLevelAddOverflow, checkSignedOverflow)
|
||||
{
|
||||
test_add<char>();
|
||||
test_add<short>();
|
||||
test_add<int>();
|
||||
test_add<long>();
|
||||
test_add<long long>();
|
||||
}
|
||||
|
||||
TEST(safeAdd, checkUnsignedOverflow)
|
||||
{
|
||||
test_safe_add<unsigned char>();
|
||||
test_safe_add<unsigned short>();
|
||||
test_safe_add<unsigned int>();
|
||||
test_safe_add<unsigned long>();
|
||||
test_safe_add<unsigned long long>();
|
||||
}
|
||||
|
||||
TEST(safeAdd, checkSignedOverflow)
|
||||
{
|
||||
test_safe_add<char>();
|
||||
test_safe_add<short>();
|
||||
test_safe_add<int>();
|
||||
test_safe_add<long>();
|
||||
test_safe_add<long long>();
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user