Closed Bug 1760674 (CVE-2022-29916) Opened 2 years ago Closed 2 years ago

Utilizing CSS variables caused a browser behavior that leaks the information on visited links

Categories

(Core :: CSS Parsing and Computation, defect)

defect

Tracking

()

VERIFIED FIXED
100 Branch
Tracking Status
firefox-esr91 100+ verified
firefox98 --- wontfix
firefox99 --- wontfix
firefox100 + verified

People

(Reporter: mat.sionkowski, Assigned: emilio)

References

(Regressed 1 open bug)

Details

(Keywords: csectype-disclosure, privacy, sec-high, Whiteboard: [reporter-external] [client-bounty-form][sec-survey][adv-main100+][adv-esr91.9+])

Attachments

(3 files)

Attached file files.zip

The video shows it the best: https://drive.google.com/file/d/1StFD0aHblp5iREsysaWbnytvCyj0DnX1/view

If you create multiple links like this:
<a href="https://facebook.com" id="facebook">facebook</a> <br>

with the style like this:
#facebook
{
--visited-facebook: url('facebook-visited.gif');
background-image: var(--visited-facebook);
}

Using the CSS variables will cause the browser to request the background image twice.
If there are multiple links like this, each one with a certain background image - the browser will do one full run of requesting the images, then it will start the second full run of requesting images.

But the behavior completely changes when the same website is refreshed. Somehow caching influences the order of requesting background images.
After refreshing - the browser still performs two runs of requesting images. The first one consists of all images in the right order. But the second run is one of two:
a) unvisited links, and then - at the end - the visited links
or
b) only visited links - but still - at the end.

So no matter what - the background images of visited links get requested right at the end.

So I added additional links at the beginning and at the end to play a role of markers. Their background images contain a "MARKER" keyword.

I opened the list of links in the iframe, and added javascript that reloads this inframe after a second to simulate a refresh.

This way a server creates logs for all requested images.
I created the script that finds the last entry of the MARKER image in the logs. This way we know that each log entry that happens after that is the entry created by visited links.
Script "get_history.sh" shows which images got requested after the MARKER ( meaning that they come from visited links).

Flags: sec-bounty?
Group: firefox-core-security → layout-core-security
Component: Security → CSS Parsing and Computation
Product: Firefox → Core
Status: UNCONFIRMED → NEW
Ever confirmed: true

Huh, that's a bit odd, we don't call TriggerImageLoads for visited styles, afaics, and we should always restyle all links (which should trigger the second request in this case) regardless of whether they were visited.

Flags: needinfo?(emilio)

The other issue is, I guess, that we don't cache the error response, so we do the request every time we restyle.

I don't want to pretend that I know anything about what is going on under the hood, but I think you mentioned a method "Link::VisitedQueryFinished" that forces the style change no matter what. But maybe this method isn't even called?

Look at the below code from BaseHistory::RegisterVisitedCallback:

=======================================================================
  // If this link has already been queried and we should notify, do so now.
  switch (links->mStatus) {
    case VisitedStatus::Unknown:
      break;
    case VisitedStatus::Unvisited:
      if (!StaticPrefs::layout_css_notify_of_unvisited()) {
        break;
      }
      [[fallthrough]];
    case VisitedStatus::Visited:
      aLink->VisitedQueryFinished(links->mStatus == VisitedStatus::Visited);
      break;
=====================================================================

It looks like some check is performed even before the "VisitedQueryFinished" is being run. So if the browser assumes the link is unvisited on this stage - it will not go deeper. Hence - will not reload styles.

(I edited the previous comment to fix the markup.)

Yeah, but layout.css.notify-of-unvisited should be true, so we should get to VisitedQueryFinished, afaict.

Assignee: nobody → emilio
Flags: needinfo?(emilio)
Attached file Bug 1760674. r=mak

The PoC uses two interesting facts to observe the restyle timing:

  • That CSS variables create new CSS values.
  • That we don't cache error image responses.

However, that arguably is not the root cause, since we should restyle
both visited and unvisited links. The root cause is what causes the
timing to be different.

This patch fixes that. If we stop tracking visited links, the next time
a link with the same URL is added to the document we will trigger an
extra query, which wouldn't happen for known-unvisited links.

This is really long-standing behavior, but I should've fixed this as
part of bug 1591717, arguably...

This isn't observable, except in the order of the query results (since
without this patch only non-visited links resolve immediately in
RegisterVisitedCallback instead of VisitedQueryFinished), so not sure
how easy it is to really test this, ideas welcome.

Adding Tor; they'll want this I think. (Unless it's safe enough to uplift to ESR for them.)

Attachment #9269209 - Attachment description: Bug 1760674 - Don't stop tracking visited links once we know they are visited. r=mak → Bug 1760674. r=mak
Flags: needinfo?(emilio)
Group: layout-core-security → core-security-release
Status: NEW → RESOLVED
Closed: 2 years ago
Resolution: --- → FIXED
Target Milestone: --- → 100 Branch

Comment on attachment 9269209 [details]
Bug 1760674. r=mak

Beta/Release Uplift Approval Request

  • User impact if declined: Potential history leak.
  • Is this code covered by automated tests?: Yes
  • Has the fix been verified in Nightly?: Yes
  • Needs manual test from QE?: Yes
  • If yes, steps to reproduce: comment 0 has STR
  • List of other uplifts needed: none
  • Risk to taking this patch: Low
  • Why is the change risky/not risky? (and alternatives if risky): Pretty simple patch.
  • String changes made/needed: none
Attachment #9269209 - Flags: approval-mozilla-beta?
Flags: qe-verify+

:emilio we are finalizing the last RC build.
If you want to modify this to approval-mozilla-release, we could consider this as a dot release ride along?

Flags: needinfo?(emilio)

Comment on attachment 9269209 [details]
Bug 1760674. r=mak

Beta/Release Uplift Approval Request

  • User impact if declined:
  • Is this code covered by automated tests?: Yes
  • Has the fix been verified in Nightly?: Yes
  • Needs manual test from QE?: Yes
  • If yes, steps to reproduce:
  • List of other uplifts needed: None
  • Risk to taking this patch: Low
  • Why is the change risky/not risky? (and alternatives if risky):
  • String changes made/needed:
Flags: needinfo?(emilio)
Attachment #9269209 - Flags: approval-mozilla-beta? → approval-mozilla-release?

Tom, just confirming that you think it's worth uplifting. Should I try to uplift to ESR too? I think it should apply cleanly.

Flags: needinfo?(tom)

It would be great to get this into a release and ESR (especially for Tor) but we shouldn't do anything special/out of the ordinary to take it this late in the cycle.

Flags: needinfo?(tom)

Comment on attachment 9269209 [details]
Bug 1760674. r=mak

ESR Uplift Approval Request

  • If this is not a sec:{high,crit} bug, please state case for ESR consideration: Privacy issue with trivial-ish fix.
  • User impact if declined: History leak.
  • Fix Landed on Version: 100
  • Risk to taking this patch: Low
  • Why is the change risky/not risky? (and alternatives if risky): Patch is very simple and well understood.
Attachment #9269209 - Flags: approval-mozilla-esr91?
QA Whiteboard: [qa-triaged]
Regressions: 1762387

We started investigation and verification of this bug, we will come back with a comment with our results when the investigation/verification is completed.

Hello! Reproduced the issue with Firefox 100.0a1 (2022-03-21) on Windows 10x64 and Ubuntu 20.04. After refreshing the page the visited links appear again after the END MARKER inside the web console and appear as visited when running the provided script from comment 0.

With Firefox 100.0a1 (2022-04-03) on Windows 10x64, macOS 11.6, and Ubuntu 20 there are no links displayed inside the Firefox web console after END MARKER, but after some refreshes, random links are displayed as visited when running the attached script. Screen recording here and here. Is this expected or am I doing something wrong here when refreshing?

Also, I noticed that sometimes after refresh not all links are loaded twice inside the web console. As can be seen inside the recording here the BEGIN MARKER is displayed and only three links are displayed after and not all links from the test webpage which is ending with END MARKER. Is this expected?

Thank you in advance!

Flags: needinfo?(emilio)

If the script isn't able to reliably detect which links are visited, that means that the fix is working.

Flags: needinfo?(emilio)

(In reply to Emilio Cobos Álvarez (:emilio) from comment #18)

If the script isn't able to reliably detect which links are visited, that means that the fix is working.

Thank you, Emilio! The script is only showing random links as visited after refreshing the page and not the exact ones that were visited. Marking this as verified with Firefox 100.0a1 (2022-04-03) per comment 17 ad comment 18.

Flags: sec-bounty? → sec-bounty+

re-rating as sec-high to match previous examples of history leaking

Keywords: sec-moderatesec-high

As part of a security bug pattern analysis, we are requesting your help with a high level analysis of this bug. It is our hope to develop static analysis (or potentially runtime/dynamic analysis) in the future to identify classes of bugs.

Please visit this google form to reply.

Flags: needinfo?(emilio)
Whiteboard: [reporter-external] [client-bounty-form] [verif?] → [reporter-external] [client-bounty-form] [verif?][sec-survey]
Flags: needinfo?(emilio)

Comment on attachment 9269209 [details]
Bug 1760674. r=mak

Rejecting release uplift request, see Comment 14

Attachment #9269209 - Flags: approval-mozilla-release? → approval-mozilla-release-
Regressions: 1763768

Comment on attachment 9269209 [details]
Bug 1760674. r=mak

The consensus is that bug 1762387 isn't a blocker to uplifting this fix. Approved for 91.9esr.

Attachment #9269209 - Flags: approval-mozilla-esr91? → approval-mozilla-esr91+

Verified fixed with 91.9.0esr (20220426172435) on Windows 10x64, macOS 11.6 and Ubuntu 20. The script is only showing random links as visited after refreshing the page and not the exact ones that were visited.

Status: RESOLVED → VERIFIED
Flags: qe-verify+
Whiteboard: [reporter-external] [client-bounty-form] [verif?][sec-survey] → [reporter-external] [client-bounty-form][sec-survey][adv-main100+][adv-esr91.9+]
Attached file advisory.txt
Alias: CVE-2022-29916
Regressions: 1777312
Group: core-security-release

This bug/ticket has been fixed and public for a while so I don't see any issues in covering it publicly by me, but just to keep you informed - I covered it in my Youtube video: https://www.youtube.com/watch?v=0upamEvT-_w

Thank you again for the whole journey!

Thank you for posting the video!

You need to log in before you can comment on or make changes to this bug.

Attachment

General

Creator:
Created:
Updated:
Size: