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. |