Entering A Safe Mirror When Logging In With Unionfs And Chroot

Want to support HowtoForge? Become a subscriber!
Submitted by stefbon (Contact Author) (Forums) on Wed, 2007-06-27 17:03. :: Security | Storage | Other

Entering A Safe Mirror When Logging In With Unionfs And Chroot

1. Introduction

When reading a 'hint' on the website of LinuxFromScratch I discovered the special capabilities of unionfs, specially in combination with chroot. Later I read a HowTo on a wikiwebsite of Gentoo, about entering a chrooted homedirectory when using a special script as shell. Combining these two brings me to using a chrooted environment, which you enter when logging in as a special user. This environment is a exact copy (mirror) of the system you're working on. Because you're in safe copy of the real system, you can do whatever you like, it will never change the system, everything stays inside the cache (the readwrite branch).

TRIP, a TRIvial Packager for LFS (and other linux systems) - Original hint at the website of LFS
Home_directory_jail - Guide to set up a chroot jail at Gentoo


2. Basic technique

Do whatever you like, install, change and remove files from the system, and no harm whatsoever. Your real system stays untouched. This may sound like magic, but is in reality just possible by combining some techniques from all those available for Linux.
By using the filesystem Unionfs, a chroot and some well chosen remounted directories, you can set up this virtual system.


2.1 Unionfs

The most important part is the use of unionfs. Unionfs gives you the possibility to create a filesystem, which is the union of at least two others. See www.unionfs.org for more info. Now by letting the new filesystem be the union of our original filesytem (the root) in only read mode, and of a temporary filesystem (the cache) in readwrite mode, you'll have a filesystem which looks exactly like your original filesystem, but in which you can modify, delete and/or add files without doing anything to your original system. This is not possible, because the root is mounted readonly. Every modification is by the unionfs stored in the cache.
The only difference between the original and the newly created system is the path: in the new system it always starts with the path of the mountpoint of the union. This is why the next step is necessary.

A special note: today [june 2007] it looks as if unionfs will be included in the kernel. Unionfs is undergoing heavy development at this moment. Look at the website for more info.

On the website you will find information howto enable unionfs. For thye latest kernels (later than 2.6.19) there is a patch for the kernelsource, for not so recent kernels there is a external module.


2.2 (Re)Mounting

One extra thing you'll have to do is (re)mounting several crucial directories like /dev, /proc and /sys. This is because the union filesystem does not preserve existing mount points.
It's also recommended to remount some special directories like /tmp and the directory you're building the software in.


2.3 Chroot

By chrooting to this mountpoint, you enter an environment which is absolutely a copy of your system. You can do whatever you like, even remove crucial directories and files. Test it! Look how far you can go before your system gets stuck.


2.4 Logging in to this environment

Like the concept explained in Home_directory_jail it is possible by creating a special loginshell to enter the environment created with unionfs and chroot.
The idea explained here is to create a special user, with a special shell. This shell will, before entering a interactive shell, first do the necessary steps like mounting the unionfilesystem, remounting some important directories and do the chroot.


3. Preparation

3.1 The cache partition

To start a partition with sufficient space to function as cache. This does not have to be a physical partition, it may be a virtual drive.

Create this drive with:

dd if=/dev/zero of=/mnt/cache.img bs=1M count=500
mkfs.ext2 /mnt/cache.img
mkdir /mnt/cache
mount /mnt/cache.img /mnt/cache -o loop

chmod 777 /mnt/cache

mkdir /mnt/union

This creates a virtual partition (or drive) of 500M.

(Note: the loopback device has to be supported in your kernel. Kernels of most distributions do.)


3.2 Special loginshell

Create a shellscript chroot-union which will do all the necessary steps:

The chroot-union script in /bin:

function mount_unionfs {
# mount temporary filesystems
if [ -z "$(mount -t unionfs | grep -w /mnt/union )" ]; then
    sudo /bin/mount -t unionfs -o dirs=/mnt/cache:/=ro unionfs /mnt/union
if [ -n "$(mount -t unionfs | grep -w /mnt/union )" ]; then
    # basic system mounts
    if [ -z "$(mount | grep -w /mnt/union/dev)" ]; then
	sudo /bin/mount --bind /dev /mnt/union/dev 2> /dev/null
    if [ -z "$(mount -t devpts | grep -w /mnt/union/dev/pts)" ]; then
	sudo /bin/mount -t devpts devpts /mnt/union/dev/pts 2> /dev/null
    if [ -z "$(mount -t tmpfs | grep -w /mnt/union/dev/shm)" ]; then
	sudo /bin/mount -t tmpfs shm /mnt/union/dev/shm 2> /dev/null
    if [ -z "$(mount -t sysfs | grep -w /mnt/union/sys)" ]; then
	sudo /bin/mount -t sysfs sysfs /mnt/union/sys 2> /dev/null
    if [ -z "$(mount -t proc | grep -w /mnt/union/proc)" ]; then
	sudo /bin/mount -t proc proc /mnt/union/proc 2> /dev/null
    if [ -z "$(mount | grep -w /mnt/union/tmp)" ]; then
	sudo /bin/mount --bind /tmp /mnt/union/tmp 2> /dev/null
    echo "Mount of /mnt/union failed."
    exit 2
function umount_unionfs {
# unmount /tmp
if [ -n "$(mount | grep -w /mnt/union/tmp)" ]; then
    sudo /bin/umount /mnt/union/tmp 2> /dev/null
# unmount /proc
if [ -n "$(mount -t proc | grep -w /mnt/union/proc)" ]; then
    sudo /bin/umount /mnt/union/proc 2> /dev/null
# unmount /sys
if [ -n "$(mount -t sysfs | grep -w /mnt/union/sys)" ]; then
    sudo /bin/umount /mnt/union/sys 2> /dev/null
# unmount /dev/shm
if [ -n "$(mount -t tmpfs | grep -w /mnt/union/dev/shm)" ]; then
    sudo /bin/umount /mnt/union/dev/shm 2> /dev/null
# unmount /dev/pts
if [ -n "$(mount -t devpts | grep -w /mnt/union/dev/pts)" ]; then
    sudo /bin/umount /mnt/union/dev/pts 2> /dev/null
# unmount /dev
if [ -n "$(mount | grep -w /mnt/union/dev)" ]; then    
    sudo /bin/umount /mnt/union/dev 2> /dev/null
if [ -n "$(mount | grep -w /mnt/union )" ]; then  
    sudo /bin/umount /mnt/union 2> /dev/null
# enter the chroot
sudo /usr/sbin/chroot /mnt/union /bin/su --shell /bin/bash --login $USER
# umount temporary filesystems

Add the new loginshell to the /etc/shells file. You'll have to do this when PAM will check the shell.


3.3 Create user and group.

Create a new group and user with this script as shell:

groupadd -g 27 uniongroup
useradd -c "Test user for chrooted union." -d /home/unionuser \
-m -s /bin/chroot-union -g uniongroup -u 27 unionuser
passwd unionuser


3.4 Give the user enough rights

Give the new user more rights with sudo. Add the following line to the configurationfile of sudo, /etc/sudoers:

unionuser ALL=(ALL) ALL

Note: there are other ways to give this user the permissions. I'm looking at them at this moment.

Note: giving these full permissions is too much for a normal user. But for a user which will install software and modify your system it's necessary.


What is possible

Safe and secure environment for normal users

This construction is very suitable for guest users, which you cannot trust. The first thing I'v tried is starting a graphical session. I did not have any problem.

Install software as this user

Another possible use is the installation of software as this user. This can be done as follows:

  • as this user install your software. Because of the special construction, all the changes go to the cache.
  • after logging out, compare the contents of the cache with the real system.
  • the controlling user (root) has the choice to do the real install by simply moving the contents from the cache to the root

For example the compilation and installation of a small package, audiofile-0.2.6. Assume the source is in /tmp. First login:

[root@hostname ]# login
hostname login: unionuser
Last login: Wed Jun 20 19:58:32 CEST 2007 on pts/0
[unionuser@hostname ]$

Now compile and install the package:

[unionuser@hostname ]$ cd /tmp/audiofile-0.2.6
[unionuser@hostname ]$ ./configure --prefix=/usr
[unionuser@hostname ]$ make
[unionuser@hostname ]$ sudo make install

Now exit the session and check the contents of the cache:

[unionuser@hostname ]$exit
[root@hostname ]# cd /mnt/cache
[root@hostname ]# ls -Al
drwxr-xr-x 3 root root 1024 2007-06-05 17:32 home
drwxr-xr-x 6 root root 1024 2007-06-05 17:37 usr
drwxr-xr-x 3 root root 1024 2007-06-05 17:32 var
[root@hostname ]#

The home directory appears here because the shell Bash changes the file .bash_history; the var directory shows up because of changes in the file /var/run/utmp and directory /var/run/sudo. This proves it works like it should.

Now, when looking to the changes in de /usr directory where I've installed the software gives:

[root@hostname ]# find usr -type f
[root@hostname ]# find usr -type d

As you can see everything is in the /mnt/cache/usr directory.

You can make a backup of this:

[root@hostname ]# find usr | sort -u > /tmp/filelist-audiofile-0.2.6

[root@hostname ]# tar --create --files-from=/tmp/filelist-audiofile-0.2.6 \
                      --file=/tmp/install-audiofile-0.2.6.tar --directory=/mnt/cache \
                      --no-recursion --absolute-names --preserve-permissions

It's also very possible to make a backup of all the files which will be overwritten:

[root@hostname ]# for installfile in $(cat /tmp/filelist-audiofile-0.2.6); do \
                    if [ -e "/$installfile ]; then \
                      echo "/$installfile" >> /tmp/backup-audiofile-0.2.6 \
                    fi \
[root@hostname ]# tar --create --files-from=/tmp/backup-audiofile-0.2.6 \
                      --file=/tmp/backup-audiofile-0.2.6.tar --directory=/mnt/cache \
                      --no-recursion --absolute-names --preserve-permissions

Now do the real install by copying all the files to the root: 

[root@hostname ]# for installfile in $(cat /tmp/filelist-audiofile-0.2.6); do \
                   cp --verbose --force --recursive --parents --no-dereference \
                      --preserve --target-directory=/ $installfile \

Note: the commands above are to illustrate the idea. I've created scripts doing the backing up, checking and installing, and it works very good.

Please do not use the comment function to ask for help! If you need help, please use our forum.
Comments will be published after administrator approval.