Why We Need a Web Control Stack
Web applications almost universally have a notion of "authenticated user" which flavors the pages they serve up. Typically the logic to support this notion is built into frameworks, so that programmers don't have to deal with it on an action-by-action basis; they can just mark which actions are required to be authenticated, and presume that the user is authenticated and her credentials are handy when the handler is running.
There are at least two decent ways of writing the standard authentication check. One is to handle failure explicitly:
fun my_handler() {
user = validate_user();
if (!user) {
return login_page(retun => my_handler);
}
# from here on down we assume that "user" holds a valid user object
if (!can_do(user, foo)) { # can user perform the current action?
return "You can't do this!"
}
do_foo(...);
}
But the intention is, you'd like to say "Show the user a login page and come back here when it's done." In the code aboev, you have to spell out where "here" is.
Ideally the programming system should know where to come back to. Here is a more attractive syntax:
fun my_handler() {
user = validate_user();
# from here on down we assume that "user" holds a valid user object
if (!can_do(user, foo)) { # can user perform the current action?
return "You can't do this!"
}
do_foo(...);
}
To sketch an implementation, suppose that the validate_user
routine has a way of returning a page to the browser, and embedding a token in that page which knows how to come back to the calling routine, preserving its context (call stack, lexical variables, etc.). Call this feature a "web stack": a call stack which is representable in a web page.
The first approach has the limitation that "my_handler" must be a top-level function and, in traditional web languages, you need to provide a table showing how to dispatch from the string "my_handler"
to the function my_handler
. Some languages/frameworks will automatically create a dispatch table that gives an external name to every top-level function, but this presents a security question, since you're implicitly exposing all functions to the network, leaving the programmer to remember to close them up. One way to patch that difficulty is to support decorators, which can be used to register a function with a dispatch table; this makes it easy to check which functions are externally-callable.
Still, these approaches require the programmer to be explict about what "here" is when telling a subroutine "come back here." Links should support the second approach.
With the "web stack" example, there is still some risk of inadvertently exposing a program point that can be invoked from the network. As a bulwark against that, one can imagine statically checking and tainting such "holes." The programmer could then be required to declare that a function may open up an entry point, and any routines which call such a routine would also be tainted—forcing the programmer to acknowledge the "hole."