Cover V14, i02

Article

feb2005.tar

The SolarisTM 10 Zone Defense

Kevin Wenchel

Zone technology is a new component of the Solaris 10 N1 Grid Computing Environment. Zones allow systems administrators to partition a single instance of the Solaris operating environment into multiple virtual OS instances, each completely isolated from and unaware of the others. In contrast to virtual machine software products, such as VMWare, which virtualize physical hardware to allow multiple operating systems to run independently on a single physical machine, Solaris zones virtualize the Solaris operating system environment itself. This allows multiple instances of the Solaris operating system environment to run within a single instance of the Solaris kernel.

Zone technology has many practical uses for system resource management and system security. In this article, I will discuss the benefits of zones to enhance system security, describe the basic procedures used to configure and manage zones, and provide a practical demonstration of zone technology to isolate an Apache Web server instance. Although this article addresses zones from a security standpoint, the information provided on zone configuration and management is generally applicable to any use of zones.

Virtualization and Security

A general security goal when deploying a network application on a server is to limit the potential for damage should an attacker compromise that application. Consider a scenario in which an attacker exploits a vulnerability in a Web server such as Apache to gain local access on the server. Now the attacker potentially has access to roam the system searching for local vulnerabilities to escalate his privileges. If successful, the attacker could potentially control the entire server and all of the data and applications hosted therein.

One tool often used in the Unix environment to isolate a network application from other resources on the system is the chroot(1M) facility. Chroot allows an administrator to lock a process and all of its descendants in a "virtual" file system. The chrooted process is assigned a root directory relative to the actual system root directory and is prevented from accessing files located outside of this root. Chrooting an application requires all files used by the application, including libraries, binaries, device, and configuration files, to be identified and duplicated within the chroot directory. This can sometimes be an arduous task.

Additionally, although chroot restricts the ability of a process to roam the file system, chrooted processes themselves are not truly isolated from other resources and processes on the system. A chrooted process can still potentially observe and communicate with non-chrooted processes. Also, methods have been proposed for breaking out of chroot environments [1]. See:

http://www.bpfh.net/simes/computing/chroot-break.html
Solaris zones technology offers a vast improvement over the use of chroot to isolate processes. Using Solaris zones allows you to create a complete virtual OS environment to isolate processes not only at the file system level, but also at the process and networking levels. In Solaris 10 parlance, every Solaris system contains one zone, known as the global zone, and zero or more non-global zones. The global zone is really no different from the environment that you're used to running on a pre-Solaris 10 system. The global zone is used to manage physical hardware devices and for administration of non-global zones.

A non-global zone is a virtual OS environment. Processes running in non-global zones are unable to observe or communicate with processes in other zones. Even the details of underlying physical hardware, such as disk device names, are hidden to processes executing in non-global zones. If the security of a non-global zone is breached, the security of the other zones on the system is not compromised. Furthermore, processes executing within non-global zones operate under various restrictions. For example, they cannot perform hardware administration or load or unload kernel modules.

The use of zones does not obviate the need for hardening operating systems and applications. If an attacker gains root access to a non-global zone, the attacker can kill processes and delete files located within the zone and also reboot the zone. However, the attacker will be unable to damage the data and applications running in other zones on the system. Also, if a zone is hacked, cleaning up after the fact is simpler. Non-global zones can be rebooted independently of other zones, so a zone can be restored or rebuilt without downing the entire system and affecting the availability of applications running in other zones on the machine.

Other flavors of Unix offer features similar to Solaris 10 zones. If fact, the FreeBSD Jail mechanism [2], first appearing in FreeBSD 4.0 released in 2000, provides essentially the same capabilities as Solaris 10 zones; see:

http://phk.freebsd.dk/pubs/sane2000-jail.pdf
Also, in the Linux environment, the Vserver project [3] provides a kernel patch and a set of tools to allow the use of virtual Linux OS environments; see:

http://linux-vserver.org/Linux-VServer-Paper
Solaris Zones Implementation

Solaris 10 supports up to 8,191 non-global zones in a single instance of the Solaris operating system. Each non-global zone is assigned a root directory relative to the global zone's root directory. Fortunately, creating a non-global zone does not require duplicating the entire Solaris operating system and its supporting files and libraries in the non-global zone's root directory. Instead, only a relative handful of system binaries and configuration files are actually duplicated. The majority of system libraries and binaries are shared between the global zone and non-global zone. The /lib, /platform, /sbin, and /usr directories are shared from the global zone to non-global zones via a read-only, loopback file system.

A non-global zone can be assigned to use one or more of the physical network interfaces present on a system. For each physical interface assigned to a non-global zone, a virtual network interface is created and assigned a unique IP address. Processes running in a non-global zone can only send and receive traffic using the virtual network interfaces that have been assigned to that zone.

Processes executing within non-global zones face various restrictions. For example, it is impossible to load or unload kernel modules from within a non-global zone, even with root privilege. Furthermore, processes running in non-global zones are restricted from using the raw socket interface for traffic other than ICMP traffic. Prohibiting the use of the raw socket interface for sending TCP and UDP traffic prevents processes from sending spoofed IP packets.

Zone Resource Requirements

As stated in Sun's documentation [4] (http://docs.sun.com/app/docs/doc/817-1592), the disk space requirements for a non-global zone depend upon the packages installed in the global zone, and Sun recommends 100 MB of free disk space when creating a non-global zone if the global zone has been installed with the "standard Solaris packages".

My global zone was installed with the "Entire Distribution Plus OEM Support" cluster, and I found that my non-global zones typically take about 85 MB of disk space. Sun also recommends an additional 40 MB of RAM for each zone as a general rule, but this is not a requirement.

At the time of writing, the production release of Solaris 10 was not yet available. All of my Solaris 10 testing was performed using the Solaris 10 x86 b63 Beta (dated august August 14, 2004). My Solaris 10 test system was actually running within a VMWare session on a Dell Precision 670 with 1 GB of RAM and dual Pentium 4 Xeon processors running Windows XP as the base OS.

Configuring a Zone

The first step in creating a zone is to create a zone configuration. To begin, gather several pieces of information, including the name of the physical Ethernet adaptor that the non-global zone will use, an IP address to assign to the non-global zone, a name to assign to the non-global zone, and a directory path to use as the non-global zone's root directory. In this example, my physical Ethernet device is pcn0, I will assign the zone an IP address of 192.168.1.11, and the zone will be named "twilight". I prefer to place my zone's root directory on a separate partition; in this case, the partition is mounted under /zone, so my zone's root directory will by /zone/twilight.

The zonecfg(1M) utility configures non-global zones. To configure a zone, run zonecfg with the -z option and specify the name of the zone. Once started, zonecfg will provide an interactive prompt from which you configure the zone using a series of subcommands.

After issuing the create subcommand, assign values to the zonepath and autoboot parameters using the set command. The zonepath parameter specifies the zone's root directory, and the autoboot parameter determines whether the zone is automatically booted at system boot time. To assign network interfaces to the zone, use the add net subcommand and then assign the physical network device and an IP address:

# zonecfg -z twilight
twilight: No such zone configured
Use 'create' to begin configuring a new zone.
zonecfg:twilight> create
zonecfg:twilight> set zonepath=/zone/twilight
zonecfg:twilight> set autoboot=true
zonecfg:twilight> add net
zonecfg:twilight:net> set address=192.168.1.11
zonecfg:twilight:net> set physical=pcn0
zonecfg:twilight:net> end
You can review the zone configuration you've just created by issuing the info subcommand. Notice the "inherit-pkg-dir" lines displayed in the info output. As mentioned earlier, it is the default behavior to share these file systems and their packages between global zone and non-global zones. The administrator can configure the sharing of additional directories between global and non-global zones (as will be demonstrated later):

zonecfg:twilight> info
zonepath: /zone/twilight
autoboot: true
pool:
inherit-pkg-dir:
        dir: /lib
inherit-pkg-dir:
        dir: /platform
inherit-pkg-dir:
        dir: /sbin
inherit-pkg-dir:
        dir: /usr
net:
        address: 192.168.1.11
        physical: pcn0
To complete zone configuration, verify the configuration, commit the changes, and then exit:

zonecfg:twilight> verify
zonecfg:twilight> commit
zonecfg:twilight> exit
Solaris stores the configuration information for each non-global zone under the /etc/zones directory. The startup script /etc/rc3.d/S99zones is responsible for starting non-global zone environments at system boot time.

Installing a Zone

The second step in creating a zone is to "install" the zone. The install process builds the non-global zone by copying necessary binaries and system configuration files to the zone's root directory and initializing the non-global zone's package database. The zoneadm(1M) command is used to install a zone:

# zoneadm -z twilight install
Preparing to install zone <twilight>.
Creating list of files to copy from the global zone.
Copying <2860> files to the zone.
Initializing zone product registry.
Determining zone package initialization order.
Preparing to initialize <765> packages on the zone.
Initialized <765> packages on zone.
Zone <twilight> is initialized.
The file </zone/twilight/root/var/sadm/system/logs/install_log> \
  contains a log of the zone installation.
During zone installation, Solaris populates the root directory of the non-global zone with copies of system configuration files such as /etc/passwd, /etc/group, /etc/default/*, /etc/inet/inetd.conf, etc. The versions of the files installed into the non-global zone are the default versions installed during a fresh OS installation. The non-global zone does not inherit versions of these files from the global zone. This means that any changes you've made to these configuration files in the global zone (e.g., in the course of hardening the system) must be reapplied in each non-global zone that you create.

The zone install will usually take a few minutes. Once completed, you must boot the zone by issuing the zoneadm command with the boot parameter from within the global zone:

# zoneadm -z twilight boot
After booting a non-global zone for the first time, you must log in to the console of the non-global zone and complete some basic setup. To log in to the non-global zone's console, use the zlogin(1) command with the -C option and specify the zone name:

# zlogin -e \@ -C twilight
The -C option to zlogin specifies the target zone. The -e option to zlogin specifies the escape character used to disconnect from the zone's console. If the -e option is not specified, zlogin uses the "~" character as the escape sequence, but this conflicts with the escape sequence used by SSH. Thus, it is best to specify an alternate escape sequence value if you're running zlogin from within an SSH session.

After logging into the new zone for the first time, you will be prompted for basic setup information such as language, locale, host name, Kerberos security options, name service configuration, time zone, and root password. Once completed, the non-global zone is ready for use.

To disconnect from the console of a non-global zone, type a "@" and a "." almost simultaneously. This is awkward and may take a few tries to get right.

Zone Management

You must perform all non-global zone management from within the global zone. Most zone management commands will return an error if you attempt to run them from within a non-global zone:

# zonecfg -z twilight
Zonecfg can only be run from the global zone
The zoneadm, zlogin, zonename, and zonecfg commands provide all of the essential zone management capabilities. To view all the zones present on the system (global and non-global), run the zoneadm command from within the global zone using the list subcommand and -v option:

# zoneadm list -v   
  ID NAME             STATUS         PATH
   0 global           running        /
   1 twilight         running        /zone/twilight
To shut down a non-global zone cleanly, use the zlogin command as shown below:

# zlogin twilight shutdown
To completely remove a non-global zone from the system, first halt the zone, then uninstall the zone, and finally un-configure the zone. Be forewarned -- removing a non-global zone results in the destruction of all files and data associated with that zone:

# zoneadm -z twilight halt
# zoneadm -z twilight uninstall
Are you sure you want to uninstall zone twilight (y/[n])? y
# zonecfg -z twilight delete
Are you sure you want to delete zone twilight (y/[n])? y
#
Finally, the zonename command, which can be run from both global and non-global zones, provides a convenient way to determine which zone you are currently in:

# zonename
global
Basic Systems Administration Task with Zones

Many of the standard Solaris system management commands have been modified to support the concept of zones. For example, the df utility now supports the use of the -Z option, which when run from the global zone displays the disk mounts present in all zones.

When run from within the global zone, /usr/bin/ps with the -ef option will display processes running in all zones. To distinguish the zone in which a particular process is running, add the -Z option. This will add an additional column to the ps command output that indicates the name of the zone in which each process is running. Alternatively, if you wish only to display the processes running in a particular zone, use the -z option and specify the zone name.

One task that doesn't change is that of killing processes. Process IDs are unique across all zones, so when issuing the kill command from the global zone, there is no need to specify a zone name.

Package and Patch Management

Package and patch management is probably the most complex part of managing Solaris zones. Although it generally makes sense, there are a couple of new rules to remember.

When the pkgadd command is run from the global zone, all zones on the system are affected by the operation. This makes it simple to install a new package to the global and all non-global zones of a system. If the pkgadd command is run from the global zone using the -G option, however, the operation will apply only to the global zone. If the packages being added with the pkgadd command already exist in the global zone, then use of the -Z option will cause the operation to apply only to non-global zones.

There is no way to run the pkgadd command from within the global zone and have the operation apply to only a subset of non-global zones. If you want to add a package to only a subset of your non-global zones, you must log into those non-global zones individually and run pkgadd.

The pkgrm command works similarly to pkgadd. There is one interesting twist, however. When pkgrm is run from the global zone with the -Z option, the package is removed from all non-global zones. Also, the package will not be installed in any future non-global zones that are created.

Patch management is a little simpler. If you run the patchadd or patchrm utilities from within the global zone, all zones will be affected. If you run patchadd or patchrm within a non-global zone, the action applies only to that zone.

There is one additional point to note regarding package and patch management within non-global zones. When running package or patch management utilities from within a non-global zone, the operation will only succeed if it does not affect packages/patches that are currently shared between the non-global zone and the global zone.

An Apache Zone

To illustrate the use of zones, I will create a non-global zone, named "webserver", to host an Apache 2.0.52 Web server instance. My strategy for building this zone is as follows. The Apache server will be built and installed within the global zone. The Web server content itself will also reside in the global zone. The Apache code tree and Web content will be shared from the global zone to the non-global zone via a read-only loop back mount. This has the advantage of protecting the Web content from alteration by processes in the non-global zone.

The first step is to build the Apache server. In my global zone, I created a mount point called /web. I will install my Apache server under /web/apache2. The sequence of commands used to build and install my Apache server follow:

# cd /tmp
# gunzip -c httpd-2.0.52.tar.gz | tar xf -
# cd httpd-2.0.52
# ./configure --prefix=/web/apache2
# make
# make install
In my global zone, I will also create an httpd group to own the Apache installation:

# groupadd -g 99 httpd
# chmod -R root:httpd /web
# chmod -R 750 /web
By default, Apache will write log files and pid files under its install directory. Because the non-global zone will mount the /web directory in read-only mode from the global zone, the Apache server running in the non-global zone will be unable to write any files under /web. The PidFile, Lockfile, ErrorLog, and CustomLog directives in the Apache configuration file (/web/apache2/conf/httpd.conf) must be modified to point to a location writable by the non-global zone. I modified these directives as shown below:

PidFile /var/run/httpd.pid
LockFile /var/run/accept.lock
ErrorLog /var/log/apache_error_log
CustomLog /var/log/apache_access_log
For the sake of security, I want the Apache server to reside under a separate, unprivileged account. Once the non-global zone is up and running, I will create an httpd user and group in the non-global zone for this purpose. In the meantime, I will modify the Apache configuration file to specify "httpd" in the User and Group directives as shown:

User httpd
Group httpd
Once the Apache server setup is finished, I will create the non-global zone. The configuration process is essentially the same as that shown in the earlier example. In this case, the zone name is "webserver", the root directory is /zone/webserver, and the IP address is 192.168.1.12. Furthermore, to share the /web directory between the global and non-global zones, I use the add inherit-pkg-dir command as shown below:

# zonecfg -z webserver
webserver: No such zone configured Use 'create' to begin 
           configuring a new zone.
zonecfg:webserver> create
zonecfg:webserver> set zonepath=/zone/webserver
zonecfg:webserver> set autoboot=true
zonecfg:webserver> add net
zonecfg:webserver:net> set address=192.168.1.12
zonecfg:webserver:net> set physical=pcn0
zonecfg:webserver:net> end
zonecfg:webserver> add inherit-pkg-dir
zonecfg:webserver:inherit-pkg-dir> set dir=/web
zonecfg:webserver:inherit-pkg-dir> end
zonecfg:webserver> info
zonepath: /zone/webserver
autoboot: true
pool:  
inherit-pkg-dir: 
dir: /lib
inherit-pkg-dir: 
dir: /platform
inherit-pkg-dir: 
dir: /sbin
inherit-pkg-dir: 
dir: /usr
inherit-pkg-dir: 
dir: /web
net: 
address: 192.168.1.12 
physical: pcn0
zonecfg:webserver> verify
zonecfg:webserver> commit
zonecfg:webserver> exit
Once the zone is configured, I install and boot the zone as described in the earlier example. Once the zone is up and running, I create the httpd user and group:

# groupadd -g 99 httpd
# useradd -d /www/apache2 -u 99 -g httpd httpd
Note that I use the same gid for the httpd group in the non-global zone as in the global zone. This ensures that httpd group in the non-global zone will have access to the files under /web/apache2, which are owned by the httpd group in the global zone.

Finally, I create an rc startup script for the Apache Web server in the non-global zone named /etc/init.d/apache_2_0_52 with the following contents:

#! /bin/sh

APACHECTL=/web/apache2/bin/apachectl

case "$1" in
start) $APACHECTL start
       ;;
restart) $APACHECTL restart
       ;;
stop) $APACHECTL stop
       ;;
*)    echo "usage: $0 start|restart|stop"
       ;;
esac
The following commands install the rc startup script:

# chmod 744 /etc/init.d/apache_2_0_52
# ln /etc/init.d/apache_2_0_52 /etc/rc3.d/S99apache
At this point, the Apache server is ready to run from the non-global zone. This configuration represents one of a number of possible variations for building a non-global zone to host Apache.

Conclusion

While clearly useful for isolating applications, the use of zones does not obviate the need for hardening operating systems and applications. The use of zones does limit the ability of an attacker to gain control over the entire system should a non-global zone be compromised and also potentially reduces the amount of cleanup necessary after a compromise. Even when using zones, you'll still want to harden both global and non-global zones as well as Web servers, using best practices such as those available from The Center for Internet Security [5]:

http://cisecurity.org
Also, there are several other features of zones not touched on in this article. These include the ability to assign resource usage limits (e.g., CPU and memory usage limits) to a zone. Further information on these capabilities can be found in the Sun documentation.

References

1. "Breaking out of a chroot() padded cell." 23 Nov. 2004 -- http://www.bpfh.net/simes/computing/chroot-break.html

2. Kamp, Poul-Henning, Robert N.M. Watson. "Jails: Confining the omnipotent root." 18 Nov. 2004 -- http://phk.freebsd.dk/pubs/sane2000-jail.pdf

3. Potzl, Herbert. "Linux-Vserver-Paper." 18 Nov. 2004 -- http://linux-vserver.org/Linux-VServer-Paper

4. "System Administration Guide: N1 Grid Containers, Resource Management, and Solaris Zones." 18 Nov. 2004 -- http://docs.sun.com/app/docs/doc/817-1592

5. "Center for Internet Security -- Standards." 18 Nov. 2004 -- http://cisecurity.org

Kevin Wenchel has an MS in computer science and 8 years of experience in systems programming and systems management. His primary work-related interests include operating system security, digital forensics, and reverse engineering. He can be reached at: kevin.wenchel@jhaupl.edu.