Hardening the XS JavaScript Engine


Updated: April 10, 2022

The XS JavaScript engine is at the core of everything Moddable does. The Moddable SDK is a standards-based runtime environment for embedded systems built on XS. Organizations around the world use the Moddable SDK as foundation for consumer and industrial IoT products. Every project Moddable delivers for its clients has XS as its starting point. Moddable is committed to ensuring that XS is as robust as possible so that projects built on XS are as reliable and secure as possible.

It has proven impossible for any organization to identify all the issues in any JavaScript engine. Even the JavaScript engines used in major web browsers regularly receive reports of issues from third party organizations, researchers, and independent developers. This article collects together information to assist those that want to contribute to discovering issues in XS. Moddable is committed to working with experts to identify and resolve issues in XS, particularly those that result in language conformance issues and security vulnerabilities.

This article is organized into the following sections:

  • Technical Introduction to XS
  • Testing Advice
  • Acknowledgements

    Technical Introduction to XS

    XS supports the same industry standard JavaScript language used in web browsers. While the JavaScript engines in a browser are optimized for speed, XS is optimized to minimize resource use – especially RAM – so that it can execute complex scripts on resource constrained hardware. The different focus of XS makes its implementation different too.

    XS does not aim to run all JavaScript that is supported in a web browser; it does aim to run JavaScript as defined by the language standard. XS takes advantage of some relatively obscure options in the JavaScript language specification. For example, it does not provide the source code for JavaScript functions via Function.prototype.toString. It also does not implement all of Annex B as this is only required in web browsers.

    The XS documentation directory in the Moddable SDK contains extensive information about many aspects of XS. It is a great resource for learning more about the implementation of XS and how XS is used in products. A good starting point is the presentation given to Ecma TC39 about the history, motivations, and data structures of XS.

    XS is implemented in C for maximum portability. There is no C++ and no assembly language. It does use some GCC built-ins, when available, to optimize certain operations.

    XS compiles scripts to byte code which is then executed by a run loop. There is no JIT or other dynamic optimization. This greatly simplifies the engine implementation thereby eliminating many sources of issues.

    XS stores strings internally in UTF-8 format. The use of UTF-8 generally reduces memory use compared to the UTF-16 format assumed by the JavaScript language specification. It also simplifies interacts with the many native APIs which operate on UTF-8 strings directly. This has caused some conformance issues. All strings entering into XS must be checked to ensure they are valid UTF-8.

    XS stores JavaScript numbers internally as either 32-bit signed integer values and 64-bit double floating point values. The language standard defines numbers as doubles: the use of integers is an optimization for embedded devices that lack floating point hardware. The internal storage format of number is not generally observable by scripts, but has been a source of conformance issues in some edge cases.

    XS has its own Regular Expression engine. This provides a much smaller code footprint than other engines while also using relatively little memory.

    XS uses a mark and sweep garbage collector for simplicity. This works well for the small memory size of embedded systems. There is no reference counting and no generational collecting. The garbage collector is a so-called "stop the world" collector, so no other activity occurs in the heap when collecting. The garbage collector combines all free blocks by compacting the heap. This maximizes memory available to scripts by eliminating memory fragmentation. This behavior of the garbage collector is not observable by scripts. The compaction feature, however, has been a source of vulnerabilities.

    XS extends some of the built-in JavaScript objects with additional methods. For example, it adds methods for integer division (Math.idiv). The language specification allows these extensions as long as a script can delete them without breaking the behavior of standard language features.


    Testing Advice

    Getting started with testing XS can be a challenge. The following sections provide advice to help. The guidance is based on experience testing XS and a review of the issues already reported. It is likely that there are approaches to testing and areas of testing that remain to be identified.

    xst – the XS test tool

    xst is a command line tool for running JavaScript tests on XS. It is available for macOS, Windows, and Linux. Moddable uses xst to run the test262 JavaScript language conformance test suite on XS. Details on how to build xst and how to use it to run test262 are provided here.

    Building and running xst yourself is the best way to be sure you are running the latest version. Moddable also provides a binary distribution of xst through jvsu, which is particularly useful for comparing conformance test results across several engines.

    Resource Exhaustion and Implementation Limits

    Tests eventually encounter resource exhaustion, such as out of memory, and implementation limits, such as strings being limited to 2 GB. These are generally not issues, but how they are handled by XS may be. If XS fails to detect a limit has been reached, it can lead to problems.

    When XS detects resource exhaustion or that it is has reached an implementation limit, it calls the fxAbort function which determines what happens next. On embedded systems, fxAbort generally restarts the system to guarantee it returns to a known good state. The implementation of fxAbort in xst throws a JavaScript exception with a RangeError when memory is exhausted, the JavaScript stack would overflow, or the C stack is close to overflowing. Some test suites require this behavior for certain tests to pass. This behavior also provides a clean exit which helps when performing automated testing.

    Once fxAbort is called, XS has done its job. Any issues that occurs after that are because of choices made in the host implementation of fxAbort. If you encounter such problems, please report them. If possible, they will be fixed to ensure a smoother testing experience. However, they are not generally critical issues as they impact testing, not production systems. When building with ASAN enabled, XS defines `mxASANStackMargin` to give ASAN additional native stack space when `fxAbort` is called.

    Conformance Testing

    The goal of conformance testing is to ensure that XS implements all the behaviors of JavaScript as defined by the language standard. This is essential to ensuring that JavaScript code runs consistently across all engines and environments. To ensure conformance, Moddable regularly runs XS against the test262 tests for language and built-ins. You can run the tests yourself using xst by following the instructions in the XS Conformance document. XS does not pass 100% of the tests (no engine does!). The conformance document has a section that briefly explains the failures. This is a helpful resource to avoid reporting known issues.

    One common source of conformance issues are the limits built into the language. For example, very large and very small numeric values have been a source of conformance issues.

    Older features of the language sometimes are sometimes not as well covered by test262 as newer features, and therefore issues can be overlooked.

    JavaScript has many dynamic features which cause a function to be invoked during the execution of another function. This happens commonly with type coercion. If the callback function changes the object being operated on, it can lead to issues. While the expected behavior is generally well documented in Ecma-262, implementing that correctly has proven to be challenging for all JavaScript engines.

    The jsvu tool is valuable for conformance testing by making it easy to run a single script across multiple JavaScript engines when used with eshost-cli. If you believe you have identified a conformance issue with XS, this is a quick way to see how it compares with other engines. Developers exploring issues with other engines using eshost-cli have noticed issues with XS and reported those.

    Vulnerability Testing

    The goal of vulnerability testing is to identify places where the implementation performs unsafe operations, such as reading or writing memory that should be inaccessible to a script. Vulnerabilities can lead to incorrect results, unpredictable execution, crashes, and security breaches. Testing for vulnerabilities requires both technical skill and creativity. This section describes some details about XS that may be useful in looking for vulnerabilities.

    Moddable is committed to resolving all reported vulnerabilities. That said, many vulnerabilities do not have an obvious path to a security exploit. A vulnerability that leads to a crash can force a device to reboot but does not necessarily not open the device to being taken over. Only a handful of reported vulnerabilities could be easily exploited. Moddable is particularly interested in reports that show how a vulnerability could be exploited.

    xst is the primary tool for vulnerability testing. Moddable recommends using a debug build of xst as this already has the AddressSanitizer (ASAN) enabled on macOS and Linux builds.

    The compacting feature of the memory manager can lead to bugs. If a relocatable pointer is not refreshed after the heap is compacted, the engine code may use a stale address. This can lead to read/write access to unintended memory. These problems are difficult to reproduce because they depend on when the garbage collector runs.

    Because XS has its own sub-allocator, ASAN cannot detect all out-of-bounds memory accesses. The XS memory manager has a mode where it uses malloc instead of the XS sub-allocator. Set mxNoChunks to 1 in xsMemory.c to enable this mode. As of XS 11.6, mxNoChunks implements a technique designed to detect use of dangling pointers. Each time the garbage collector runs, every chunk is moved to a new address by allocating a new memory block with malloc, copying the data, and releasing the previous block. This takes time but allows many more dangling pointers to be detected by ASAN. The effectiveness of this technique can be increased by modifying XS to garbage collect more frequently as the mxStress option does.

    Because it targets resource constrained devices, XS uses 32-bit integers for buffer sizes. These values can overflow, leading to crashes and out-of-bounds memory access. XS uses fxAddChunkSizes and fxMultiplyChunkSizes functions internally to detect integer overflow when calculating buffer sizes and offsets. These functions work reliably, but when they are not used when needed, issues can appear.

    XS contains three separate parsers: the JavaScript language itself, JSON (a subset of JavaScript that more-or-less requires a separate parser for conformance), and Regular Expressions. Parsers are a well known source of vulnerabilities. Native stack overflows are one potential parser vulnerability which XS only partially defends against. Issue reports here are still expected.

    When the execution of one JavaScript functions invokes another JavaScript function, there is a great deal of potential for trouble. For example, if the comparison function passed to sort an Array shrinks the length of the array, XS must not access elements that are no longer part of the array. These malicious callbacks are a common source of vulnerabilities in JavaScript engines.

    Fuzzing Support

    The xst tool can be used for fuzz testing. It may be necessary to modify xst to implement the requirements of a particular fuzzing engine.

    xst implements support for the Fuzzilli fuzzer published by Google Project Zero. This support is active when xst is built with the FUZZILLI flag set. The documentation for the XS Profile in the Fuzzilli repository explains how to build xst for use with Fuzzilli and how to invoke it.


    Reporting Issues

    Issues may be reported directly to the Moddable repository on GitHub. At this time, issues may be reported publicly. If you prefer to report the issue privately, please send it by email to info@moddable.com. If you are unsure if a behavior is an issue or not, please report it. A good way to do that is using Discussions on our GitHub repository.

    It is a lot of work to find an issue. It is important to report the issue as clearly as practical so that it can be quickly assessed and resolved. Here are some things you can do in your issue report to help. These are guidelines, not requirements: do the best you can.

    • A concise description of the problem
    • A simple proof-of-concept snippet of code that demonstrates the issue. (Direct output of complex code from fuzzers can slow down verification the report!)
    • If possible, write the proof-of-concept as a test262 test. This helps with our goal of having regression tests for all reported issues.
    • A citation of the relevant specification text from Ecma-262, if it is a conformance issue.
    • The XS version number and/or commit used
    • Any other details that might help to understand the issue

    Acknowledgements

    Moddable greatly appreciates the efforts of everyone who has reported issues about XS. The following sections list those individuals together with links to their reports. All reports here have been confirmed as valid, and most have been fixed. If there are any errors or omissions, please let us know.

    Conformance Reports

    bakkot (7)
    82 For-in prints deleted keys
    87 Numeric keys larger than 2**24 - 2*20 + 1 are given as the empty string
    115 Correctness issue when shadowing parameters which are mutated
    155 Object rest incorrectly accepts complex assignment targets
    285 JSON.parse passes wrong argument to reviver in base case when deleting properties
    678 new BigInt64Array(new Int32Array(0)) should throw
    704 correctness: -(1 << -1) evaluates to -2147483648, but should evaluate to 2147483648
    dckc (6)
    258 Symbol.keyFor() as in @agoric/marshal gives undefined
    259 cannot coerce undefined to object! in template literal
    565 SyntaxError from const { default: greeting } = { default: 1 }
    577 Map get fails on BigInt key
    621 Symbol.for('bar').toString() missing 'bar' (missing [[Description]] value)
    652 bigint string form is codepoint rather than numeral in OS210603
    devsnek (2)
    297 fxIsFunction gives wrong result for revoked proxy
    464 iteratorRecord.[[Done]] is not respected
    dtex (1)
    282 Constructor that returns an async IIFE fails
    gibson042 (6)
    665 code points above Latin1 are not recognized as white space
    726 ( ,) is incorrectly treated as valid
    876 function name is set incorrectly on a method for a property key that is a registered symbol
    879 Change Date.parse to round milliseconds down
    885 ToNumber incorrectly accepts "INFINITY"
    886 TypedArrays incorrectly write to "NaN" properties
    hax (1)
    284 thisArg of accessors in strict mode should not be coercion ToObject
    jugglinmike (4)
    315 XS allows trailing comma in Expressions
    316 XS misinterprets await in arrow functions
    328 XS misinterprets combinations of AwaitExpression and async arrow functions
    335 incorrectly interprets zero when used as a multiplication factor
    littledan (1)
    13 Variable-length RegExp lookbehind does not work
    lll000111 (1)
    140 "cannot coerce undefined to object" - Spread into array syntax issues
    mathiasbynens (3)
    112 JSON.stringify correctness issue
    137 JSON.parse('-0') should evaluate to -0
    674 Number.prototype.toLocaleString complains about radix
    mhofman (1)
    727 TypeError: super: no constructor when prototype.constructor deleted
    mhofman (1)
    818 Async generator not closed when yielding rejected promise
    michaelfig (1)
    322 async function: silent swallowing of the "override mistake"
    mzgoddard (1)
    330 Misinterprets arrow functions without parentheses in many expressions
    Reinose (19)
    400 Semantics of PropertyDefinitionEvaluation in Object Initializer
    401 Array length property is 0 if it is defined with specific syntax
    403 Optional chains are always evaluated
    404 Array.prototype.toString behaves differently against the specification
    405 Map iterator and Set iterator have excessive properties
    406 String.prototype.split behaves differently against the specification
    407 Number.prototype.toString radix violation does not covered
    409 For-in statement over a string does not work
    410 Cannot parse Destructuring assignment - ObjectAssignmentPattern
    411 Array.prototype.slice does not work as specification
    412 Switch statement contains class declaration is not parsed
    413 new.target in generator function does not work
    414 Destructuring Assignment - DestructuringAssignmentTarget early error does not occur
    415 do-while statement parsing error
    416 Cannot parse Destructuring assignment - ArrayAssignmentPattern
    417 Function.prototype.bind has excessive properties
    418 Cannot parse Destructuring assignment when undefined exists
    420 Generator Object has unwanted Symbol Property
    421 Property descriptor for set/get is different
    rwaldron (1)
    451 Valid switch + nested class syntax producing a SyntaxError exception
    tevador (5)
    58 Incorrect string representation of 'NaN'
    59 Numeric literals not parsed according to specification
    60 JSON.stringify incorrectly considers object cyclic
    62 Integer overflow
    64 Eval preventing const declaration in the caller scope
    YaoHouyou (2)
    526 The syntaxError about illegal token '}' is not thrown as expected
    556 An issue about calling Object.prototype.hasOwnProperty without parameters

    Vulnerability Reports

    bakkot (1)
    680 SIGBUS when overloading regexp.prototype.exec
    dckc (2)
    567 unbounded heap growth crashes xst, xsnap (SIGSEGV)
    642 print(5n ** 5n ** 6n) crashes xst (in GC?)
    devsnek (1)
    113 Array#sort aborts when elements are deleted
    eternalsakura (9)
    735 AddressSanitizer: FPE on unknown address 0x000000773e98
    738 AddressSanitizer: Null pointer dereference in fx_Array_prototype_slice
    739 stack-overflow in xs/sources/xsType.c:518 in fxOrdinaryGetProperty
    742 AddressSanitizer: memcpy-param-overlap: memory ranges overlap in fx_Array_prototype_slice
    743 AddressSanitizer: stack-overflow /home/sakura/moddable/xs/sources/xsMemory.c:651 in fxMarkInstance
    744 AddressSanitizer: SEGV xs/sources/xsNumber.c:457:16 in fx_Number_prototype_toString
    747 AddressSanitizer: stack-overflow xs/sources/xsMemory.c:950 in fxMarkValue
    748 Null pointer dereference in fx_Function_prototype_hasInstance
    749 AddressSanitizer: SEGV in xs/sources/xsDataView.c:2709:8 in fxFloat32Getter
    gibson042 (1)
    698 SIGBUS when overloading regexp.prototype.exec
    hope-fly (8)
    750 SEGV xs/sources/xsProxy.c:506 in fxProxyGetPrototype
    751 Heap-buffer-overflow in __libc_start_main
    759 Heap-buf-overflow (/usr/local/bin/xst+0x4ec9ab) in __asan_memcpy
    760 A weird stack overflow when compiled with ASAN
    766 SEGV xs/sources/xsArray.c:2237:7 in fx_Array_prototype_sort
    768 SEGV (/usr/local/bin/xst+0xdfee5f) in _fini
    769 Negative-size-param (/usr/local/bin/xst+0x4ed5ec) in __asan_memmove
    774 SEGV xs/sources/xsDataView.c:559:24 in fx_ArrayBuffer_prototype_concat
    jessysaurusrex (17)
    779 AddressSanitizer: stack-overflow xsMemory.c:950 in fxMarkValue
    780 AddressSanitizer: BUS xsRun.c:772 in fxRunID
    781 AddressSanitizer: SEGV xsArray.c:523 in fxGetArrayLimit
    782 AddressSanitizer: SEGV xsFunction.c:546 in fx_Function_prototype_hasInstance
    783 AddressSanitizer: SEGV xsArray.c:2577 in fx_ArrayIterator_prototype_next
    784 AddressSanitizer: SEGV xsMemory.c:1711 in fxSweepValue
    785 AddressSanitizer: heap-buffer-overflow xsMemory.c:955 in fxMarkValue
    788 SEGV xsRun.c:4140 in fxRunID
    789 heap-buffer-overflow xsProperty.c:300 in fxGetIndexProperty
    790 SEGV xsMemory.c:1692 in fxSweepValue
    792 SEGV xsAPI.c:948 in fxGetAll
    795 SEGV xsObject.c:492 in fx_Object_copy
    797 SEGV xsMemory.c:947 in fxMarkValue
    798 stack-overflow xsMemory.c:945 in fxMarkValue
    805 stack-overflow xsAPI.c in fxThrowMessage
    807 BUS xsRun.c:774 in fxRunID
    815 stack-overflow xsMemory.c:640 in fxMarkInstance
    kvenux (21)
    431 Heap buffer overflow at moddable/xs/sources/xsDebug.c:783
    432 Heap buffer overflow at moddable/xs/sources/xsSyntaxical.c:3562
    440 SEGV at moddable/xs/sources/xsCommon.c:916
    441 SEGV at moddable/xs/sources/xsProxy.c:171
    442 SEGV at moddable/xs/sources/xsSyntaxical.c:3419
    446 SEGV at moddable/xs/sources/xsProxy.c:171 (similar with #441)
    447 SEGV at moddable/xs/sources/xsSyntaxical.c:3620
    448 SEGV at moddable/xs/sources/xsRun.c:697
    449 SEGV at moddable/xs/sources/xsSyntaxical.c:3277
    450 SEGV at moddable/xs/sources/xsFunction.c:389
    452 SEGV at moddable/xs/sources/xsArray.c:2139
    453 SEGV at moddable/xs/sources/xsArray.c:2414
    454 Heap buffer overflow at moddable/xs/sources/xsLexical.c:760
    460 SEGV at moddable/xs/sources/xsString.c:1610
    461 SEGV at moddable/xs/sources/xsSyntaxical.c:3583
    462 SEGV at moddable/xs/sources/xsSyntaxical.c:3148
    463 SEGV at moddable/xs/sources/xsSyntaxical.c:3499
    469 SEGV at moddable/xs/sources/xsPromise.c:431
    483 heap-buffer-overflow at xs/sources/xsBigInt.c:1354
    484 SEGV at xs/sources/xsAll.c:161
    485 SEGV at /xs/sources/xsBigInt.c:1182
    MKGaru (1)
    540 Variables declared with let are treated as const
    natashenka (2)
    107 Memory Corruption in Array.prototype.map
    123 Memory Corruption in Array.prototype.copyWithin
    rain6851 (16)
    353 NULL pointer dereference
    364 stack overflow
    374 Access of Uninitialized Pointer
    375 double free
    376 free invalid pointer
    377 heap overflow
    378 null pointer dereference
    379 null pointer dereference
    580 heap-buffer-overflow(fx_ArrayBuffer)
    581 heap-buffer-overflow(fx_String_prototype_pad)
    582 heap-buffer-overflow(fx_String_prototype_repeat)
    583 heap-buffer-overflow(fxIDToString)
    585 over access(fxEnvironmentGetProperty)
    586 stack-overflow
    587 stack-overflow(fxBinaryExpressionNodeDistribute)
    635 heap-buffer-overflow(fx_RegExp_prototype_get_flags)

    Moddable's Vulnerability Disclosure Policy

    Moddable's vulnerability disclosure policy is based on the policy generated by disclose.io. Our intention in adopting the work of disclose.io is to give security researchers a policy that is both comprehensive and familiar. Because it is a widely used, the disclose.io policy has a broader scope than is strictly necessary for XS. Testing of XS should generally be limited to execution of a test environment such as xst.

    Introduction

    Moddable Tech, Inc. welcomes feedback from security researchers and the general public to help improve our security. If you believe you have discovered a vulnerability, privacy issue, exposed data, or other security issues in any of our assets, we want to hear from you. This policy outlines steps for reporting vulnerabilities to us, what we expect, and what you can expect from us.

    Systems in Scope

    This policy applies to any digital assets owned, operated, or maintained by Moddable Tech, Inc.

    Out of Scope

    This policy does not apply to assets or other equipment not owned by parties participating in this policy.

    Vulnerabilities discovered or suspected in out-of-scope systems should be reported to the appropriate vendor or applicable authority.

    Our Commitments

    When working with us, according to this policy, you can expect us to:

    • Respond to your report promptly, and work with you to understand and validate your report;
    • Strive to keep you informed about the progress of a vulnerability as it is processed;
    • Work to remediate discovered vulnerabilities in a timely manner, within our operational constraints; and
    • Extend Safe Harbor for your vulnerability research that is related to this policy.

    Our Expectations

    In participating in our vulnerability disclosure program in good faith, we ask that you:

    • Play by the rules, including following this policy and any other relevant agreements. If there is any inconsistency between this policy and any other applicable terms, the terms of this policy will prevail;
    • Report any vulnerability you’ve discovered promptly;
    • Avoid violating the privacy of others, disrupting our systems, destroying data, and/or harming user experience;
    • Use only the Official Channels to discuss vulnerability information with us;
    • Provide us a reasonable amount of time (at least 60 days from the initial report) to resolve the issue before you disclose it publicly;
    • Perform testing only on in-scope systems, and respect systems and activities which are out-of-scope;
    • If a vulnerability provides unintended access to data: Limit the amount of data you access to the minimum required for effectively demonstrating a Proof of Concept; and cease testing and submit a report immediately if you encounter any user data during testing, such as Personally Identifiable Information (PII), Personal Healthcare Information (PHI), credit card data, or proprietary information;
    • You should only interact with test accounts you own or with explicit permission from the account holder; and
    • Do not engage in extortion.

    Official Channels

    Please report security issues by creating a new issue report in the public Moddable SDK repository on GitHub. Issues may also be reported via email to info@moddable.com. Please provide all relevant information. The more details you provide, the easier it will be for us to triage and fix the issue.

    At this time, all issues may be reported publicly. This may change in the future.

    Safe Harbor

    When conducting vulnerability research, according to this policy, we consider this research conducted under this policy to be:

    • Authorized concerning any applicable anti-hacking laws, and we will not initiate or support legal action against you for accidental, good-faith violations of this policy;
    • Authorized concerning any relevant anti-circumvention laws, and we will not bring a claim against you for circumvention of technology controls;
    • Exempt from restrictions in our Terms of Service (TOS) and/or Acceptable Usage Policy (AUP) that would interfere with conducting security research, and we waive those restrictions on a limited basis; and
    • Lawful, helpful to the overall security of the Internet, and conducted in good faith.

    You are expected, as always, to comply with all applicable laws. If legal action is initiated by a third party against you and you have complied with this policy, we will take steps to make it known that your actions were conducted in compliance with this policy.

    If at any time you have concerns or are uncertain whether your security research is consistent with this policy, please submit a report through one of our Official Channels before going any further.

    Note that the Safe Harbor applies only to legal claims under the control of the organization participating in this policy, and that the policy does not bind independent third parties.