Skip to content

[css-paint-api] CSS Paint API leaks browsing history #791

Closed

Description

@deian

The Privacy considerations should be updated in my option to reflect the dangers of leaking browser history (at high rates). We have a paper that will be published next week at WOOT discussing this (my copy here). Chromium addressed this by disabling the API on link elements and their children.

Activity

  1. AmeliaBR commented on Aug 10, 2018

    @AmeliaBR

    @deian

    Beyond the privacy impact warning, are you making any recommendations for changes to this API? It looks like your advice is instead focused on the underlying :visited styles which expose browsing history to the current page in the first place.

  2. deian commented on Aug 11, 2018

    @deian
    MemberAuthor

    It definitely is! I think we should try to cover visited links under the SOP umbrella so that specs like this don't need to special case links or worry about history sniffing. This is a larger conversation that we need to have, though, and I'm guessing that may take a while.

    Until this is done, I think recommending Chrome's approach is pretty reasonable. (Unfortunately, the Paint API exposes a higher bandwidth channel than the other things we looked at.) Alternatively, plugging the side channels (registerPaint throwing an exception and paintlet-width leak [pg 5]) to address the amplified attack may be reasonable (though other side channels may similarly exist so I'm less excited about this to be honest).

  3. tabatkins commented on Aug 12, 2018

    @tabatkins
    Member

    Ugh, nice attack. I don't see a reasonable way around this, and disabling the API on all <a> elements or their descendants is not a viable forward tactic.

    We really need to finally handle this at the higher level, and censor :visited state entirely (not matching :visited at all) unless the visited state is already observable to the page via a standard channel. I filed w3c/csswg-drafts#3012 to see if we can solve this in CSS properly.

  4. dbaron commented on Aug 13, 2018

    @dbaron
    Member

    I would think a reasonable way around this would be something like the following:

    • when a paint callback is invoked, record what it does (either (preferably) as a sequence of canvas drawing commands, or as a bitmap)
    • only invoke the paint callback again when it is invoked with a different set of dependent properties (exposing only unvisited styles in this set of properties) or a different size (which can't change as a result of visitedness)

    That said, although the paper doesn't explicitly say so, I'm assuming the "Amplified attack" variant is also counting calls to the paint callback and only doing the registerPaint that marks the link as visited on the second call... if that's not the case then I don't actually understand how that attack is working.

  5. spinda commented on Aug 13, 2018

    @spinda

    @dbaron

    That said, although the paper doesn't explicitly say so, I'm assuming the "Amplified attack" variant is also counting calls to the paint callback and only doing the registerPaint that marks the link as visited on the second call... if that's not the case then I don't actually understand how that attack is working.

    Yes, that's correct. We use registerPaint to store persistent state across paint callback invocations.

    On the first invocation of our paint callback, we do registerPaint('target_foo', P).

    On the second invocation, we do registerPaint('target_foo', P) again, but this time it throws an exception since target_foo is already registered. We catch the exception and do registerPaint('target_foo_visited', P) in response.

  6. spinda commented on Aug 13, 2018

    @spinda

    With regard to

    either (preferably) as a sequence of canvas drawing commands

    I worry that I could chain up a long series of canvas drawing commands from my paintlet, then use a timing attack to detect when the browser replays them.

  7. tabatkins commented on Aug 13, 2018

    @tabatkins
    Member

    Yeah, the timing channel revealing when repaints occur is always going to be there; we can't prevent expensive repaints in general. The "good" attack with Paint API just makes it more reliable, but anything that can cause painting to be computationally expensive will work.

  8. asajeffrey commented on Aug 13, 2018

    @asajeffrey

    Hi @deian, it's a small world. (I'm the implementor of the paint API in Servo.) FWIW I agree with the approach in the paper, of keeping a separate visited store per security domain. If we did it per-eTLD+1 then this would line up nicely with content processes.

  9. tabatkins commented on Oct 25, 2019

    @tabatkins
    Member

    All right, I've added an entry to the Privacy Considerations summarizing the problem and linking here. @deian, can you confirm this resolves the issue for you?

    (The larger issue of how to fix this is, of course, still unresolved.)

  10. deian commented on Oct 25, 2019

    @deian
    MemberAuthor

    @tabatkins thanks! I think this is a completely reasonable take. I agree with your take on trying to solve this for real by getting at the root of the problem.

  11. added a commit that references this issue on Jul 28, 2025
    3c72275
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions