Closed Bug 1899736 Opened 3 months ago Closed 1 month ago

Can not use 2nd webcam at webcamtests.com with privacy.resistFingerprinting enabled

Categories

(Fenix :: Media, defect)

defect

Tracking

(firefox126 wontfix, firefox131 fixed)

RESOLVED FIXED
131 Branch
Tracking Status
firefox126 --- wontfix
firefox131 --- fixed

People

(Reporter: rbucata, Assigned: fkilic)

References

(Depends on 1 open bug, Blocks 2 open bugs, )

Details

Attachments

(1 file, 3 obsolete files)

Environment:
Operating system: Android 13
Firefox version: Firefox Mobile 126.0

Preconditions:
privacy.resistFingerprinting enabled
2nd webcam available

Steps to reproduce:

  1. Navigate to:https://webcamtests.com/check
  2. Try to access the 2nd camera

Expected Behavior:
Camera can be accessed

Actual Behavior:
Camera is not detected

Notes:

  • Reproduces regardless of the status of ETP
  • Reproduces in Firefox Release
  • Does not reproduce in Firefox Nightly, Chrome

Created from https://github.com/webcompat/web-bugs/issues/137588

Summary: Can not used 2nd webcam at webcamtests.com with privacy.resistFingerprinting enabled → Can not use 2nd webcam at webcamtests.com with privacy.resistFingerprinting enabled

Not only in this website,
It happens for all website I seen.

From memory, RFP shows a user as having a single camera and microphone. I think it will report that even if the user doesn't have any!

Before a permission prompt is granted, it makes sense that the site would only see one. After the permission prompt is granted, I belief is that RFP should allow the site to see all of the devices. I don't know if this is how it actually works, but I think it's how it should work.

It's possible that this is not how RFP should work, in which case the course of action is to confirm with Tor they agree with me, and then change it. Alternately, maybe it is how RFP works and the site is not polling after the permission grant or something else is happening resulting in the lack of access.

one camera access is for security.
I appreciate it. But sometimes I need 2nd camera also.

So,here you can come with a feature for mobile phone that I can choose my camera from permission manager.

Firefox ask for permission with camera I want to use and
And also add a option for changing camera from cam1 to cam2

So,here you can come with a feature for mobile phone that I can choose my camera from permission manager.

which a feature for mobile phone that I can choose my camera from permission manager.

Flags: needinfo?(fakeid.30x)

RFP is not a supported feature in Firefox, so the changes we would make to support this use case are fairly limited. If you're running into compatibility problems because of this, I would recommend disabling RFP and instead using Fingerprinting Protection (available in ETP Strict under the name 'Suspected Fingerprinters') which is a supported configuration.

After the permission prompt is granted, I belief is that RFP should allow the site to see all of the devices.

I agree.

Assignee: nobody → fkilic
Status: NEW → ASSIGNED

The patch I submitted would "solve" the issue by exposing the devices if permission is granted but I think we have to wait for an answer because this behavior could be intentional

Thanks bro.
I wish dev will consider

Flags: needinfo?(fakeid.30x) → needinfo?(rbucata)

Pinging Tor to see what they think of this. We're going to test the patch a bit before landing, so it definetly won't make it to 128

Flags: needinfo?(simon.mainey)
Flags: needinfo?(pierov)

So first question is "What exactly is exposed here?" after permission is granted - number + type + name of devices. Plus other properties when used (capabilities which we can't hide)?

I think we can be smarter. E.g. if granted but you have no (audio/video) devices... do not expose more than the one (fake) video/audio. If granted but you only have one device ... etc. And I think we can still enumerate/hide any high-information density - back to the first question, what do we actually reveal (I'm a little hazy and lack devices too)?

Flags: needinfo?(simon.mainey)

(In reply to Simon Mainey from comment #11)

So first question is "What exactly is exposed here?" after permission is granted - number + type + name of devices. Plus other properties when used (capabilities which we can't hide)?

With resistFingerprinting on, we expose only two fake devices with fake ids, names, and groups ids before and after permission grant, BUT after giving permission, track object gives the actual device id rather than the fake id while enumerateDevices still reports the fake id (which i think is a bug?), we also overwrite channel count and some other properties I believe. On desktop, we have the option to select which device is used for camera, but on mobile we don't have it AFAIK.

thanks Fatih

what is track object (who can see that? the website?). If this is not relevant, then ignore. I would hope with current RFP we don't expose any additional info after granting permission

OT: when can we flip legacy gUM off :)

while enumerateDevices still reports the fake id (which i think is a bug?)

after permissions, we could still report all devices, but still use fake ids (and always report 1 of each as a minimum: e.g I have none on this device and if I grant permission it should stay as fake data). If I recall correctly we use labels Internal Camera and Internal Microphone - whilst not super end-user friendly, can we not just expose the number of each but append #2, #3 etc. Not many people would have more than 1 or 2 devices (of each) - maybe we have that in telemetry somewhere

(In reply to Simon Mainey from comment #13)

what is track object (who can see that? the website?). If this is not relevant, then ignore.

Yes unfortunately the browser has access to it. The following code would give an example of it. MDN documentation of the settings/info we can get.

const stream = await navigator.mediaDevices.getUserMedia({audio: true, video: true});
const tracks = stream.getTracks();
const settings = tracks.map(t => t.getSettings());

I would hope with current RFP we don't expose any additional info after granting permission

I'm not really sure, except for device ids, we leak the real ids. My testing shows we spoof microphone channel count to 3, but I couldn't find the actual spoofs applied in the source code, so I'm not really sure about other properties.

If RFP expose additional info, you can take this settings to separate flags.
So, I can enable or disable when I needed.
Example: I'll disable when I'll join a video call. After finishing it I'll enable it.

Or it would better if I could give permission to count camera for one websites when needed.

Alternately, maybe it is how RFP works and the site is not polling after the permission grant or something else is happening

Well we can't do much if the site doesn't offer a refreshed list of devices (although F5 might suffice in some cases). But first of all, let's confirm that in the current state of RFP, after granting permissions that we are in fact still only reporting fake ids

Flags: needinfo?(rbucata)

Before a permission prompt is granted, it makes sense that the site would only see one. After the permission prompt is granted, I belief is that RFP should allow the site to see all of the devices. I don't know if this is how it actually works, but I think it's how it should work.

I agree.
Thanks for checking with us!

Flags: needinfo?(pierov)
Attachment #9405057 - Attachment is obsolete: true
Depends on: 1900402

So I think there's a few threads to this.

  1. (Bug) After a user grants the permission, we still only report a single device. I think we can fix this simply with the patch on this bug.
  2. (Behavior) Because we report a fake id on the device, during a permission prompt on Desktop we will prompt the user which device they want to use. (All the devices will be listed, and this is browser chrome, not reported to the user until they've accepted the prompt.) I think this behavior is fine, desirable even.
  3. (Bug) After the user has accepted a permission prompt, the device's real ID is leaked via the track object. However gUM will report a fake id. After #1 is fixed, it will still report fake ids for everything. I think we should modify this to be consistent - either use consistent fake ids, or the real ids. I'm inclined to say the real ids, as that will be easier, but don't have a strong preference. I don't expect we will be able to patch this timely.
  4. (Bug) The compliment of #2 on Android - there is no mechanism on Android to let the user select a device in chrome UI. This is Bug 1900402. AIUI, un theory a website can work around it after #1 and #3 are fixed by first requesting permission, the user grants permission, then the website enumerates the devices and lets the user select, then requests that device by id. But that's obviously messy. I don't expect we can fix Bug 1900402 timely either.

I think the best path forward is to land the patch here for #1, and file a bug about #3.

(In reply to Thorin [:thorin] from comment #16)

Alternately, maybe it is how RFP works and the site is not polling after the permission grant or something else is happening

Well we can't do much if the site doesn't offer a refreshed list of devices (although F5 might suffice in some cases). But first of all, let's confirm that in the current state of RFP, after granting permissions that we are in fact still only reporting fake ids

https://bugzilla.mozilla.org/show_bug.cgi?id=1876636#c0 ... after granting, then we report Internal.. fake names

snippet

  • In about:config set privacy.resistFingerprinting to true
  • Open https://jsfiddle.net/jib1/LbtxeLvw/ and revoke any persistent permission (from the URL bar)
  • Click Start! and Allow
  • Click Enumerate!

(In reply to Tom Ritter [:tjr] from comment #18)

  1. (Bug) After the user has accepted a permission prompt, the device's real ID is leaked via the track object. However gUM will report a fake id. After #1 is fixed, it will still report fake ids for everything. I think we should modify this to be consistent - either use consistent fake ids, or the real ids. I'm inclined to say the real ids, as that will be easier, but don't have a strong preference. I don't expect we will be able to patch this timely.

Yup, this is Bug 1876636

See Also: → 1876636

And there's another wrinkle here, which is Bug 1876810

See Also: → 1876810
Component: Site Reports → Media
Product: Web Compatibility → Fenix

The severity field is not set for this bug.
:tthibaud, could you have a look please?

For more information, please visit BugBot documentation.

Flags: needinfo?(tthibaud)

With the patch I just submitted, the behaviour would be as the following:

  • No permission
    • Enumerate devices returns only two devices just like before
    • Stream Track is not applicable since there's no active video/audio steam
  • With permission
    • Enumerate devices returns all the available devices with fake ids, fake group ids, and fake names.
    • Stream Track returns the fake ids and names

MediaManager::AnonymizeDevices seems more integrated. So I think modifying it instead of MediaEngineFake makes more sense.

Unfortunately, the patch won't fix https://webcamtests.com/check's issue. Firefox returns the full list after permission grant, but the website doen't refresh the media device list after permission grant. This is something they would have to do.

The patch, however, would fix Bug 1876636.

This patch modifes two things.

First thing is, since we already spoof device properties in MediaManager::AnonymizeDevices, we can use real devices, it won't leak any extra device information. Fallback to fake engine if the user doesn't have at least one camera or mic.

Second one is ReduceConstraint. We can allow deviceId since you can't really extract anything useful. Allowing device id in reduced constraint would allow websites to pick the correct input devices without exposing any identifiable data. (Except for the previously seen device, but again, if the user grants permission with getUserMedia, then the device ids would be exposed anyway)

In summary, this patch would fix websites that depend on switching input devices (zoom, google meet etc.), without leaking real device information.

Severity: -- → S3
Flags: needinfo?(tthibaud)

:thorin do you think the behaviour explained above is fine? I'll summarize here again and go in detail after.

In summary, we will still return fake ids and fake names with or without permissions. Stream track label will return the fake ids as well. After granting permission, the website will see all the devices (still with fake ids and names), and it will be able to select the device by its id.

"Longer explanation"

  • Fake ids, names: Firefox uses MediaManager::AnonymizeDevices to anonymize device ids per origin and group ids per origin + session. So, instead of returning fake devices from MediaFakeEngine and relying on device selection prompt, this patch will actually link devices to their fake ids.
  • StreamTrack deviceId difference: Since we are actually linking real devices to to fake ids, it will return fake ids matching with enumerateDevices().
  • Listing all devices: As discussed in earlier comments, we decided it makes sense to expose all devices after the user grants device permissions. So this patch will expose all the input devices (still with fake ids and names) after the user grants permission.
  • Allowing deviceId on ReduceContraint function: I assume this was initially implement to prevent against querying device properties, but I think it doesn't hurt expose deviceId since you can't really make any use of device id for tracking purposes. Allowing device id would also make websites that depend on switching input devices work properly.
Flags: needinfo?(thorin)

That sounds perfect with "Fallback to fake engine if the user doesn't have at least one camera or mic."

Flags: needinfo?(thorin)

Oh right sorry I forgot to mention it in the summary. thank you for reviewing!

As a note, if this patch lands we can close Bug 1876636 as resolved and Bug 1876810 will not be applicable anymore, so I guess we can close that too.

(In reply to Fatih Kilic from comment #24)

Unfortunately, the patch won't fix https://webcamtests.com/check's issue. Firefox returns the full list after permission grant, but the website doen't refresh the media device list after permission grant. This is something they would have to do.

So whilst outside pondering life the universe and everything, I had a moment .. which we could follow up in another issue - before granting permissions, we currently spoof as 1 audio + 1 video. This particularly sucks for android which would, one assumes, have at least two cameras. What if we spoofed as 2 audio and 2 video as a default (or even 1 audio and 3 video on android). The spoof itself doesn't matter since we're protecting the real values, but it would allow better enumeration for when granted

Blocks: 1909198

Hahaha, yeah it makes sense to me, I can work on it. I agree on following this up in a new bug. I created Bug 1909198. I'll follow up there. Thank you!

The number of cameras and microphones on the user's system constitute a fingerprint.

An advantage of the existing RFP model on desktop was that users could (revoke permission in the url bar and) use the camera and microphone picker native to Firefox's permission prompt to choose their devices, without leaking this information.

I gather this does not work on mobile since we appear to have no picker UX there. Was adding such UX considered?

Of course anyone seeking anonymity should avoid exposing camera and microphone entirely, so the use case here seems a bit unclear.

I think it doesn't hurt expose deviceId since you can't really make any use of device id for tracking purposes.

Legacy=false helps, but note we still need to fix bug 1589685.

A deviceId persisted between visits isn't "fake". AFAICT the patch here only modifies one of two inputs to AnonymizeId(id, aOriginKey) which still creates a (persisted if persist is true) deviceId unique to the origin.

Allowing device id would also make websites that depend on switching input devices work properly.

Yes it's a known flaw in the web model that it leaves it to websites to manage multiple devices.

We could perhaps not persist the deviceIds in RFP, at the cost of websites not remembering which device was used last. Tradeoffs.

Hmm so your suggestion is keeping the exposed number of devices same before and after gUM?

I wrote the patches according to the discussion done here but I can change them.

Before a permission prompt is granted, it makes sense that the site would only see one. After the permission prompt is granted, I belief is that RFP should allow the site to see all of the devices. I don't know if this is how it actually works, but I think it's how it should work.

after permissions, we could still report all devices, but still use fake ids

I don't understand this though

A deviceId persisted between visits isn't "fake". AFAICT the patch here only modifies one of two inputs to AnonymizeId(id, aOriginKey) which still creates a (persisted if persist is true) deviceId unique to the origin.

The current implementation uses the fake media engine and it creates a fake device with a constant name and id. The id persists with this patch or without it. If we want to prevent cross origin tracking then we need to fix bug 1589685 as you mentioned.

The goal for the patches here was

  • Expose real # of devices
  • Expose ids but anonymize them (remember previous solution had constant ids and names which is the same in these patches)
  • Allow selecting device with gUM with device id constraint

All of these would allow websites with device pickers to work instead of depending on the browser.

If we don't want to expose real # of devices, then the only remaining solution is implementing the mobile device picker (bug 1900402).

The real problem with exposing real devices is their properties I think. Fake media engine also fakes channel count, resolutions etc. Although I'm not sure what would be the best way to fix it.

Leaking the real # of devices after gUM is a tradeoff that was agreed here previously but what about leaking device properties after gUM? With fake engine we won't leak them, but fake engine also won't allow websites to switch devices. Real # of devices don't leak lots of information, but device properties can be a lot.

I agree with "Of course anyone seeking anonymity should avoid exposing camera and microphone entirely, so the use case here seems a bit unclear." Considering the ways of tracking mentioned after gUM, it doesn't make sense to protect against fingerprinting if a user is willing to give permission for camera and/or microphone access. It doesn't matter if we use fake or real devices.

Once access to a video stream from a capture device is obtained, that stream can most likely be used to fingerprint uniquely the said device (e.g. via dead pixel detection). Similarly, once access to an audio stream is obtained, that stream can most likely be used to fingerprint user location down to the level of a room or even simultaneous occupation of a room by disparate users (e.g. via analysis of ambient audio or of unique audio purposely played out of the device speaker). User-level mitigation for both audio and video consists of covering up the camera and/or microphone or revoking permission via User Agent chrome controls.

Fake media engine also fakes channel count, resolutions etc.

Note the use of MediaEngineFake() for enumeration and actual capture inside MediaManager itself is limited to the media.navigator.streams.fake pref and the non-standard getUserMedia({video: true, fake: true}). It produces a green/color-cycling rectangle with timestamp bits encoded in the upper left corner.

It's use by RFP in the outer MediaDevices::FilterExposedDevices seems superficial, in that it never gets back to MediaManager, and is used only to filter out what's returned through enumerateDevices().

So the tracks returned by gUM are still the real thing. Here's a fiddle interrogating resolutions. Even with privacy.resistFingerprinting it finds my real camera's resolutions.

640x480x30
1280x720x30
1920x1080x30

So the use of the fake engine only protects output from enumerateDevices, not output from gUM.

... The id persists with this patch or without it.

I see this as well. I think it might be a mistake. With RFP, getUserMedia ignores deviceId constraints, so the value in persisting these ids escapes me.

This seems filed as defect when the limitations are by design. The OP is expecting to do something RFP disallows because decisions made years ago that the MediaCapture API was too much of a fingerprinting vector. If folks here wish to relax these mitigations that's fine, but shouldn't it be marked enhancement?

... it doesn't make sense to protect against fingerprinting if a user is willing to give permission for camera and/or microphone access

You're suggesting the existing mitigations add no value? — I'm a bit hesitant to agree. Not every camera has a dead pixel, and it sounds a bit like the argument against fingerprinting mitigations in general (why remove bits when there are so many? lost battle etc.)

I sometimes wonder why we allow camera/microphone in Tor in the first place. I suppose it's a gray area with room for a lot of judgement.

I also wonder if Tor users understand the tradeoffs.

So currently Tor Browser does not allow mediaDevices (navigator.media, peerconnection both disabled). But Mullvad Browser does as it allows webRTC (so peerconnection is enabled which in turn exposes mediaDevices enumeration). RFP protects this - enumeration as pointed out. At some point Tor Browser (but I can't really speak for them, I am not an employee) will also allow webRTC (with UX/warnings perhaps), and since this is behind a user action/decision to trust that site and to actively engage in webRTC, then we can do what we say here. Does that clarify anything?

So the tracks returned by gUM are still the real thing

Oh right, sorry, it's been a while since this has been mentioned I forgot https://bugzilla.mozilla.org/show_bug.cgi?id=1899736#c19.

This seems filed as defect when the limitations are by design. The OP is expecting to do something RFP disallows because decisions made years ago that the MediaCapture API was too much of a fingerprinting vector. If folks here wish to relax these mitigations that's fine, but shouldn't it be marked enhancement?

That's true. The discussion started off as if it being a bug, but ended up with relaxing RFP protections. Top level goal of the bug became exposing all the devices and allowing websites to select the correct device after gUM in RFP.

I can think of two ways that we can go ahead, implementing a device picker for mobile, or exposing all devices after gUM. The websites will still be able to detect we have different devices by looking at the device properties returned by the stream/track but that could be handled in another bug.

You're suggesting the existing mitigations add no value? — I'm a bit hesitant to agree. Not every camera has a dead pixel, and it sounds a bit like the argument against fingerprinting mitigations in general (why remove bits when there are so many? lost battle etc.)

My thinking yesterday was if someone is willing to show their face and talk etc. but there are virtual cams and other ways to anonymize yourself I guess. So it is not really applicable. Sorry.

IMO whether we decide not exposing all devices and implementing a device picker, or exposing all the devices after gUM, I do think we should actually use real devices in AnonymizeDevices instead of using the fake engine just for compatibility (e.g. enumerateDevices and stream/track return matching device properties. On firefox side, it won't matter as we remove all the constraints including id, but it is a strange behaviour in general).

In any case, I still don't have deep understanding of media devices API or privacy protections fully, so I'm open to updating/abandoning patches depending on what's decided here.

I can think of two ways that we can go ahead, implementing a device picker for mobile, or exposing all devices after gUM. The websites will still be able to detect we have different devices by looking at the device properties returned by the stream/track ...

Today, websites cannot detect multiple devices if the user always chooses the same device in the Firefox (desktop) picker. The user would have to cycle through all their devices before this number matches the number of devices of their system.

That said, if the user experience is going to be:

  • Internal Microphone 1
  • Internal Microphone 2
  • Internal Microphone 3

...then users may end up having to cycle through them before finding the right one (not a great experience).

In contrast, Firefox's device picker uses real labels. With RFP on, it also satisfies § 9.2. Use care when exposing APIs for selecting or enumerating devices. I also think it mirrors how users unfamiliar with the Mediacapture API think things work — we get lots of bug reports from users who think device selection happens in the browser today.

That seems a better experience and tradeoff to me.

Hmm I see. Yeah it makes sense. Then I believe the best way forward is to implement a device picker on mobile. What's your opinion on using the fake media engine and continuing with returning different ids vs moving the RFP logic to AnonymizeDevices? Should we return actual devices with anonymized fields or fake devices with fake properties?

If we implement a device picker on mobile, it would presumably be to continue to expose only one camera and one microphone to the website (which is absolved from handling device switching)?

Provided we maintain that, if moving the logic helps fix bug 1876636 that might be useful. The challenge there is not knowing which device the user will pick ahead of time. We could perhaps solve that by giving all devices of a kind the same deviceId.

I also think we should consider setting persist to false if RFP is true (again because the website is absolved from handling devices then).

For mobile it might also make sense to allow facingMode as input to gUM (by adding an exception for it in ReduceConstraints). Not all video conferencing sites use it unfortunately, but it appears to have decent usage at 0.02%. This might solve the use case of picking the back camera.

I also think we should consider setting persist to false if RFP is true (again because the website is absolved from handling devices then).

I agree. Just one quick question though, would setting persist to false make it session based? If yes, do we want to flip persist or add window id like it's currently being done for group ids? Would doing both provide any benefit?

Attachment #9411290 - Attachment is obsolete: true

I just updated patch D215670.

It will

  • Remove the use the of MediaEngineFake by RFPTarget::MediaDevices
  • Make all the devices of each kind have the same id.
  • Allow facing mode constraint on mobile devices.
  • Disable origin key persistence if RFPTarget::MediaDevices is enabled.

I also added another patch D218642. It will add window id to device id. If you think it is a good addition, we can continue with it or abandon it.

Non-persisted deviceId is memory (process) based (with special code for private browsing), e.g. you might see the same id in two tabs until you close both (a lot of old code here).

So yes doing both to limit to the session would provide benefit. Good idea!

Attachment #9417949 - Attachment description: WIP: Bug 1899736: Salt device id with window id to make it session based if RFPTarget::MediaDevices is enabled → Bug 1899736: Salt device id with window id to make it session based if RFPTarget::MediaDevices is enabled. r?timhuang

I see. Great, thank you!

Attachment #9417949 - Attachment is obsolete: true
Pushed by fkilic@mozilla.com:
https://hg.mozilla.org/integration/autoland/rev/68f781561b82
Use MediaManager::AnonymizeDevices for MediaDevices RFP target. r=timhuang
Status: ASSIGNED → RESOLVED
Closed: 1 month ago
Resolution: --- → FIXED
Target Milestone: --- → 131 Branch
You need to log in before you can comment on or make changes to this bug.

Attachment

General

Creator:
Created:
Updated:
Size: