Skip to content

Bug: breadcrumb-webview-post-executes-under-upgrade-lifetime-lock #500

Description

@drmoisan
  • Work Mode: full-bug

Summary

BreadcrumbCoordinatorUpgradeLifetime.TryRunCurrent invokes the caller's action while holding its
_sync monitor, so a WebView2 post runs under two nested locks (lifetime._sync then hub._sync)
and reaches an out-of-process SDK call from inside both. Because lock is re-entrant, the lock does
not deliver the atomicity it appears to: a re-entrant call on the same thread can mutate _current
between the currency check and the completion of the action, which is the exact invariant
TryRunCurrent exists to enforce.

Environment

  • OS/version: Windows 11 Pro 10.0.26200
  • Python version: n/a (C# / .NET Framework 4.8.1 WinForms VSTO add-in with Microsoft WebView2)
  • Command/flags used: n/a - reached through the QuickFiler ItemViewer breadcrumb selector
  • Data source or fixture: any breadcrumb suggestion population that issues a render/selector post

Steps to Reproduce

This is a concurrency and re-entrancy defect established by code inspection rather than a
deterministic user-facing repro. No existing test reproduces it, and constructing one requires a
re-entrant STA message pump, which repository unit-test policy prohibits.

  1. Populate breadcrumb suggestions so BreadcrumbBridgeCoordinator.PostRenderAndSelectorAsync runs.
  2. Observe that _messenger.PostJson executes inside BreadcrumbCoordinatorUpgradeLifetime._sync.
  3. In production the messenger is BreadcrumbMessengerHub, whose PostJson takes its own _sync
    and, still holding it, calls PostToSurface, reaching the WebView2 SDK.

Expected Behavior

The currency check and the guarded action should be atomic with respect to lease invalidation, and
no out-of-process SDK call should be made while a lock is held. Locks should cover state mutation
only, with the action invoked outside them, re-checking currency as needed.

Actual Behavior

The action runs inside the lock. Because Monitor is re-entrant, a re-entrant BeginPopulation,
Invalidate, or TryDispose on the same thread acquires lifetime._sync successfully and mutates
_current mid-action, defeating the guarantee. Separately, a re-entrant Attach/Detach during the
hub's broadcast would throw InvalidOperationException because the hub holds _sync across its
foreach.

Logs / Screenshots

  • Attached minimal logs or screenshot
  • Snippet: n/a - no exception is raised on the current wiring; the defect is a silently unenforced
    invariant.

Impact / Severity

  • Blocker
  • High
  • Medium
  • Low

Rationale: no deadlock is demonstrable on current code and the lock ordering is consistent, so this
is a latent correctness hazard rather than an active failure. It is recorded at Medium because an STA
COM call can pump messages and re-enter managed code, which is precisely the condition that voids the
intended atomicity.

Source

From: docs/features/potential/2026-08-08-breadcrumb-webview-post-executes-under-upgrade-lifetime-lock.md

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions