9.2 Creating Policies
Creating a systrace policy for an application is relatively straightforward, but entails an interactive series of steps. Policies must be edited to support matching arbitrary versions of shared libraries of network sockets, for example. The steps outlined here illustrate how the policy for the chat client program gaim was developed on one of the authors’ systems.
Creating an initial policy is done using the built-in systrace -A command:
$ systrace -A gaim
At this stage, an initial policy is created (in this case, in the directory /home/jose/.systrace/ usr local bin gaim). It contains specific network addresses, shared library versions, and filenames. Many of these can be edited to support arbitrary versions.
29.2.1 Editing Policies
Policy editing consists of two main questions. The first question is “which attributes can be generalized into expressions to match, rather than specific instances?” Here the policy is edited to move from“eq”tests to“match”tests with some form of globbing. The second question is “What kind of filesystem limitations should be added?” For applications that write to the filesystem, it may be worthwhile to control the directories to which they have access.
For network applications, one of the major issues in policy editing is the handling of name servers. Thefile /etc/resolv.conf allows for multiple DNS servers, yet the application typically uses only one:
native-connect: sockaddr eq "inet-[192.168.4.3]:53" then permit
In editing this entry, it is important to examine both the resolver configuration and the network environment. A laptop that uses DHCP on several networks and unknown DNS servers should edit the line to look like the following:
native-connect: sockaddr match "inet-*:53" then permit
Here any IP address on port 53 can be accessed. If a predefined list of servers is sufficient, then they can be enumerated:
native-connect: sockaddr eq "inet-[192.168.4.1]:53" then permit native-connect: sockaddr eq "inet-[192.168.4.3]:53" then permit native-connect: sockaddr eq "inet-[192.168.4.4]:53" then permit
In the case of gaim, the client connects to a variety of servers for load-balancing efforts and possibly on different ports, depending on the protocol. Here blanket socket connections could probably be allowed.
In the case of filenames, specifically for shared libraries, one can convert the policy to use the “match” operator and globbing rules. For example, a policy rule such as
native-fsread: filename eq "/usr/lib/libc.so.29.0" then permit
is easily rewritten as
native-fsread: filename match "/usr/lib/libc.so.*" then permit
and made more portable when the system is upgraded. If you wish to allow arbitrary library access and not enumerate libraries by name, the entries
native-fsread: filename eq "/usr/lib/libz.so.2.0" then permit native-fsread: filename eq "/usr/lib/libperl.so.8.0" then permit native-fsread: filename eq "/usr/lib/libm.so.1.0" then permit native-fsread: filename eq "/usr/lib/libutil.so.8.0" then permit native-fsread: filename eq "/usr/local/lib/libintl.so.1.1" \ then permit native-fsread: filename eq "/usr/local/lib/libiconv.so.3.0" \ then permit native-fsread: filename eq "/usr/lib/libc.so.29.0" then permit
can be rewritten as follows:
native-fsread: filename match "/usr/lib/*" then permit native-fsread: filename match "/usr/local/lib/*" then permit
The application can now read any file in the directory /usr/lib or /user/local/lib. Note that it will not be allowed to write to any file in that directory unless an action is stated to permit that operation.
After editing the policy, the application should be rerun with the policy used:
$ systrace gaim
Errors will be handled in two ways. First, with no auto-enforcement (systrace -a) in use, the system will ask you how you want to handle policy violations. Second, denied actions will be logged to the system’s logs:
/var/log/messages.1.gz:Jan 25 23:58:29 tank systrace: deny user: jose, prog: /usr/local/bin/gaim, pid: 27552(0)[0], policy: /usr/local/bin/gaim, filters: 104, syscall: native-connect(98), sockaddr: inet-[192.168.4.2]:53
After examining both of these feedback mechanisms, the policy can be edited to remove them and gracefully handle errors. As stated earlier, this strategy involves an interactive process of policy editing and testing.
29.2.2 The Benefit of a Local Caching Name Server
One of the complexities of a systrace policy with respect to a dynamic system (such as a laptop) is the variety of networking changes it generates as it travels around. For example, the DNS servers used by the system will change for each network used, as reflected in the varied entries in /etc/resolv.conf. As network client applications are used, they will contact these new DNS servers. If the systrace policy is restrictive in terms of the IP address of the socket used for DNS, then the application will fail as it enforces this policy.
One option is to build a generic systrace policy that can connect to any address on port 53 (for DNS):
native-connect: sockaddr match "*:53" then permit
Any IP address will then be matched and DNS will be allowed.
Another option is to use a local, caching-only DNS server as your primary DNS system, along with a configuration that keeps this option static. For example, a name server entry in the file resolv.conf that specifies nameserver 127.0.0.1 will cause client applications to use the local DNS server. For DHCP users, not requesting the domain-name-servers option will also be useful. In the file /etc/dhclient.conf, such a configuration will request other information, but not DNS server information, and actively reject the DNS server information offered:
interface "fxp0" { request subnet-mask, broadcast-address, time-offset, routers, domain-name, host-name; supersede domain-name-servers 127.0.0.1; }
Now the local DNS server will be used. On the one hand, this scheme requires the additional complexity of running a local, caching-only DNS server, which is not desirable for some systems. On the other hand, it gives a static systrace option for all network client applications.