mirror of
https://github.com/OpenVPN/openvpn3.git
synced 2024-09-19 19:52:15 +02:00
Add numeric limiting headers and tests
This commit adds two useful numeric limiting functions in two headers plus a third supporting header and unit tests. The unit tests cover all code paths and many conditions but may not be 100% complete from a viewpoint of covering all edge cases. Signed-off-by: Charlie Vigue <charlie.vigue@openvpn.net>
This commit is contained in:
parent
c71d81f0a4
commit
d111fc301c
70
openvpn/common/clamp_typerange.hpp
Normal file
70
openvpn/common/clamp_typerange.hpp
Normal file
@ -0,0 +1,70 @@
|
||||
// OpenVPN -- An application to securely tunnel IP networks
|
||||
// over a single port, with support for SSL/TLS-based
|
||||
// session authentication and key exchange,
|
||||
// packet encryption, packet authentication, and
|
||||
// packet compression.
|
||||
//
|
||||
// Copyright (C) 2023 OpenVPN Inc.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License Version 3
|
||||
// as published by the Free Software Foundation.
|
||||
//
|
||||
// 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 Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program in the COPYING file.
|
||||
// If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <limits>
|
||||
#include <algorithm>
|
||||
|
||||
#include "numeric_util.hpp"
|
||||
|
||||
namespace openvpn::numeric_util {
|
||||
|
||||
/* ============================================================================================================= */
|
||||
// clamp_to_typerange
|
||||
/* ============================================================================================================= */
|
||||
|
||||
/**
|
||||
* @brief Clamps the input value to the legal range for the output type
|
||||
*
|
||||
* @tparam OutT Output type
|
||||
* @tparam InT Input type
|
||||
* @param inVal Input value
|
||||
* @return OutT safely converted from InT, range limited to fit.
|
||||
*/
|
||||
template <typename OutT, typename InT>
|
||||
OutT clamp_to_typerange(InT inVal)
|
||||
{
|
||||
if constexpr (numeric_util::is_int_rangesafe<OutT, InT>())
|
||||
{
|
||||
return static_cast<OutT>(inVal);
|
||||
}
|
||||
else if constexpr (numeric_util::is_int_u2s<OutT, InT>())
|
||||
{
|
||||
auto unsignedInVal = static_cast<uintmax_t>(inVal);
|
||||
return static_cast<OutT>(std::min(static_cast<uintmax_t>(std::numeric_limits<OutT>::max()), unsignedInVal));
|
||||
}
|
||||
else if constexpr (numeric_util::is_int_s2u<OutT, InT>())
|
||||
{
|
||||
auto lowerVal = static_cast<uintmax_t>(std::max(inVal, 0));
|
||||
auto upperLimit = static_cast<uintmax_t>(std::numeric_limits<OutT>::max());
|
||||
return static_cast<OutT>(std::min(lowerVal, upperLimit));
|
||||
}
|
||||
else
|
||||
{
|
||||
auto outMin = static_cast<InT>(std::numeric_limits<OutT>::min());
|
||||
auto outMax = static_cast<InT>(std::numeric_limits<OutT>::max());
|
||||
return static_cast<OutT>(std::clamp(inVal, outMin, outMax));
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace openvpn::numeric_util
|
91
openvpn/common/numeric_cast.hpp
Normal file
91
openvpn/common/numeric_cast.hpp
Normal file
@ -0,0 +1,91 @@
|
||||
// OpenVPN -- An application to securely tunnel IP networks
|
||||
// over a single port, with support for SSL/TLS-based
|
||||
// session authentication and key exchange,
|
||||
// packet encryption, packet authentication, and
|
||||
// packet compression.
|
||||
//
|
||||
// Copyright (C) 2023 OpenVPN Inc.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License Version 3
|
||||
// as published by the Free Software Foundation.
|
||||
//
|
||||
// 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 Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program in the COPYING file.
|
||||
// If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "numeric_util.hpp"
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
#include <openvpn/common/exception.hpp> // For OPENVPN_EXCEPTION_INHERIT
|
||||
|
||||
/***
|
||||
* @brief Exception type for numeric conversion failures
|
||||
*/
|
||||
OPENVPN_EXCEPTION_INHERIT(std::range_error, numeric_out_of_range);
|
||||
|
||||
namespace openvpn::numeric_util {
|
||||
|
||||
/* ============================================================================================================= */
|
||||
// numeric_cast
|
||||
/* ============================================================================================================= */
|
||||
|
||||
/**
|
||||
* @brief Tests attempted casts to ensure the input value does not exceed the capacity of the output type
|
||||
*
|
||||
* If the types are the same, or the range of the output type equals or exceeds the range of the input type
|
||||
* we just cast and return the value which should ideally optimize away completely. Otherwise we do appropriate
|
||||
* range checks and if those succeed we cast, otherwise the failure exception openvpn::numeric_out_of_range
|
||||
* is thrown.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* int64_t s64 = std::numeric_limits<int64_t>::max();
|
||||
* EXPECT_THROW(numeric_cast<int16_t>(s64), numeric_out_of_range);
|
||||
*
|
||||
* @param inVal The value to be converted.
|
||||
* @return The safely converted inVal.
|
||||
* @tparam InT Source (input) type, inferred from 'inVal'
|
||||
* @tparam OutT Desired result type
|
||||
*/
|
||||
template <typename OutT, typename InT>
|
||||
OutT numeric_cast(InT inVal)
|
||||
{
|
||||
if constexpr (!numeric_util::is_int_rangesafe<OutT, InT>() && numeric_util::is_int_u2s<OutT, InT>())
|
||||
{
|
||||
// Conversion to uintmax_t should be safe for ::max() in all integral cases
|
||||
if (static_cast<uintmax_t>(inVal) > static_cast<uintmax_t>(std::numeric_limits<OutT>::max()))
|
||||
{
|
||||
throw numeric_out_of_range("Range exceeded for unsigned --> signed integer conversion");
|
||||
}
|
||||
}
|
||||
else if constexpr (!numeric_util::is_int_rangesafe<OutT, InT>() && numeric_util::is_int_s2u<OutT, InT>())
|
||||
{
|
||||
// Cast to uintmax_t only applied if inVal is positive ...
|
||||
if (inVal < 0 || static_cast<uintmax_t>(inVal) > static_cast<uintmax_t>(std::numeric_limits<OutT>::max()))
|
||||
{
|
||||
throw numeric_out_of_range("Range exceeded for signed --> unsigned integer conversion");
|
||||
}
|
||||
}
|
||||
else if constexpr (!numeric_util::is_int_rangesafe<OutT, InT>())
|
||||
{
|
||||
// We already know the in and out are sign compatible
|
||||
if (std::numeric_limits<OutT>::min() > inVal || std::numeric_limits<OutT>::max() < inVal)
|
||||
{
|
||||
throw numeric_out_of_range("Range exceeded for integer conversion");
|
||||
}
|
||||
}
|
||||
|
||||
return static_cast<OutT>(inVal);
|
||||
}
|
||||
|
||||
} // namespace openvpn::numeric_util
|
63
openvpn/common/numeric_util.hpp
Normal file
63
openvpn/common/numeric_util.hpp
Normal file
@ -0,0 +1,63 @@
|
||||
// OpenVPN -- An application to securely tunnel IP networks
|
||||
// over a single port, with support for SSL/TLS-based
|
||||
// session authentication and key exchange,
|
||||
// packet encryption, packet authentication, and
|
||||
// packet compression.
|
||||
//
|
||||
// Copyright (C) 2023 OpenVPN Inc.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License Version 3
|
||||
// as published by the Free Software Foundation.
|
||||
//
|
||||
// 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 Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program in the COPYING file.
|
||||
// If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <limits>
|
||||
#include <type_traits>
|
||||
#include <functional>
|
||||
|
||||
|
||||
namespace openvpn::numeric_util {
|
||||
|
||||
// Evaluates true if both template args are integral.
|
||||
template <typename OutT, typename InT>
|
||||
constexpr bool is_int_conversion()
|
||||
{
|
||||
return std::is_integral_v<InT> && std::is_integral_v<OutT>;
|
||||
}
|
||||
|
||||
// Returns true if the in param is an unsigned integral type and out param is a signed integral type.
|
||||
template <typename OutT, typename InT>
|
||||
constexpr bool is_int_u2s()
|
||||
{
|
||||
return is_int_conversion<OutT, InT>() && std::is_unsigned_v<InT> && std::is_signed_v<OutT>;
|
||||
}
|
||||
|
||||
// Returns true if the in param is a signed integral type and out param is an unsigned integral type.
|
||||
template <typename OutT, typename InT>
|
||||
constexpr bool is_int_s2u()
|
||||
{
|
||||
return is_int_conversion<OutT, InT>() && std::is_signed_v<InT> && std::is_unsigned_v<OutT>;
|
||||
}
|
||||
|
||||
// Returns true if both args are integral and the range of OutT can contain the range of InT
|
||||
template <typename OutT, typename InT>
|
||||
constexpr bool is_int_rangesafe()
|
||||
{
|
||||
constexpr auto out_digits = std::numeric_limits<OutT>::digits;
|
||||
constexpr auto in_digits = std::numeric_limits<InT>::digits;
|
||||
|
||||
return is_int_conversion<OutT, InT>() && !is_int_s2u<OutT, InT>() && out_digits >= in_digits;
|
||||
}
|
||||
|
||||
} // namespace openvpn::numeric_util
|
@ -55,12 +55,14 @@ add_executable(coreUnitTests
|
||||
test_continuation.cpp
|
||||
test_crypto.cpp
|
||||
test_optfilt.cpp
|
||||
test_clamp_typerange.cpp
|
||||
test_pktstream.cpp
|
||||
test_remotelist.cpp
|
||||
test_relack.cpp
|
||||
test_http_proxy.cpp
|
||||
test_peer_fingerprint.cpp
|
||||
test_safestr.cpp
|
||||
test_numeric_cast.cpp
|
||||
test_dns.cpp
|
||||
test_header_deps.cpp
|
||||
test_capture.cpp
|
||||
|
106
test/unittests/test_clamp_typerange.cpp
Normal file
106
test/unittests/test_clamp_typerange.cpp
Normal file
@ -0,0 +1,106 @@
|
||||
// OpenVPN -- An application to securely tunnel IP networks
|
||||
// over a single port, with support for SSL/TLS-based
|
||||
// session authentication and key exchange,
|
||||
// packet encryption, packet authentication, and
|
||||
// packet compression.
|
||||
//
|
||||
// Copyright (C) 2023 OpenVPN Inc.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License Version 3
|
||||
// as published by the Free Software Foundation.
|
||||
//
|
||||
// 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 Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program in the COPYING file.
|
||||
// If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
#include "test_common.h"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include <openvpn/common/clamp_typerange.hpp>
|
||||
|
||||
using namespace openvpn::numeric_util;
|
||||
|
||||
TEST(clamp_to_typerange, same_type_nocast1)
|
||||
{
|
||||
int32_t i32 = -1;
|
||||
auto result = clamp_to_typerange<int32_t>(i32);
|
||||
EXPECT_EQ(result, i32);
|
||||
}
|
||||
|
||||
TEST(clamp_to_typerange, sign_mismatch_32_1)
|
||||
{
|
||||
int32_t i32 = -1;
|
||||
auto result = clamp_to_typerange<uint32_t>(i32);
|
||||
EXPECT_EQ(result, 0);
|
||||
}
|
||||
|
||||
TEST(clamp_to_typerange, sign_mismatch_32_2)
|
||||
{
|
||||
uint32_t u32 = std::numeric_limits<uint32_t>::max();
|
||||
auto result = clamp_to_typerange<int32_t>(u32);
|
||||
EXPECT_EQ(result, std::numeric_limits<int32_t>::max());
|
||||
}
|
||||
|
||||
TEST(clamp_to_typerange, sign_mismatch_32_3)
|
||||
{
|
||||
uint32_t u32 = 0;
|
||||
auto result = clamp_to_typerange<int32_t>(u32);
|
||||
EXPECT_EQ(result, 0);
|
||||
}
|
||||
|
||||
TEST(clamp_to_typerange, sign_mismatch_32_4)
|
||||
{
|
||||
uint32_t u32 = 42;
|
||||
auto result = clamp_to_typerange<int32_t>(u32);
|
||||
EXPECT_EQ(result, 42);
|
||||
}
|
||||
|
||||
TEST(clamp_to_typerange, sign_mismatch_32_5)
|
||||
{
|
||||
uint32_t u32 = uint32_t(std::numeric_limits<int32_t>::max());
|
||||
auto result = clamp_to_typerange<int32_t>(u32);
|
||||
EXPECT_EQ(result, std::numeric_limits<int32_t>::max());
|
||||
}
|
||||
|
||||
TEST(clamp_to_typerange, sign_mismatch_32_6)
|
||||
{
|
||||
int32_t s32 = std::numeric_limits<int32_t>::max();
|
||||
auto result = clamp_to_typerange<uint8_t>(s32);
|
||||
EXPECT_EQ(result, std::numeric_limits<uint8_t>::max());
|
||||
}
|
||||
|
||||
TEST(clamp_to_typerange, sign_mismatch_32_7)
|
||||
{
|
||||
int32_t s32 = 42;
|
||||
auto result = clamp_to_typerange<uint8_t>(s32);
|
||||
EXPECT_EQ(result, 42);
|
||||
}
|
||||
|
||||
TEST(clamp_to_typerange, s_range_mismatch_16_64_1)
|
||||
{
|
||||
int64_t s64 = std::numeric_limits<int64_t>::max();
|
||||
auto result = clamp_to_typerange<int16_t>(s64);
|
||||
EXPECT_EQ(result, std::numeric_limits<int16_t>::max());
|
||||
}
|
||||
|
||||
TEST(clamp_to_typerange, s_range_match_16_64_1)
|
||||
{
|
||||
int64_t s64 = 0;
|
||||
auto result = clamp_to_typerange<int16_t>(s64);
|
||||
EXPECT_EQ(result, 0);
|
||||
}
|
||||
|
||||
TEST(clamp_to_typerange, u_range_mismatch_16_64_1)
|
||||
{
|
||||
uint64_t u64 = std::numeric_limits<uint64_t>::max();
|
||||
auto result = clamp_to_typerange<uint16_t>(u64);
|
||||
EXPECT_EQ(result, std::numeric_limits<uint16_t>::max());
|
||||
}
|
104
test/unittests/test_numeric_cast.cpp
Normal file
104
test/unittests/test_numeric_cast.cpp
Normal file
@ -0,0 +1,104 @@
|
||||
// OpenVPN -- An application to securely tunnel IP networks
|
||||
// over a single port, with support for SSL/TLS-based
|
||||
// session authentication and key exchange,
|
||||
// packet encryption, packet authentication, and
|
||||
// packet compression.
|
||||
//
|
||||
// Copyright (C) 2023 OpenVPN Inc.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License Version 3
|
||||
// as published by the Free Software Foundation.
|
||||
//
|
||||
// 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 Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program in the COPYING file.
|
||||
// If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
#include "test_common.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <cctype>
|
||||
|
||||
#include <openvpn/common/numeric_cast.hpp>
|
||||
|
||||
|
||||
using namespace openvpn::numeric_util;
|
||||
|
||||
|
||||
TEST(numeric_cast, same_type_nocast1)
|
||||
{
|
||||
int32_t i32 = -1;
|
||||
auto result = numeric_cast<int32_t>(i32);
|
||||
EXPECT_EQ(result, i32);
|
||||
}
|
||||
|
||||
TEST(numeric_cast, sign_mismatch_32_1)
|
||||
{
|
||||
int32_t i32 = -1;
|
||||
EXPECT_THROW(numeric_cast<uint32_t>(i32), numeric_out_of_range);
|
||||
}
|
||||
|
||||
TEST(numeric_cast, sign_mismatch_32_2)
|
||||
{
|
||||
uint32_t u32 = std::numeric_limits<uint32_t>::max();
|
||||
EXPECT_THROW(numeric_cast<int32_t>(u32), numeric_out_of_range);
|
||||
}
|
||||
|
||||
TEST(numeric_cast, sign_mismatch_32_3)
|
||||
{
|
||||
uint32_t u32 = 0;
|
||||
auto result = numeric_cast<int32_t>(u32);
|
||||
EXPECT_EQ(result, 0);
|
||||
}
|
||||
|
||||
TEST(numeric_cast, sign_mismatch_32_4)
|
||||
{
|
||||
uint32_t u32 = 42;
|
||||
auto result = numeric_cast<int32_t>(u32);
|
||||
EXPECT_EQ(result, 42);
|
||||
}
|
||||
|
||||
TEST(numeric_cast, sign_mismatch_32_5)
|
||||
{
|
||||
uint32_t u32 = uint32_t(std::numeric_limits<int32_t>::max());
|
||||
auto result = numeric_cast<int32_t>(u32);
|
||||
EXPECT_EQ(result, std::numeric_limits<int32_t>::max());
|
||||
}
|
||||
|
||||
TEST(numeric_cast, sign_mismatch_32_6)
|
||||
{
|
||||
int32_t s32 = std::numeric_limits<int32_t>::max();
|
||||
EXPECT_THROW(numeric_cast<uint8_t>(s32), numeric_out_of_range);
|
||||
}
|
||||
|
||||
TEST(numeric_cast, sign_mismatch_32_7)
|
||||
{
|
||||
int32_t s32 = 42;
|
||||
auto result = numeric_cast<uint8_t>(s32);
|
||||
EXPECT_EQ(result, 42);
|
||||
}
|
||||
|
||||
TEST(numeric_cast, s_range_mismatch_16_64_1)
|
||||
{
|
||||
int64_t s64 = std::numeric_limits<int64_t>::max();
|
||||
EXPECT_THROW(numeric_cast<int16_t>(s64), numeric_out_of_range);
|
||||
}
|
||||
|
||||
TEST(numeric_cast, s_range_match_16_64_1)
|
||||
{
|
||||
int64_t s64 = 0;
|
||||
auto result = numeric_cast<int16_t>(s64);
|
||||
EXPECT_EQ(result, 0);
|
||||
}
|
||||
|
||||
TEST(numeric_cast, u_range_mismatch_16_64_1)
|
||||
{
|
||||
uint64_t u64 = std::numeric_limits<uint64_t>::max();
|
||||
EXPECT_THROW(numeric_cast<uint16_t>(u64), numeric_out_of_range);
|
||||
}
|
Loading…
Reference in New Issue
Block a user