Conversation
WalkthroughReworked RegistryProcessor creation and lifecycle in ObjectProcessors to use Lazy initialization with a StrategyExecutor, per-domain GetOrAdd caching, and ComputerStatusEvent subscribe/detach; added a commented reference block in Sharphound.csproj. No other behavioral changes reported. Changes
Sequence Diagram(s)sequenceDiagram
participant OP as ObjectProcessors
participant Map as RegistryProcessorMap
participant LazyRP as Lazy<RegistryProcessor>
participant RP as RegistryProcessor
participant Exec as StrategyExecutor
OP->>Map: GetOrAdd(domain, Lazy{ new RP(null, Exec, domain) })
alt Lazy already created
Map-->>OP: Lazy (value created) -> RP
OP->>RP: ReadRegistrySettings(...)
else Lazy not yet created
Map-->>OP: Lazy (not created)
OP->>LazyRP: Value (creates RP with Exec)
LazyRP-->>RP: instantiate RP(null, Exec, domain)
RP->>OP: subscribe ComputerStatusEvent
OP->>RP: ReadRegistrySettings(...)
end
OP->>RP: Detach ComputerStatusEvent in ClearEventHandlers()
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/Runtime/ObjectProcessors.cs (1)
377-386:⚠️ Potential issue | 🟠 MajorTOCTOU race on
_registryProcessorMap: useGetOrAddto avoid leaked processors and duplicate event subscriptions.
TryGetValue+TryAddon aConcurrentDictionaryis not atomic. If two threads process computers with the sameDomainSidconcurrently, both can failTryGetValue, both create aRegistryProcessorwith a subscribed event handler, and only one winsTryAdd. The losing thread's processor is used but never tracked in the map, soClearEventHandlers(Line 89-91) will never unsubscribe it — leaking the event handler.Additionally,
new StrategyExecutor()is allocated per race loser unnecessarily.Replace with
GetOrAdd(or theLazy<T>variant ifRegistryProcessorconstruction has side effects you want to deduplicate):Proposed fix
if (_methods.HasFlag(CollectionMethod.NTLMRegistry)) { await _context.DoDelay(); - if (_registryProcessorMap.TryGetValue(resolvedSearchResult.DomainSid, out var processor)) { - ret.NTLMRegistryData = await processor.ReadRegistrySettings(resolvedSearchResult.DisplayName); - } else { - var newProcessor = new RegistryProcessor(null, new StrategyExecutor(), resolvedSearchResult.Domain); - newProcessor.ComputerStatusEvent += HandleCompStatusEvent; - _registryProcessorMap.TryAdd(resolvedSearchResult.DomainSid, newProcessor); - ret.NTLMRegistryData = await newProcessor.ReadRegistrySettings(resolvedSearchResult.DisplayName); - } + var processor = _registryProcessorMap.GetOrAdd(resolvedSearchResult.DomainSid, _ => { + var p = new RegistryProcessor(null, new StrategyExecutor(), resolvedSearchResult.Domain); + p.ComputerStatusEvent += HandleCompStatusEvent; + return p; + }); + ret.NTLMRegistryData = await processor.ReadRegistrySettings(resolvedSearchResult.DisplayName); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/Runtime/ObjectProcessors.cs` around lines 377 - 386, The code has a TOCTOU on _registryProcessorMap around the NTLMRegistry path: replace the TryGetValue/TryAdd pattern with ConcurrentDictionary.GetOrAdd (or GetOrAdd with Lazy<RegistryProcessor>) so RegistryProcessor instances and their ComputerStatusEvent subscription (HandleCompStatusEvent) are created and attached atomically; ensure you only subscribe HandleCompStatusEvent once on the instance returned from GetOrAdd and avoid allocating new StrategyExecutor() for losers by creating the RegistryProcessor only inside the factory passed to GetOrAdd (or Lazy.Value), then call ReadRegistrySettings on the returned processor to populate ret.NTLMRegistryData.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Outside diff comments:
In `@src/Runtime/ObjectProcessors.cs`:
- Around line 377-386: The code has a TOCTOU on _registryProcessorMap around the
NTLMRegistry path: replace the TryGetValue/TryAdd pattern with
ConcurrentDictionary.GetOrAdd (or GetOrAdd with Lazy<RegistryProcessor>) so
RegistryProcessor instances and their ComputerStatusEvent subscription
(HandleCompStatusEvent) are created and attached atomically; ensure you only
subscribe HandleCompStatusEvent once on the instance returned from GetOrAdd and
avoid allocating new StrategyExecutor() for losers by creating the
RegistryProcessor only inside the factory passed to GetOrAdd (or Lazy.Value),
then call ReadRegistrySettings on the returned processor to populate
ret.NTLMRegistryData.
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/Runtime/ObjectProcessors.cs`:
- Around line 379-383: The current use of _registryProcessorMap.GetOrAdd with a
factory can create multiple RegistryProcessor instances that subscribe to
ComputerStatusEvent and leak if discarded; change the dictionary to store
Lazy<RegistryProcessor> instead, update the GetOrAdd call to add a new Lazy that
constructs the RegistryProcessor and subscribes to ComputerStatusEvent inside
the Lazy factory (using resolvedSearchResult.Domain and DomainSid), and update
any code that reads processors to use .Value (e.g., when accessing the
processor) as well as modify ClearEventHandlers to iterate the stored Lazy
values and unsubscribe from each Lazy.Value.ComputerStatusEvent (or otherwise
access Value once) so that construction and subscription happen exactly once per
key.
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/Runtime/ObjectProcessors.cs`:
- Around line 382-386: The RegistryProcessor is being constructed with the wrong
signature in the Lazy factory: replace the three-argument call new
RegistryProcessor(null, new StrategyExecutor(), resolvedSearchResult.Domain)
with the two-argument constructor that matches RegistryProcessor(ILogger log,
string domain); i.e., call new RegistryProcessor(null,
resolvedSearchResult.Domain) inside the Lazy<RegistryProcessor> factory and keep
the existing ComputerStatusEvent hookup (HandleCompStatusEvent) unchanged.
Description
Add CompStatus Logging To Registry Processor
RunLog on Collection Failure
RegistryProcessor Unit Tests
Motivation and Context
BED-7374
How Has This Been Tested?
See SHCommon PR #272
Types of changes
Checklist:
Summary by CodeRabbit