[num-utils] add SafeMultiply() for overflow-safe multiplication (#12220)

This commit introduces `SafeMultiply()` in `num_utils.hpp` as a
centralized and safe way to multiply two unsigned integers while
checking for overflow.

It updates `Coap::TxParameters::IsValid()` to use this new helper for
validating `TxParameters`, replacing a less robust local `Multiply`
implementation.

It also updates `Heap::CAlloc()` to use this function for safely
calculating the total allocation size.

Unit tests are updated to verify `SafeMultiply()` implementation.
This commit is contained in:
Abtin Keshavarzian
2025-12-17 13:01:36 -08:00
committed by GitHub
parent 79d973dce6
commit b9bbf71d34
5 changed files with 103 additions and 35 deletions
+29 -29
View File
@@ -1631,42 +1631,42 @@ void ResponsesQueue::HandleTimer(void)
mTimer.FireAt(nextDequeueTime);
}
/// Return product of @p aValueA and @p aValueB if no overflow otherwise 0.
static uint32_t Multiply(uint32_t aValueA, uint32_t aValueB)
{
uint32_t result = 0;
VerifyOrExit(aValueA);
result = aValueA * aValueB;
result = (result / aValueA == aValueB) ? result : 0;
exit:
return result;
}
bool TxParameters::IsValid(void) const
{
bool rval = false;
bool isValid = false;
uint32_t duration;
uint32_t retryFactor;
// support fire and forget requests
if (mAckTimeout == 0)
{
rval = true;
}
else if ((mAckRandomFactorDenominator > 0) && (mAckRandomFactorNumerator >= mAckRandomFactorDenominator) &&
(mAckTimeout >= OT_COAP_MIN_ACK_TIMEOUT) && (mMaxRetransmit <= OT_COAP_MAX_RETRANSMIT))
{
// Calculate exchange lifetime step by step and verify no overflow.
uint32_t tmp = Multiply(mAckTimeout, (1U << (mMaxRetransmit + 1)) - 1);
tmp = Multiply(tmp, mAckRandomFactorNumerator);
tmp /= mAckRandomFactorDenominator;
rval = (tmp != 0 && (tmp + mAckTimeout + 2 * kDefaultMaxLatency) > tmp);
// Support fire and forget requests
isValid = true;
ExitNow();
}
return rval;
VerifyOrExit(mAckRandomFactorDenominator > 0);
VerifyOrExit(mAckRandomFactorNumerator >= mAckRandomFactorDenominator);
VerifyOrExit(mAckTimeout >= kMinAckTimeout);
VerifyOrExit(mMaxRetransmit <= kMaxRetransmit);
// Calculate exchange lifetime max duration step by step and verify no overflow.
static_assert(kMaxRetransmit < 31, "kMaxRetransmit is not valid");
retryFactor = static_cast<uint32_t>((1U << (mMaxRetransmit + 1)) - 1);
SuccessOrExit(SafeMultiply<uint32_t>(mAckTimeout, retryFactor, duration));
SuccessOrExit(SafeMultiply<uint32_t>(duration, mAckRandomFactorNumerator, duration));
duration /= mAckRandomFactorDenominator;
VerifyOrExit(duration > 0);
VerifyOrExit(CanAddSafely<uint32_t>(mAckTimeout, 2 * kDefaultMaxLatency));
VerifyOrExit(CanAddSafely<uint32_t>(duration, mAckTimeout + 2 * kDefaultMaxLatency));
isValid = true;
exit:
return isValid;
}
uint32_t TxParameters::CalculateInitialRetransmissionTimeout(void) const
+2
View File
@@ -128,6 +128,8 @@ private:
static constexpr uint8_t kDefaultAckRandomFactorDenominator = 2;
static constexpr uint8_t kDefaultMaxRetransmit = 4;
static constexpr uint32_t kDefaultMaxLatency = 100000; // in msec
static constexpr uint8_t kMaxRetransmit = OT_COAP_MAX_RETRANSMIT;
static constexpr uint32_t kMinAckTimeout = OT_COAP_MIN_ACK_TIMEOUT;
uint32_t CalculateInitialRetransmissionTimeout(void) const;
uint32_t CalculateExchangeLifetime(void) const;
+33
View File
@@ -34,6 +34,8 @@
#ifndef NUM_UTILS_HPP_
#define NUM_UTILS_HPP_
#include "common/code_utils.hpp"
#include "common/error.hpp"
#include "common/numeric_limits.hpp"
#include "common/type_traits.hpp"
@@ -229,6 +231,37 @@ template <> inline int ThreeWayCompare(bool aFirst, bool aSecond)
return (aFirst == aSecond) ? 0 : (aFirst ? 1 : -1);
}
/**
* Safely multiplies two unsigned integers and checks for overflow.
*
* @tparam UintType The value type (MUST be `uint8_t`, `uint16_t`, `uint32_t`, or `uint64_t`).
*
* @param[in] aFirstValue The first operand in the multiplication.
* @param[in] aSecondValue The second operand in the multiplication.
* @param[out] aResult A reference to return the multiplication result.
*
* @retval kErrorNone If the multiplication was performed safely without overflow. @p aResult is updated.
* @retval kErrorInvalidArgs If the multiplication would result in an overflow.
*/
template <typename UintType> inline Error SafeMultiply(UintType aFirstValue, UintType aSecondValue, UintType &aResult)
{
static_assert(TypeTraits::IsUint<UintType>::kValue, "UintType must be an unsigned int (8, 16, 32, or 64 bit len)");
Error error = kErrorNone;
if (aFirstValue == 0 || aSecondValue == 0)
{
aResult = 0;
ExitNow();
}
aResult = aFirstValue * aSecondValue;
VerifyOrExit(aResult / aFirstValue == aSecondValue, error = kErrorInvalidArgs);
exit:
return error;
}
/**
* This template function divides two numbers and rounds the result to the closest integer.
*
+4 -6
View File
@@ -39,6 +39,7 @@
#include "common/code_utils.hpp"
#include "common/debug.hpp"
#include "common/num_utils.hpp"
#include "common/numeric_limits.hpp"
namespace ot {
@@ -66,7 +67,6 @@ void *Heap::CAlloc(size_t aCount, size_t aSize)
void *ret = nullptr;
Block *prev = nullptr;
Block *curr = nullptr;
size_t totalSize;
uint16_t size;
// Verify that the requested allocation size will not cause an overflow.
@@ -79,12 +79,10 @@ void *Heap::CAlloc(size_t aCount, size_t aSize)
VerifyOrExit(aCount <= NumericLimits<uint16_t>::kMax);
VerifyOrExit(aSize <= NumericLimits<uint16_t>::kMax);
totalSize = aCount * aSize;
VerifyOrExit(totalSize <= NumericLimits<uint16_t>::kMax - kTotalSizeGuard);
SuccessOrExit(SafeMultiply<uint16_t>(static_cast<uint16_t>(aCount), static_cast<uint16_t>(aSize), size));
size = static_cast<uint16_t>(totalSize);
VerifyOrExit(size);
VerifyOrExit(size > 0);
VerifyOrExit(size <= NumericLimits<uint16_t>::kMax - kTotalSizeGuard);
size += kAlignSize - 1 - kBlockRemainderSize;
size &= ~(kAlignSize - 1);
+35
View File
@@ -151,6 +151,41 @@ void TestNumUtils(void)
VerifyOrQuit(ThreeWayCompare<bool>(true, false) > 0);
VerifyOrQuit(ThreeWayCompare<bool>(false, true) < 0);
SuccessOrQuit(SafeMultiply<uint16_t>(0, 0, u16));
VerifyOrQuit(u16 == 0);
SuccessOrQuit(SafeMultiply<uint16_t>(0, 0xffff, u16));
VerifyOrQuit(u16 == 0);
SuccessOrQuit(SafeMultiply<uint16_t>(0xffff, 0, u16));
VerifyOrQuit(u16 == 0);
SuccessOrQuit(SafeMultiply<uint16_t>(1, 0xffff, u16));
VerifyOrQuit(u16 == 0xffff);
SuccessOrQuit(SafeMultiply<uint16_t>(0xffff, 1, u16));
VerifyOrQuit(u16 == 0xffff);
SuccessOrQuit(SafeMultiply<uint16_t>(256, 255, u16));
VerifyOrQuit(u16 == 65280);
SuccessOrQuit(SafeMultiply<uint16_t>(255, 256, u16));
VerifyOrQuit(u16 == 65280);
VerifyOrQuit(SafeMultiply<uint16_t>(256, 256, u16) == kErrorInvalidArgs);
for (uint16_t num = 2; num < 255; num++)
{
uint16_t div = 0xffff / num;
SuccessOrQuit(SafeMultiply<uint16_t>(num, div, u16));
VerifyOrQuit(u16 == num * div);
SuccessOrQuit(SafeMultiply<uint16_t>(div, num, u16));
VerifyOrQuit(u16 == num * div);
VerifyOrQuit(SafeMultiply<uint16_t>(num, div + 1, u16) == kErrorInvalidArgs);
VerifyOrQuit(SafeMultiply<uint16_t>(div + 1, num, u16) == kErrorInvalidArgs);
VerifyOrQuit(SafeMultiply<uint16_t>(num + 1, div, u16) == kErrorInvalidArgs);
VerifyOrQuit(SafeMultiply<uint16_t>(div, num + 1, u16) == kErrorInvalidArgs);
}
VerifyOrQuit(DivideAndRoundToClosest<uint8_t>(2, 1) == 2);
VerifyOrQuit(DivideAndRoundToClosest<uint8_t>(1, 3) == 0);
VerifyOrQuit(DivideAndRoundToClosest<uint8_t>(1, 2) == 1);