Hiding Data Flows with Covert Channels

Saumya Debray and Jon Stephens, University of Arizona

Secure information flow ensures the confidentiality and privacy of sensitive data from being publicly leaked or illegitimately being accessed by attackers. This is usually achieved by defining securely typed programming languages and/or run-time systems that keep track of (aka taint) sensitive data and prevent unwanted leaks [1]. Securely typed languages tag variables with different security labels and enforce some security controls at run-time, ensuring a secure flow of information. However, running a piece of code on the underlying system that facilitates the computation (including the hardware, operating system, etc.) incurs some side effects upon the system. These side effects can be exploited to infer information about the running code or the data used in the computation. These side effects are referred to as side-channels: they are not the primary channels of communication and are therefore easily overlooked. As a result, they are often not properly monitored to ensure the secure flow of information. As an example, the running time of a piece of code or the amount of power it consumes can be considered side-channels. Previous successful attacks using side-channels show that ignoring side-channels imposes significant security risks in protecting sensitive data.

On the other hand, current complex run-time systems, such as interpretive systems including Java/Python or even operating systems, involve many complicated and large components that actively interact with each other. For instance, an interpreter is usually equipped with a JIT compiler, which, as it will be shown later, can be affected by the input program in a way that makes it possible to exfiltrate information. Other parts of the run-time system, such as the thread library or garbage collector, can also potentially leak information. Keeping track of all the side-effects caused by input program/data is infeasible, in part due to the complexity and size of the whole run-time system. Even if the overhead of doing so is acceptable, it leads to too many false positives since normal programs also tend to cause similar side-effects. Furthermore, an attacker is not bound to use only one of the mentioned side-channels—multiple side-channels can be utilized where each side-channel contributes to leaking a bit of information. In such a case, combining all the bits from multiple channels provides the full-fledged side-channel-based obfuscation.

Here we briefly discuss some of the available challenges and opportunities in exploiting the side effects of large run-time systems in order to gain access to and propagate protected information. We will also briefly discuss the difficulties of coping with these types of information leaks due to the complexity and the wide domain where these side-channels can be utilized.

Side-Channel-Based Obfuscations

There are numerous ways to construct side-channel based obfuscations, but we will target interpretive systems. The attack is based on run-time JIT compilation, which is common in modern interpreters. While interpretive systems are specifically being targeted, the idea is generally applicable to run-time systems. For instance, the example can be adapted to use the CPU’s data cache or the kernel’s file cache rather than the JIT compiler.

In this work, we use time-based side-channels in run-time systems where the behavior of the execution is affected by the inputs. Most interpretive systems, such as Java, use a JIT compiler to optimize parts of the code that tend to take much of the execution time. With JIT compilation enabled, if a function is executed for a number of times greater than a threshold, JIT compiler compiles the function into machine code that tends to execute faster than the interpreted version. The following code in Python (Note: CPython implementation does not come with a JIT compiler but there are other versions that do) uses a time-based obfuscation technique to take advantage of the run-time JIT compilation to propagate a value.

#Foo is a list of k functions, each taking T(ms) to execute
Secret = key
Public = 0
for i in range(k):
         c = (Secret&(1 << k))*LARGE
         while c:
                     Foo[k]()
                     c = c-1
         s = time.time()
         Foo[k]()
         Public = Public | (time.time()-s)/T

The idea is to force the JIT compiler to compile a function based on the value that needs to be propagated (k bits of the variable Secret in the code sample). Assuming the JIT-compiled and the not-JIT-compiled versions of the function take different amounts of time to execute, it is possible to infer some information about the data. Depending on the bit that needs to be copied, we can force JIT compilation of a function. Measuring the time needed to execute the function tells us whether it has been JIT-compiled or not, and hence reveals the value of the bit.

Function Foo[k]() takes more than T milliseconds to run if not JIT-compiled. The T is computed such that Foo[k] takes more than T to finish if interpreted and less than T if JIT-compiled (T can be computed at run-time by choosing a value slightly less than the running time of Foo[k]). Line 5 assigns a large number to c if the kth bit of Secret is one and zero otherwise. This forces the JIT compiler to compile Foo only if the kth bit in Secret is one meaning that by later executing Foo[k]() and measuring the time taken to execute Foo, it is possible to infer the bit was zero or one. To leak k bits of information from Secret, it is only needed to repeat the process using k functions, one for each of the k bits.

Summary

Traditionally, side-channels of information refer to the data flows derived from non-primary channels of communication (e.g., input/outputs). Carefully observing some (external) characteristics of a run-time system, such as measuring run-times and/or consumed energy, can provide a powerful channel of information that can leak data or the algorithm used in the computation.

We discuss the idea of side-channels in a broader context. Specially, wide-spread use of complex run-time systems, such as interpretive systems, opens up different attack vectors to gain unauthorized access to confidential data. Moreover, wide-range of possible side-channel sources and the complexity of such run-time systems, makes the problem of preventing these types of attacks specially challenging.

References

[1]   Sabelfeld, Andrei, and Andrew C. Myers. “Language-based information-flow security.” IEEE Journal on selected areas in communications 21, no. 1 (2003): 5-19.