0
0
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:
Charlie Vigue 2023-02-22 19:58:08 +00:00 committed by David Sommerseth
parent c71d81f0a4
commit d111fc301c
No known key found for this signature in database
GPG Key ID: 86CF944C9671FDF2
6 changed files with 436 additions and 0 deletions

View 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

View 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

View 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

View File

@ -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

View 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());
}

View 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);
}