Enforcing high level protocols in low level software
Download
1 / 20

Enforcing High Level Protocols in Low-Level Software - PowerPoint PPT Presentation


  • 61 Views
  • Uploaded on

Enforcing High Level Protocols in Low-Level Software. Robert DeLine and Manuel Fahndrich. Vault. C like language, with references Adds “capabilities” to the language Capabilities are a static notion, so are only used at type-checking time, and carry no runtime overhead. Typing environment.

loader
I am the owner, or an agent authorized to act on behalf of the owner, of the copyrighted work described.
capcha
Download Presentation

PowerPoint Slideshow about ' Enforcing High Level Protocols in Low-Level Software' - solomon-robertson


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.While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server.


- - - - - - - - - - - - - - - - - - - - - - - - - - E N D - - - - - - - - - - - - - - - - - - - - - - - - - -
Presentation Transcript
Enforcing high level protocols in low level software

Enforcing High Level Protocols in Low-Level Software

Robert DeLine and Manuel Fahndrich


Vault
Vault

  • C like language, with references

  • Adds “capabilities” to the language

  • Capabilities are a static notion, so are only used at type-checking time, and carry no runtime overhead


Typing environment
Typing environment

  • Includes standard  for binding names to types

  • Additionally, we include a set C to determine if a capability is present

  • As expected, C starts out empty

  • However, while  will be full of bindings at the end of a function (remember it’s a C-like language) C must be empty


Tracked types
Tracked Types

Start with tracked types:

  • Declared by tracked(K) T v = e

  • T is an arbitrary type, but not a tracked type

  • e is an expression of type tracked(K) T

  • K is a name for the key associated with the object returned by e

  • The key bound to K is also in the capability set, indicating that v is accessible

  • Keys are created, and added to capability set C, by new tracked …

  • Because keys are created only by new tracked, and are part of the type, the type gains a one-to-one mapping to the lifetime of each object. Any attempt to assign a different instance to the variable will fail, since it cannot have the same key, let alone lexical key.

  • Keys are removed by free(v) where the key is read out of the type for v


Example
Example

void foo() {

tracked(K) point p = new tracked point {x = 1; y = 2;}

p.x = 2;

p.x *= 2;

free(p);

}

C starts out empty.

After point p is created, C contains a new key, named by K.

Key K persists until the call to free, after which point, key K is no longer in the capability set

Reading or writing the fields of p becomes invalid once K is removed


Control flow
Control Flow

tracked(K) point p = …;

bool b = random_bool();

if(b) free(v);

if(!b) free(v);

Vault requires that the capability sets be equal for all incoming edges at a join point.

The code snippet above does not type check, because, after the first free, there is a join point, from which one edge has capability K, but the other edge does not. The capability sets do not match, so type checking fails.


Capabilities and functions
Capabilities and functions

  • Currently, resources cannot escape functions. This is not useful, if we want to write a file library, for example.

  • This is caused by the restriction that there are no capabilities at function entry and exit. If an argument has a tracked type, it would be unusable in the function body

  • By annotating the function type we can relax that requirement


File handle example
File handle example

tracked(H) FILE fopen(…) [new H]

void fread(tracked(H) FILE f, …) [H]

void fwrite(tracked(H) FILE f, …) [H]

void fclose(tracked(H) FILE f) [-H]

This demonstrates how the function types are annotated. When type checking the body of the function, Vault verifies that the function meets the type spec for consuming, or creating capabilities.

[new H] means that H is a fresh capability. [H] means that key H is required before function entry, and is still available upon return. [-H] removes key H from the capability set upon return.

When type checking users of the code, Vault is able to check that the capabilities are used correctly. Here a capability is an open file handle. If a function does not declare that it leaves a file handle open in its type, Vault will ensure that there are no dangling open file handles for all function exit points.

The function annotations do not make any sense if there is no lexical matching key elsewhere in the type.


Capabilities with state
Capabilities with state

  • Capabilities thus far are limited to binary state. Good for object lifetimes, but not much else.

  • To handle objects with more complicated interfaces (e.g. sockets), Vault adds state to capabilities.

  • Whereas before we had capability K, now we can have capability K at state s: K@s

  • Changing the state of a capability is handled at function boundaries only. We add arrows to the function capability annotations.


Socket example
Socket example

interface SOCKET {

type sock;

variant domain [‘UNIX | ‘INET];

variant comm_style [‘STREAM | ‘DGRAM];

tracked(@raw) sock socket(domain, comm_style, int);

struct sockaddr {…};

void bind(tracked(S) sock, sockaddr) [S@raw->named];

void listen(tracked(S) sock, int) [S@named->listening];

tracked(N) sock accept(tracked(S) sock, sockaddr)

[S@listening, new N@ready];

void receive(tracked(S) sock, byte[]) [S@ready];

void close(tracked(S) sock) [-S];

}

void handle_requests() {

tracked mysock socket(‘INET, ‘STREAM, 0)

bind(mysock, new sockaddr{port = 80, …});

listen(mysock, 10);

while(true) {

sockaddr = new sockaddr();

tracked connect_sock = accept(mysock, sockaddr);

…; // do actual handling code

close(connect_sock);

}

}


Using sockets correctly
Using sockets correctly

void handle_requests() {

tracked mysock socket(‘INET, ‘STREAM, 0)

bind(mysock, new sockaddr{port = 80, …});

listen(mysock, 10);

while(true) {

sockaddr = new sockaddr();

tracked connect_sock = accept(mysock, sockaddr);

byte [] buf = new buf[20];

receive(connect_sock, buf);

…; // do actual handling code

close(connect_sock);

delete sockaddr;

delete buf;

}

close(mysock);

}


Receive on wrong socket
Receive on wrong socket

void handle_requests() {

tracked (A) mysock socket(‘INET, ‘STREAM, 0)

bind(mysock, new sockaddr{port = 80, …});

listen(mysock, 10);

while(true) {

sockaddr = new sockaddr();

tracked (B) connect_sock = accept(mysock, sockaddr);

byte [] buf = new buf[20];

// capability set: {A@listening, B@ready}

receive(mysock, buf);

// error: mysock has type “tracked(A) sock”

// capability A is in listening state

// receive requires the capability be in ready state

…; // do actual handling code

close(connect_sock);

delete sockaddr;

delete buf;

}

close(mysock);

}


Forgetting to close socket
Forgetting to close socket

void handle_requests() {

tracked (A) mysock socket(‘INET, ‘STREAM, 0)

bind(mysock, new sockaddr{port = 80, …});

listen(mysock, 10);

// capability set: {A@listening}

while(true) { // type error: capability sets of predecessors do not match

sockaddr = new sockaddr();

tracked (B) connect_sock = accept(mysock, sockaddr);

byte [] buf = new buf[20];

receive(connect_sock, buf);

…; // do actual handling code

//close(connect_sock);

delete sockaddr;

delete buf;

// capability set: {A@listening, B@ready}

}

close(mysock);

}


Parameterized types
Parameterized types

  • Socket example is good, but doesn’t have room for error handling. What if bind fails? Is the socket in the raw state, or the named state?

  • Currently, we associate exactly one state with a capability, so cannot fix this

  • Solution: parameterized variant types


Parameterized types1
Parameterized types

  • variant option<key K> [ ‘SOME{K} | ‘NONE]

  • When constructing a key-parameterized type, the key is consumed by the type.

  • When deconstructing, the key is added back into the capability set

  • With this, we can force the users of code to check error codes


Sockets with error checking
Sockets with error checking

variant status<key K> [‘Ok {K@named} |

‘Error(error_code){K@raw} ];

tracked status<S> bind(tracked(S) sock, sockaddr) [-S@raw];

Now users of the code lose access to the socket after calling bind. In order to regain that access, they must check the error code by deconstructing it. In either case, they regain the key, but in different states. If there is no error, execution can continue as normal. If there is an error, however, the code path must do something to put the key into the named state, otherwise there will be a type error at the join point, as demonstrated before.


Tying other variables
Tying other variables

  • What about exposing internal pointers of tracked objects?

  • Examples: region memory allocation, returning an internal pointer of a struct.

  • Those values should be usable while the parent object is valid, but once the parent object is freed, these constituent objects also become invalid.

  • Solution: tie them to the same key as the parent


Region example
Region example

  • Region memory allocation puts objects into regions, and then the entire region is freed, instead of each individual object

  • References to objects in the region are no longer valid after the region is freed

  • Introduce new construct for region memory allocation: new(rgn) T [init]

  • Rgn is a region tracked by key R, and the whole expression returns a value of type R:T


Region example1
Region example

void okay() {

tracked(R) region rgn = Region.create();

R:point pt = new(rgn) point {x=1; y=2;};

pt.x++;

Region.delete(rgn);

}

void dangling() {

tracked(R) region rgn = Region.create();

R:point pt = new(rgn) point {x=1; y=2;};

Region.delete(rgn);

pt.x++; // error: key R not in held-key set

}

void leaky() {

tracked(R) region rgn = Region.create();

R:point pt = new(rgn) point {x=1; y=2;};

pt.x++;

// error: extra key R in held-key set

}


Limitations
Limitations

  • Would like to be able to put tracked objects inside other objects, and keep all the benefits

  • Currently, the container object’s type needs to be parameterized for every object it contains, so we can’t use anything growable

  • Parameterization means any value of the container type must also be tracked

    Tracked(A) Z a = …;

    Tracked(B) Y b = …;

    Tracked(A,B) ZYPair p= new Pair<A, B>(a, b)


ad