Lecture 24: More static information flow

Topics:

Static information flow control is appealing because it's a way to enforce strong information security properties, with low runtime overhead. But to make it work in practice we need to add several features. This lecture discusses some of the limitations of simple dependency-based enforcement of noninterference, and how they have been addressed in the Jif programming language and in other security-typed programming languages.

Run-time labels

Security policies (labels) are not always known at compile time. For example, if information is read from a file, we may want to treat the file permissions as a strong information security policy by mapping them into a label. But at compile time we can't predict this label. Another case is a system in which we need a multilevel channel (as described in the Orange Book), which can carry information of different security levels. To implement this, information must be accompanied by its security level -- but this cannot be predicted at compile time.

One solution to this problem, as introduced in the Jif programming language, is to treat labels as first-class values that can be manipulated at run-time, yet that describe the security policies on other data in the program. The challenge with this approach is that because these labels are determined at run time, they become an information channel that must be controlled. Static (compile-time) checking is needed to control this information channel.

Here is an example showing how data with a run-time label (in the variable x) can be manipulated.

label lab;
int {*lab} x;
int {Ly} y;
if (lab <= Ly) {
    y = x;
}

By doing a run-time test on the label lab, we can learn that an assignment into y is okay. But this may leak information depending on how lab (and Ly) are computed. To control this information channel, we need to check that the information in the label of x and the label of y can flow into y. If we can't prove this at compile time, the assignment might leak information.

Concurrency

Concurrency poses a problem for information flow tracking because there can be covert timing channels between threads. Consider this concurrent program:
THREAD 1:
if (high) {
    sleep(200);
}
low = 1;

THREAD 2:
sleep(100);
low = 0;

Assuming the sleep function takes some time to run, then if high=1 at the beginning, it's likely that low=1 at the end. And if high=0, then low=0 at the end. If high is either 0 or 1, this program is nearly equivalent to the assignment low=high! This is an example of a covert timing channel. The Jif programming language is a sequential programming language because of this problem.

There is a larger problem with concurrency, which is the definition of security in a concurrent system. Concurrent systems are nondeterministic, meaning that the outcome of a program is not uniquely defined by its inputs. So an adequate description of the "behavior" of the program when run on an input is not just a single behavior, but something that includes a set of behaviors (and perhaps their probabilities as well). Can we extend or interpret our definition of noninterference for such systems? It turns out that there are multiple ways to do this, none of them completely satisfactory.

Possibilistic noninterference

Here the idea is that two sets of behaviors are indistinguishable if for every element of one set, there exists an indistinguishable element in the other set. The problem is that in real systems, the relative probabilities of different outcomes do communicate information. For example, in this program, which is "possibilistically secure", assuming that high is either 0 or 1:

THREAD 1: low = high
THREAD 2: low = 0
THREAD 3: low = 1

The possible outcomes are low=0 and low=1, regardless of high. Yet the system will have to work implausibly hard to make sure that the probabilities of these outcomes do not change for different high values.

Probabilistic noninterference

The probabilities of indistinguishable things must be the same across both sets. This is a strong security condition, but hard to check because we tend not to know enough about probabilities of events in real systems.

Observational determinism

Two sets are indistinguishable if every element in both sets is indistinguishable from every other element. This is an strong but enforceable property. The problem is that it prevents us from considering some interesting concurrent programs secure, such as servers that receive concurrent requests from a number of clients and want to process them concurrently in a nondeterministic order.

Downgrading: declassification and endorsement

Real systems need to violate noninterference. For example, sometimes keep information confidential for some time and then release it. An example of this is the game of battleship, where your board is initially secret but is progressively revealed to your opponent. Or they need to leak small amounts of confidential information, such as in a password checker. One way to deal with these phenomena is to add a mechanism for downgrading security labels.

Downgrading confidentiality labels is known as declassification. Downgrading integrity labels is known as endorsement.

Downgrading integrity labels corresponds to boosting the integrity of the information more than seems to be justified based on the inputs the information was computed from. This might be reasonable if the computed information is the average of a large number of less trustworthy values. Or sometimes it is just part of the rules of the system that a value is supposed to be treated as being trustworthy. An example of this is in the game of Battleship, where the opponent's move must be treated as a trusted value because it's part of the rules of the game that the opponent gets to make a move.

Jif has downgrading mechanisms:

Both of these mechanisms are effectively like type casts: they return a copy of the information under a new type. They are also dangerous. How do we prevent them from being misused, for example to launder information from a high confidentiality label to a low one?

Selective and robust declassification

Jif has two mechanisms to prevent abuse of downgrading. Selective declassification and endorsement rely on access controls to prevent downgrading from being used in code that is not trusted by principals whose security is affected by the downgrading operation. This is enforced statically.

Robust declassification uses integrity labels to mediate declassification. A declassification operations cannot be influenced by any principal to whom the information will be released by the principal. Robustness prevents a kind of self-dealing. There are two kinds of influence that need to be prevented: the value being declassified shouldn't be determined by principals to whom the information is being released (recipients), because that allows different information that intended to be laundered. Also, the decision to declassify should not be influenced by recipients, because it's a security-critical operation. Both of these conditions can be enforced by integrity constraints: the integrity of both the value being declassified, and of the pc label at the declassification point, must be sufficiently high that the recipients cannot influence them.

Interestingly, this means that integrity and confidentiality are not longer perfectly dual to each other. They are coupled through the static robustness checks.

Summary

Jif has been used to build a variety of systems with interesting security requirements. However, the technologies needed to put this approach into wide practice are still developing. More work is needed on information flow in concurrent systems. And finer-grained information release mechanisms are needed -- especially to control precisely what information is released and when it is released.