a sense of time for javascript and node - usenix · • new first-class asynchronous primitives...

44
COMPUTER SCIENCE -1- James C. Davis Eric R. Williamson Dongyoon Lee A Sense of Time for JavaScript and Node.js First-Class Timeouts as a Cure for Event Handler Poisoning

Upload: others

Post on 17-Jun-2020

18 views

Category:

Documents


0 download

TRANSCRIPT

Page 1: A Sense of Time for JavaScript and Node - USENIX · • New first-class asynchronous primitives like async/await and Promises make this possible • We only support global timeouts

COMPUTER SCIENCE- 1 -

James C. DavisEric R. Williamson

Dongyoon Lee

A Sense of Time for JavaScript and Node.js

First-Class Timeouts as a Cure forEvent Handler Poisoning

Page 2: A Sense of Time for JavaScript and Node - USENIX · • New first-class asynchronous primitives like async/await and Promises make this possible • We only support global timeouts

- 2 -

Attack: Event Handler PoisoningDefinitionAnalysis

Detect + recover: First-Class TimeoutsConceptPrototype

Engagement with the Node.js communityGuideCore APIs: Documentation and repairs

Contributions

Page 3: A Sense of Time for JavaScript and Node - USENIX · • New first-class asynchronous primitives like async/await and Promises make this possible • We only support global timeouts

- 3 -

7M+ developers (2017) 2x YoY760K+ modules (Aug. 2018) 2x YoY24B+ module downloads/month (July 2018) 12x YoY

Node.js: A JS framework for web services

Page 4: A Sense of Time for JavaScript and Node - USENIX · • New first-class asynchronous primitives like async/await and Promises make this possible • We only support global timeouts

- 4 -

Web server architectures

Page 5: A Sense of Time for JavaScript and Node - USENIX · • New first-class asynchronous primitives like async/await and Promises make this possible • We only support global timeouts

- 5 -

One Thread per Client Architecture (OTPCA)

• Each client gets its own worker thread• Multithreading enables scalability• Example:

ClientA

ResA

ResB

Thread pool (1000s of threads)

ReqA

ReqB

Dispatcher

…ClientB

Page 6: A Sense of Time for JavaScript and Node - USENIX · • New first-class asynchronous primitives like async/await and Promises make this possible • We only support global timeouts

- 6 -

Event-Driven Architecture (EDA)

• Clients multiplexed; shared threads reduce threading overhead• Cooperative multitasking via (1) Partitioning and (2) Offloading• Example:

… CBA1

CBB1

CBA3ResA

ResB TaskB1

……

Event loop(single-threaded)

Worker pool(k threads)

Task QEvent Q

Done Q

ReqB

ReqA

Page 7: A Sense of Time for JavaScript and Node - USENIX · • New first-class asynchronous primitives like async/await and Promises make this possible • We only support global timeouts

- 7 -

OTPCA

def serveFile(req):cont =readFile(req.file)

z = zip(cont)e = encrypt(z)return e

EDA

def serveFile(req):cont = awaitreadFile(req.file)

z = await zip(cont)e = await encrypt(z)return e

Server architecture dictates programming style

Preemptivemulti-taskingSynchronous

Cooperativemulti-taskingAsynchronous

Page 8: A Sense of Time for JavaScript and Node - USENIX · • New first-class asynchronous primitives like async/await and Promises make this possible • We only support global timeouts

- 8 -

Event Handler Poisoning Attacks(EHP)

Page 9: A Sense of Time for JavaScript and Node - USENIX · • New first-class asynchronous primitives like async/await and Promises make this possible • We only support global timeouts

- 9 -

The EDA gains efficiency, loses isolation

Architecture Threads # Threads Multi-taskingOTPCA Dedicated Thousands Preemptive

EDA Shared Tens Cooperative

EventHandlers=limitedresourceExhaustresourceà DoS

Page 10: A Sense of Time for JavaScript and Node - USENIX · • New first-class asynchronous primitives like async/await and Promises make this possible • We only support global timeouts

- 10 -

Behavior during EHP attack on the Event Loop

• Event loop is poisoned• Throughput drops to zero

……

Event loop(single-threaded)

Worker pool(k threads)

Task QEvent Q

Done Q

ReqA

ReqB CBA1

On the worker pool:k malicious requests

Page 11: A Sense of Time for JavaScript and Node - USENIX · • New first-class asynchronous primitives like async/await and Promises make this possible • We only support global timeouts

- 11 -

Vulnerable server

def serveFile(name):if name.match(/(\/.+)+$/):readFile(name).then(

...)

ReDoS

IO-DoSArbitraryfileread

Super-linearregex

Page 12: A Sense of Time for JavaScript and Node - USENIX · • New first-class asynchronous primitives like async/await and Promises make this possible • We only support global timeouts

- 12 -

ReDoS-based EHP attack

0

500

1000

1500

2000

2500

3000

0 1 2 3 4 5 6

Thro

ughp

ut (

reqs

/sec

)

Time (seconds)

Baseline REDOS

NodeCure REDOS

Inject malicious input during steady-state

TimeoutException delivered

Page 13: A Sense of Time for JavaScript and Node - USENIX · • New first-class asynchronous primitives like async/await and Promises make this possible • We only support global timeouts

- 13 -

0 50 100 150 200 250 300

Other(CWEs330,208,601,90,…)

ImproperAuthentication(CWE…

InformationExposure(CWE201)

MaliciousPackage(CWE506)

CommandInjection(CWE77)

DenialofService(CWE400)

ManintheMiddle(CWE300)

Cross-SiteScripting(CWE79)

DirectoryTraversal(CWE22)

EHPNotEHP

35% of NPM vulnerabilities enable EHP

115 ReDoS

266 IO-DoS

Page 14: A Sense of Time for JavaScript and Node - USENIX · • New first-class asynchronous primitives like async/await and Promises make this possible • We only support global timeouts

- 14 -

What should we do about EHP?

Page 15: A Sense of Time for JavaScript and Node - USENIX · • New first-class asynchronous primitives like async/await and Promises make this possible • We only support global timeouts

- 15 -

Idea• Heartbeat on each Event Handler• If any heartbeats fail, restart the server

Problems• Every connected client gets DoS’d• Repeat attacks

Naïve 1: Restart the server

Page 16: A Sense of Time for JavaScript and Node - USENIX · • New first-class asynchronous primitives like async/await and Promises make this possible • We only support global timeouts

- 16 -

Naïve 2: Prevent through partitioning

def sum(L):s = 0for n in L:

s += nreturn s

async def sum(L):s = 0until done:

s += <10 numbers>yield

return s

=

Short L

Long L

Long L

+Yield!Yield!

Page 17: A Sense of Time for JavaScript and Node - USENIX · • New first-class asynchronous primitives like async/await and Promises make this possible • We only support global timeouts

- 17 -

Only protects code under the application dev.’s controlNot modulesNot frameworkNot language

Good for algorithms – but how to meaningfully partition I/O?

Ongoing maintenance burden

Partitioning is partial and ad hoc

Page 18: A Sense of Time for JavaScript and Node - USENIX · • New first-class asynchronous primitives like async/await and Promises make this possible • We only support global timeouts

- 18 -

Our proposed solution:

First-Class Timeouts

Page 19: A Sense of Time for JavaScript and Node - USENIX · • New first-class asynchronous primitives like async/await and Promises make this possible • We only support global timeouts

- 19 -

AnalogyBuffer overflow à Out of bounds exceptionEDA “time overflow” à Timeout exception

IdeaTime-aware cooperative multi-tasking

• Bound the synchronous time of every Callback and Task• Deliver a TimeoutException if this bound is exceeded

Analysis• Soundly defeats EHP attacks• Straightforward refactoring: try-catch in Promise chains• Non-destructive: Existing clients unharmed

First-Class Timeouts

Page 20: A Sense of Time for JavaScript and Node - USENIX · • New first-class asynchronous primitives like async/await and Promises make this possible • We only support global timeouts

- 20 -

Node.cureDesign and Evaluation

Page 21: A Sense of Time for JavaScript and Node - USENIX · • New first-class asynchronous primitives like async/await and Promises make this possible • We only support global timeouts

- 21 -

Desired behavior

Event Handler Old behavior New behavior

Event Loop Unbounded execution ThrowTimeoutException

Worker Pool '' ReturnTimeoutException

Page 22: A Sense of Time for JavaScript and Node - USENIX · • New first-class asynchronous primitives like async/await and Promises make this possible • We only support global timeouts

- 22 -

Adding first-class timeouts to Node.js

Application

JS Engine

C++

JS

Event-ing

Node.js libs

libuv

Worker poolEvent loop

V8

Time-aware JSTimeoutExceptionInterrupt handler

Time-awareEventLoopTimeoutWatchdogMonitorCBentry/exitSetT.E.interrupt

Time-awareWorkerPoolManagerswatchfortimeoutsDisposableWorkers

Page 23: A Sense of Time for JavaScript and Node - USENIX · • New first-class asynchronous primitives like async/await and Promises make this possible • We only support global timeouts

- 23 -

• Built on Node.js v8.8.1 (LTS)• 4 KLoC across 50 files

• Compatible• Passes Node.js core test suite*

• Available on GitHub

Node.cure prototype

Page 24: A Sense of Time for JavaScript and Node - USENIX · • New first-class asynchronous primitives like async/await and Promises make this possible • We only support global timeouts

- 24 -

Security guarantees

• Every vulnerable Language and Framework API is safe• Applications built with these APIs are safe, too!

• Passes our EHP test suite• All vulnerable Node.js APIs• Including all used in the npm vulnerabilities

However• Detect: Must choose timeout thresholds (Goldilocks problem)• Respond: Tight threshold or blacklisting

Page 25: A Sense of Time for JavaScript and Node - USENIX · • New first-class asynchronous primitives like async/await and Promises make this possible • We only support global timeouts

- 25 -

Micro-benchmarks Macro-benchmarks (summary)

Performance penalty

App. type Overhead

Server 0-2 %

Utility 0-8 %

Middleware 6-24 %

Component Overhead

New interrupt 0%

Instr. CBs 1.01-2.4 x

I/O buffers 1.3 x

Page 26: A Sense of Time for JavaScript and Node - USENIX · • New first-class asynchronous primitives like async/await and Promises make this possible • We only support global timeouts

- 26 -

Community Engagement

Page 27: A Sense of Time for JavaScript and Node - USENIX · • New first-class asynchronous primitives like async/await and Promises make this possible • We only support global timeouts

- 27 -

Don’t Block the Event Loop (or the Worker Pool)Reviews Node.js architectureEHP attacks + examplesAdvice about npm module safety

Guide on nodejs.org

Page 28: A Sense of Time for JavaScript and Node - USENIX · • New first-class asynchronous primitives like async/await and Promises make this possible • We only support global timeouts

- 28 -

Documentationfs

readFile

cryptorandomBytesrandomFill

child_processspawn

Codefs.readFile

Changes to Node.js core

Page 29: A Sense of Time for JavaScript and Node - USENIX · • New first-class asynchronous primitives like async/await and Promises make this possible • We only support global timeouts

- 29 -

Closing Remarks

Page 30: A Sense of Time for JavaScript and Node - USENIX · • New first-class asynchronous primitives like async/await and Promises make this possible • We only support global timeouts

- 30 -

The EDA has an EHP problem.

First-class timeouts can cure it.

We:• Defined an attack• Demonstrated its presence in the wild• Designed and prototyped a defense• Disseminated to the practitioner community

Thank you for your attention!

COMPUTER SCIENCE

Page 31: A Sense of Time for JavaScript and Node - USENIX · • New first-class asynchronous primitives like async/await and Promises make this possible • We only support global timeouts

- 31 -

Bonus Material

Page 32: A Sense of Time for JavaScript and Node - USENIX · • New first-class asynchronous primitives like async/await and Promises make this possible • We only support global timeouts

- 32 -

• The tighter the timeout, the less effective the EHP attack• Loose timeouts à blacklist attackers• No DDoS (threat model)• Blacklisting is relatively easy with First-Class Timeouts because the TimeoutException is delivered in the context of the malicious request

Choosing a timeout

Page 33: A Sense of Time for JavaScript and Node - USENIX · • New first-class asynchronous primitives like async/await and Promises make this possible • We only support global timeouts

- 33 -

• Choose timeout – minimize CB variance during tuning• Goldilocks problem

• Add error handling – a global exception handler and per-request handlers

• New first-class asynchronous primitives like async/await and Promises make this possible

• We only support global timeouts but could refine thresholds on a per-CB and per-Task basis

Programming with First-Class Timeouts

Page 34: A Sense of Time for JavaScript and Node - USENIX · • New first-class asynchronous primitives like async/await and Promises make this possible • We only support global timeouts

- 34 -

• Heartbeat• Partitioning• First-class timeouts

• Larger worker pool• Preemptible callbacks and tasks• Speculative concurrent execution• Serverless

Various ideas towards EHP-safety

Dedicating resources toeach client: OTPCA

Within the EDA paradigm

Page 35: A Sense of Time for JavaScript and Node - USENIX · • New first-class asynchronous primitives like async/await and Promises make this possible • We only support global timeouts

- 35 -

• Attacker can trigger worst-case behavior• No DDoS

Thus:• Include EHP, a problem unique to EDA• Exclude DDoS, a general problem for problem web servers

Threat model

Page 36: A Sense of Time for JavaScript and Node - USENIX · • New first-class asynchronous primitives like async/await and Promises make this possible • We only support global timeouts

- 36 -

More details on time-aware Event Handlers

Event Loop Worker PoolExecutors

ManagerAlways has a Worker

WorkerDisposable

…Priority Executor

uv_queue_work

uv_queue_work_prio

Hangman

Dangling Worker

Page 37: A Sense of Time for JavaScript and Node - USENIX · • New first-class asynchronous primitives like async/await and Promises make this possible • We only support global timeouts

- 37 -

Implementation details

Layer Changes

Language • Add TimeoutException• Add interrupt

Framework

• Timeout Watchdog• Handle T.E. from async APIs• Offload sync. APIs• Time-aware C++ add-ons

Application • Handle T.E.

Page 38: A Sense of Time for JavaScript and Node - USENIX · • New first-class asynchronous primitives like async/await and Promises make this possible • We only support global timeouts

- 38 -

• Node.js applications can contain:• Pure JavaScript• C++ add-ons

− e.g. for performance or using systems libraries

• Application-defined C++ add-ons are unprotected by F.C.T• Must be made time-aware, similar to how we made Node.js’s own

C++ bindings time-aware• Only 0.7% of npm modules have C++ add-ons

C++ add-ons

Page 39: A Sense of Time for JavaScript and Node - USENIX · • New first-class asynchronous primitives like async/await and Promises make this possible • We only support global timeouts

- 39 -

Experimental slides

Page 40: A Sense of Time for JavaScript and Node - USENIX · • New first-class asynchronous primitives like async/await and Promises make this possible • We only support global timeouts

- 40 -

Node.js attack – with ReDoS and IO-DoS

0

500

1000

1500

2000

2500

3000

0 1 2 3 4 5 6

Thr

ough

put

(req

/sec

)

Time (seconds)

Baseline REDOSBaseline ReadDOSNodeCure REDOSNodeCure ReadDOS

Page 41: A Sense of Time for JavaScript and Node - USENIX · • New first-class asynchronous primitives like async/await and Promises make this possible • We only support global timeouts

- 41 -

Dispatcher

Handle request

Handle request

Thread pool

One-thread-per-client architecture (OTPCA)

Page 42: A Sense of Time for JavaScript and Node - USENIX · • New first-class asynchronous primitives like async/await and Promises make this possible • We only support global timeouts

- 42 -

Event-driven architecture (EDA)

Event LoopWorker Pool

offloads

returns completed work

Pending events

Page 43: A Sense of Time for JavaScript and Node - USENIX · • New first-class asynchronous primitives like async/await and Promises make this possible • We only support global timeouts

- 43 -

Long-running request in OTPCA

Dispatcher

Handle request

Handle request

Thread pool☠

Page 44: A Sense of Time for JavaScript and Node - USENIX · • New first-class asynchronous primitives like async/await and Promises make this possible • We only support global timeouts

- 44 -

Long-running request in EDA

offloads

returns completed work

Pending eventsWorker Pool

Event Loop