Skip to content

Commit 1d574cf

Browse files
authored
Fix to expmod precompile gas calculation (#1176)
Fix and refactor gas cost calculation for the expmod precompile. The issue was that some computations could overflow and the result was incorrect. This was found by a fuzzer so some test vectors are included.
1 parent 4977c94 commit 1d574cf

File tree

2 files changed

+86
-41
lines changed

2 files changed

+86
-41
lines changed

Diff for: test/state/precompiles.cpp

+55-41
Original file line numberDiff line numberDiff line change
@@ -100,63 +100,77 @@ PrecompileAnalysis expmod_analyze(bytes_view input, evmc_revision rev) noexcept
100100
{
101101
using namespace intx;
102102

103-
static constexpr size_t INPUT_HEADER_REQUIRED_SIZE = 3 * sizeof(uint256);
104-
const int64_t min_gas = (rev >= EVMC_BERLIN) ? 200 : 0;
105-
106-
uint8_t input_header[INPUT_HEADER_REQUIRED_SIZE]{};
107-
// NOLINTNEXTLINE(bugprone-suspicious-stringview-data-usage)
108-
std::copy_n(input.data(), std::min(input.size(), INPUT_HEADER_REQUIRED_SIZE), input_header);
109-
110-
const auto base_len = be::unsafe::load<uint256>(&input_header[0]);
111-
const auto exp_len = be::unsafe::load<uint256>(&input_header[32]);
112-
const auto mod_len = be::unsafe::load<uint256>(&input_header[64]);
103+
const auto calc_adjusted_exp_len = [input](size_t offset, uint32_t len) noexcept {
104+
const auto head_len = std::min(size_t{len}, size_t{32});
105+
const auto head_explicit_bytes =
106+
offset < input.size() ?
107+
input.substr(offset, std::min(head_len, input.size() - offset)) :
108+
bytes_view{};
113109

114-
if (base_len == 0 && mod_len == 0)
115-
return {min_gas, 0};
116-
117-
static constexpr auto LEN_LIMIT = std::numeric_limits<size_t>::max();
118-
if (base_len > LEN_LIMIT || exp_len > LEN_LIMIT || mod_len > LEN_LIMIT)
119-
return {GasCostMax, 0};
120-
121-
const auto adjusted_len = [input](size_t offset, size_t len) {
122-
const auto head_len = std::min(len, size_t{32});
123-
const auto head_explicit_len =
124-
std::max(std::min(offset + head_len, input.size()), offset) - offset;
125-
const bytes_view head_explicit_bytes(&input[offset], head_explicit_len);
126110
const auto top_byte_index = head_explicit_bytes.find_first_not_of(uint8_t{0});
127-
const size_t exp_bit_width =
111+
const auto exp_bit_width =
128112
(top_byte_index != bytes_view::npos) ?
129-
(head_len - top_byte_index - 1) * 8 +
130-
static_cast<size_t>(std::bit_width(head_explicit_bytes[top_byte_index])) :
113+
8 * (head_len - top_byte_index - 1) +
114+
static_cast<unsigned>(std::bit_width(head_explicit_bytes[top_byte_index])) :
131115
0;
132116

133-
return std::max(
134-
8 * (std::max(len, size_t{32}) - 32) + (std::max(exp_bit_width, size_t{1}) - 1),
135-
size_t{1});
117+
const auto tail_len = len - head_len;
118+
const auto head_bits = std::max(exp_bit_width, size_t{1}) - 1;
119+
return std::max(8 * uint64_t{tail_len} + uint64_t{head_bits}, uint64_t{1});
136120
};
137121

138-
static constexpr auto mult_complexity_eip2565 = [](const uint256& x) noexcept {
139-
const auto w = (x + 7) >> 3;
140-
return w * w;
122+
static constexpr auto calc_mult_complexity_eip2565 = [](uint32_t y) noexcept {
123+
const auto x = uint64_t{y};
124+
const auto w = (x + 7) / 8;
125+
return w * w; // max value: 0x04000000'00000000
141126
};
142-
static constexpr auto mult_complexity_eip198 = [](const uint256& x) noexcept {
127+
static constexpr auto calc_mult_complexity_eip198 = [](uint32_t y) noexcept {
128+
const auto x = uint64_t{y};
143129
const auto xx = x * x;
144130
if (x <= 64)
145131
return xx;
146132
else if (x <= 1024)
147-
return (xx >> 2) + 96 * x - 3072;
133+
return xx / 4 + 96 * x - 3072;
148134
else
149-
return (xx >> 4) + 480 * x - 199680;
135+
return xx / 16 + 480 * x - 199680; // max value: 0x100001df'dffcf220
136+
};
137+
138+
struct Params
139+
{
140+
int64_t min_gas;
141+
unsigned final_divisor;
142+
uint64_t (*calc_mult_complexity)(uint32_t y) noexcept;
150143
};
144+
static constexpr Params byzantium_params{0, 20, calc_mult_complexity_eip198};
145+
static constexpr Params berlin_params{200, 3, calc_mult_complexity_eip2565};
146+
const auto& [min_gas, final_divisor, calc_mult_complexity] =
147+
(rev >= EVMC_BERLIN) ? berlin_params : byzantium_params;
148+
149+
static constexpr size_t INPUT_HEADER_REQUIRED_SIZE = 3 * sizeof(uint256);
150+
uint8_t input_header[INPUT_HEADER_REQUIRED_SIZE]{};
151+
// NOLINTNEXTLINE(bugprone-suspicious-stringview-data-usage)
152+
std::copy_n(input.data(), std::min(input.size(), INPUT_HEADER_REQUIRED_SIZE), input_header);
153+
154+
const auto base_len256 = be::unsafe::load<uint256>(&input_header[0]);
155+
const auto exp_len256 = be::unsafe::load<uint256>(&input_header[32]);
156+
const auto mod_len256 = be::unsafe::load<uint256>(&input_header[64]);
157+
158+
if (base_len256 == 0 && mod_len256 == 0)
159+
return {min_gas, 0};
160+
161+
static constexpr auto LEN_LIMIT = std::numeric_limits<uint32_t>::max();
162+
if (base_len256 > LEN_LIMIT || exp_len256 > LEN_LIMIT || mod_len256 > LEN_LIMIT)
163+
return {GasCostMax, 0};
164+
165+
const auto base_len = static_cast<uint32_t>(base_len256);
166+
const auto exp_len = static_cast<uint32_t>(exp_len256);
167+
const auto mod_len = static_cast<uint32_t>(mod_len256);
151168

169+
const auto adjusted_exp_len = calc_adjusted_exp_len(sizeof(input_header) + base_len, exp_len);
152170
const auto max_len = std::max(mod_len, base_len);
153-
const auto adjusted_exp_len = adjusted_len(
154-
sizeof(input_header) + static_cast<size_t>(base_len), static_cast<size_t>(exp_len));
155-
const auto gas = (rev >= EVMC_BERLIN) ?
156-
mult_complexity_eip2565(max_len) * adjusted_exp_len / 3 :
157-
mult_complexity_eip198(max_len) * adjusted_exp_len / 20;
158-
return {std::max(min_gas, static_cast<int64_t>(std::min(gas, uint256{GasCostMax}))),
159-
static_cast<size_t>(mod_len)};
171+
const auto gas = umul(calc_mult_complexity(max_len), adjusted_exp_len) / final_divisor;
172+
const auto gas_clamped = std::clamp<uint128>(gas, min_gas, GasCostMax);
173+
return {static_cast<int64_t>(gas_clamped), mod_len};
160174
}
161175

162176
PrecompileAnalysis point_evaluation_analyze(bytes_view, evmc_revision) noexcept

Diff for: test/unittests/precompiles_expmod_test.cpp

+31
Original file line numberDiff line numberDiff line change
@@ -56,3 +56,34 @@ TEST(expmod, test_vectors)
5656
EXPECT_EQ(hex(result), expected_result);
5757
}
5858
}
59+
60+
TEST(expmod, analysis_oog)
61+
{
62+
// Tests the gas cost calculation of the expmod precompile.
63+
// The result cost is expected to prohibit execution.
64+
static constexpr auto GAS_LIMIT = 1'000'000'000;
65+
static constexpr std::array inputs{
66+
// clang-format off
67+
"0000000000000000000000000000000000000000000000000000000000000001 000000000000000000000000000000000000000000000000 80000000 00000020 0000000000000000000000000000000000000000000000000000000000000001 80",
68+
"0000000000000000000000000000000000000000000000000000000000000001 000000000000000000000000000000000000000000000000 40000000 00000020 0000000000000000000000000000000000000000000000000000000000000001 80",
69+
"0000000000000000000000000000000000000000000000000000000000000001 000000000000000000000000000000000000000000000000 20000000 00000020 0000000000000000000000000000000000000000000000000000000000000001 80",
70+
"0000000000000000000000000000000000000000000000000000000000000001 000000000000000000000000000000000000000000000000 10000000 00000020 0000000000000000000000000000000000000000000000000000000000000001 80",
71+
"0000000000000000000000000000000000000000000000000000000000000001 000000000000000000000000000000000000000000000000 00000000 80000020 0000000000000000000000000000000000000000000000000000000000000001 80",
72+
"0000000000000000000000000000000000000000000000000000000000000001 000000000000000000000000000000000000000000000000 00000000 40000020 0000000000000000000000000000000000000000000000000000000000000001 80",
73+
"0000000000000000000000000000000000000000000000000000000000000001 000000000000000000000000000000000000000000000000 00000000 20000020 0000000000000000000000000000000000000000000000000000000000000001 80",
74+
"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff9 0000000000000000000000000000000000000000000000000000000000000001 0000000000000000000000000000000000000000000000000000000000000001",
75+
"0000000000000000000000000000000000000000000000000000000000000001 0000000000000000000000000000000000000000000000000000000000000040 00000000000000000000000000000000000000000000000000000000ffffffff 80"
76+
"0000000000000000000000000000000000000000000000000000000000000001 0000000000000000000000000000000000000000000000000000000000000040 00000000000000000000000000000000000000000000000000000000ffffffff 80",
77+
"00000000000000000000000000000000000000000000000000000000ffffffff 0000000000000000000000000000000000000000000000000000000000000001 0000000000000000000000000000000000000000000000000000000000000001 80",
78+
"0000000000000000000000000000000000000000000000000000000000000001 00000000000000000000000000000000000000000000000000000000ffffffff 0000000000000000000000000000000000000000000000000000000000000001 80",
79+
"0000000000000000000000000000000000000000000000000000000080000000 0000000000000000000000000000000000000000000000000000000080000000 0000000000000000000000000000000000000000000000000000000000000001 80",
80+
// clang-format on
81+
};
82+
83+
for (const auto& input_hex : inputs)
84+
{
85+
const auto input = evmc::from_spaced_hex(input_hex).value();
86+
const auto [gas_cost, max_output_size] = evmone::state::expmod_analyze(input, EVMC_PRAGUE);
87+
EXPECT_GT(gas_cost, GAS_LIMIT);
88+
}
89+
}

0 commit comments

Comments
 (0)