Full Report
Q (lolz) is a proof of stake EVM compatible blockchain. It's native currency is Q tokens that are used for voting, staking and much more. The voting mechanism has four components: A proposal is created. Voters lock their Q Tokens. Voters cast their votes on the proposal. The proposal is either accepted or rejected. The _vote() function counts votes that are proportional to the amount of tokens they have. After enough time the proposal is either accepted or rejected. To use a Q token for voting, the token is locked. Users can delegate their voting power to other users as well. The voting power of a user is calculated based upon the quantity of Q tokens that have been locked until the end of the proposal voting period. The vulnerability is about the counting the votes. Using a particular flow, we can get tokens to be counted twice. User A delegates their Q tokens to User B. User B votes on a proposal, with the incorporating voting power from User A. User A announces the unlocking of their Q Tokens. User A votes on the same proposal with the same Q tokens being counted twice. Why does announcing the unlock make this possible? It seems like such a weird flow! The flow of operations does not seem to consider the case where a delegated entity had already voted then decided to unlock their tokens. At this point, it is assumed that the person will take their tokens out; but, they are still able to vote. Voting bugs are always fun! Double counting bugs on voting are terrible and compromise the eco-system. Write up could have been more clear on WHY this happens instead of the teaser they include.
Analysis Summary
# Vulnerability: Double-Voting via Delegation and Unlock-Annexation in Q Blockchain
## CVE Details
- **CVE ID**: Not Assigned (Common in Web3/Bug Bounty findings)
- **CVSS Score**: 9.0 - 10.0 (Critical - Estimated)
- **CWE**: CWE-670: Always-Incorrect Control Flow Implementation / CWE-837: Improper Enforcement of Message Integrity (Double Counting)
## Affected Systems
- **Products**: Q Blockchain Governance Framework
- **Versions**: Commit `a11d9c0fec2f85b9e9b5f09e871559597e782b3e`
- **Configurations**: Any voting scenario involving `RootsVoting.sol` or contracts inheriting from the `IVoting` interface where delegation and token unlocking are enabled.
## Vulnerability Description
The vulnerability lies in the logic governing how voting power is calculated and tracked during a proposal's lifecycle. Specifically, the system fails to account for state changes when a user moves from a "delegated" status to an "unlocking" status.
The flaw occurs because:
1. When User A delegates to User B, User B’s voting power includes User A’s tokens.
2. If User B votes, the `_vote()` function records that **User B** has voted and adds the combined weight to the proposal counters.
3. If User A subsequently initiates an "unlocking" process, the system treats User A as an independent entity again to facilitate their exit.
4. However, the system does not check if User A’s tokens were *already* used by a delegate (User B) for the same proposal.
5. Because the `hasUserVoted` mapping only tracks the address that called the function, User A (the delegator) is not marked as having voted, allowing them to cast a second vote with the same tokens.
## Exploitation
- **Status**: PoC confirmed (Verified by Immunefi/Q Blockchain team)
- **Complexity**: Low
- **Attack Vector**: Network
- **Steps to Exploit**:
1. **User A** delegates Q tokens to **User B**.
2. **User B** votes on a proposal (Weight = B + A).
3. **User A** "announces" an unlock of their tokens.
4. **User A** votes on the same proposal (Weight = A).
5. **Result**: Total weight counted is B + 2A.
## Impact
- **Confidentiality**: None
- **Integrity**: Critical (Governance integrity is compromised; malicious actors can pass or fail proposals unfairly)
- **Availability**: High (Ability to disrupt network governance and financial parameters)
## Remediation
### Patches
- The Q Blockchain team has updated the voting logic to ensure that if tokens have contributed toward a vote via a delegate, they cannot be reused by the delegator during the same proposal period. (Specific commit hash not provided in the source article, but confirmed as resolved by October 24, 2022).
### Workarounds
- Temporary suspension of the "Unlock Announcement" feature or manual reconciliation of votes before proposal execution.
## Detection
- **Indicators of Compromise**: Monitor for high-volume voting activity where delegators and their corresponding delegates both cast votes on the same Proposal ID.
- **Detection methods**: Query the blockchain for transactions calling `voteFor`/`voteAgainst` from addresses that have an active delegation relationship during the same block window or proposal period.
## References
- Q Blockchain Bug Bounty: [https://immunefi.com/bounty/qblockchain/](https://immunefi.com/bounty/qblockchain/)
- Original Write-up: [https://medium.com/@blockian/striking-gold-at-30-000-feet-uncovering-a-critical-vulnerability-in-q-blockchain-for-50-000-ab335042147b](https://medium.com/@blockian/striking-gold-at-30-000-feet-uncovering-a-critical-vulnerability-in-q-blockchain-for-50-000-ab335042147b)