1/*
2 * Copyright (C) 2018 Yusuke Suzuki <yusukesuzuki@slowstart.org>.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#include "config.h"
27#include "WasmStreamingParser.h"
28
29#if ENABLE(WEBASSEMBLY)
30
31#include "WasmModuleParser.h"
32#include "WasmSectionParser.h"
33#include <wtf/Optional.h>
34#include <wtf/UnalignedAccess.h>
35
36namespace JSC { namespace Wasm {
37
38namespace WasmStreamingParserInternal {
39static constexpr bool verbose = false;
40}
41
42#define WASM_STREAMING_PARSER_FAIL_IF_HELPER_FAILS(helper) \
43 do { \
44 auto helperResult = helper; \
45 if (UNLIKELY(!helperResult)) { \
46 m_errorMessage = helperResult.error(); \
47 return State::FatalError; \
48 } \
49 } while (0)
50
51ALWAYS_INLINE Optional<uint8_t> parseUInt7(const uint8_t* data, size_t& offset, size_t size)
52{
53 if (offset >= size)
54 return false;
55 uint8_t result = data[offset++];
56 if (result < 0x80)
57 return result;
58 return WTF::nullopt;
59}
60
61template <typename ...Args>
62NEVER_INLINE auto WARN_UNUSED_RETURN StreamingParser::fail(Args... args) -> State
63{
64 using namespace FailureHelper; // See ADL comment in namespace above.
65 m_errorMessage = makeString("WebAssembly.Module doesn't parse at byte "_s, String::number(m_offset), ": "_s, makeString(args)...);
66 dataLogLnIf(WasmStreamingParserInternal::verbose, m_errorMessage);
67 return State::FatalError;
68}
69
70StreamingParser::StreamingParser(ModuleInformation& info)
71 : m_info(info)
72{
73 dataLogLnIf(WasmStreamingParserInternal::verbose, "starting validation");
74}
75
76auto StreamingParser::parseModuleHeader(Vector<uint8_t>&& data) -> State
77{
78 ASSERT(data.size() == moduleHeaderSize);
79 dataLogLnIf(WasmStreamingParserInternal::verbose, "header validation");
80 WASM_PARSER_FAIL_IF(data[0] != '\0' || data[1] != 'a' || data[2] != 's' || data[3] != 'm', "modules doesn't start with '\\0asm'");
81 uint32_t versionNumber = WTF::unalignedLoad<uint32_t>(data.data() + 4);
82 WASM_PARSER_FAIL_IF(versionNumber != expectedVersionNumber, "unexpected version number ", versionNumber, " expected ", expectedVersionNumber);
83 return State::SectionID;
84}
85
86auto StreamingParser::parseSectionID(Vector<uint8_t>&& data) -> State
87{
88 ASSERT(data.size() == sectionIDSize);
89 size_t offset = 0;
90 auto result = parseUInt7(data.data(), offset, data.size());
91 WASM_PARSER_FAIL_IF(!result, "can't get section byte");
92
93 Section section = Section::Custom;
94 WASM_PARSER_FAIL_IF(!decodeSection(*result, section), "invalid section");
95 ASSERT(section != Section::Begin);
96 WASM_PARSER_FAIL_IF(!validateOrder(m_previousKnownSection, section), "invalid section order, ", m_previousKnownSection, " followed by ", section);
97 m_section = section;
98 if (isKnownSection(section))
99 m_previousKnownSection = section;
100 return State::SectionSize;
101}
102
103auto StreamingParser::parseSectionSize(uint32_t sectionLength) -> State
104{
105 m_sectionLength = sectionLength;
106 if (m_section == Section::Code)
107 return State::CodeSectionSize;
108 return State::SectionPayload;
109}
110
111auto StreamingParser::parseCodeSectionSize(uint32_t functionCount) -> State
112{
113 m_functionCount = functionCount;
114 m_functionIndex = 0;
115 m_codeOffset = m_offset;
116
117 WASM_PARSER_FAIL_IF(functionCount == std::numeric_limits<uint32_t>::max(), "Code section's count is too big ", functionCount);
118 WASM_PARSER_FAIL_IF(functionCount != m_info->functions.size(), "Code section count ", functionCount, " exceeds the declared number of functions ", m_info->functions.size());
119
120 if (m_functionIndex == m_functionCount) {
121 WASM_PARSER_FAIL_IF((m_codeOffset + m_sectionLength) != m_nextOffset, "parsing ended before the end of ", m_section, " section");
122 return State::SectionID;
123 }
124 return State::FunctionSize;
125}
126
127auto StreamingParser::parseFunctionSize(uint32_t functionSize) -> State
128{
129 m_functionSize = functionSize;
130 WASM_PARSER_FAIL_IF(functionSize > maxFunctionSize, "Code function's size ", functionSize, " is too big");
131 return State::FunctionPayload;
132}
133
134auto StreamingParser::parseFunctionPayload(Vector<uint8_t>&& data) -> State
135{
136 auto& function = m_info->functions[m_functionIndex];
137 function.start = m_offset;
138 function.end = m_offset + m_functionSize;
139 function.data = WTFMove(data);
140 dataLogLnIf(WasmStreamingParserInternal::verbose, "Processing function starting at: ", function.start, " and ending at: ", function.end);
141 ++m_functionIndex;
142 if (m_functionIndex == m_functionCount) {
143 WASM_PARSER_FAIL_IF((m_codeOffset + m_sectionLength) != (m_offset + m_functionSize), "parsing ended before the end of ", m_section, " section");
144 return State::SectionID;
145 }
146 return State::FunctionSize;
147}
148
149auto StreamingParser::parseSectionPayload(Vector<uint8_t>&& data) -> State
150{
151 SectionParser parser(data.data(), data.size(), m_offset, m_info.get());
152 switch (m_section) {
153#define WASM_SECTION_PARSE(NAME, ID, DESCRIPTION) \
154 case Section::NAME: { \
155 WASM_STREAMING_PARSER_FAIL_IF_HELPER_FAILS(parser.parse ## NAME()); \
156 break; \
157 }
158 FOR_EACH_KNOWN_WASM_SECTION(WASM_SECTION_PARSE)
159#undef WASM_SECTION_PARSE
160
161 case Section::Custom: {
162 WASM_STREAMING_PARSER_FAIL_IF_HELPER_FAILS(parser.parseCustom());
163 break;
164 }
165
166 case Section::Begin: {
167 RELEASE_ASSERT_NOT_REACHED();
168 break;
169 }
170 }
171
172 WASM_PARSER_FAIL_IF(parser.length() != parser.offset(), "parsing ended before the end of ", m_section, " section");
173
174 return State::SectionID;
175}
176
177auto StreamingParser::consume(const uint8_t* bytes, size_t bytesSize, size_t& offsetInBytes, size_t requiredSize) -> Optional<Vector<uint8_t>>
178{
179 if (m_remaining.size() == requiredSize) {
180 Vector<uint8_t> result = WTFMove(m_remaining);
181 m_nextOffset += requiredSize;
182 return result;
183 }
184
185 if (m_remaining.size() > requiredSize) {
186 Vector<uint8_t> result(requiredSize);
187 memcpy(result.data(), m_remaining.data(), requiredSize);
188 m_remaining.remove(0, requiredSize);
189 m_nextOffset += requiredSize;
190 return result;
191 }
192
193 ASSERT(m_remaining.size() < requiredSize);
194 size_t bytesRemainingSize = bytesSize - offsetInBytes;
195 size_t totalDataSize = m_remaining.size() + bytesRemainingSize;
196 if (totalDataSize < requiredSize) {
197 m_remaining.append(bytes + offsetInBytes, bytesRemainingSize);
198 offsetInBytes = bytesSize;
199 return WTF::nullopt;
200 }
201
202 size_t usedSize = requiredSize - m_remaining.size();
203 m_remaining.append(bytes + offsetInBytes, usedSize);
204 offsetInBytes += usedSize;
205 Vector<uint8_t> result = WTFMove(m_remaining);
206 m_nextOffset += requiredSize;
207 return result;
208}
209
210auto StreamingParser::consumeVarUInt32(const uint8_t* bytes, size_t bytesSize, size_t& offsetInBytes, IsEndOfStream isEndOfStream) -> Expected<uint32_t, State>
211{
212 constexpr size_t maxSize = WTF::LEBDecoder::maxByteLength<uint32_t>();
213 size_t bytesRemainingSize = bytesSize - offsetInBytes;
214 size_t totalDataSize = m_remaining.size() + bytesRemainingSize;
215 if (m_remaining.size() >= maxSize) {
216 // Do nothing.
217 } else if (totalDataSize >= maxSize) {
218 size_t usedSize = maxSize - m_remaining.size();
219 m_remaining.append(bytes + offsetInBytes, usedSize);
220 offsetInBytes += usedSize;
221 } else {
222 m_remaining.append(bytes + offsetInBytes, bytesRemainingSize);
223 offsetInBytes += bytesRemainingSize;
224 // If the given bytes are the end of the stream, we try to parse VarUInt32
225 // with the current remaining data since VarUInt32 may not require `maxSize`.
226 if (isEndOfStream == IsEndOfStream::No)
227 return makeUnexpected(m_state);
228 }
229
230 size_t offset = 0;
231 uint32_t result = 0;
232 if (!WTF::LEBDecoder::decodeUInt32(m_remaining.data(), m_remaining.size(), offset, result))
233 return makeUnexpected(State::FatalError);
234 size_t consumedSize = offset;
235 m_remaining.remove(0, consumedSize);
236 m_nextOffset += consumedSize;
237 return result;
238}
239
240auto StreamingParser::addBytes(const uint8_t* bytes, size_t bytesSize, IsEndOfStream isEndOfStream) -> State
241{
242 if (m_state == State::FatalError)
243 return m_state;
244
245 m_totalSize += bytesSize;
246 if (UNLIKELY(m_totalSize.hasOverflowed() || m_totalSize.unsafeGet() > maxModuleSize)) {
247 m_state = fail("module size is too large, maximum ", maxModuleSize);
248 return m_state;
249 }
250
251 if (UNLIKELY(Options::useEagerWebAssemblyModuleHashing()))
252 m_hasher.addBytes(bytes, bytesSize);
253
254 size_t offsetInBytes = 0;
255 while (true) {
256 ASSERT(offsetInBytes <= bytesSize);
257 switch (m_state) {
258 case State::ModuleHeader: {
259 auto result = consume(bytes, bytesSize, offsetInBytes, moduleHeaderSize);
260 if (!result)
261 return m_state;
262 m_state = parseModuleHeader(WTFMove(*result));
263 break;
264 }
265
266 case State::SectionID: {
267 auto result = consume(bytes, bytesSize, offsetInBytes, sectionIDSize);
268 if (!result)
269 return m_state;
270 m_state = parseSectionID(WTFMove(*result));
271 break;
272 }
273
274 case State::SectionSize: {
275 auto result = consumeVarUInt32(bytes, bytesSize, offsetInBytes, isEndOfStream);
276 if (!result) {
277 if (result.error() == State::FatalError)
278 m_state = failOnState(m_state);
279 else
280 m_state = result.error();
281 return m_state;
282 }
283 m_state = parseSectionSize(*result);
284 break;
285 }
286
287 case State::SectionPayload: {
288 auto result = consume(bytes, bytesSize, offsetInBytes, m_sectionLength);
289 if (!result)
290 return m_state;
291 m_state = parseSectionPayload(WTFMove(*result));
292 break;
293 }
294
295 case State::CodeSectionSize: {
296 auto result = consumeVarUInt32(bytes, bytesSize, offsetInBytes, isEndOfStream);
297 if (!result) {
298 if (result.error() == State::FatalError)
299 m_state = failOnState(m_state);
300 else
301 m_state = result.error();
302 return m_state;
303 }
304 m_state = parseCodeSectionSize(*result);
305 break;
306 }
307
308 case State::FunctionSize: {
309 auto result = consumeVarUInt32(bytes, bytesSize, offsetInBytes, isEndOfStream);
310 if (!result) {
311 if (result.error() == State::FatalError)
312 m_state = failOnState(m_state);
313 else
314 m_state = result.error();
315 return m_state;
316 }
317 m_state = parseFunctionSize(*result);
318 break;
319 }
320
321 case State::FunctionPayload: {
322 auto result = consume(bytes, bytesSize, offsetInBytes, m_functionSize);
323 if (!result)
324 return m_state;
325 m_state = parseFunctionPayload(WTFMove(*result));
326 break;
327 }
328
329 case State::Finished:
330 case State::FatalError:
331 return m_state;
332 }
333
334 m_offset = m_nextOffset;
335 }
336}
337
338auto StreamingParser::failOnState(State) -> State
339{
340 switch (m_state) {
341 case State::ModuleHeader:
342 return fail("expected a module of at least ", moduleHeaderSize, " bytes");
343 case State::SectionID:
344 return fail("can't get section byte");
345 case State::SectionSize:
346 return fail("can't get ", m_section, " section's length");
347 case State::SectionPayload:
348 return fail(m_section, " section of size ", m_sectionLength, " would overflow Module's size");
349 case State::CodeSectionSize:
350 return fail("can't get Code section's count");
351 case State::FunctionSize:
352 return fail("can't get ", m_functionIndex, "th Code function's size");
353 case State::FunctionPayload:
354 return fail("Code function's size ", m_functionSize, " exceeds the module's remaining size");
355 case State::Finished:
356 case State::FatalError:
357 return m_state;
358 }
359 return m_state;
360}
361
362auto StreamingParser::finalize() -> State
363{
364 addBytes(nullptr, 0, IsEndOfStream::Yes);
365 switch (m_state) {
366 case State::ModuleHeader:
367 case State::SectionSize:
368 case State::SectionPayload:
369 case State::CodeSectionSize:
370 case State::FunctionSize:
371 case State::FunctionPayload:
372 m_state = failOnState(m_state);
373 break;
374
375 case State::Finished:
376 case State::FatalError:
377 break;
378
379 case State::SectionID:
380 if (m_remaining.isEmpty()) {
381 if (UNLIKELY(Options::useEagerWebAssemblyModuleHashing()))
382 m_info->nameSection->setHash(m_hasher.computeHexDigest());
383 m_state = State::Finished;
384 } else
385 m_state = failOnState(State::SectionID);
386 break;
387 }
388 return m_state;
389}
390
391} } // namespace JSC::Wasm
392
393#endif // ENABLE(WEBASSEMBLY)
394