Commit ac8f9263 authored by perkj's avatar perkj Committed by Commit bot

Don't auto allow access to media devices unless a the security origin of the...

Don't auto allow access to media devices unless a the security origin of the requester is the same as its ancestors.

This adds a field in MediaStreamRequest to verify that the security origin of the requester is the same as its ancestors.
This field have to be set on the UI-thread and is therefore set just before sending the request to the UI.

BUG=448378
TEST= please see bug report

Review URL: https://codereview.chromium.org/795703003

Cr-Commit-Position: refs/heads/master@{#313904}
parent c27bd1f7
......@@ -498,6 +498,11 @@ void MediaStreamDevicesController::RequestFinished() {
}
bool MediaStreamDevicesController::IsRequestAllowedByDefault() const {
// If not all ancestors of the requesting frame have the same origin, do not
// allow the request per default.
if (!request_.all_ancestors_have_same_origin)
return false;
// The request from internal objects like chrome://URLs is always allowed.
if (CheckAllowAllMediaStreamContentForOrigin(profile_,
request_.security_origin)) {
......
......@@ -97,6 +97,10 @@ class CONTENT_EXPORT FrameTreeNode {
replication_state_.sandbox_flags = sandbox_flags;
}
bool HasSameOrigin(const FrameTreeNode& node) const {
return replication_state_.origin.IsSameAs(node.replication_state_.origin);
}
const FrameReplicationState& current_replication_state() const {
return replication_state_;
}
......
......@@ -238,7 +238,9 @@ class MediaStreamManager::DeviceRequest {
salt_callback(salt_callback),
state_(NUM_MEDIA_TYPES, MEDIA_REQUEST_STATE_NOT_REQUESTED),
audio_type_(MEDIA_NO_SERVICE),
video_type_(MEDIA_NO_SERVICE) {
video_type_(MEDIA_NO_SERVICE),
target_process_id_(-1),
target_frame_id_(-1) {
}
~DeviceRequest() {}
......@@ -264,6 +266,8 @@ class MediaStreamManager::DeviceRequest {
void CreateUIRequest(const std::string& requested_audio_device_id,
const std::string& requested_video_device_id) {
DCHECK(!ui_request_);
target_process_id_ = requesting_process_id;
target_frame_id_ = requesting_frame_id;
ui_request_.reset(new MediaStreamRequest(requesting_process_id,
requesting_frame_id,
page_request_id,
......@@ -279,9 +283,10 @@ class MediaStreamManager::DeviceRequest {
// Creates a tab capture specific MediaStreamRequest object that is used by
// this request when UI is asked for permission and device selection.
void CreateTabCaptureUIRequest(int target_render_process_id,
int target_render_frame_id,
const std::string& tab_capture_id) {
int target_render_frame_id) {
DCHECK(!ui_request_);
target_process_id_ = target_render_process_id;
target_frame_id_ = target_render_frame_id;
ui_request_.reset(new MediaStreamRequest(target_render_process_id,
target_render_frame_id,
page_request_id,
......@@ -292,10 +297,12 @@ class MediaStreamManager::DeviceRequest {
"",
audio_type_,
video_type_));
ui_request_->tab_capture_device_id = tab_capture_id;
}
const MediaStreamRequest* UIRequest() const { return ui_request_.get(); }
bool HasUIRequest() const { return ui_request_.get() != nullptr; }
scoped_ptr<MediaStreamRequest> DetachUIRequest() {
return ui_request_.Pass();
}
// Update the request state and notify observers.
void SetState(MediaStreamType stream_type, MediaRequestState new_state) {
......@@ -313,14 +320,8 @@ class MediaStreamManager::DeviceRequest {
if (!media_observer)
return;
// If |ui_request_| doesn't exist, it means that the request has not yet
// been setup fully and there are no valid observers.
if (!ui_request_)
return;
media_observer->OnMediaRequestStateChanged(
ui_request_->render_process_id, ui_request_->render_frame_id,
ui_request_->page_request_id, ui_request_->security_origin,
target_process_id_, target_frame_id_, page_request_id, security_origin,
stream_type, new_state);
}
......@@ -365,11 +366,15 @@ class MediaStreamManager::DeviceRequest {
scoped_ptr<MediaStreamUIProxy> ui_proxy;
std::string tab_capture_device_id;
private:
std::vector<MediaRequestState> state_;
scoped_ptr<MediaStreamRequest> ui_request_;
MediaStreamType audio_type_;
MediaStreamType video_type_;
int target_process_id_;
int target_frame_id_;
};
MediaStreamManager::EnumerationCache::EnumerationCache()
......@@ -1163,7 +1168,7 @@ void MediaStreamManager::DeleteRequest(const std::string& label) {
void MediaStreamManager::PostRequestToUI(const std::string& label,
DeviceRequest* request) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
DCHECK(request->UIRequest());
DCHECK(request->HasUIRequest());
DVLOG(1) << "PostRequestToUI({label= " << label << "})";
const MediaStreamType audio_type = request->audio_type();
......@@ -1203,7 +1208,7 @@ void MediaStreamManager::PostRequestToUI(const std::string& label,
}
request->ui_proxy->RequestAccess(
*request->UIRequest(),
request->DetachUIRequest(),
base::Bind(&MediaStreamManager::HandleAccessRequestResponse,
base::Unretained(this), label));
}
......@@ -1340,10 +1345,10 @@ bool MediaStreamManager::SetupTabCaptureRequest(DeviceRequest* request) {
request->video_type() != MEDIA_NO_SERVICE)) {
return false;
}
request->tab_capture_device_id = capture_device_id;
request->CreateTabCaptureUIRequest(target_render_process_id,
target_render_frame_id,
capture_device_id);
target_render_frame_id);
DVLOG(3) << "SetupTabCaptureRequest "
<< ", {capture_device_id = " << capture_device_id << "}"
......@@ -1913,7 +1918,7 @@ void MediaStreamManager::HandleAccessRequestResponse(
if (device_info.device.type == content::MEDIA_TAB_VIDEO_CAPTURE ||
device_info.device.type == content::MEDIA_TAB_AUDIO_CAPTURE) {
device_info.device.id = request->UIRequest()->tab_capture_device_id;
device_info.device.id = request->tab_capture_device_id;
// Initialize the sample_rate and channel_layout here since for audio
// mirroring, we don't go through EnumerateDevices where these are usually
......
......@@ -5,6 +5,7 @@
#include "content/browser/renderer_host/media/media_stream_ui_proxy.h"
#include "base/command_line.h"
#include "content/browser/frame_host/frame_tree_node.h"
#include "content/browser/frame_host/render_frame_host_delegate.h"
#include "content/browser/frame_host/render_frame_host_impl.h"
#include "content/public/browser/browser_thread.h"
......@@ -13,13 +14,35 @@
namespace content {
void SetAndCheckAncestorFlag(MediaStreamRequest* request) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
RenderFrameHostImpl* rfh =
RenderFrameHostImpl::FromID(request->render_process_id,
request->render_frame_id);
if (rfh == NULL) {
// RenderFrame destroyed before the request is handled?
return;
}
FrameTreeNode* node = rfh->frame_tree_node();
while (node->parent() != NULL) {
if (!node->HasSameOrigin(*node->parent())) {
request->all_ancestors_have_same_origin = false;
return;
}
node = node->parent();
}
request->all_ancestors_have_same_origin = true;
}
class MediaStreamUIProxy::Core {
public:
explicit Core(const base::WeakPtr<MediaStreamUIProxy>& proxy,
RenderFrameHostDelegate* test_render_delegate);
~Core();
void RequestAccess(const MediaStreamRequest& request);
void RequestAccess(scoped_ptr<MediaStreamRequest> request);
bool CheckAccess(const GURL& security_origin,
MediaStreamType type,
int process_id,
......@@ -58,11 +81,11 @@ MediaStreamUIProxy::Core::~Core() {
}
void MediaStreamUIProxy::Core::RequestAccess(
const MediaStreamRequest& request) {
scoped_ptr<MediaStreamRequest> request) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
RenderFrameHostDelegate* render_delegate = GetRenderFrameHostDelegate(
request.render_process_id, request.render_frame_id);
request->render_process_id, request->render_frame_id);
// Tab may have gone away, or has no delegate from which to request access.
if (!render_delegate) {
......@@ -71,10 +94,11 @@ void MediaStreamUIProxy::Core::RequestAccess(
scoped_ptr<MediaStreamUI>());
return;
}
SetAndCheckAncestorFlag(request.get());
render_delegate->RequestMediaAccessPermission(
request, base::Bind(&Core::ProcessAccessRequestResponse,
weak_factory_.GetWeakPtr()));
*request, base::Bind(&Core::ProcessAccessRequestResponse,
weak_factory_.GetWeakPtr()));
}
bool MediaStreamUIProxy::Core::CheckAccess(const GURL& security_origin,
......@@ -154,14 +178,15 @@ MediaStreamUIProxy::~MediaStreamUIProxy() {
}
void MediaStreamUIProxy::RequestAccess(
const MediaStreamRequest& request,
scoped_ptr<MediaStreamRequest> request,
const ResponseCallback& response_callback) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
response_callback_ = response_callback;
BrowserThread::PostTask(
BrowserThread::UI, FROM_HERE,
base::Bind(&Core::RequestAccess, base::Unretained(core_.get()), request));
base::Bind(&Core::RequestAccess, base::Unretained(core_.get()),
base::Passed(&request)));
}
void MediaStreamUIProxy::CheckAccess(
......@@ -262,7 +287,7 @@ void FakeMediaStreamUIProxy::SetCameraAccess(bool access) {
}
void FakeMediaStreamUIProxy::RequestAccess(
const MediaStreamRequest& request,
scoped_ptr<MediaStreamRequest> request,
const ResponseCallback& response_callback) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
......@@ -289,25 +314,25 @@ void FakeMediaStreamUIProxy::RequestAccess(
for (MediaStreamDevices::const_iterator it = devices_.begin();
it != devices_.end(); ++it) {
if (!accepted_audio &&
IsAudioInputMediaType(request.audio_type) &&
IsAudioInputMediaType(request->audio_type) &&
IsAudioInputMediaType(it->type) &&
(request.requested_audio_device_id.empty() ||
request.requested_audio_device_id == it->id)) {
(request->requested_audio_device_id.empty() ||
request->requested_audio_device_id == it->id)) {
devices_to_use.push_back(*it);
accepted_audio = true;
} else if (!accepted_video &&
IsVideoMediaType(request.video_type) &&
IsVideoMediaType(request->video_type) &&
IsVideoMediaType(it->type) &&
(request.requested_video_device_id.empty() ||
request.requested_video_device_id == it->id)) {
(request->requested_video_device_id.empty() ||
request->requested_video_device_id == it->id)) {
devices_to_use.push_back(*it);
accepted_video = true;
}
}
// Fail the request if a device doesn't exist for the requested type.
if ((request.audio_type != MEDIA_NO_SERVICE && !accepted_audio) ||
(request.video_type != MEDIA_NO_SERVICE && !accepted_video)) {
if ((request->audio_type != MEDIA_NO_SERVICE && !accepted_audio) ||
(request->video_type != MEDIA_NO_SERVICE && !accepted_video)) {
devices_to_use.clear();
}
......
......@@ -38,7 +38,7 @@ class CONTENT_EXPORT MediaStreamUIProxy {
// WebContentsDelegate::RequestMediaAccessPermission(). The specified
// |response_callback| is called when the WebContentsDelegate approves or
// denies request.
virtual void RequestAccess(const MediaStreamRequest& request,
virtual void RequestAccess(scoped_ptr<MediaStreamRequest> request,
const ResponseCallback& response_callback);
// Checks if we have permission to access the microphone or camera. Note that
......@@ -97,7 +97,7 @@ class CONTENT_EXPORT FakeMediaStreamUIProxy : public MediaStreamUIProxy {
void SetCameraAccess(bool access);
// MediaStreamUIProxy overrides.
void RequestAccess(const MediaStreamRequest& request,
void RequestAccess(scoped_ptr<MediaStreamRequest> request,
const ResponseCallback& response_callback) override;
void CheckAccess(const GURL& security_origin,
MediaStreamType type,
......
......@@ -17,8 +17,8 @@ using testing::Return;
using testing::SaveArg;
namespace content {
namespace {
namespace {
class MockRenderFrameHostDelegate : public RenderFrameHostDelegate {
public:
MOCK_METHOD2(RequestMediaAccessPermission,
......@@ -76,28 +76,31 @@ class MediaStreamUIProxyTest : public testing::Test {
MATCHER_P(SameRequest, expected, "") {
return
expected.render_process_id == arg.render_process_id &&
expected.render_frame_id == arg.render_frame_id &&
expected.tab_capture_device_id == arg.tab_capture_device_id &&
expected.security_origin == arg.security_origin &&
expected.request_type == arg.request_type &&
expected.requested_audio_device_id == arg.requested_audio_device_id &&
expected.requested_video_device_id == arg.requested_video_device_id &&
expected.audio_type == arg.audio_type &&
expected.video_type == arg.video_type;
expected->render_process_id == arg.render_process_id &&
expected->render_frame_id == arg.render_frame_id &&
expected->security_origin == arg.security_origin &&
expected->request_type == arg.request_type &&
expected->requested_audio_device_id == arg.requested_audio_device_id &&
expected->requested_video_device_id == arg.requested_video_device_id &&
expected->audio_type == arg.audio_type &&
expected->video_type == arg.video_type;
}
TEST_F(MediaStreamUIProxyTest, Deny) {
MediaStreamRequest request(0, 0, 0, GURL("http://origin/"), false,
scoped_ptr<MediaStreamRequest> request (
new MediaStreamRequest(0, 0, 0, GURL("http://origin/"),
false,
MEDIA_GENERATE_STREAM, std::string(),
std::string(),
MEDIA_DEVICE_AUDIO_CAPTURE,
MEDIA_DEVICE_VIDEO_CAPTURE);
MEDIA_DEVICE_VIDEO_CAPTURE));
MediaStreamRequest* request_ptr = request.get();
proxy_->RequestAccess(
request, base::Bind(&MockResponseCallback::OnAccessRequestResponse,
base::Unretained(&response_callback_)));
request.Pass(), base::Bind(&MockResponseCallback::OnAccessRequestResponse,
base::Unretained(&response_callback_)));
MediaResponseCallback callback;
EXPECT_CALL(delegate_, RequestMediaAccessPermission(SameRequest(request), _))
EXPECT_CALL(delegate_, RequestMediaAccessPermission(SameRequest(request_ptr),
_))
.WillOnce(SaveArg<1>(&callback));
message_loop_.RunUntilIdle();
ASSERT_FALSE(callback.is_null());
......@@ -114,16 +117,20 @@ TEST_F(MediaStreamUIProxyTest, Deny) {
}
TEST_F(MediaStreamUIProxyTest, AcceptAndStart) {
MediaStreamRequest request(0, 0, 0, GURL("http://origin/"), false,
scoped_ptr<MediaStreamRequest> request (
new MediaStreamRequest(0, 0, 0,
GURL("http://origin/"), false,
MEDIA_GENERATE_STREAM, std::string(),
std::string(),
MEDIA_DEVICE_AUDIO_CAPTURE,
MEDIA_DEVICE_VIDEO_CAPTURE);
MEDIA_DEVICE_VIDEO_CAPTURE));
MediaStreamRequest* request_ptr = request.get();
proxy_->RequestAccess(
request, base::Bind(&MockResponseCallback::OnAccessRequestResponse,
base::Unretained(&response_callback_)));
request.Pass(), base::Bind(&MockResponseCallback::OnAccessRequestResponse,
base::Unretained(&response_callback_)));
MediaResponseCallback callback;
EXPECT_CALL(delegate_, RequestMediaAccessPermission(SameRequest(request), _))
EXPECT_CALL(delegate_, RequestMediaAccessPermission(SameRequest(request_ptr),
_))
.WillOnce(SaveArg<1>(&callback));
message_loop_.RunUntilIdle();
ASSERT_FALSE(callback.is_null());
......@@ -148,16 +155,20 @@ TEST_F(MediaStreamUIProxyTest, AcceptAndStart) {
// Verify that the proxy can be deleted before the request is processed.
TEST_F(MediaStreamUIProxyTest, DeleteBeforeAccepted) {
MediaStreamRequest request(0, 0, 0, GURL("http://origin/"), false,
scoped_ptr<MediaStreamRequest> request (
new MediaStreamRequest(0, 0, 0,
GURL("http://origin/"), false,
MEDIA_GENERATE_STREAM, std::string(),
std::string(),
MEDIA_DEVICE_AUDIO_CAPTURE,
MEDIA_DEVICE_VIDEO_CAPTURE);
MEDIA_DEVICE_VIDEO_CAPTURE));
MediaStreamRequest* request_ptr = request.get();
proxy_->RequestAccess(
request, base::Bind(&MockResponseCallback::OnAccessRequestResponse,
base::Unretained(&response_callback_)));
request.Pass(), base::Bind(&MockResponseCallback::OnAccessRequestResponse,
base::Unretained(&response_callback_)));
MediaResponseCallback callback;
EXPECT_CALL(delegate_, RequestMediaAccessPermission(SameRequest(request), _))
EXPECT_CALL(delegate_, RequestMediaAccessPermission(SameRequest(request_ptr)
, _))
.WillOnce(SaveArg<1>(&callback));
message_loop_.RunUntilIdle();
ASSERT_FALSE(callback.is_null());
......@@ -170,16 +181,20 @@ TEST_F(MediaStreamUIProxyTest, DeleteBeforeAccepted) {
}
TEST_F(MediaStreamUIProxyTest, StopFromUI) {
MediaStreamRequest request(0, 0, 0, GURL("http://origin/"), false,
scoped_ptr<MediaStreamRequest> request (
new MediaStreamRequest(0, 0, 0,
GURL("http://origin/"), false,
MEDIA_GENERATE_STREAM, std::string(),
std::string(),
MEDIA_DEVICE_AUDIO_CAPTURE,
MEDIA_DEVICE_VIDEO_CAPTURE);
MEDIA_DEVICE_VIDEO_CAPTURE));
MediaStreamRequest* request_ptr = request.get();
proxy_->RequestAccess(
request, base::Bind(&MockResponseCallback::OnAccessRequestResponse,
base::Unretained(&response_callback_)));
request.Pass(), base::Bind(&MockResponseCallback::OnAccessRequestResponse,
base::Unretained(&response_callback_)));
MediaResponseCallback callback;
EXPECT_CALL(delegate_, RequestMediaAccessPermission(SameRequest(request), _))
EXPECT_CALL(delegate_, RequestMediaAccessPermission(SameRequest(request_ptr)
, _))
.WillOnce(SaveArg<1>(&callback));
message_loop_.RunUntilIdle();
ASSERT_FALSE(callback.is_null());
......@@ -214,22 +229,22 @@ TEST_F(MediaStreamUIProxyTest, StopFromUI) {
}
TEST_F(MediaStreamUIProxyTest, WindowIdCallbackCalled) {
MediaStreamRequest request(0,
0,
0,
GURL("http://origin/"),
false,
MEDIA_GENERATE_STREAM,
std::string(),
scoped_ptr<MediaStreamRequest> request (
new MediaStreamRequest(0, 0, 0,
GURL("http://origin/"), false,
MEDIA_GENERATE_STREAM, std::string(),
std::string(),
MEDIA_NO_SERVICE,
MEDIA_DESKTOP_VIDEO_CAPTURE);
MEDIA_DESKTOP_VIDEO_CAPTURE));
MediaStreamRequest* request_ptr = request.get();
proxy_->RequestAccess(
request,
request.Pass(),
base::Bind(&MockResponseCallback::OnAccessRequestResponse,
base::Unretained(&response_callback_)));
MediaResponseCallback callback;
EXPECT_CALL(delegate_, RequestMediaAccessPermission(SameRequest(request), _))
EXPECT_CALL(delegate_, RequestMediaAccessPermission(SameRequest(request_ptr),
_))
.WillOnce(SaveArg<1>(&callback));
message_loop_.RunUntilIdle();
......
......@@ -103,7 +103,8 @@ MediaStreamRequest::MediaStreamRequest(
requested_audio_device_id(requested_audio_device_id),
requested_video_device_id(requested_video_device_id),
audio_type(audio_type),
video_type(video_type) {
video_type(video_type),
all_ancestors_have_same_origin(false) {
}
MediaStreamRequest::~MediaStreamRequest() {}
......
......@@ -218,9 +218,6 @@ struct CONTENT_EXPORT MediaStreamRequest {
// identifying this request. This is used for cancelling request.
int page_request_id;
// Used by tab capture.
std::string tab_capture_device_id;
// The WebKit security origin for the current request (e.g. "html5rocks.com").
GURL security_origin;
......@@ -242,6 +239,9 @@ struct CONTENT_EXPORT MediaStreamRequest {
// Flag to indicate if the request contains video.
MediaStreamType video_type;
// True if all ancestors of the requesting frame have the same origin.
bool all_ancestors_have_same_origin;
};
// Interface used by the content layer to notify chrome about changes in the
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment