Specifying and Verifying Device Drivers

23
Specifying and Verifying Device Drivers Wes Weimer George Necula Gregoire Sutre

description

Specifying and Verifying Device Drivers. Wes Weimer George Necula Gregoire Sutre. Overview. Background and Motivation Safety Properties Events and History Registers Driver States Specifications Verification Implementation. Background and Motivation. Why bother? PCC-style background - PowerPoint PPT Presentation

Transcript of Specifying and Verifying Device Drivers

Page 1: Specifying and Verifying Device Drivers

Specifying and VerifyingDevice Drivers

Wes Weimer

George Necula

Gregoire Sutre

Page 2: Specifying and Verifying Device Drivers

Overview

• Background and Motivation

• Safety Properties

• Events and History Registers

• Driver States

• Specifications

• Verification

• Implementation

Page 3: Specifying and Verifying Device Drivers

Background and Motivation

• Why bother?• PCC-style background

– Want a sound analysis

• Decided on an all-paths analysis– Similar to PREfix, ESC, Slam, Magick– Check pre- and post-conditions, kernel API– But also specify resource management, etc.

• Greg will recast as model checking later

Page 4: Specifying and Verifying Device Drivers

Safety Properties

• We aim to verify that …– Allocated resources are freed

• Over the lifetime of the driver

• Enforce acquire order: no deadlocks

– Kernel functions are called correctly• Also includes ordering requirements and

obligations, like schedule() and signal_pending().

– Certain minimum actions are performed

Page 5: Specifying and Verifying Device Drivers

Events

• Three broad types:+ allocation, - deallocation, ! event

• Applied to uninterpreted constructors:irq, dma, memory, schedule

• And given program values as arguments:+irq(5), -dma(3), !schedule

• Function call and return may be treated as events

Page 6: Specifying and Verifying Device Drivers

History Register

• Abstract register used to verify programs– Like the “memory register” in PCC/VCGen– Never appears in user-written code

• Keeps an ever-increasing list of events– E.g., [ +irq(7) ; !schedule ; +misc(&qpmouse) ]

• May refer to it in conditions– E.g., PRE(!historyContains(+irq(i)))

Page 7: Specifying and Verifying Device Drivers

Driver States

S0: Uninitialized

S1: Initialized

S2: Opened

init_module=0cleanup_module=0

misc_open=0 misc_release=0

misc_read=*

misc_write=*Inv(S0) = trueInv(S1) = historyContains(+misc(any))

Page 8: Specifying and Verifying Device Drivers

Driver States (cont’d)

• Kernel API guarantees a certain state-like behavior for device drivers– E.g., misc_open will only be called if the driver is in S1

• We associate an invariant with each state– Captures history register values– And global variables in the driver

• Must check all paths from S0 to S0.– For resource leaks, etc. – Similar to data-flow analysis

Page 9: Specifying and Verifying Device Drivers

Specifications

• Write spec in C, just like driver– May use data and control non-determinism– May use pre-conditions: like assert(), but will not fail

• Spec is minimal– List only things that must be done– Looks like a “template” device driver– Normal device drivers often written by copying others

• Side-conditions govern optional behavior– Like matching alloc/free– Or checking signals after sleeping

Page 10: Specifying and Verifying Device Drivers

Specification Example

int init_module() { /* user code must look like this */PRE(!historyContains(+misc(any)));switch (__rand_int_range(0,3)) {

case 0: misc_register(any); /* use kernel API */ return 0;

case 1: return –5; /* EIO */case 2: return –6; /* ENXIO */case 3: return –19; /* ENODEV */

}}

Page 11: Specifying and Verifying Device Drivers

Specification Example (2)

int misc_fasync(void *fd, void *filp, int on) {PRE(fd != 0 && filp && (on == 1 || on == 0)

&& current->state == TASK_RUNNING&& historyCount(!misc_open) >

historyCount(!misc_release));int retval = fasync_helper(fd, filp, on, any);if (retval < 0) return retval;else return 0;}

Page 12: Specifying and Verifying Device Drivers

Kernel API Spec

• Model behavior of kernel functions– Like kmalloc(), schedule(), kfree()– May modify history register

• Example:int request_irq(int i, void *f, …) {PRE(historyCount(+irq(i)) == historyCount(-irq(i)) &&

implements(f, irq_handler) && …);if (__rand_int_range(0,1)) { return –5; } else { historyAdd(+irq(i)); return 0; }}

Page 13: Specifying and Verifying Device Drivers

Kernel API Spec (2)int kmalloc(int size, int prio) {PRE(size > 0 && size < (128*1024) && (prio & GFP_KERNEL || prio & GFP_ATOMIC)

&& !(prio & GFP_KERNEL && prio & GFP_ATOMIC));if (prio & GFP_KERNEL) {

int old_state = current->state;current->state = TASK_INTERRUPTIBLE;schedule();current->state = old_state;historyAdd(!signal_pending);

}if (__rand_int_range(0,1) {

void *retval = __rand_int_range(1, MAX_INT/4)*4;historyAdd(+memory(retval));return retval;

} else return NULL;}

Page 14: Specifying and Verifying Device Drivers

Side Conditions

• Used to verify optional behavior– Like resource allocation, scheduling– Conditions on the history register

• Examples:– Every !schedule must match a !signal_pending– After !must_return(-512), must return –512– All +irq(…) before any +dma(…)– All +irq(…) occur after !misc_open

Page 15: Specifying and Verifying Device Drivers

Story so far …

• We have the user source code

• We have the history register abstraction– Keep track of the past, etc.

• We have a spec for the user code– Minimal, lists things that must be done

• We have a spec for the kernel API

• We have side conditions

Page 16: Specifying and Verifying Device Drivers

Verification

• When does the user code meet the spec?– Each user function F “matches up” (defined

next) with our spec for function F– If the user calls a kernel function with a pre-

condition, the condition will always be satisfied– All of the side conditions hold over the life of

the device driver

Page 17: Specifying and Verifying Device Drivers

“Matching Up”

• Can be phrased as a trace inclusion problem– More on that with the next speaker

• We’ll do it as an all-paths analysis

• Symbolically execute all paths in user code

• Keep track of history register

• Remove +resource(X) and –resource(X)

• Make sure other important events match

Page 18: Specifying and Verifying Device Drivers

How to Verify

• Symbolically execute all paths in user code– End up with a set of final states U– Verify pre-conditions, use theorem prover

• Symbolically execute all paths in spec– End up with a set of final states S

• For every uU, find an sS such that– They have the same return value– Their history registers match up– If s proves T then u proves T (for a few global T’s)

Page 19: Specifying and Verifying Device Drivers

How to Verify: States

• Start in S0, then check init_module()– Match each final state against the spec– Check all side-conditions in each final state

• Gather all states R with return value 0– Their LUB becomes the invariant for S1– Analyze all paths out of S1 (= symbolically

execute those functions with that invariant)

• Repeat until this terminates.

Page 20: Specifying and Verifying Device Drivers

What can go wrong?

• User code can call a kernel function and fail to meet the pre-condition

• User code can fail to meet a side condition

• User code can fail to match up to the spec

• If so, report an error!

• And hope that it’s not a false positive.

Page 21: Specifying and Verifying Device Drivers

Loop Invariants

• Used in symbolic execution to model loops

• Currently, we guess “true”– or use any other heuristic (e.g., on “for” loops)

• Except for the history register

• Gather all history changes in the loop body

• Add a special history element:• X_copies_of(history_element_list)

Page 22: Specifying and Verifying Device Drivers

Does it work?

• Sample implementation

• Tested on init_module() in 46 misc device drivers (about 300k post-processed each)

• 30 successful terminations (25 meet spec)– Others look fine, spec needs to be refined

• 16 “no answer after 45 seconds” – All-paths analysis is expensive: O(2n)

Page 23: Specifying and Verifying Device Drivers

Conclusions and Future Work

• Method for specifying and verifying– Spec looks like original code– Sound, incomplete, expensive– Uses abstract events

• Captures allocation, function ordering, other API and behavioral restrictions

• Next steps: – folding similar paths– handling concurrency (e.g., irq handlers)