Today I saw this post: <
https://rachelbythebay.com/w/2018/04/27/uid/>,
and it got me thinking again about a particular way of framing the
effectiveness of capabilities that's been floating around in my head,
but that I haven't seen expressed anywhere. I'm curious as to other's
thoughts on the below, as well as pointers to prior art on the
particular framing.
It seems like capabilities really do two conceptually separate things
for you:
1. Eliminate confused deputy vulnerabilities, by following the central
dogma "Don't separate designation from authority"
2. Drastically reduce then tension between security and usability
(2) is done by *aligning* the mechanisms of composability with the
mechanisms of security, so that fine grained security naturally falls
out of the way we already organize and reason about code to keep it
maintainable, even outside of a security setting. This way, security
and functionality are not fighting each other.
But (2) doesn't necessarily imply the central dogma; people do sometimes
use separate designators in normal code, e.g. array indexes. This is
where the post comes in. The post posits some code that iterates over
user ids in an database where ids are allocated sequentially:
ban_senders_of_messages(messages) {
for (i = 0; i < messages.size(); ++i) {
ban_account(i);
}
}
This code has a *serious* bug; the author offers the corrected version:
ban_senders_of_messages(messages) {
for (i = 0; i < messages.size(); ++i) {
ban_account(message[i].sender);
}
}
The bug is that the first version passes the *loop index* to
ban_account() as opposed to using it to fetch the account id that is
supposed to be banned. (As an aside, the new code has a bug too, but a
much less serious one and orthogonal to both the author's point and
mine. Do you see it?)
The post goes on to make a point about the ills of numeric IDs, but the
point I'd like to make is: this is not a security bug, but the central
dogma still fixes it. Here's a (correct) version in Python:
def ban_senders_of_messages(messages):
for message in messages:
ban_account(message.sender)
Note that there is no loop index, since Python's for loop is really a
for-each loop.
I would argue that this bug is due to the violation of a non-security
analog of the central dogma: `i` is an out-of-band designator, and we
could imagine in a capability context, `message.sender` is the
"capability." So the bug has arisen by separating designation (`i`) from
"authority" (`message.sender`). Authority is the wrong word in this
case, since access control isn't the issue, but it's the same footgun.
Often when issues around sequential user ids come up, many people
suggest an alternate fix: use uuids, or something else that has a large
keyspace, so if you have a bug it's unlikely to result in accessing the
wrong object. I would argue that this is the non-security analogue of
using cryptographic secrets -- the designator is still separate from
the "authority" in some sense (see also all the discussion about why
cryptographic caps are less powerful than true ocaps), but it solves the
issue by making it hard to come up with the designator if you're not
supposed to be accessing that object.
So I think the core of capability security is really *two* insights,
which are conceptually separate things:
1. A general guideline for avoiding a certain class of bugs, independent
of whether those bugs are security related or not. This is the
central dogma.
2. An approach to making security play nice with other software
engineering concerns, but leveraging the using techniques of software
development in a security context.
Thoughts?
-Ian