I am exploring using annotated grammars to formalize and enforce parts of contracts between nodes in a distributed application.
I've found a number of articles on languages for specifying fairly general types of contracts, mostly based around the idea of a contract as a state machine; and others (mostly object-capability schemes) based around the idea of identifying a "safe" subset of a language; but none that use a grammar as the specification of the contract.
I was wondering how attempts to formalize contracts tend to fail, and what else should one keep in mind.
Below are details of the particular kind of contract I am trying to enforce.
Given a BNF grammar for part of HTML:
HTML ::== (Text | Link)*;
Text ::== ("&" | "<" | """ | [^&<"])+;
Link ::== "<a" Href? ">" Text "</a>";
Href ::== "href=\"" Text+ "\"";
I can infer which strings are in the language and which are not, and the structure of a parse tree, but by adding annotations thus
HTML ::== (Text | Link)*;
Text ::== @String((@CharValue{"&"}"&"
| @CharValue{"<"}"<"
| @CharValue{"\""}"""
| [^&<"]))+;
Link ::== "<a" Href? ">" Text? "</a>";
Href ::== " href=\"" @Embeds{URI}Text+ "\"";
I can infer additional facts:
- That plain text can be encoded/decoded by replacing
"&"
with"&"
, etc. and that a string can be encoded as a string that matches theText
non-terminal. - That the URI grammar should be recursively applied to the content of the
href
post-decoding.
From this I hope to generate several artifacts:
- Encoders. An encoder for non-terminal T is a
'a -> T string
function that encodes a data value. - Sanitizers. A function
T string -> T string
that produces a similar string that falls in a subset of the language that is well-handled by many different implementations and which does not contain high-power instructions. - A template context propagator. A function
string * context -> string * context
which takes a string from a trusted source and the context before the string (context is a type describing a set of prefixes of strings in the language) and produces a string in the well-handled subset of the language and the context after that string is appended to any of the prefixes described by the input context. This is useful for composing a string from trusted and untrusted code.
With these tools, I hope to be able to enforce the contract that any powerful instructions which are parsed by the receiver of a network message composed using these tools are instructions that are present in snippets of code authored by the message sender, and not instructions authored by a provider of untrusted data.
If the sender uses the derived tools to encode or sanitize all untrusted data appropriately, and uses the template propagation algorithm to choose encoders/sanitizers appropriate to the context of the buffer on which the message is being built, then high-power instructions in the buffer must have come from a privileged source.
If the recipient receives a message over a secure channel, and uses the same grammar to parse and decode the message, then it can be confident of the provenance of the instructions it receives.
So the annotated grammar serves as a formal description of a contract, and the tools move the burden of preserving the contract from the application author to the grammar author. Since there are many application authors who tend to know the well-tested parts of a language, a much smaller number of language specialists who know the poorly tested parts of the grammars as well should be able to make applications robust against code injection.