Reject non-canonical binaries (#6277)

* Reject non-canonical binaries

* Review fixes

* Cleanup Number2 class

* Use enum instead of 0
This commit is contained in:
Olek
2026-01-27 16:30:51 -05:00
committed by GitHub
parent 977caea0a5
commit c1c1b4ea67
3 changed files with 95 additions and 71 deletions

View File

@@ -2411,6 +2411,7 @@ struct HostFuncImpl_test : public beast::unit_test::suite
{
testcase("floatSet");
using namespace test::jtx;
using namespace wasm_float;
Env env{*this};
OpenView ov{*env.current()};
@@ -2435,7 +2436,7 @@ struct HostFuncImpl_test : public beast::unit_test::suite
{
auto const result =
hfs.floatSet(1, wasm_float::maxExponent + normalExp + 1, 0);
hfs.floatSet(1, wasmMaxExponent + normalExp + 1, 0);
BEAST_EXPECT(!result) &&
BEAST_EXPECT(
result.error() ==
@@ -2444,46 +2445,35 @@ struct HostFuncImpl_test : public beast::unit_test::suite
{
auto const result =
hfs.floatSet(1, wasm_float::maxExponent + normalExp + 1, 0);
BEAST_EXPECT(!result) &&
BEAST_EXPECT(
result.error() ==
HostFunctionError::FLOAT_COMPUTATION_ERROR);
}
{
auto const result =
hfs.floatSet(1, wasm_float::minExponent + normalExp - 1, 0);
hfs.floatSet(1, wasmMinExponent + normalExp - 1, 0);
BEAST_EXPECT(result) && BEAST_EXPECT(*result == floatIntZero);
}
{
auto const result =
hfs.floatSet(1, wasm_float::maxExponent + normalExp, 0);
auto const result = hfs.floatSet(1, wasmMaxExponent + normalExp, 0);
BEAST_EXPECT(result) && BEAST_EXPECT(*result == floatMaxExp);
}
{
auto const result =
hfs.floatSet(-1, wasm_float::maxExponent + normalExp, 0);
hfs.floatSet(-1, wasmMaxExponent + normalExp, 0);
BEAST_EXPECT(result) && BEAST_EXPECT(*result == floatMinusMaxExp);
}
{
auto const result =
hfs.floatSet(1, wasm_float::maxExponent + normalExp - 1, 0);
hfs.floatSet(1, wasmMaxExponent + normalExp - 1, 0);
BEAST_EXPECT(result) && BEAST_EXPECT(*result == floatPreMaxExp);
}
{
auto const result =
hfs.floatSet(STAmount::cMaxValue, wasm_float::maxExponent, 0);
hfs.floatSet(STAmount::cMaxValue, wasmMaxExponent, 0);
BEAST_EXPECT(result) && BEAST_EXPECT(*result == floatMaxIOU);
}
{
auto const result =
hfs.floatSet(1, wasm_float::minExponent + normalExp, 0);
auto const result = hfs.floatSet(1, wasmMinExponent + normalExp, 0);
BEAST_EXPECT(result) && BEAST_EXPECT(*result == floatMinExp);
}
@@ -3077,7 +3067,7 @@ struct HostFuncImpl_test : public beast::unit_test::suite
}
void
testFloatNonIOU()
testFloatSpecialCases()
{
testcase("float Xrp+Mpt");
using namespace test::jtx;
@@ -3136,6 +3126,20 @@ struct HostFuncImpl_test : public beast::unit_test::suite
!result &&
result.error() == HostFunctionError::FLOAT_INPUT_MALFORMED);
}
testcase("float non-canonical");
{ // non-canonical mantissa 10 000 000 000 000 000
Bytes x = float1;
*reinterpret_cast<uint64_t*>(x.data()) = 0x0000C16FF286A3D4ull;
{
auto const result =
hfs.floatCompare(makeSlice(x), makeSlice(float1));
BEAST_EXPECT(
!result &&
result.error() == HostFunctionError::FLOAT_INPUT_MALFORMED);
}
}
}
void
@@ -3153,7 +3157,7 @@ struct HostFuncImpl_test : public beast::unit_test::suite
testFloatRoot();
testFloatPower();
testFloatLog();
testFloatNonIOU();
testFloatSpecialCases();
}
void

View File

@@ -293,10 +293,10 @@ public:
namespace wasm_float {
// The range for the mantissa and exponent when normalized
static std::int64_t constexpr minMantissa = 1'000'000'000'000'000ll;
static std::int64_t constexpr maxMantissa = (1ull << 54) - 1;
static int constexpr minExponent = -96;
static int constexpr maxExponent = 80;
static std::int64_t constexpr wasmMinMantissa = 1'000'000'000'000'000ll;
static std::int64_t constexpr wasmMaxMantissa = wasmMinMantissa * 10 - 1;
static int constexpr wasmMinExponent = -96;
static int constexpr wasmMaxExponent = 80;
} // namespace wasm_float

View File

@@ -16,17 +16,25 @@ namespace detail {
class Number2 : public Number
{
protected:
static Bytes const FLOAT_NULL;
static Bytes const floatNull;
static unsigned constexpr encodedFloatSize = 8;
static int32_t constexpr encodedMantissaBits = 54;
static int32_t constexpr encodedExponentBits = 8;
static_assert(wasmMinExponent < 0);
static uint64_t constexpr maxEncodedMantissa =
(1ull << (encodedMantissaBits + 1)) - 1;
bool good_;
public:
Number2(Slice const& data) : Number(), good_(false)
{
if (data.size() != 8)
if (data.size() != encodedFloatSize)
return;
if (std::ranges::equal(FLOAT_NULL, data))
if (std::ranges::equal(floatNull, data))
{
good_ = true;
return;
@@ -36,45 +44,53 @@ public:
if (!(v & STAmount::cIssuedCurrency))
return;
int64_t const neg = (v & STAmount::cPositive) ? 1 : -1;
int32_t const e = static_cast<uint8_t>((v >> (64 - 10)) & 0xFFull);
if (e < 1 || e > 177)
int32_t const e =
static_cast<int32_t>((v >> encodedMantissaBits) & 0xFFull);
int32_t const decodedExponent = e + wasmMinExponent - 1; // e - 97
if (decodedExponent < wasmMinExponent ||
decodedExponent > wasmMaxExponent)
return;
int64_t const m = neg * static_cast<int64_t>(v & ((1ull << 54) - 1));
int64_t const neg = (v & STAmount::cPositive) ? 1 : -1;
int64_t const m =
neg * static_cast<int64_t>(v & ((1ull << encodedMantissaBits) - 1));
if (!m)
return;
Number x(makeNumber(m, e + wasm_float::minExponent - 1));
Number x(makeNumber(m, decodedExponent));
if (m != x.mantissa() || decodedExponent != x.exponent())
return; // not canonical
*static_cast<Number*>(this) = x;
good_ = true;
}
Number2() : Number(), good_(true)
template <class T>
Number2(T mantissa = 0, int32_t exponent = 0) : Number(), good_(false)
{
}
if (!mantissa)
{
good_ = true;
return;
}
Number2(int64_t x) : Number(makeNumber(x, 0)), good_(true)
{
}
auto const n = makeNumber(mantissa, exponent);
auto const e = n.exponent();
if (e < wasmMinExponent)
{
good_ = true; // value is zero(as in Numbers behavior)
return;
}
Number2(uint64_t x) : Number(0), good_(false)
{
using mtype = std::invoke_result_t<decltype(&Number::mantissa), Number>;
if (x <= std::numeric_limits<mtype>::max())
*this = makeNumber(x, 0);
else
*this = makeNumber(x / 10, 1);
if (e > wasmMaxExponent)
return;
*static_cast<Number*>(this) = n;
good_ = true;
}
Number2(int64_t mantissa, int32_t exponent)
: Number(makeNumber(mantissa, exponent)), good_(true)
{
}
Number2(Number const& n)
: Number(makeNumber(n.mantissa(), n.exponent())), good_(true)
: Number2(n.mantissa(), n.exponent()) // ensure Number canonized
{
}
@@ -100,35 +116,33 @@ public:
Expected<Bytes, HostFunctionError>
toBytes() const
{
uint64_t v = mantissa() >= 0 ? STAmount::cPositive : 0;
if (!good_)
return Unexpected(HostFunctionError::FLOAT_COMPUTATION_ERROR);
auto const m = mantissa();
auto const e = exponent();
uint64_t v = m >= 0 ? STAmount::cPositive : 0;
v |= STAmount::cIssuedCurrency;
uint64_t const absM = mantissa() >= 0 ? mantissa() : -mantissa();
uint64_t const absM = std::abs(m);
if (!absM)
{
using etype =
std::invoke_result_t<decltype(&Number::exponent), Number>;
if (exponent() != std::numeric_limits<etype>::lowest())
{
return Unexpected(
HostFunctionError::
FLOAT_COMPUTATION_ERROR); // LCOV_EXCL_LINE
}
return FLOAT_NULL;
return floatNull;
}
else if (absM > wasm_float::maxMantissa)
else if (absM > maxEncodedMantissa)
{
return Unexpected(
HostFunctionError::FLOAT_COMPUTATION_ERROR); // LCOV_EXCL_LINE
}
else if (exponent() > wasm_float::maxExponent)
return Unexpected(HostFunctionError::FLOAT_COMPUTATION_ERROR);
else if (exponent() < wasm_float::minExponent)
return FLOAT_NULL;
int const e = exponent() - wasm_float::minExponent + 1; //+97
v |= absM;
v |= ((uint64_t)e) << 54;
if (e > wasmMaxExponent)
return Unexpected(HostFunctionError::FLOAT_COMPUTATION_ERROR);
else if (e < wasmMinExponent)
return floatNull;
uint64_t const normExp = e - wasmMinExponent + 1; //+97
v |= normExp << encodedMantissaBits;
Serializer msg;
msg.add64(v);
@@ -147,7 +161,7 @@ public:
}
};
Bytes const Number2::FLOAT_NULL =
Bytes const Number2::floatNull =
{0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
struct FloatState
@@ -187,6 +201,8 @@ struct FloatState
std::string
floatToString(Slice const& data)
{
// set default mode as we don't expect it will be used here
detail::FloatState rm(Number::rounding_mode::to_nearest);
detail::Number2 const num(data);
if (!num)
{
@@ -251,6 +267,8 @@ floatSetImpl(int64_t mantissa, int32_t exponent, int32_t mode)
if (!rm)
return Unexpected(HostFunctionError::FLOAT_INPUT_MALFORMED);
detail::Number2 num(mantissa, exponent);
if (!num)
return Unexpected(HostFunctionError::FLOAT_COMPUTATION_ERROR);
return num.toBytes();
}
catch (...)
@@ -264,6 +282,8 @@ floatCompareImpl(Slice const& x, Slice const& y)
{
try
{
// set default mode as we don't expect it will be used here
detail::FloatState rm(Number::rounding_mode::to_nearest);
detail::Number2 xx(x);
if (!xx)
return Unexpected(HostFunctionError::FLOAT_INPUT_MALFORMED);
@@ -416,7 +436,7 @@ floatPowerImpl(Slice const& x, int32_t n, int32_t mode)
{
try
{
if ((n < 0) || (n > wasm_float::maxExponent))
if ((n < 0) || (n > wasmMaxExponent))
return Unexpected(HostFunctionError::FLOAT_INPUT_MALFORMED);
detail::FloatState rm(mode);