1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "mozilla/dom/JSValidatorChild.h"
8 #include "mozilla/dom/JSOracleChild.h"
10 #include "mozilla/Encoding.h"
11 #include "mozilla/dom/ScriptDecoding.h"
12 #include "mozilla/ipc/Endpoint.h"
14 #include "js/CompileOptions.h"
16 #include "js/SourceText.h"
17 #include "js/experimental/CompileScript.h"
18 #include "js/experimental/JSStencil.h"
19 #include "xpcpublic.h"
21 using namespace mozilla::dom
;
22 using Encoding
= mozilla::Encoding
;
24 mozilla::UniquePtr
<mozilla::Decoder
> TryGetDecoder(
25 const mozilla::Span
<const uint8_t>& aSourceBytes
,
26 const nsACString
& aContentCharset
, const nsAString
& aHintCharset
,
27 const nsAString
& aDocumentCharset
) {
28 const Encoding
* encoding
;
29 mozilla::UniquePtr
<mozilla::Decoder
> unicodeDecoder
;
31 std::tie(encoding
, std::ignore
) = Encoding::ForBOM(aSourceBytes
);
33 unicodeDecoder
= encoding
->NewDecoderWithBOMRemoval();
36 if (!unicodeDecoder
) {
37 encoding
= Encoding::ForLabel(aContentCharset
);
39 unicodeDecoder
= encoding
->NewDecoderWithoutBOMHandling();
42 if (!unicodeDecoder
) {
43 encoding
= Encoding::ForLabel(aHintCharset
);
45 unicodeDecoder
= encoding
->NewDecoderWithoutBOMHandling();
49 if (!unicodeDecoder
) {
50 encoding
= Encoding::ForLabel(aDocumentCharset
);
52 unicodeDecoder
= encoding
->NewDecoderWithoutBOMHandling();
57 if (!unicodeDecoder
&& !IsUtf8(mozilla::Span(reinterpret_cast<const char*>(
58 aSourceBytes
.Elements()),
59 aSourceBytes
.Length()))) {
60 // Curiously, there are various callers that don't pass aDocument. The
61 // fallback in the old code was ISO-8859-1, which behaved like
63 unicodeDecoder
= WINDOWS_1252_ENCODING
->NewDecoderWithoutBOMHandling();
66 return unicodeDecoder
;
69 mozilla::ipc::IPCResult
JSValidatorChild::RecvIsOpaqueResponseAllowed(
70 IsOpaqueResponseAllowedResolver
&& aResolver
) {
71 mResolver
.emplace(aResolver
);
76 mozilla::ipc::IPCResult
JSValidatorChild::RecvOnDataAvailable(Shmem
&& aData
) {
78 MOZ_ASSERT(!CanSend());
82 if (!mSourceBytes
.Append(Span(aData
.get
<char>(), aData
.Size
<char>()),
84 // To prevent an attacker from flood the validation process,
85 // we don't validate here.
86 Resolve(ValidatorResult::Failure
);
93 mozilla::ipc::IPCResult
JSValidatorChild::RecvOnStopRequest(
94 const nsresult
& aReason
, const nsACString
& aContentCharset
,
95 const nsAString
& aHintCharset
, const nsAString
& aDocumentCharset
) {
100 if (NS_FAILED(aReason
)) {
101 Resolve(ValidatorResult::Failure
);
102 } else if (mSourceBytes
.IsEmpty()) {
103 // The empty document parses as JavaScript.
104 Resolve(ValidatorResult::JavaScript
);
106 UniquePtr
<Decoder
> unicodeDecoder
= TryGetDecoder(
107 mSourceBytes
, aContentCharset
, aHintCharset
, aDocumentCharset
);
109 if (!unicodeDecoder
) {
110 Resolve(ShouldAllowJS(mSourceBytes
));
112 BufferUniquePtr
<Utf8Unit
[]> buffer
;
113 auto result
= GetUTF8EncodedContent(mSourceBytes
, buffer
, unicodeDecoder
);
114 if (result
.isErr()) {
115 Resolve(ValidatorResult::Failure
);
117 Resolve(ShouldAllowJS(result
.unwrap()));
125 void JSValidatorChild::ActorDestroy(ActorDestroyReason aReason
) {
127 Resolve(ValidatorResult::Failure
);
131 void JSValidatorChild::Resolve(ValidatorResult aResult
) {
132 MOZ_ASSERT(mResolver
);
133 Maybe
<Shmem
> data
= Nothing();
134 if (aResult
== ValidatorResult::JavaScript
&& !mSourceBytes
.IsEmpty()) {
137 JSValidatorUtils::CopyCStringToShmem(this, mSourceBytes
, sharedData
);
138 if (NS_SUCCEEDED(rv
)) {
139 data
= Some(std::move(sharedData
));
143 mResolver
.ref()(std::tuple
<mozilla::Maybe
<Shmem
>&&, const ValidatorResult
&>(
144 std::move(data
), aResult
));
148 mozilla::Result
<mozilla::Span
<const char>, nsresult
>
149 JSValidatorChild::GetUTF8EncodedContent(
150 const mozilla::Span
<const uint8_t>& aData
,
151 BufferUniquePtr
<Utf8Unit
[]>& aBuffer
, UniquePtr
<Decoder
>& aDecoder
) {
152 MOZ_ASSERT(aDecoder
);
153 // We need the output buffer to be UTF8
154 CheckedInt
<size_t> bufferLength
=
155 ScriptDecoding
<Utf8Unit
>::MaxBufferLength(aDecoder
, aData
.Length());
156 if (!bufferLength
.isValid()) {
157 return mozilla::Err(NS_ERROR_FAILURE
);
160 CheckedInt
<size_t> bufferByteSize
= bufferLength
* sizeof(Utf8Unit
);
161 if (!bufferByteSize
.isValid()) {
162 return mozilla::Err(NS_ERROR_FAILURE
);
165 aBuffer
.reset(static_cast<Utf8Unit
*>(js_malloc(bufferByteSize
.value())));
167 return mozilla::Err(NS_ERROR_FAILURE
);
170 size_t written
= ScriptDecoding
<Utf8Unit
>::DecodeInto(
171 aDecoder
, aData
, Span(aBuffer
.get(), bufferLength
.value()),
172 /* aEndOfSource = */ true);
173 MOZ_ASSERT(written
<= bufferLength
.value());
175 IsUtf8(Span(reinterpret_cast<const char*>(aBuffer
.get()), written
)));
177 return Span(reinterpret_cast<const char*>(aBuffer
.get()), written
);
180 JSValidatorChild::ValidatorResult
JSValidatorChild::ShouldAllowJS(
181 const mozilla::Span
<const char>& aSpan
) const {
182 // It's possible that the data we get is not valid UTF-8, so aSpan
183 // ends empty here. We should treat it as a failure because this
185 if (aSpan
.IsEmpty()) {
186 return ValidatorResult::Failure
;
189 MOZ_DIAGNOSTIC_ASSERT(IsUtf8(aSpan
));
191 JS::FrontendContext
* fc
= JSOracleChild::JSFrontendContext();
193 return ValidatorResult::Failure
;
196 JS::SourceText
<Utf8Unit
> srcBuf
;
197 if (!srcBuf
.init(fc
, aSpan
.Elements(), aSpan
.Length(),
198 JS::SourceOwnership::Borrowed
)) {
199 JS::ClearFrontendErrors(fc
);
200 return ValidatorResult::Failure
;
203 // Parse to JavaScript
204 JS::PrefableCompileOptions prefableOptions
;
205 xpc::SetPrefableCompileOptions(prefableOptions
);
206 // For the syntax validation purpose, asm.js doesn't need to be enabled.
207 prefableOptions
.setAsmJSOption(JS::AsmJSOption::DisabledByAsmJSPref
);
209 JS::CompileOptions
options(prefableOptions
);
210 RefPtr
<JS::Stencil
> stencil
=
211 JS::CompileGlobalScriptToStencil(fc
, options
, srcBuf
);
214 JS::ClearFrontendErrors(fc
);
215 return ValidatorResult::Other
;
218 MOZ_ASSERT(!aSpan
.IsEmpty());
221 if (IsAscii(aSpan
)) {
222 // Ascii is a subset of Latin1, and JS_ParseJSON can take Latin1 directly
224 reinterpret_cast<const JS::Latin1Char
*>(aSpan
.Elements()),
226 return ValidatorResult::JSON
;
230 nsresult rv
= UTF_8_ENCODING
->DecodeWithBOMRemoval(
231 Span(reinterpret_cast<const uint8_t*>(aSpan
.Elements()),
235 return ValidatorResult::Failure
;
238 if (JS::IsValidJSON(decoded
.BeginReading(), decoded
.Length())) {
239 return ValidatorResult::JSON
;
243 // Since the JSON parsing failed, we confirmed the file is Javascript and not
245 return ValidatorResult::JavaScript
;