Hi All,
I'm wondering about the history of JS fingerprinting mitigation in Tor Browser. What prompted the change of approach from JavaScript hooks to C++ patches? I had read something about a race condition discovered, but I haven't found more details.
I've been thinking about the idea of developing a C++ patch for Tor Browser (and Firefox) that allows extensions to securely replace arbitrary members (functions and properties) of the global window object at runtime, before content is loaded. By "secure" I mean that, by design, there would be no workaround for content scripts to access the original window object members. (Maybe this capability already exists -- I don't know.)
The advantages of this monkey patching approach over addressing fingerprinting vulnerabilities with C++ patches is (1) it would (I think) simplify fingerprinting countermeasures, and (2) it would reduce the number of Firefox C++ patches that Mozilla needs to accept.
Is this idea worth pursuing further?
Thanks, Arthur
Arthur D. Edelstein:
Hi All,
I'm wondering about the history of JS fingerprinting mitigation in Tor Browser. What prompted the change of approach from JavaScript hooks to C++ patches? I had read something about a race condition discovered, but I haven't found more details.
I've been thinking about the idea of developing a C++ patch for Tor Browser (and Firefox) that allows extensions to securely replace arbitrary members (functions and properties) of the global window object at runtime, before content is loaded. By "secure" I mean that, by design, there would be no workaround for content scripts to access the original window object members. (Maybe this capability already exists -- I don't know.)
The advantages of this monkey patching approach over addressing fingerprinting vulnerabilities with C++ patches is (1) it would (I think) simplify fingerprinting countermeasures, and (2) it would reduce the number of Firefox C++ patches that Mozilla needs to accept.
Is this idea worth pursuing further?
Possibly, but even if we address all the race conditions and edge cases (like javascript: urls and data: urls that contain script), historically JS hooking is very fragile, with lots of ways to undo/break it cropping up regularly in new releases.
IIRC, such "secure"/"sealed"/"non-configurable"/non-revocable hooking recently became standardized as part of Object.defineProperty, Object.seal, and friends, but this may still have issues. Some things might still be forbidden from being overridden for various reasons. The last time I went down this road, it was an endless sinkhole of issues, and I only did it because patching the browser was not an option at that point.
On top of this, it doesn't allow us to cleanly hook events, callback parameters, and other async things. In some cases, we can probably work around this by hooking the callback registration to install our own wrapper callbacks that alter their args before passing them in to the registered callback, but that too takes care to ensure that the true arguments cannot be discovered by inspecting the callchain and associated parent scopes from inside the final content's callback.
It also won't help with CSS-based vectors for fingerprinting, or complicated interactions between styling and JS.
It's something to consider if it really looks like Mozilla won't take any of our fingerprinting defenses, but I think we should continue to prioritize direct patches for now.
That said, if you see a clean way to create an API to do secure script injection and feel like hacking it up real quick, feel free. It may prove useful eventually, but I suspect we'll uncover a whole slough of surprises once we actually try to use it. We'll probably also need regression tests in-tree for every single function/callback/property we hook, to make sure that an implementation change doesn't suddenly break our ability to hook something in the way we want.
On Mon, Sep 1, 2014 at 1:15 PM, Mike Perry mikeperry@torproject.org wrote:
Arthur D. Edelstein:
Hi All,
I'm wondering about the history of JS fingerprinting mitigation in Tor Browser. What prompted the change of approach from JavaScript hooks to C++ patches? I had read something about a race condition discovered, but I haven't found more details.
I've been thinking about the idea of developing a C++ patch for Tor Browser (and Firefox) that allows extensions to securely replace arbitrary members (functions and properties) of the global window object at runtime, before content is loaded. By "secure" I mean that, by design, there would be no workaround for content scripts to access the original window object members. (Maybe this capability already exists -- I don't know.)
The advantages of this monkey patching approach over addressing fingerprinting vulnerabilities with C++ patches is (1) it would (I think) simplify fingerprinting countermeasures, and (2) it would reduce the number of Firefox C++ patches that Mozilla needs to accept.
Is this idea worth pursuing further?
Possibly, but even if we address all the race conditions and edge cases (like javascript: urls and data: urls that contain script), historically JS hooking is very fragile, with lots of ways to undo/break it cropping up regularly in new releases.
IIRC, such "secure"/"sealed"/"non-configurable"/non-revocable hooking recently became standardized as part of Object.defineProperty, Object.seal, and friends, but this may still have issues. Some things might still be forbidden from being overridden for various reasons. The last time I went down this road, it was an endless sinkhole of issues, and I only did it because patching the browser was not an option at that point.
On top of this, it doesn't allow us to cleanly hook events, callback parameters, and other async things. In some cases, we can probably work around this by hooking the callback registration to install our own wrapper callbacks that alter their args before passing them in to the registered callback, but that too takes care to ensure that the true arguments cannot be discovered by inspecting the callchain and associated parent scopes from inside the final content's callback.
It also won't help with CSS-based vectors for fingerprinting, or complicated interactions between styling and JS.
It's something to consider if it really looks like Mozilla won't take any of our fingerprinting defenses, but I think we should continue to prioritize direct patches for now.
That said, if you see a clean way to create an API to do secure script injection and feel like hacking it up real quick, feel free. It may prove useful eventually, but I suspect we'll uncover a whole slough of surprises once we actually try to use it. We'll probably also need regression tests in-tree for every single function/callback/property we hook, to make sure that an implementation change doesn't suddenly break our ability to hook something in the way we want.
You make a compelling argument for the direct C++ patches. My thinking mainly was to use JS patches as a stopgap while C++ patches are being developed, but after your explanation it's not so clear to me that JS patches will be any easier or quicker.
Arthur D. Edelstein:
On Mon, Sep 1, 2014 at 1:15 PM, Mike Perry mikeperry@torproject.org wrote:
Arthur D. Edelstein:
Hi All,
I'm wondering about the history of JS fingerprinting mitigation in Tor Browser. What prompted the change of approach from JavaScript hooks to C++ patches? I had read something about a race condition discovered, but I haven't found more details.
I've been thinking about the idea of developing a C++ patch for Tor Browser (and Firefox) that allows extensions to securely replace arbitrary members (functions and properties) of the global window object at runtime, before content is loaded. By "secure" I mean that, by design, there would be no workaround for content scripts to access the original window object members. (Maybe this capability already exists -- I don't know.)
The advantages of this monkey patching approach over addressing fingerprinting vulnerabilities with C++ patches is (1) it would (I think) simplify fingerprinting countermeasures, and (2) it would reduce the number of Firefox C++ patches that Mozilla needs to accept.
Is this idea worth pursuing further?
Possibly, but even if we address all the race conditions and edge cases (like javascript: urls and data: urls that contain script), historically JS hooking is very fragile, with lots of ways to undo/break it cropping up regularly in new releases.
IIRC, such "secure"/"sealed"/"non-configurable"/non-revocable hooking recently became standardized as part of Object.defineProperty, Object.seal, and friends, but this may still have issues. Some things might still be forbidden from being overridden for various reasons. The last time I went down this road, it was an endless sinkhole of issues, and I only did it because patching the browser was not an option at that point.
On top of this, it doesn't allow us to cleanly hook events, callback parameters, and other async things. In some cases, we can probably work around this by hooking the callback registration to install our own wrapper callbacks that alter their args before passing them in to the registered callback, but that too takes care to ensure that the true arguments cannot be discovered by inspecting the callchain and associated parent scopes from inside the final content's callback.
It also won't help with CSS-based vectors for fingerprinting, or complicated interactions between styling and JS.
It's something to consider if it really looks like Mozilla won't take any of our fingerprinting defenses, but I think we should continue to prioritize direct patches for now.
That said, if you see a clean way to create an API to do secure script injection and feel like hacking it up real quick, feel free. It may prove useful eventually, but I suspect we'll uncover a whole slough of surprises once we actually try to use it. We'll probably also need regression tests in-tree for every single function/callback/property we hook, to make sure that an implementation change doesn't suddenly break our ability to hook something in the way we want.
You make a compelling argument for the direct C++ patches. My thinking mainly was to use JS patches as a stopgap while C++ patches are being developed, but after your explanation it's not so clear to me that JS patches will be any easier or quicker.
Yeah, as a stopgap idea maybe. Apart from the technical difficulties (btw I had used mutation events/mutation observers back then to break the JS hooks) I think there is another reason, too, to not go the hooking road again: the patches should get incorporated into vanilla Firefox so that a lot more users could use an anon mode without much configuration (e.g. installing extra extensions) which is error-prone. And pursuing this is a good strategy for us showing Mozilla that we are serious about it (and don't want to drop random patches into mozilla-central) and are thinking about their users, too, which might be a more compelling reason for them to merge and deploy our stuff.
Georg
That said, if you see a clean way to create an API to do secure script injection and feel like hacking it up real quick, feel free. It may prove useful eventually, but I suspect we'll uncover a whole slough of surprises once we actually try to use it. We'll probably also need regression tests in-tree for every single function/callback/property we hook, to make sure that an implementation change doesn't suddenly break our ability to hook something in the way we want.
As an experiment, I came up with a very simple JS module that lets you inject a script to overwrite arbitrary members of the global "window" object, before any content is loaded. The trick is listening for "content-document-global-created" notifications, as described in https://developer.mozilla.org/en-US/docs/Observer_Notifications#Documents.
If anyone is interested, you can see the injection code at https://github.com/arthuredelstein/torbutton/blob/f138fa2a5e/src/chrome/cont... and there's an example of the script to be injected at https://github.com/arthuredelstein/torbutton/blob/f138fa2a5e/src/chrome/cont... (The latter script is one way to solve #5926, though my final implementation is a C++ patch.) Of course, all of Mike and Georg's caveats about JS hooks apply here.