| Attribute | Value |
|---|---|
| CVSS v3.1 Score | 9.1 (Critical) |
| CVSS v3.1 Vector | CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:N |
| CVE ID | CVE-2026-4931 |
| Marginal GitHub Reference | MarginalProtocol/v1-core#10 |
| GitHub Advisory Lookup | CVE-2026-4931 (GitHub Advisories) |
| CERT/CC Case | VU#643748 |
| CWE | CWE-197: Numeric Truncation Error |
| Affected Contract | 0x3A6C55Ce74d940A9B5dDDE1E57eF6e70bC8757A7 (Ethereum Mainnet) |
| Status | Patched (block 24,386,649 — Feb 04, 2026) |
Several vulnerability scanners and advisory feeds are incorrectly reporting CVE-2026-4931 as "moderate severity" or "high attack complexity". This is wrong.
| Attribute | Correct Value | Common Misclassification |
|---|---|---|
| Severity | Critical (9.1) | Moderate / High |
| Attack Complexity (AC) | Low (AC:L) | High (AC:H) |
Why AC:L is correct: The exploit uses only universally available flash-loan primitives. There are no race conditions, no information leakage requirements, no special timing, and no environmental preconditions. The overflow threshold is deterministic and easily reachable. This meets every criterion for Low complexity under the CVSS v3.1 specification.
The authoritative sources — CERT/CC VU#643748 and NVD CVE-2026-4931 — both reflect CVSS 9.1 Critical with AC:L. A machine-readable OSV-format advisory with the correct scoring is included in this repository at .github/advisories/CVE-2026-4931.json.
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:N
- AV:N — Network: Attacker exploits remotely using universally available flash loans
- AC:L — Low Complexity: Standard DeFi flash loan primitives; no special conditions
- PR:N — No Privileges Required: Any external account can execute the attack
- UI:N — No User Interaction: Fully autonomous, single-transaction attack
- S:C — Scope Changed: Impact extends to all pool liquidity providers and lenders
- C:H — High Confidentiality: Complete extraction of deposited funds
- I:H — High Integrity: Full corruption of debt accounting (99.999999% precision loss)
- A:N — Availability: Not applicable (economic drain, not service outage)
The MarginalV1Pool contract uses Q96 fixed-point arithmetic (256-bit) internally
but downcasts to uint160 without overflow protection during settlement calculations:
uint160 price = uint160(sqrtPriceX96); // NO BOUNDS CHECKAt the EVM bytecode level, this is a bitmask operation (AND 0xfff...fff for 160 bits).
When sqrtPriceX96 exceeds type(uint160).max, the high bits are silently dropped and
the transaction does not revert.
An attacker uses flash loans to push sqrtPrice past the uint160 overflow threshold,
causing the protocol to calculate a $100,000,000 debt as worth 0.000000000000057005 ETH.
Original Value: 1461501637330902918203684832716283019655932599981
Truncated Value: 57005
Precision Loss: 99.999999%
Actual Debt: $100,000,000 USDC
Settlement Cost: 0.000000000000057005 ETH
Attacker Profit: $99,999,999.99
The Marginal V1 team deployed a patch at block 24,386,649 (Feb 04, 2026) using
OpenZeppelin's SafeCast library (SafeCast: value doesn't fit in 160 bits),
confirmed on-chain in transaction:
0xe021842bc2fe89865e41ef20aa84a8f649efe82d515fe3980b6dd160b564189a
The correct fix is:
// Use SafeCast instead of direct downcast:
uint160 price = SafeCast.toUint160(sqrtPriceX96);
// or:
require(sqrtPriceX96 <= type(uint160).max, "Price overflow");
uint160 price = uint160(sqrtPriceX96);