1 / 23

Specifying and Verifying Device Drivers

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

leiko
Télécharger la présentation

Specifying and Verifying Device Drivers

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. Specifying and VerifyingDevice Drivers Wes Weimer George Necula Gregoire Sutre

  2. Overview • Background and Motivation • Safety Properties • Events and History Registers • Driver States • Specifications • Verification • Implementation

  3. 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

  4. 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

  5. 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

  6. 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)))

  7. Driver States S0: Uninitialized cleanup_module=0 init_module=0 S1: Initialized misc_release=0 misc_open=0 S2: Opened misc_read=* misc_write=* Inv(S0) = true Inv(S1) = historyContains(+misc(any))

  8. 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

  9. 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

  10. 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 */ }}

  11. 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; }

  12. 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; } }

  13. 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; }

  14. 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

  15. 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

  16. 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

  17. “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

  18. 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 uÎU, find an sÎS 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)

  19. 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.

  20. 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.

  21. 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)

  22. 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)

  23. 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)

More Related