VirtualizationControls access to the external resources or different program's components
The chapter expands on the usage of context based polymorphism, in other words, reasoning over CFG, as a ground to implement application level virtualization.
One way to approach the virtualization problem is to model it in terms of satisfying constraints imposed by environment over agents that operate within it, as presented below:
- sizo (distorted abbreviation for SEcurity ZOne): logical entity introduced to represent an environment and describe the desired virtualization outcome.
- zek (distorted abbreviation for SEcurity aGent): represents behaviour of the code in terms of virtualized resources access.
Basic idea is to automatically reason over information defined by sizos and zeks and produce virtualization plan as a solution that dictates which parts of code should be virtualized and how exactly. Such reasoning enables two features:
- Optimization. Allows choosing virtualization technique with the smallest performance penalty nevertheless satisfying necessary requirements.
- Safety. Validates manually chosen virtualization plan to ensure it is solid and operable by checking that it indeed satisfies requirements.
In other words, context based reasoning provides improvements by virtualizing only necessary sections of code and only for necessary type of resources by employing as lightweight virtualization strategy as possible – just enough to comply with the safety and security requirements expressed by annotations in the code.
Background
Virtualization refers to abstracting code from the underlying resources used by it. Here the term 'resource' depicts any external entity such as a device, file, network connection etc, for which it is desirable to regulate access.
Virtualization is a vast area and broad term that includes number of techniques on different levels to achieve several important goals such as:
- Shared access — allowing several clients to use the same resource while behaving as if each client were sole resource user, to simplify development and testing.
- Isolation — cornerstone of safety and behaviour repeatability achieved by minimizing influence of isolated clients between each other and external environment.
- Adaptation — allowing client application work in an unexpected environment it was not developed for, by emulating "native" familiar environment thus reducing adaptation and support costs.
Due to the importance of goals achievable with virtualization, it is unavoidable in a long run. That being said, basic virtualization techniques have performance penalties arising from indirect and regulated access to underlying resources.
Further discussion is concerned with what can be done to alleviate major virtualization inefficiencies by fine-grained control over what should be virtualized, when, and how.
Access Control
A whole program can be broken down into one or more virtualization zones, each having different appropriate type of virtualization strategy. Such approach allows to model hybrid virtualization, i.e. different parts of a program are virtualized differently depending on certain conditions. To capture this concept, the term sizo is introduced, that refers to a logical entity that holds information about a particular zone necessary to find best suited virtualization strategy.
There is an annotation assign_sizo to specify sizo a code block is assigned to:
- sizo-ref unique sizo's identifier
Next thing is to specify which resources a particular sizo controls access to, as demonstrated below:
It indicates that the current sizo (sizo that spans over the code block wherein the annotation is located) regulates all access to resources of a given type, resource-type. Conversely, if for a particular environment there is no need to control e.g. file system access, no virtualization for file operations is applied.
On the other hand, there is an annotation to mark a function that accesses one or another resource:
Let's consider an example to demonstrate all the above:
import raw ("scripts/cfa/context.lp"). //enable context reasoning import raw ("scripts/virtualization/virtualization.lp"). //enable virtualization reasoning guard:: strategy(direct) { openFile = function(filename::string):: int { printf("direct file access") } } guard:: strategy(common) { openFile = function(filename::string):: int; assign_zek_access(files) { printf("virtualized file access") } } main = function:: int; entry { context:: assign_sizo(zoneA); assign_sizo_control(files). openFile("/some/file") }
The example outlines a dummy function openFile to model file system access. The function includes two specializations with the strategy(direct) guard to model direct access, and the strategy(common)guard to be invoked if virtualization is enabled. It is also annotated with assign_zek_access(files) to indicate that it accesses the file system. On the other hand, the context of the function main defines the zoneA sizo and enables control over file operations.
Reasoning works with the provided information and decides whether it is necessary to enable virtualization. In this case, the answer is yes for zoneA, because of the fact that the sizo controls file operations and that there is actually a function within the sizo that requires file access. Consequently, the example outputs the following:
virtualized file access
confirming that specifically virtualized specialization of openFile was invoked.
Isolation
As shown in the previous section, it is possible to enable (or disable) virtualization on a per resource basis. However, such functionality is limited in a sense that if several sizos allow access to the same resource they can interfere with each other. Thus, next step to consider is isolation, i.e. zeks in different sizos should not have the ability to access the same resource, but rather work with their own set of resources associated with a particular sizo. The following examples, just as the previous one, are focused on file operations as the most ubiquitous type of resources.
One way to isolate file access is to associate a unique file prefix with each sizo. If virtualization is enabled, all filenames in the sizo are silently transformed on the fly by adding an assigned prefix. This way, all the file operations from one sizo are confined within a specific directory allocated solely for that particular sizo, or simply have a unique prefix if the same directory contains files belonging to a different sizo(s).
main = function:: int; entry { seq { context:: assign_sizo(domainA); assign_sizo_control(files). openFile("test") } { context:: assign_sizo(domainA). openFile("test") } { context:: assign_sizo(domainB); assign_sizo_control(files). openFile("test") } }
In this example, the file test is accessed from different sizos domainA and domanB. As several "competing" sizos are declared, they are isolated, and openFile resolves test to a different filename depending on which sizo it was called from. One possible way to implement the discussed strategy is shown below:
import raw ("scripts/cfa/context.lp"). //enable context reasoning import raw ("scripts/virtualization/virtualization.lp"). //enable virtualization reasoning import raw ("scripts/virtualization/test-Isolation_1.assembly.lp"). //additional configuration DictSizo = type slave dict_sizo. Sizo = type slave virt_sizo. guard:: strategy(direct) { resolveFilename = function(filename:: string):: string; assign_zek_access(files) { filename } } guard:: strategy(prefix) { resolveFilename = function(filename:: string):: string; assign_zek_access(files) { dictSizo = intrinsic query("dict_sizo")::[DictSizo]. sizoId = intrinsic query late("sizo_current"->sizoCurrent:: Sizo):: int; demand(sizo) { loop fold(dictSizo->entry::DictSizo, 0->id):: int { if(entry[0] == sizoCurrent):: int { entry[1] } else { id } } }. buf = "00"::string. seq { sprintf(buf, "%d/%s", sizoId, filename) } { buf } } } openFile = function(filename:: string):: int { filenameReal = resolveFilename(filename):: string. printf("File opened: '%s'%c", filenameReal, 10) }
Example outputs:
File opened: '0/test' File opened: '0/test' File opened: '1/test'
In this example, the function openFile calls resolveFilename to find out the real filename. It can be said that resolveFilename serves as hypervisor dereferencing file pseudonym into a real filename. For this purpose, resolveFilename consists of two specializations: specialization strategy(direct) serves the non-virtualized environment leaving the filename without any processing, while the other specialization strategy(prefix) implements the resolving strategy by adding a sizo-associated prefix to each file. More specifically, a unique index is assigned to each sizo, and resolveFilename uses the index as a file name prefix.
Resolution function resolveFilename has only one parameter filename, deriving the required prefix from late context associated with a particular sizo.
Isolation Categories
Every optimization technique is applicable only if certain preconditions are met. Indeed, only a general approach can handle a general task. However, for practical instances some improvements are always possible by tailoring to a particular use case specifics and subtle details. In other words, the more information available, the more space there is for improvements. And the first step along this road is the very ability to express and reason about such additional information.
As a demonstration, in order to improve reasoning to find out the optimal virtualization strategy for a particular use case, different sizo categories can be introduced, as below:
- Inward Isolation. The category describes sizo that prohibits access of other sizos to its internal resources, but is able to access external resources freely. For example, monitoring and supervision software may have been assigned this type of isolation, where it freely accesses subordinate zones but cannot be influenced from the outside.
- Outward isolation. The exact opposite of inward isolation. Allows access from external sizos, but is only allowed to use its own internal resources, so no influence over the outside world is possible. Appropriate for various sandboxes and testing environments to run a potentially insecure code.
For file operations, inward isolation may be implemented as a virtualization strategy that requires from other sizos compulsory usage of file prefixes so that no other sizo could access internal data of inwardly isolated sizo. Conversely, outward isolation is compatible with the strategy that involves assigning a prefix for this very sizo, so it can in no way access any external data, being at the same time exposed to the outside world and any sizo that has a permission to know a unique assigned prefix able to access the internal data of the sizo in question. To put it simply, strategy for these types can be described with following points:
- Inward isolation — requires prefixes for other sizos.
- Outward isolation — requires a prefix for itself.
There is an annotation introduced to declare a category for a current sizo:
Consider the example below:
test = function:: int; entry { seq { context:: assign_sizo(zoneA); assign_sizo_control(files); assign_sizo_category(inward). openFile("test1") } { context:: assign_sizo(zoneB); assign_sizo_control(files); assign_sizo_category(outward). openFile("test1") } }
There are two sizos declared in the code above. Using the reasoning apparatus developed in the previous sections, both sizos activate virtualization, for both of them control some file resources and both contain openFile that actually requires file access. However, this time additional bits of information are available, namely zoneA and zoneB are declared as inward and outward, respectively. According to the strategy outlined above, zoneA enables prefix based isolation strategy for zoneB, and zoneB enables isolation for itself as well. As a result, it is enough to virtualize only one zone (zoneB) leaving zoneA to enjoy a direct access to file resources. Example outputs are shown below, confirming that the direct file access is granted to zoneA:
File opened: 'test1' File opened: '1/test1'