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).
Links:
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:
#!/bin/bash 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 fi 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 fi 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 fi 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 fi if [ -z "$(mount -t sysfs | grep -w /mnt/union/sys)" ]; then sudo /bin/mount -t sysfs sysfs /mnt/union/sys 2> /dev/null fi if [ -z "$(mount -t proc | grep -w /mnt/union/proc)" ]; then sudo /bin/mount -t proc proc /mnt/union/proc 2> /dev/null fi if [ -z "$(mount | grep -w /mnt/union/tmp)" ]; then sudo /bin/mount --bind /tmp /mnt/union/tmp 2> /dev/null fi else echo "Mount of /mnt/union failed." exit 2 fi } function umount_unionfs { # # unmount /tmp # if [ -n "$(mount | grep -w /mnt/union/tmp)" ]; then sudo /bin/umount /mnt/union/tmp 2> /dev/null fi # # unmount /proc # if [ -n "$(mount -t proc | grep -w /mnt/union/proc)" ]; then sudo /bin/umount /mnt/union/proc 2> /dev/null fi # # unmount /sys # if [ -n "$(mount -t sysfs | grep -w /mnt/union/sys)" ]; then sudo /bin/umount /mnt/union/sys 2> /dev/null fi # # 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 fi # # 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 fi # # unmount /dev # if [ -n "$(mount | grep -w /mnt/union/dev)" ]; then sudo /bin/umount /mnt/union/dev 2> /dev/null fi if [ -n "$(mount | grep -w /mnt/union )" ]; then sudo /bin/umount /mnt/union 2> /dev/null fi } mount_unionfs # enter the chroot sudo /usr/sbin/chroot /mnt/union /bin/su --shell /bin/bash --login $USER # umount temporary filesystems umount_unionfs EOF
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
Password:
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
usr/lib/pkgconfig/audiofile.pc
usr/lib/libaudiofile.la
usr/lib/libaudiofile.a
usr/lib/libaudiofile.so.0.0.2
usr/include/audiofile.h
usr/include/aupvlist.h
usr/include/af_vfs.h
usr/bin/audiofile-config
usr/bin/sfconvert
usr/bin/sfinfo
usr/share/aclocal/audiofile.m4
[root@hostname ]# find usr -type d
usr
usr/lib
usr/lib/pkgconfig
usr/include
usr/bin
usr/share
usr/share/aclocal
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 \
done
[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 \
done
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.