1 checking interface usage with vault rob deline 23 may 2001 cs 590 md

28
1 Checking Interface Usage with Vault Rob DeLine 23 May 2001 CS 590 MD

Upload: vaughn-wagner

Post on 15-Dec-2015

226 views

Category:

Documents


1 download

TRANSCRIPT

Page 1: 1 Checking Interface Usage with Vault Rob DeLine 23 May 2001 CS 590 MD

1

Checking Interface Usage

with Vault

Rob DeLine23 May 2001

CS 590 MD

Page 2: 1 Checking Interface Usage with Vault Rob DeLine 23 May 2001 CS 590 MD

2

Interfaces have (unenforced) usage rules

Usage rules are in the documentation — we hope!“Call A(x) before calling B(x)”“If you call A(x), you must eventually call B(x)”“Call A(x) before accessing variable y”“After calling B(x), don’t access variable y”

Disobeying a rule often leads to a crash

Page 3: 1 Checking Interface Usage with Vault Rob DeLine 23 May 2001 CS 590 MD

3

Vault enforces interface usage rules

Vault is a new programming languageInterface provider records usage rulesClient code is rejected if it violates a rule (type error)Every violation is guaranteed to be found

Vault’s type system tracks individual objectsRecall our rule: “Call A(x) before calling B(x)”We track an object’s availability (can I access it?)We track an object’s symbolic state (what can I call on it?)

Page 4: 1 Checking Interface Usage with Vault Rob DeLine 23 May 2001 CS 590 MD

4

Tracking individual objects

Rule 1: “Close every file that you open.”

Rule 2: “Do not read from a file after closing it.”

void ReadFromFiles (FILE f1, FILE f2) {char x = fgetc(f1);fclose(f1);char y = fgetc(f2);fclose(f2);

}

Are we obeying the rules?

Page 5: 1 Checking Interface Usage with Vault Rob DeLine 23 May 2001 CS 590 MD

5

Tracking individual objects

Rule 1: “Close every file that you open.”

Rule 2: “Do not read from a file after closing it.”

void ReadFromFiles (FILE f1, FILE f2) {char x = fgetc(f1);fclose(f1);char y = fgetc(f2);fclose(f2);

}

Are we obeying the rules?

Maybe not, consider ReadFromFiles(x,x)

Page 6: 1 Checking Interface Usage with Vault Rob DeLine 23 May 2001 CS 590 MD

6

Aliases make it tricky to enforce rules

Vault uses keys to spell out aliasingKey = compile-time name for run-time objectSame key same objectTracked type associates a key with an objectTracked types (only) have aliasing restrictions

void ReadFromFiles1 (tracked(K) FILE f1, tracked(K) FILE f2)

// f1 and f2 must be aliases

void ReadFromFiles2 (tracked(K) FILE f1, tracked(J) FILE f2)

// f1 and f2 cannot be aliases

Page 7: 1 Checking Interface Usage with Vault Rob DeLine 23 May 2001 CS 590 MD

7

Using keys to track object availability and state

At each program point, Vault computes...Keys held (which objects are available)Each key’s state (symbolic state of each object)

Vault function signature includes effects on keysPre- and post-condition combined as one change spec

Add key tracked(K) T foo() [new K];Delete key void foo(tracked(K) T) [-K];Change key state void foo(tracked(K) T) [K@a->b];

Allocation adds new key to key settracked(K) Point p = new tracked Point {x=1;y=2;};

Page 8: 1 Checking Interface Usage with Vault Rob DeLine 23 May 2001 CS 590 MD

8

Controlling data access with keys

Guarded types control data access K:T xCan read/write variable x only when K is in the key setAllows us to correlate the lifetimes of objects

type region;

tracked(R) region make() [new R];

void delete(tracked(R) region) [-R];

R:byte[] alloc(tracked(R) region, int) [R];

Page 9: 1 Checking Interface Usage with Vault Rob DeLine 23 May 2001 CS 590 MD

9

Specializing types to particular keys

Type can have type parm list<T:type>

Type can also have key parm mutex<K:key>

type KEVENT<R:key>;

KEVENT<E> KeInitializeEvent<T> (tracked(E) T) [E];

NTSTATUS KeSignalEvent(KEVENT<E>) [-E];

NTSTATUS KeWaitForEvent(KEVENT<E>) [+E];fork

{E}

{E}

{E}

{}

{}

signalwait

Page 10: 1 Checking Interface Usage with Vault Rob DeLine 23 May 2001 CS 590 MD

10

Holding keys conditionally

Datatypes (variants) divide values into casesSyntactic tags distinguish cases (constructors, pattern matching)Vault datatypes can hold keys, as well as values

tracked variant OptKey<K:key> [ `NoKey | `SomeKey{K} ];

void foo(tracked(X) T x) [X] { {X} tracked OptKey<X> y = `SomeKey{X}; {} ...

{} switch (y) {

{} case `SomeKey{X}: ... {X} case `NoKey: ...

{} }

Page 11: 1 Checking Interface Usage with Vault Rob DeLine 23 May 2001 CS 590 MD

11

Vault checks keys for each function

Calculate function body’s effect on keysCreate CFG for bodyTake initial key set from preconditionEnsure needed keys held at each node (guards,preconds)Ensure final key set matches postconditionUnify key sets at join points

y=new tracked Ti++{ K23 }{ }

{ }

y=new tracked Ty=new tracked T{ K23 }{ K56 }

{ }

{ K98 }

Page 12: 1 Checking Interface Usage with Vault Rob DeLine 23 May 2001 CS 590 MD

12

Checking Windows 2000 device drivers

Driver handles requests from the kernel e.g. start, read, write, shutdown, ... driver exports a function for each request type lifetime of request lifetime of function call

Request is encapsulated in a data structure I/O Request Packet (IRP) Driver handles request by side-effecting IRP IRP ownership and lifetime are important

Page 13: 1 Checking Interface Usage with Vault Rob DeLine 23 May 2001 CS 590 MD

13

Drivers form a stack

Kernel sends IRP to top driver

in stack

Driver may... handle IRP itself pass IRP down pass new IRP(s) down

file system driver

KERNEL

storage class driver

floppy driver

bus driver

Page 14: 1 Checking Interface Usage with Vault Rob DeLine 23 May 2001 CS 590 MD

14

IRP Ownership

IoCompleteRequestVOID

IoCompleteRequest(

IN PIRP Irp,

IN CCHAR PriorityBoost );

IoCompleteRequest indicates the caller has completed all processing for a given I/O request and is returning the given IRP to the I/O Manager.

Parameters

Irp Points to the IRP to be completed.

PriorityBoost Specifies a system-defined constant by which to increment the runtime priority of the original thread that requested the operation. This value is IO_NO_INCREMENT if the original thread requested an operation the driver could complete quickly (so the requesting thread is not compensated for its assumed wait on I/O) or if the IRP is completed with an error. Otherwise, the set of PriorityBoost constants are device-type-specific. See ntddk.h or wdm.h for these constants.

Comments

When a driver has finished all processing for a given IRP, it calls IoCompleteRequest. The I/O Manager checks the IRP to determine whether any higher-level drivers have set up an IoCompletion routine for the IRP. If so, each IoCompletion routine is called, in turn, until every layered driver in the chain has completed the IRP.

When all drivers have completed a given IRP, the I/O Manger returns status to the original requestor of the operation. Note that a higher-level driver that sets up a driver-created IRP must supply an IoCompletion routine to release the IRP it created.

Callers of IoCompleteRequest must be running at IRQL <= DISPATCH_LEVEL.

See Also

IoSetCompletionRoutine

“IoCompleteRequest indicates the caller has completed all processing for a given I/O request and is returning the given IRP to the I/O Manager.”

Page 15: 1 Checking Interface Usage with Vault Rob DeLine 23 May 2001 CS 590 MD

15

IRP Ownership

IoCompleteRequestVOID

IoCompleteRequest(

IN PIRP Irp,

IN CCHAR PriorityBoost );

IoCompleteRequest indicates the caller has completed all processing for a given I/O request and is returning the given IRP to the I/O Manager.

Parameters

Irp Points to the IRP to be completed.

PriorityBoost Specifies a system-defined constant by which to increment the runtime priority of the original thread that requested the operation. This value is IO_NO_INCREMENT if the original thread requested an operation the driver could complete quickly (so the requesting thread is not compensated for its assumed wait on I/O) or if the IRP is completed with an error. Otherwise, the set of PriorityBoost constants are device-type-specific. See ntddk.h or wdm.h for these constants.

Comments

When a driver has finished all processing for a given IRP, it calls IoCompleteRequest. The I/O Manager checks the IRP to determine whether any higher-level drivers have set up an IoCompletion routine for the IRP. If so, each IoCompletion routine is called, in turn, until every layered driver in the chain has completed the IRP.

When all drivers have completed a given IRP, the I/O Manger returns status to the original requestor of the operation. Note that a higher-level driver that sets up a driver-created IRP must supply an IoCompletion routine to release the IRP it created.

Callers of IoCompleteRequest must be running at IRQL <= DISPATCH_LEVEL.

See Also

IoSetCompletionRoutine

void IoCompleteRequest(tracked(I) IRP, CHAR) [-I];

Page 16: 1 Checking Interface Usage with Vault Rob DeLine 23 May 2001 CS 590 MD

16

IRP Ownership

IoCallDriverNTSTATUS

IoCallDriver(

IN PDEVICE_OBJECT DeviceObject,

IN OUT PIRP Irp );

IoCallDriver sends an IRP to the next-lower-level driver after the caller has set up the I/O stack location in the IRP for that driver.

Parameters

DeviceObject Points to the next-lower driver's device object, representing the target device for the requested I/O operation.

Irp Points to the IRP.

Return Value

IoCallDriver returns the NTSTATUS value that a lower driver set in the I/O status block for the given request or STATUS_PENDING if the request was queued for additional processing.

Comments

IoCallDriver assigns the DeviceObject input parameter to the device object field of the IRP stack location for the next lower driver.

An IRP passed in a call to IoCallDriver becomes inaccessible to the higher-level driver, unless the higher-level driver has set up its IoCompletion routine for the IRP with IoSetCompletionRoutine. If it does, the IRP input to the driver-supplied IoCompletion routine has its I/O status block set by the lower driver(s) and all lower-level driver(s)' I/O stack locations filled with zeros.

Drivers must not use IoCallDriver to pass power IRPs (IRP_MJ_POWER). Use PoCallDriver instead.

Callers of IoCallDriver must be running at IRQL <= DISPATCH_LEVEL.

See Also

IoAllocateIrp, IoBuildAsynchronousFsdRequest, IoBuildDeviceIoControlRequest, IoBuildSynchronousFsdRequest, IoSetCompletionRoutine, PoCallDriver

“An IRP passed in a call to IoCallDriver becomes inaccessible to the higher-level driver, …”

Page 17: 1 Checking Interface Usage with Vault Rob DeLine 23 May 2001 CS 590 MD

17

IRP Ownership

IoCallDriverNTSTATUS

IoCallDriver(

IN PDEVICE_OBJECT DeviceObject,

IN OUT PIRP Irp );

IoCallDriver sends an IRP to the next-lower-level driver after the caller has set up the I/O stack location in the IRP for that driver.

Parameters

DeviceObject Points to the next-lower driver's device object, representing the target device for the requested I/O operation.

Irp Points to the IRP.

Return Value

IoCallDriver returns the NTSTATUS value that a lower driver set in the I/O status block for the given request or STATUS_PENDING if the request was queued for additional processing.

Comments

IoCallDriver assigns the DeviceObject input parameter to the device object field of the IRP stack location for the next lower driver.

An IRP passed in a call to IoCallDriver becomes inaccessible to the higher-level driver, unless the higher-level driver has set up its IoCompletion routine for the IRP with IoSetCompletionRoutine. If it does, the IRP input to the driver-supplied IoCompletion routine has its I/O status block set by the lower driver(s) and all lower-level driver(s)' I/O stack locations filled with zeros.

Drivers must not use IoCallDriver to pass power IRPs (IRP_MJ_POWER). Use PoCallDriver instead.

Callers of IoCallDriver must be running at IRQL <= DISPATCH_LEVEL.

See Also

IoAllocateIrp, IoBuildAsynchronousFsdRequest, IoBuildDeviceIoControlRequest, IoBuildSynchronousFsdRequest, IoSetCompletionRoutine, PoCallDriver

void IoCallDriver(DEVICE_OBJECT,tracked(I) IRP) [-I];

Page 18: 1 Checking Interface Usage with Vault Rob DeLine 23 May 2001 CS 590 MD

18

Example: Driver request

NTSTATUS Read(DEVICE_OBJECT Dev, tracked(I) IRP Irp) [-I] {

if (GetRequestLength(Irp) == 0) {

NTSTATUS status = `STATUS_SUCCESS(`TransferBytes(0));

IoCompleteRequest(Irp, status);

return status;

} else

return IoCallDriver(NextDriver,Irp);

}

Page 19: 1 Checking Interface Usage with Vault Rob DeLine 23 May 2001 CS 590 MD

19

Example: Driver request

NTSTATUS Read(DEVICE_OBJECT Dev, tracked(I) IRP Irp) [-I] { {I}

if (GetRequestLength(Irp) == 0) { {I}

NTSTATUS status = `STATUS_SUCCESS(`TransferBytes(0)); {I}

IoCompleteRequest(Irp, status); {}

return status; {}

} else {I}

return IoCallDriver(NextDriver,Irp); {}

}

Page 20: 1 Checking Interface Usage with Vault Rob DeLine 23 May 2001 CS 590 MD

20

IRP completion routines

Getting IRP ownership back Driver A hands IRP to B and wants it back after B is done Driver A sets “completion routine” on IRP

void IoSetCompletionRoutine(tracked(K) IRP Irp,

COMPLETION_ROUTINE<K> Fun) [K];

type COMPLETION_ROUTINE<key K> =

tracked COMPLETION_RESULT<K>(DEVICE_OBJECT Dev,

tracked(K) IRP Irp) [-K];

tracked variant COMPLETION_RESULT<key K> [

| `MoreProcessingRequired

| `Finished(NTSTATUS) {K} ];

Page 21: 1 Checking Interface Usage with Vault Rob DeLine 23 May 2001 CS 590 MD

21

Completion routine example

NTSTATUS PlugPlay(DEVICE_OBJECT Dev, tracked(R) IRP Irp) [-R] {

KEVENT<R> DoneEvent = KeInitializeEvent(Irp);

tracked COMPLETION_RESULT<I>

CompletePnP(DEVICE_OBJECT Dev, tracked(I) IRP Irp) [-I] {

KeSignalEvent(DoneEvent);

return `MoreProcessingRequired;

}

IoSetCompletionRoutine(Irp, CompletePnP);

CALL_RESULT<R> result = IoCallDriver(lowerDriver, Irp);

KeWaitForEvent(DoneEvent);

...

}

Page 22: 1 Checking Interface Usage with Vault Rob DeLine 23 May 2001 CS 590 MD

22

Completion routine example

NTSTATUS PlugPlay(DEVICE_OBJECT Dev, tracked(R) IRP Irp) [-R] { {R}

KEVENT<R> DoneEvent = KeInitializeEvent(Irp); {R}

tracked COMPLETION_RESULT<I>

CompletePnP(DEVICE_OBJECT Dev, tracked(I) IRP Irp) [-I] { {I=R}

KeSignalEvent(DoneEvent); {}

return `MoreProcessingRequired; {}

}

{R}

IoSetCompletionRoutine(Irp, CompletePnP); {R}

CALL_RESULT<R> result = IoCallDriver(lowerDriver, Irp); {}

KeWaitForEvent(DoneEvent); {R}

...

}

Page 23: 1 Checking Interface Usage with Vault Rob DeLine 23 May 2001 CS 590 MD

23

Interrupt levels (IRQLs)

KeSetPriorityThreadKPRIORITY

KeSetPriorityThread(

IN PKTHREAD Thread,

IN KPRIORITY Priority );

KeSetPriorityThread sets the run-time priority of a driver-created thread.

Parameters

Thread Pointer to the driver-created thread.

Priority Specifies the priority of the driver-created thread, usually to the real-time priority value, LOW_REALTIME_PRIORITY. The value LOW_PRIORITY is reserved for system use.

Return Value

KeSetPriorityThread returns the old priority of the thread.

Comments

If a call to KeSetPriorityThread resets the thread's priority to a lower value, execution of the thread can be rescheduled even if it is currently running or is about to be dispatched for execution.

Callers of KeSetPriorityThread must be running at IRQL PASSIVE_LEVEL.

See Also

KeGetCurrentThread, KeQueryPriorityThread, KeSetBasePriorityThread

“Callers of KeSetPriorityThread must be running at IRQL PASSIVE_LEVEL.”

Page 24: 1 Checking Interface Usage with Vault Rob DeLine 23 May 2001 CS 590 MD

24

Interrupt levels (IRQLs)

KeReleaseSemaphoreLONG

KeReleaseSemaphore(

IN PRKSEMAPHORE Semaphore,

IN KPRIORITY Increment,

IN LONG Adjustment,

IN BOOLEAN Wait );

Parameters

Semaphore Pointer to an initialized semaphore object for which the caller provides the storage.

Increment Specifies the priority increment to be applied if releasing the semaphore causes a wait to be satisfied.

Adjustment Specifies a value to be added to the current semaphore count. This value must be positive.

Wait Specifies whether the call to KeReleaseSemaphore is to be followed immediately by a call to one of the KeWaitXxx.

Return Value

If the return value is zero, the previous state of the semaphore object is not signaled.

Comments

Callers of KeReleaseSemaphore must be running at IRQL <= DISPATCH_LEVEL provided that Wait is set to FALSE. Otherwise, the caller must be running at IRQL PASSIVE_LEVEL.

Releasing a semaphore object causes the semaphore count to be augmented by the value of the Adjustment parameter. If the resulting value is greater than the limit of the semaphore object, the count is not adjusted and an exception, STATUS_SEMAPHORE_COUNT_EXCEEDED, is raised.

Augmenting the semaphore object count causes the semaphore to attain a signaled state, and an attempt is made to satisfy as many waits as possible on the semaphore object.

If the value of the Wait parameter is TRUE, the return to the caller is executed without lowering IRQL or releasing the dispatcher database spin lock. Therefore, the call to KeReleaseSemaphore must be followed immediately by a call to one of the KeWaitXxx.

“Callers of KeReleaseSemaphore must be running at IRQL <= DISPATCH_LEVEL…”

Page 25: 1 Checking Interface Usage with Vault Rob DeLine 23 May 2001 CS 590 MD

25

Interrupt levels (IRQLs)

KeAcquireSpinLockVOID

KeAcquireSpinLock(

IN PKSPIN_LOCK SpinLock,

OUT PKIRQL OldIrql );

KeAcquireSpinLock acquires a spin lock so the caller can synchronize access to shared data in a multiprocessor-safe way by raising IRQL.

Parameters

SpinLock Pointer to an initialized spin lock for which the caller provides the storage.

OldIrql Pointer to a variable that is set to the current IRQL when this call occurs.

Comments

Callers of KeAcquireSpinLock must be running at IRQL <= DISPATCH_LEVEL.

The current IRQL is saved in OldIrql. Then, the current IRQL is reset to DISPATCH_LEVEL, and the specified spin lock is acquired.

The OldIrql value must be specified when the spin lock is released with KeReleaseSpinLock.

Spin locks can cause serious problems if not used judiciously. In particular, no deadlock protection is performed and dispatching is disabled while the spin lock is held. Therefore:

•The code within a critical region guarded by an spin lock must neither be pageable nor make any references to pageable data.

•The code within a critical region guarded by a spin lock can neither call any external function that might access pageable data or raise an exception, nor can it generate any exceptions.

•The caller should release the spin lock with KeReleaseSpinLock as quickly as possible.

See Also

KeAcquireSpinLockAtDpcLevel, KeInitializeSpinLock, KeReleaseSpinLock

“Callers of KeAcquireSpinLock must be running at IRQL <= DISPATCH_LEVEL.

The current IRQL is saved in OldIrql. Then, the current IRQL is reset to DISPATCH_LEVEL, and the specified spin lock is acquired.”

Page 26: 1 Checking Interface Usage with Vault Rob DeLine 23 May 2001 CS 590 MD

26

Checking interrupt levels

stateset IRQ_LEVEL = [ PASSIVE_LEVEL < APC_LEVEL < DISPATCH_LEVEL < DIRQL ];

key IRQL @ IRQ_LEVEL;

KPRIORITY

KeSetPriorityThread(KTHREAD Thread, KPRIORITY Priority)

[ IRQL @ PASSIVE_LEVEL ] ;

LONG

KeReleaseSemaphore(KSEMAPHORE Sem,KPRIORITY Pri,LONG Adj)

[ IRQL <= DISPATCH_LEVEL ] ;

KIRQL<$current>

KeAcquireSpinLock(KSPIN_LOCK SpinLock)

[ IRQL @ $current <= DISPATCH_LEVEL -> DISPATCH_LEVEL ] ;

Page 27: 1 Checking Interface Usage with Vault Rob DeLine 23 May 2001 CS 590 MD

27

Vault project goals

Commit to a verification approachMS mostly relies on testing for improving quality (incomplete)Type checking offers exhaustive analysis

Consider cost effectivenessFocus on bugs that are hard to repro (e.g., scribbles, races)Make developers fix bugs before check-in, when cheapest

Focus on real software, real developersModeled actual rules of Windows 2000 device driversBuilt a working floppy disk driver (~5 kloc)Sweat the details of syntax/conceptual model

Page 28: 1 Checking Interface Usage with Vault Rob DeLine 23 May 2001 CS 590 MD

28

Future work

Does Vault check useful interface rules?Try interfaces beyond drivers (DirectX?)

Does Vault support convenient programming?Currently, cannot type imperative loops over tracked dataKey annotations for regions are too burdensome

Can we add Vault features to existing languages?Going to add keys to C#/CIL

research.microsoft.com/vault