CVE-2024-10463
Overview
Background
- `VideoFrameContainer`
- The DOM media object that holds the sequence of decoded video
Images destined for a<video>element and tracks the securityPrincipalHandlethat owns those frames. - `PrincipalHandle`
- An opaque token identifying the origin (principal) that produced a set of media frames, used to enforce cross-origin restrictions such as tainting a
<canvas>drawn from a<video>. - `ImageContainer::FrameID`
- A monotonically increasing identifier assigned to each frame pushed into the container, used to order frames and to reason about which frames precede a principal-handle transition.
- `mFrameIDForPendingPrincipalHandle`
- The
FrameIDat which a newly pendingPrincipalHandletakes effect, i.e. the first frame that belongs to the new principal.
Root Cause Analysis
When a media source changes origin, VideoFrameContainer::SetCurrentFramesLocked stores the new PrincipalHandle in mPendingPrincipalHandle and records mFrameIDForPendingPrincipalHandle as the first FrameID that belongs to it; the code must not promote the pending handle to mLastPrincipalHandle until every frame produced under the old principal has actually drained out of the container. The buggy condition computed lastFrameIDForOldPrincipalHandle = mFrameIDForPendingPrincipalHandle - 1 and promoted the new principal whenever the last old image OR the first new image reached that boundary, which could fire while frames belonging to the old (or a mismatched) principal were still present in the set of images being displayed. This violated the invariant that the notified principal must strictly cover all currently visible frames, so a frame could be attributed to, and rendered under, the wrong origin.
The fix replaces the fragile off-by-one boundary math with a direct check: the pending handle is promoted only when aImages is empty or its first frame’s mFrameID is at least mFrameIDForPendingPrincipalHandle, guaranteeing no earlier-principal frames remain. Because the new test keys off the actual first frame of the incoming set rather than a derived - 1 boundary, the old principal’s frames are provably flushed before the origin transition is announced.
mFrameIDForPendingPrincipalHandle - 1) combined with an over-permissive OR condition to decide when the old principal was flushed, letting the origin change be announced while old-origin frames were still live; the fix eliminates the derived boundary and requires that the first surviving frame’s mFrameID be >= mFrameIDForPendingPrincipalHandle.Attack Path
- Set up a cross-origin video source
An attacker page loads or feeds a
<video>/media element whose stream transitions between two different origins, causing a pendingPrincipalHandlechange with a specificmFrameIDForPendingPrincipalHandle. - Trigger the frame update
Frames are pushed through
SetCurrentFramesLockedso that the last old image or the first new image lands exactly on thelastFrameIDForOldPrincipalHandleboundary while old-principal frames remain in the displayed set. - Force premature principal promotion
The flawed OR condition promotes
mPendingPrincipalHandletomLastPrincipalHandleeven though a frame from the previous origin is still current. - Capture the leaked frame
The attacker draws the
<video>to a<canvas>and reads it back (e.g.getImageData), obtaining pixel data from the other origin because the container reported the frame as belonging to the attacker-controlled principal.
Impact Assessment
FrameID boundary; the disclosure is limited to leaked frames, giving it a moderate severity rating.Files Changed
dom/media/VideoFrameContainer.cpp
Audit Directions
- Off-by-one boundary math on ordered IDsReview any logic that derives a threshold via
id - 1(or+ 1) to gate a security-relevant state transition, since such derived boundaries frequently admit frames/events on the wrong side of the transition. - Principal/origin transition gatingAudit other media and stream code that buffers frames across a
PrincipalHandlechange to confirm the new principal is announced only after all older-principal data is provably flushed. - Over-permissive OR conditionsScan for guard clauses that OR together “old set” and “new set” tests where a stricter AND/coverage check is required, as satisfying either branch alone can leak state before the intended invariant holds.
Patch
diff --git a/dom/media/VideoFrameContainer.cpp b/dom/media/VideoFrameContainer.cpp
index 22ecb995094..f7471cdcca2 100644
--- a/dom/media/VideoFrameContainer.cpp
+++ b/dom/media/VideoFrameContainer.cpp
@@ -152,18 +152,13 @@ void VideoFrameContainer::SetCurrentFramesLocked(
mImageContainer->GetCurrentImages(&oldImages);
PrincipalHandle principalHandle = PRINCIPAL_HANDLE_NONE;
- ImageContainer::FrameID lastFrameIDForOldPrincipalHandle =
- mFrameIDForPendingPrincipalHandle - 1;
if (mPendingPrincipalHandle != PRINCIPAL_HANDLE_NONE &&
- ((!oldImages.IsEmpty() &&
- oldImages.LastElement().mFrameID >= lastFrameIDForOldPrincipalHandle) ||
- (!aImages.IsEmpty() &&
- aImages[0].mFrameID > lastFrameIDForOldPrincipalHandle))) {
- // We are releasing the last FrameID prior to
- // `lastFrameIDForOldPrincipalHandle` OR there are no FrameIDs prior to
- // `lastFrameIDForOldPrincipalHandle` in the new set of images. This means
- // that the old principal handle has been flushed out and we can notify our
- // video element about this change.
+ (aImages.IsEmpty() ||
+ aImages[0].mFrameID >= mFrameIDForPendingPrincipalHandle)) {
+ // There are no FrameIDs prior to `mFrameIDForPendingPrincipalHandle`
+ // in the new set of images.
+ // This means that the old principal handle has been flushed out and we
+ // can notify our video element about this change.
principalHandle = mPendingPrincipalHandle;
mLastPrincipalHandle = mPendingPrincipalHandle;
mPendingPrincipalHandle = PRINCIPAL_HANDLE_NONE;