Easy Containers
January 25, 2017 —
BarryK
I might as well give this effort a name, calling it "Easy Containers". I am having a go at creating containers for Quirky, using grassroots utilities, rather than the heavy-duty packaged techniques such as LXC or Docker.
Thanks to jamesbond and heaps of posts on the Internet, the basics of Easy Containers is looking good. Well, it is fun anyway.
The container is to be a layered filesystem, using aufs or overlayfs, with the operation system SFS file underneath, and the rw layer either a folder, tmpfs or a partition. The SFS would be your Quirky, Puppy, or Fatdog .sfs, straight from the live-CD.
Layered root filesystem
Setting up the layers is straightforward, for example using overlayfs:
# mount -t squashfs -o loop,noatime q.sfs q_ro/ro1
# mount -t overlay -o lowerdir=q_ro/ro1,upperdir=q_rw/rw1,workdir=q_rw/work1 overlay q_top1
Where q_ro/ro1, q_rw/rw1, q_rw/work1 and q_top1 are just folders.
After doing this, I made some changes on top, that is, in ./q_top1:
With Quirky, /sys and /run do not exist, so created them.
I want to avoid any "mount -bind ..." or "mount -rbind ...", so instead of doing that for /dev, I created static device nodes in q_top1/dev -- you can copy these out of initrd-tree/dev in Woof.
Don't copy /dev/* from the host system, it has all sorts of stuff mounted. I also manually created folders /dev/pts and /dev/shm.
The online advice is to execute "mount --rbind /dev ./q_top1/dev" prior to the chroot, and I don't know what the downside is with just having static device nodes. A device node that is in-use in the host, what of that? -- having a copy of the node in the rootfs, is that unusable? In what situation would this matter?
chroot into rootfs
To chroot into q_top1, just do this:
# cp -f /etc/resolv.conf ./q_top1/etc/resolv.conf
# unshare -piumU --fork --mount-proc --map-root-user --setgroups=deny env -i TERM=xterm DISPLAY=:0 /bin/busybox chroot ./q_top1 /bin/sh
Then inside the chroot, do this:
# mount -t proc proc /proc
# mount -t tmpfs shmfs /dev/shm
You can then run leafpad, geany, rox, seamonkey, etc. However, there is a bug, the first time an X app is started, it crashes with this error message:
"BadShmSeg (invalid shared segment parameter)"
Thereafter, X apps start OK.
The CLI full-screen 'mp' text editor, that uses ncurses, works. As it is not an X app, it does not have that crashing problem.
It is interesting that ":0" works inside the chroot, some online docs state that won't work, and you need to use other methods, such as the following...
DISPLAY=localhost:0
This is probably a better way to do it, as I aim to make the chroot environment more isolated. It is odd, when I tested this, the above-mentioned "BadShmSeg" crash did not occur, yet just now did it the same way, and that error is back. So, there is a mystery here.
I read if you remove the "-nolisten tcp" on the Xorg invocation line in /usr/bin/xwin, and run "xhost +localhost", you can then set DISPLAY=localhost:0 inside the chroot filesystem.
Two problems with this. Firstly, it is considered to be a security risk, and xhost is considered to not be a very secure safeguard. Second, I could not get it to work, the value of DISPLAY is reported as invalid inside the chroot environment.
However, there is another, secure way of doing it. You need the 'socat' utility, which is available as a DEB, if you are running Ubuntu or Debian based puplet. I found out how to do it here:
http://askubuntu.com/questions/41330/let-xorg-listen-on-tcp-but-only-to-localhost
Create a little script, I have called it 'ec-chroot':
#!/bin/sh
if ! pidof socat; then
# -ly causes log to syslog.
socat -ly -d -d TCP-LISTEN:6000,fork,bind=localhost UNIX-CONNECT:/tmp/.X11-unix/X0 &
fi
#needed for internet access...
cp -f /etc/resolv.conf ./q_top1/etc/resolv.conf
#--map-root-user needed if have -U, otherwise env will fail.
unshare -piumU --fork --mount-proc --map-root-user --setgroups=deny env -i TERM=xterm DISPLAY=localhost:0 /bin/busybox chroot ./q_top1 /bin/sh
Once inside the chroot, run these:
# mount -t proc proc /proc
# mount -t tmpfs shmfs /dev/shm
# leafpad
...and leafpad starts without crashing, or rather, it did first time I tested this, not today!
SeaMonkey works too, and accesses the Internet, except that churns out dbus errors. I tested with starting udevd beforehand, but no good. SM still runs. I could compile SM without dbus support, to avoid the problem. Or, could figure out why dbus doesn't work.
Multiple containers
I have tested running two containers simultaneously, both with the underlying q.sfs. Got them running right now, one is running geany, the other leafpad, no problem with these windows.
Just an observation, not quantified, but I think there is a slight startup delay of the X app when using the DISPLAY=localhost:0 method.
Thoughts
What to do about that "BadShmSeg"? This question has come up many times on the Internet, but haven't found a definitive solution. My wild guess is that there is some shared-memory that Xorg is using in the host, that is not valid in the chroot environment, and Xorg detects that when the first X app is run. There are many online posts of a fix for Qt-based apps, using "export QT_X11_NO_MITSHM=1", however, that is not a fix, it is just avoiding the problem.
A less-than-satisfactory solution would be to run a little do-nothing X app on first entry to the chroot environment.
It is most pertinent to now ask, how secure is this? I have used 'unshare' to unshare everything, have not run "mount --bind" or "mount --rbind", "env -i" has removed most environment variables. What more can we do to improve security?
Here is something interesting to think about. Inside the container:
sh-4.3# mkdir /mnt/sda2
sh-4.3# busybox mount -t ext4 /dev/sda2 /mnt/sda2
mount: permission denied (are you root?)
sh-4.3# whoami
root
...which is good from a security viewpoint. But why doesn't it work, and can it be got around?
This is such an interesting topic, so I will start a Forum thread for feedback.
Comments
Forum thread is here:http://murga-linux.com/puppy/viewtopic.php?t=109527