# Reduction gate in binop operation is unsafe

zkSync binop reduction gate only range-checks and/or results, letting xor_result overflow UInt8 and forge arbitrary and/or outputs in-circuit.

- Payout: Contest reward
- Severity: high
- Program: zkSync
- Credited: xuwinnie
- Disclosed: 2024-02-29
- Write-up quality: 7/10
- Original: https://github.com/code-423n4/2023-10-zksync-findings/issues/679
- Library page: https://bugbountylibrary.com/r/c4%3A2023-10-zksync%3AH-05
- Tags: domain:web3 impact:integrity kind:contest platform:code4rena sev:high tech:rust technique:source-review vuln:memory-corruption

**Target:** zkSync circuit binop reduction gate
**Root cause:** The reduction gate decomposes composite_result into and/or/xor but only range-checks and_result and or_result (<128); xor_result is wrapped with from_variable_unchecked, so it can overflow UInt8 and stay in the circuit.
**Steps:**
1. In get_binop_subresults, composite_result is obtained via lookup for and/or/xor.
2. ReductionGate enforces composite_value = (xor_result << 32) | (or_result << 16) | and_result.
3. Range check only performs lookup on `[source_set[0], source_set[1]]` (and_result, or_result), not xor_result.
4. For chosen and_result/or_result <128, pick xor_result = (composite_value - (or_result<<16) - and_result) >> 32, which may exceed 255.
5. xor_result is wrapped via `UInt8::from_variable_unchecked`, leaving an overflowed UInt8 in the circuit.
**How found:** Code review of get_binop_subresults: noticed the range-check lookup only covers two of the three decomposed results and that from_variable_unchecked skips overflow checks.
**Exploitation:** Attacker supplies malicious witness values so the reduction gate accepts forged and/or results and an overflowing xor_result, breaking circuit soundness for binop opcodes.
**Insight:** When a composite value is split into limbs, range-check every limb that is later cast unchecked; a lookup on a subset leaves the remaining limb free to absorb the modulus.
