Bug 1177242 - Verify whether sandboxed Content process has permissions to access the camera/mic. r=jesup

This commit is contained in:
Gian-Carlo Pascutto 2016-02-17 18:57:26 +01:00
parent 403ceb2af6
commit f96bb1b52c
17 changed files with 184 additions and 57 deletions

View File

@ -279,6 +279,7 @@ function prompt(aBrowser, aRequest) {
requestTypes: requestTypes} = aRequest;
let uri = Services.io.newURI(aRequest.documentURI, null, null);
let host = getHost(uri);
let principal = Services.scriptSecurityManager.createCodebasePrincipal(uri, {});
let chromeDoc = aBrowser.ownerDocument;
let chromeWin = chromeDoc.defaultView;
let stringBundle = chromeWin.gNavigatorBundle;
@ -374,7 +375,16 @@ function prompt(aBrowser, aRequest) {
if (micPerm == perms.PROMPT_ACTION)
micPerm = perms.UNKNOWN_ACTION;
let camPermanentPerm = perms.testExactPermanentPermission(principal, "camera");
let camPerm = perms.testExactPermission(uri, "camera");
// Session approval given but never used to allocate a camera, remove
// and ask again
if (camPerm && !camPermanentPerm) {
perms.remove(uri, "camera");
camPerm = perms.UNKNOWN_ACTION;
}
if (camPerm == perms.PROMPT_ACTION)
camPerm = perms.UNKNOWN_ACTION;
@ -515,11 +525,14 @@ function prompt(aBrowser, aRequest) {
let listId = "webRTC-select" + (sharingScreen ? "Window" : "Camera") + "-menulist";
let videoDeviceIndex = chromeDoc.getElementById(listId).value;
let allowCamera = videoDeviceIndex != "-1";
if (allowCamera)
if (allowCamera) {
allowedDevices.push(videoDeviceIndex);
if (aRemember) {
perms.add(uri, "camera",
allowCamera ? perms.ALLOW_ACTION : perms.DENY_ACTION);
// Session permission will be removed after use
// (it's really one-shot, not for the entire session)
perms.add(uri, "camera", perms.ALLOW_ACTION,
aRemember ? perms.EXPIRE_NEVER : perms.EXPIRE_SESSION);
} else if (aRemember) {
perms.add(uri, "camera", perms.DENY_ACTION);
}
}
if (audioDevices.length) {

View File

@ -634,13 +634,15 @@ AudioDevice::GetSource()
}
nsresult VideoDevice::Allocate(const dom::MediaTrackConstraints &aConstraints,
const MediaEnginePrefs &aPrefs) {
return GetSource()->Allocate(aConstraints, aPrefs, mID);
const MediaEnginePrefs &aPrefs,
const nsACString& aOrigin) {
return GetSource()->Allocate(aConstraints, aPrefs, mID, aOrigin);
}
nsresult AudioDevice::Allocate(const dom::MediaTrackConstraints &aConstraints,
const MediaEnginePrefs &aPrefs) {
return GetSource()->Allocate(aConstraints, aPrefs, mID);
const MediaEnginePrefs &aPrefs,
const nsACString& aOrigin) {
return GetSource()->Allocate(aConstraints, aPrefs, mID, aOrigin);
}
nsresult VideoDevice::Restart(const dom::MediaTrackConstraints &aConstraints,
@ -1203,7 +1205,8 @@ public:
nsresult rv;
if (mAudioDevice) {
rv = mAudioDevice->Allocate(GetInvariant(mConstraints.mAudio), mPrefs);
rv = mAudioDevice->Allocate(GetInvariant(mConstraints.mAudio),
mPrefs, mOrigin);
if (NS_FAILED(rv)) {
LOG(("Failed to allocate audiosource %d",rv));
Fail(NS_LITERAL_STRING("SourceUnavailableError"),
@ -1212,7 +1215,8 @@ public:
}
}
if (mVideoDevice) {
rv = mVideoDevice->Allocate(GetInvariant(mConstraints.mVideo), mPrefs);
rv = mVideoDevice->Allocate(GetInvariant(mConstraints.mVideo),
mPrefs, mOrigin);
if (NS_FAILED(rv)) {
LOG(("Failed to allocate videosource %d\n",rv));
if (mAudioDevice) {

View File

@ -94,7 +94,8 @@ public:
NS_IMETHOD GetType(nsAString& aType);
Source* GetSource();
nsresult Allocate(const dom::MediaTrackConstraints &aConstraints,
const MediaEnginePrefs &aPrefs);
const MediaEnginePrefs &aPrefs,
const nsACString& aOrigin);
nsresult Restart(const dom::MediaTrackConstraints &aConstraints,
const MediaEnginePrefs &aPrefs);
};
@ -108,7 +109,8 @@ public:
NS_IMETHOD GetType(nsAString& aType);
Source* GetSource();
nsresult Allocate(const dom::MediaTrackConstraints &aConstraints,
const MediaEnginePrefs &aPrefs);
const MediaEnginePrefs &aPrefs,
const nsACString& aOrigin);
nsresult Restart(const dom::MediaTrackConstraints &aConstraints,
const MediaEnginePrefs &aPrefs);
};

View File

@ -351,13 +351,15 @@ int
CamerasChild::AllocateCaptureDevice(CaptureEngine aCapEngine,
const char* unique_idUTF8,
const unsigned int unique_idUTF8Length,
int& capture_id)
int& capture_id,
const nsACString& aOrigin)
{
LOG((__PRETTY_FUNCTION__));
nsCString unique_id(unique_idUTF8);
nsCString origin(aOrigin);
nsCOMPtr<nsIRunnable> runnable =
media::NewRunnableFrom([this, aCapEngine, unique_id]() -> nsresult {
if (this->SendAllocateCaptureDevice(aCapEngine, unique_id)) {
media::NewRunnableFrom([this, aCapEngine, unique_id, origin]() -> nsresult {
if (this->SendAllocateCaptureDevice(aCapEngine, unique_id, origin)) {
return NS_OK;
}
return NS_ERROR_FAILURE;

View File

@ -181,7 +181,8 @@ public:
int AllocateCaptureDevice(CaptureEngine aCapEngine,
const char* unique_idUTF8,
const unsigned int unique_idUTF8Length,
int& capture_id);
int& capture_id,
const nsACString& aOrigin);
int GetCaptureCapability(CaptureEngine aCapEngine,
const char* unique_idUTF8,
const unsigned int capability_number,

View File

@ -11,10 +11,14 @@
#include "mozilla/Assertions.h"
#include "mozilla/unused.h"
#include "mozilla/Services.h"
#include "mozilla/Logging.h"
#include "mozilla/ipc/BackgroundParent.h"
#include "mozilla/Preferences.h"
#include "nsIPermissionManager.h"
#include "nsThreadUtils.h"
#include "nsXPCOM.h"
#include "nsNetUtil.h"
#include "webrtc/common_video/libyuv/include/webrtc_libyuv.h"
@ -641,39 +645,127 @@ CamerasParent::RecvGetCaptureDevice(const int& aCapEngine,
return true;
}
static nsresult
GetPrincipalFromOrigin(const nsACString& aOrigin, nsIPrincipal** aPrincipal)
{
nsAutoCString originNoSuffix;
mozilla::PrincipalOriginAttributes attrs;
if (!attrs.PopulateFromOrigin(aOrigin, originNoSuffix)) {
return NS_ERROR_FAILURE;
}
nsCOMPtr<nsIURI> uri;
nsresult rv = NS_NewURI(getter_AddRefs(uri), originNoSuffix);
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIPrincipal> principal = mozilla::BasePrincipal::CreateCodebasePrincipal(uri, attrs);
principal.forget(aPrincipal);
return NS_OK;
}
// Find out whether the given origin has permission to use the
// camera. If the permission is not persistent, we'll make it
// a one-shot by removing the (session) permission.
static bool
HasCameraPermission(const nsCString& aOrigin)
{
// Name used with nsIPermissionManager
static const char* cameraPermission = "camera";
bool allowed = false;
bool permanent = false;
nsresult rv;
nsCOMPtr<nsIPermissionManager> mgr =
do_GetService(NS_PERMISSIONMANAGER_CONTRACTID, &rv);
if (NS_SUCCEEDED(rv)) {
nsCOMPtr<nsIIOService> ioServ(do_GetIOService());
nsCOMPtr<nsIURI> uri;
rv = ioServ->NewURI(aOrigin, nullptr, nullptr, getter_AddRefs(uri));
if (NS_SUCCEEDED(rv)) {
// Permanent permissions are only retrievable via principal, not uri
nsCOMPtr<nsIPrincipal> principal;
rv = GetPrincipalFromOrigin(aOrigin, getter_AddRefs(principal));
if (NS_SUCCEEDED(rv)) {
uint32_t video = nsIPermissionManager::UNKNOWN_ACTION;
rv = mgr->TestExactPermissionFromPrincipal(principal,
cameraPermission,
&video);
if (NS_SUCCEEDED(rv)) {
allowed = (video == nsIPermissionManager::ALLOW_ACTION);
// Was allowed, now see if this is a persistent permission
// or a session one.
if (allowed) {
rv = mgr->TestExactPermanentPermission(principal,
cameraPermission,
&video);
if (NS_SUCCEEDED(rv)) {
permanent = (video == nsIPermissionManager::ALLOW_ACTION);
}
}
}
// Session permissions are removed after one use.
if (allowed && !permanent) {
mgr->RemoveFromPrincipal(principal, cameraPermission);
}
}
}
}
return allowed;
}
bool
CamerasParent::RecvAllocateCaptureDevice(const int& aCapEngine,
const nsCString& unique_id)
const nsCString& unique_id,
const nsCString& aOrigin)
{
LOG((__PRETTY_FUNCTION__));
LOG(("%s: Verifying permissions for %s", __PRETTY_FUNCTION__, aOrigin.get()));
RefPtr<CamerasParent> self(this);
RefPtr<nsRunnable> webrtc_runnable =
media::NewRunnableFrom([self, aCapEngine, unique_id]() -> nsresult {
int numdev = -1;
int error = -1;
if (self->EnsureInitialized(aCapEngine)) {
error = self->mEngines[aCapEngine].mPtrViECapture->AllocateCaptureDevice(
unique_id.get(), MediaEngineSource::kMaxUniqueIdLength, numdev);
RefPtr<nsRunnable> mainthread_runnable =
media::NewRunnableFrom([self, aCapEngine, unique_id, aOrigin]() -> nsresult {
// Verify whether the claimed origin has received permission
// to use the camera, either persistently or this session (one shot).
bool allowed = HasCameraPermission(aOrigin);
if (!allowed) {
// Developer preference for turning off permission check.
if (Preferences::GetBool("media.navigator.permission.disabled", false)
|| Preferences::GetBool("media.navigator.permission.fake")) {
allowed = true;
LOG(("No permission but checks are disabled or fake sources active"));
} else {
LOG(("No camera permission for this origin"));
}
}
RefPtr<nsIRunnable> ipc_runnable =
media::NewRunnableFrom([self, numdev, error]() -> nsresult {
if (self->IsShuttingDown()) {
return NS_ERROR_FAILURE;
}
if (error) {
Unused << self->SendReplyFailure();
return NS_ERROR_FAILURE;
} else {
LOG(("Allocated device nr %d", numdev));
Unused << self->SendReplyAllocateCaptureDevice(numdev);
return NS_OK;
}
// After retrieving the permission (or not) on the main thread,
// bounce to the WebRTC thread to allocate the device (or not),
// then bounce back to the IPC thread for the reply to content.
RefPtr<nsRunnable> webrtc_runnable =
media::NewRunnableFrom([self, allowed, aCapEngine, unique_id]() -> nsresult {
int numdev = -1;
int error = -1;
if (allowed && self->EnsureInitialized(aCapEngine)) {
error = self->mEngines[aCapEngine].mPtrViECapture->AllocateCaptureDevice(
unique_id.get(), MediaEngineSource::kMaxUniqueIdLength, numdev);
}
RefPtr<nsIRunnable> ipc_runnable =
media::NewRunnableFrom([self, numdev, error]() -> nsresult {
if (self->IsShuttingDown()) {
return NS_ERROR_FAILURE;
}
if (error) {
Unused << self->SendReplyFailure();
return NS_ERROR_FAILURE;
} else {
LOG(("Allocated device nr %d", numdev));
Unused << self->SendReplyAllocateCaptureDevice(numdev);
return NS_OK;
}
});
self->mPBackgroundThread->Dispatch(ipc_runnable, NS_DISPATCH_NORMAL);
return NS_OK;
});
self->mPBackgroundThread->Dispatch(ipc_runnable, NS_DISPATCH_NORMAL);
self->DispatchToVideoCaptureThread(webrtc_runnable);
return NS_OK;
});
DispatchToVideoCaptureThread(webrtc_runnable);
NS_DispatchToMainThread(mainthread_runnable);
return true;
}

View File

@ -86,7 +86,7 @@ public:
static already_AddRefed<CamerasParent> Create();
// Messages received form the child. These run on the IPC/PBackground thread.
virtual bool RecvAllocateCaptureDevice(const int&, const nsCString&) override;
virtual bool RecvAllocateCaptureDevice(const int&, const nsCString&, const nsCString&) override;
virtual bool RecvReleaseCaptureDevice(const int&, const int &) override;
virtual bool RecvNumberOfCaptureDevices(const int&) override;
virtual bool RecvNumberOfCapabilities(const int&, const nsCString&) override;

View File

@ -45,7 +45,7 @@ parent:
async GetCaptureCapability(int engine, nsCString unique_idUTF8, int capability_number);
async GetCaptureDevice(int engine, int num);
async AllocateCaptureDevice(int engine, nsCString unique_idUTF8);
async AllocateCaptureDevice(int engine, nsCString unique_idUTF8, nsCString origin);
async ReleaseCaptureDevice(int engine, int numdev);
async StartCapture(int engine, int numdev, CaptureCapability capability);
async StopCapture(int engine, int numdev);

View File

@ -171,7 +171,8 @@ public:
/* This call reserves but does not start the device. */
virtual nsresult Allocate(const dom::MediaTrackConstraints &aConstraints,
const MediaEnginePrefs &aPrefs,
const nsString& aDeviceId) = 0;
const nsString& aDeviceId,
const nsACString& aOrigin) = 0;
virtual uint32_t GetBestFitnessDistance(
const nsTArray<const dom::MediaTrackConstraintSet*>& aConstraintSets,

View File

@ -87,7 +87,8 @@ MediaEngineDefaultVideoSource::GetBestFitnessDistance(
nsresult
MediaEngineDefaultVideoSource::Allocate(const dom::MediaTrackConstraints &aConstraints,
const MediaEnginePrefs &aPrefs,
const nsString& aDeviceId)
const nsString& aDeviceId,
const nsACString& aOrigin)
{
if (mState != kReleased) {
return NS_ERROR_FAILURE;
@ -396,7 +397,8 @@ MediaEngineDefaultAudioSource::GetBestFitnessDistance(
nsresult
MediaEngineDefaultAudioSource::Allocate(const dom::MediaTrackConstraints &aConstraints,
const MediaEnginePrefs &aPrefs,
const nsString& aDeviceId)
const nsString& aDeviceId,
const nsACString& aOrigin)
{
if (mState != kReleased) {
return NS_ERROR_FAILURE;

View File

@ -45,7 +45,8 @@ public:
nsresult Allocate(const dom::MediaTrackConstraints &aConstraints,
const MediaEnginePrefs &aPrefs,
const nsString& aDeviceId) override;
const nsString& aDeviceId,
const nsACString& aOrigin) override;
nsresult Deallocate() override;
nsresult Start(SourceMediaStream*, TrackID) override;
nsresult Stop(SourceMediaStream*, TrackID) override;
@ -114,7 +115,8 @@ public:
nsresult Allocate(const dom::MediaTrackConstraints &aConstraints,
const MediaEnginePrefs &aPrefs,
const nsString& aDeviceId) override;
const nsString& aDeviceId,
const nsACString& aOrigin) override;
nsresult Deallocate() override;
nsresult Start(SourceMediaStream*, TrackID) override;
nsresult Stop(SourceMediaStream*, TrackID) override;

View File

@ -100,7 +100,8 @@ MediaEngineRemoteVideoSource::Shutdown()
nsresult
MediaEngineRemoteVideoSource::Allocate(const dom::MediaTrackConstraints& aConstraints,
const MediaEnginePrefs& aPrefs,
const nsString& aDeviceId)
const nsString& aDeviceId,
const nsACString& aOrigin)
{
LOG((__PRETTY_FUNCTION__));
AssertIsOnOwningThread();
@ -120,11 +121,12 @@ MediaEngineRemoteVideoSource::Allocate(const dom::MediaTrackConstraints& aConstr
if (mozilla::camera::GetChildAndCall(
&mozilla::camera::CamerasChild::AllocateCaptureDevice,
mCapEngine, GetUUID().get(), kMaxUniqueIdLength, mCaptureIndex)) {
mCapEngine, GetUUID().get(), kMaxUniqueIdLength, mCaptureIndex, aOrigin)) {
return NS_ERROR_FAILURE;
}
mState = kAllocated;
LOG(("Video device %d allocated", mCaptureIndex));
LOG(("Video device %d allocated for %s", mCaptureIndex,
PromiseFlatCString(aOrigin).get()));
} else if (MOZ_LOG_TEST(GetMediaManagerLog(), mozilla::LogLevel::Debug)) {
MonitorAutoLock lock(mMonitor);
if (mSources.IsEmpty()) {

View File

@ -73,7 +73,8 @@ public:
nsresult Allocate(const dom::MediaTrackConstraints& aConstraints,
const MediaEnginePrefs& aPrefs,
const nsString& aDeviceId) override;
const nsString& aDeviceId,
const nsACString& aOrigin) override;
nsresult Deallocate() override;;
nsresult Start(SourceMediaStream*, TrackID) override;
nsresult Stop(SourceMediaStream*, TrackID) override;

View File

@ -128,7 +128,8 @@ MediaEngineTabVideoSource::GetUUID(nsACString_internal& aUuid)
nsresult
MediaEngineTabVideoSource::Allocate(const dom::MediaTrackConstraints& aConstraints,
const MediaEnginePrefs& aPrefs,
const nsString& aDeviceId)
const nsString& aDeviceId,
const nsACString& aOrigin)
{
// windowId is not a proper constraint, so just read it.
// It has no well-defined behavior in advanced, so ignore it there.

View File

@ -23,7 +23,8 @@ class MediaEngineTabVideoSource : public MediaEngineVideoSource, nsIDOMEventList
void GetUUID(nsACString_internal&) override;
nsresult Allocate(const dom::MediaTrackConstraints &,
const mozilla::MediaEnginePrefs&,
const nsString& aDeviceId) override;
const nsString& aDeviceId,
const nsACString& aOrigin) override;
nsresult Deallocate() override;
nsresult Start(mozilla::SourceMediaStream*, mozilla::TrackID) override;
void SetDirectListeners(bool aHasDirectListeners) override {};

View File

@ -72,7 +72,8 @@ public:
void GetUUID(nsACString& aUUID) override;
nsresult Allocate(const dom::MediaTrackConstraints& aConstraints,
const MediaEnginePrefs& aPrefs,
const nsString& aDeviceId) override
const nsString& aDeviceId,
const nsACString& aOrigin) override
{
// Nothing to do here, everything is managed in MediaManager.cpp
return NS_OK;
@ -435,7 +436,8 @@ public:
nsresult Allocate(const dom::MediaTrackConstraints& aConstraints,
const MediaEnginePrefs& aPrefs,
const nsString& aDeviceId) override;
const nsString& aDeviceId,
const nsACString& aOrigin) override;
nsresult Deallocate() override;
nsresult Start(SourceMediaStream* aStream, TrackID aID) override;
nsresult Stop(SourceMediaStream* aSource, TrackID aID) override;

View File

@ -220,7 +220,8 @@ uint32_t MediaEngineWebRTCMicrophoneSource::GetBestFitnessDistance(
nsresult
MediaEngineWebRTCMicrophoneSource::Allocate(const dom::MediaTrackConstraints &aConstraints,
const MediaEnginePrefs &aPrefs,
const nsString& aDeviceId)
const nsString& aDeviceId,
const nsACString& aOrigin)
{
AssertIsOnOwningThread();
if (mState == kReleased) {