1 of 27

JavaScript: The Bad Parts

A (biased) JSVM engineers’ perspective

Benedikt Meurer

@bmeurer

2 of 27

Disclaimer

“I don’t do JavaScript!”

3 of 27

with statement

4 of 27

with statement

const o = {� x: 1,� y: 2�};

with (o) {� x = x + 1;�}

// o.x is 2 now

with binds object properties

to (locally) scoped names, looks like a handy feature!

5 of 27

6 of 27

with statement

class A {� constructor() { this.x = 0; }� bar() { return "Bar"; }

}��function foo() { return 1; }��const a = new A;�with (a) {� x = foo();�}

// a.x is 1 now

7 of 27

with statement

class A {� constructor() { this.x = 0; }

bar() { return "Bar"; }�}��function foo() { return 1; }��const a = new A;�with (a) {� x = foo();�}

// a.x is 1 now

class A {� constructor() { this.x = 0; }� foo() { return "Bar"; }�}��function foo() { return 1; }��const a = new A;�with (a) {� x = foo();�}

// a.x is "Bar" now

8 of 27

with statement

var keys = [];�with (Array.prototype) {� keys.push('foo');�}

  • Code like this was found in popular JavaScript libraries during the ES2015 development
  • Broke with Array.prototype.keys

9 of 27

10 of 27

with statement

Object.keys(Array.prototype[Symbol.unscopables]);�// ['copyWithin', 'entries', 'fill', 'find', 'findIndex', 'keys']��var keys = [];�with (Array.prototype) {� keys.push('foo');�}

“The Array.prototype[Symbol.unscopables] property contains property names that were not included in the ECMAScript standard prior to the ES2015 version. These properties are excluded from with statement bindings.”

developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/@@unscopables

11 of 27

with statement

  • Not compositional, breaks any kind of (local) reasoning
  • Makes it difficult to advance the language
  • Impossible to optimize for modern VMs

12 of 27

Built-in subclassing

13 of 27

Array subclassing

class MyArray extends Array { }��const a = new MyArray(1, 2, 3);��const b = a.filter(x => x > 1);

// Now b is also an instance of MyArray with [2, 3]

14 of 27

Array subclassing

15 of 27

Array subclassing

16 of 27

Sample Array[@@species] bug (CVE-2016-7200, Edge)

class Dummy {� // Always constructs a non-empty Array instance

constructor() { return [1, 2, 3]; }�}��class MyArray extends Array {� static get [Symbol.species]() { return Dummy; }�}��var a = new MyArray({}, [], "natalie", 7, 7, 7, 7, 7);�var o = a.filter(i => true); // Memory corruption!

17 of 27

Array[@@species] vulnerabilities

  • CVE-2017-5030: Out-of-bounds read in V8 Array.concat (Chrome)
  • CVE-2017-8634: Overflow in Array.concat (Edge)
  • CVE-2017-7064: appendMemcpy uninitialized memory copy (Safari)
  • CVE-2016-7190: Heap Overflow in Array.map (Edge)
  • CVE-2016-7200: Heap Overflow in Array.filter (Edge)
  • CVE-2017-0134: Overflow in Array.concat (Edge)
  • crbug.com/725865: Array Species Optimization Issue (Chrome)

18 of 27

Array indexed accessor bugs with Array subclassing

  • PZ 1230: uninitialized memory reference in arrayProtoFuncSplice (Safari)
  • CVE-2016-1646: v8 Array.concat OOB access (Chrome)
  • CVE-2016-1677: type confusion lead to information leak in decodeURI (Chrome)
  • CVE-2017-0141: memory corruption in Array.reverse (Edge)
  • CVE-2017-2447: Out-of-bounds read when calling bound function (Safari)
  • CVE-2017-6980: arrayProtoFuncSplice doesn't initialize all indices (Safari)
  • CVE-2017-7005: JSGlobalObject::haveABadTime causes type confusion (Safari)
  • CVE-2017-6984: heap buffer overflow in Intl.getCanonicalLocales (Safari)

19 of 27

Array subclassing security

  • Violates developer expectation by adding call to user code in new location
    • Has side effects
  • Modified functions were generally written years ago
  • “Array[@@species], Array Index Accessors and Security”�by Natalie Silvanovich, Project Zero, goo.gl/QsrA8V

20 of 27

Performance impact of Promise subclassing

21 of 27

Temporal dead zone

vs.�Transpilers

22 of 27

Temporal dead zone

“In ECMAScript 6, accessing a let or const variable before its declaration (within its scope) causes a ReferenceError. The time span when that happens, between the creation of a variable’s binding and its declaration, is called the temporal dead zone.”

-- Axel Rauschmayer

2ality.com/2015/10/why-tdz.html

23 of 27

RxJS Observable example

forEach(next: (value: T) => void, PromiseCtor?: typeof Promise): Promise<void> {� return new PromiseCtor<void>((resolve, reject) => {� const subscription = this.subscribe(value => {� if (subscription) {� try {� next(value);� } catch (err) {� reject(err);� subscription.unsubscribe();� }� } else {� next(value);� }� }, reject, resolve);� });�}

subscription might be accessed

before the assignment happens

=> ReferenceError

24 of 27

RxJS Observable example

forEach(next: (value: T) => void, PromiseCtor?: typeof Promise): Promise<void> {� return new PromiseCtor<void>((resolve, reject) => {� const subscription = this.subscribe(value => {� if (subscription) {� try {� next(value);� } catch (err) {� reject(err);� subscription.unsubscribe();� }� } else {� next(value);� }� }, reject, resolve);� });�}

TypeScript was hiding the bug because it turns the const into

var during transpilation

25 of 27

RxJS Observable example

26 of 27

Simplified example

function foo(c) { return c(); }��const x = foo(() => x ? 1 : 2 );��// ReferenceError: x is not defined

function foo(c) { return c(); }��var x = foo(function() {� return x ? 1 : 2;�});��// x is assigned the value 2

27 of 27

Conclusion

  • JavaScript is really cool
  • JavaScript is pretty bad
  • Modern JSVMs are challenging
  • Avoid fancy concepts or make sure you understand them first
  • Don’t trust transpilers blindly
  • NEVER EVER USE with!