Skip to content

fix(interactsh): serialize InternalEvent access#7322

Open
dwisiswant0 wants to merge 1 commit intodevfrom
dwisiswant0/fix/interactsh/serialize-InternalEvent-access
Open

fix(interactsh): serialize InternalEvent access#7322
dwisiswant0 wants to merge 1 commit intodevfrom
dwisiswant0/fix/interactsh/serialize-InternalEvent-access

Conversation

@dwisiswant0
Copy link
Copy Markdown
Member

@dwisiswant0 dwisiswant0 commented Apr 2, 2026

Proposed changes

fix(interactsh): serialize InternalEvent access

interactsh.(*Client).processInteractionForRequest
was updating the wrapped event under lock but
releasing it before calling Operators.Execute(),
allowing async interactsh updates to race with
matcher expr evaluation on the same InternalEvent
map, causing fatal concurrent map iteration/write
crashes.

Hold the event lock while populating interactsh
fields and running operators, and use a locked
eventHash() helper for matched-template cache
lookups that access the same event data.

Fixes #7319

Proof

patch:

$ go test -count=1 -v -run ^TestProcessInteractionForRequestConcurrentEventUpdate$ ./pkg/protocols/common/interactsh
=== RUN   TestProcessInteractionForRequestConcurrentEventUpdate
--- PASS: TestProcessInteractionForRequestConcurrentEventUpdate (0.50s)
PASS
ok  	github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/interactsh	0.597s

dev:

$ go test -count=1 -v -run ^TestProcessInteractionForRequestConcurrentEventUpdate$ ./pkg/protocols/common/interactsh
=== RUN   TestProcessInteractionForRequestConcurrentEventUpdate
fatal error: concurrent map iteration and map write

goroutine 34 [running]:
internal/runtime/maps.fatal({0x1bc97f5?, 0x17d79a0?})
	/usr/local/go/src/runtime/panic.go:1181 +0x18
internal/runtime/maps.(*Iter).Next(0x17d79a0?)
	/usr/local/go/src/internal/runtime/maps/table.go:819 +0x86
github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/expressions.getFunctionsNames(...)
	/home/dw1/Development/PD/nuclei/pkg/protocols/common/expressions/expressions.go:186
github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/expressions.isExpression({0x2b55c9eca002, 0x4}, 0x2b55c9c9b2f0)
	/home/dw1/Development/PD/nuclei/pkg/protocols/common/expressions/expressions.go:142 +0x1aa
github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/expressions.FindExpressions({0x2b55c9eca000?, 0x0?}, {0x1b151ba, 0x2}, {0x1b151bc, 0x2}, 0x2b55c9c9b2f0)
	/home/dw1/Development/PD/nuclei/pkg/protocols/common/expressions/expressions.go:120 +0x245
github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/expressions.evaluate({0x2b55c9eca000, 0x992}, 0x2b55c9c9b2f0)
	/home/dw1/Development/PD/nuclei/pkg/protocols/common/expressions/expressions.go:46 +0x53
github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/expressions.Evaluate(...)
	/home/dw1/Development/PD/nuclei/pkg/protocols/common/expressions/expressions.go:31
github.com/projectdiscovery/nuclei/v3/pkg/operators/matchers.(*Matcher).MatchWords(0x2b55cad9c000, {0x1b89388?, 0x428145?}, 0x60?)
	/home/dw1/Development/PD/nuclei/pkg/operators/matchers/match.go:68 +0x129
github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/interactsh.TestProcessInteractionForRequestConcurrentEventUpdate.func1(0x2b55c9c9b2f0, 0x2b55cad9c000)
	/home/dw1/Development/PD/nuclei/pkg/protocols/common/interactsh/interactsh_test.go:60 +0x6d
github.com/projectdiscovery/nuclei/v3/pkg/operators.(*Operators).Execute(0x2b55c9c200c0, 0x2b55c9c9b2f0, 0x2b55c97d40f0, 0x24ef390, 0x0)
	/home/dw1/Development/PD/nuclei/pkg/operators/operators.go:303 +0xb08
github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/interactsh.(*Client).processInteractionForRequest(0x2b55cada3e98, 0x2b55c992c4d0, 0x2b55c9d76700)
	/home/dw1/Development/PD/nuclei/pkg/protocols/common/interactsh/interactsh.go:169 +0x358
github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/interactsh.TestProcessInteractionForRequestConcurrentEventUpdate(0x2b55c98bcd88)
	/home/dw1/Development/PD/nuclei/pkg/protocols/common/interactsh/interactsh_test.go:85 +0x7f2
testing.tRunner(0x2b55c98bcd88, 0x24ef2d0)
	/usr/local/go/src/testing/testing.go:2036 +0xea
created by testing.(*T).Run in goroutine 1
	/usr/local/go/src/testing/testing.go:2101 +0x4c5
FAIL	github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/interactsh	0.100s
FAIL

Checklist

  • Pull request is created against the dev branch
  • All checks passed (lint, unit/integration/regression tests etc.) with my changes
  • I have added tests that prove my fix is effective or that my feature works
  • I have added necessary documentation (if appropriate)

Summary by CodeRabbit

  • Bug Fixes

    • Improved concurrent event handling to prevent race conditions during template matching and event processing.
    • Enhanced error logging for operator execution to ensure proper synchronization.
  • Tests

    • Added comprehensive testing for concurrent event updates during interaction processing.

`interactsh.(*Client).processInteractionForRequest`
was updating the wrapped event under lock but
releasing it before calling `Operators.Execute()`,
allowing async interactsh updates to race with
matcher expr evaluation on the same `InternalEvent`
map, causing fatal concurrent map iteration/write
crashes.

Hold the event lock while populating interactsh
fields and running operators, and use a locked
`eventHash()` helper for matched-template cache
lookups that access the same event data.

Fixes #7319

Signed-off-by: Dwi Siswanto <git@dw1.io>
@auto-assign auto-assign bot requested a review from dogancanbakir April 2, 2026 04:51
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 2, 2026

Walkthrough

Updated interactsh event processing to use a hash wrapper function instead of directly hashing event data, preventing concurrent map iteration issues during template matching. Refactored operator execution error handling to ensure proper unlock sequencing, and added a concurrent event mutation test to validate thread-safety.

Changes

Cohort / File(s) Summary
Event Hash & Cache Updates
pkg/protocols/common/interactsh/interactsh.go
Introduced eventHash() wrapper function for template-match cache lookups. Updated early-exit checks in polling callback and RequestEvent, cache writes via matchedTemplates.SetWithExpire, and AlreadyMatched membership checks to use the new hash wrapper. Refactored operator execution error logging to ensure data.Event.Unlock() executes before logging when operators are nil, preserving locking semantics.
Concurrent Event Update Test
pkg/protocols/common/interactsh/interactsh_test.go
Added TestProcessInteractionForRequestConcurrentEventUpdate to validate thread-safe event processing. Test constructs an event with dynamic keys, triggers concurrent mutations (lock/unlock/modify/delete cycles) during operator matching, and verifies no panics or incorrect matches occur under concurrent access patterns.

Estimated Code Review Effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Poem

🐰 A race condition caught, a map now safe and sound,
With hashing guards and locks that keep our events homebound,
Concurrent dancers twirl—no crashes, no despair,
The Interactsh polls on, through mutex'd mountain air!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main fix: serializing InternalEvent access to prevent race conditions in the interactsh package.
Linked Issues check ✅ Passed The PR directly addresses all coding objectives from issue #7319: it prevents concurrent map iteration/write panics by holding the event lock during operator execution and cache lookups, and includes a test validating the fix.
Out of Scope Changes check ✅ Passed All changes are directly scoped to fixing the interactsh race condition: lock serialization in event processing, eventHash helper for thread-safe cache access, and a focused concurrency test.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch dwisiswant0/fix/interactsh/serialize-InternalEvent-access

Comment @coderabbitai help to get the list of available commands and usage tips.

@neo-by-projectdiscovery-dev
Copy link
Copy Markdown

neo-by-projectdiscovery-dev bot commented Apr 2, 2026

Neo - PR Security Review

No security issues found

Comment @pdneo help for available commands. · Open in Neo

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
pkg/protocols/common/interactsh/interactsh_test.go (1)

55-60: Consider adding a brief comment explaining the synchronization pattern.

The MatchFunc closure orchestrates the race condition scenario, but the purpose isn't immediately obvious.

📝 Suggested documentation improvement
 		MatchFunc: func(data map[string]interface{}, matcher *matchers.Matcher) (bool, []string) {
+			// Signal writer goroutine to start mutating while we're in the middle of matching.
+			// With the fix, the writer will block waiting for the lock that processInteractionForRequest holds.
 			startWriter.Do(func() {
 				close(writerStarted)
 			})
+			// Yield to allow writer goroutine to be scheduled (it will block on the lock).
 			runtime.Gosched()
 			return matcher.MatchWords("not-present-in-corpus", data)
 		},
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/protocols/common/interactsh/interactsh_test.go` around lines 55 - 60, Add
a short comment inside the MatchFunc closure (near startWriter.Do,
close(writerStarted), and runtime.Gosched()) that explains the synchronization
pattern: that startWriter.Do signals the writer goroutine to begin by closing
writerStarted, runtime.Gosched is used to yield the scheduler to increase the
likelihood of the race for the test, and
matcher.MatchWords("not-present-in-corpus", data) is then evaluated to exercise
the race condition; reference the symbols MatchFunc, startWriter, writerStarted,
runtime.Gosched, and matcher.MatchWords to make the intent and safety of the
ordering explicit.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@pkg/protocols/common/interactsh/interactsh_test.go`:
- Around line 55-60: Add a short comment inside the MatchFunc closure (near
startWriter.Do, close(writerStarted), and runtime.Gosched()) that explains the
synchronization pattern: that startWriter.Do signals the writer goroutine to
begin by closing writerStarted, runtime.Gosched is used to yield the scheduler
to increase the likelihood of the race for the test, and
matcher.MatchWords("not-present-in-corpus", data) is then evaluated to exercise
the race condition; reference the symbols MatchFunc, startWriter, writerStarted,
runtime.Gosched, and matcher.MatchWords to make the intent and safety of the
ordering explicit.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: aa46cbb1-277c-46ad-8ac1-18d8f8a3677c

📥 Commits

Reviewing files that changed from the base of the PR and between c6fb1ae and 68e49a7.

📒 Files selected for processing (2)
  • pkg/protocols/common/interactsh/interactsh.go
  • pkg/protocols/common/interactsh/interactsh_test.go

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fatal error: concurrent map iteration and map write with Interactsh in v3.7.1

1 participant