How To: Execute Firefox in a jail using iocage and ssh/jailme

H

hukadan

Guest
Motivations

The main reason to put a browser in a jail is quite simple : browsers cannot be trusted. They are too much exposed. Executing a browser inside a jail(8) is a way to be sure that the damages induced by a malicious software are contained (as much as possible). I decided to write this tutorial after posting in the Thread x11-applications-in-iocage-jails.53224 since I did not find any on the internet (including this forum). There is nothing new here but I just gathered information here and there to put everything together in one and the same place.

In this tutorial I will describe the steps allowing to execute a browser inside a jail(8). Of course, it can be adapted to other software such as e-mail clients. I will take Firefox (www/firefox) as browser but once again, it applies to others with almost no modification.

We will use different tools to achieve this goal. First, we will create a jail(8) using iocage(8) but you can do the same using other tools such as ezjail(8). We will also use pf(4) to NAT our jail(8) and nullfs(5) to share folders between the host and the jail(8). In order to launch the browser, I show two alternatives. With the first one, a ssh(1) server is used with X11 forwarding. With the second one, we use a tool called jailme() (sysutils/jailme) which does a work similar to jexec(8) but without needing the root credentials.

1- Let's begin with the jail network

In this section we will use pf(4) to NAT our jail(8). This configuration makes sure that my jails do not interfere with my house network and can also take advantage of my firewall settings.

Create a lo1 interface that will be used for the jail(8) network and assign an IP address to it.
# ifconfig lo1 create
# ifconfig lo1 inet 10.0.0.254/24


In order to make this change permanent, add the following lines to /etc/rc.conf.
Code:
cloned_interfaces="lo1"
ifconfig_lo1="inet 10.0.0.254 netmask 255.255.255.0"
Next, configure pf(4) to NAT the future jail(8) by creating/editing the /etc/pf.conf file (this is a minimal configuration to NAT the jail and by no means a firewall configuration. You have to adapt it to your existing rules) and replacing re0 with your network interface name (you can have a list of your network interfaces with ifconfig -l).
Code:
ext_if="re0"
int_if="lo1"
localnet=$int_if:network

scrub in all fragment reassemble
set skip on lo0
set skip on lo1

#nat for jails
nat on $ext_if inet from $localnet to any -> ($ext_if)
Then launch pf(4) to activate this network.
# pfctl -e -f /etc/pf.conf

Do not forget to make sure that pf(4) is launched at boot.
Code:
pf_enable="YES"
pflog_enable="YES"

2- jail(8) creation using iocage(8)

Create a jail(8) named (more exactly tagged) injail with an IP address 10.0.0.1 and a hostname injail.
# iocage create hostname=injail tag=injail ip4_addr="lo1|10.0.0.1"
# iocage set hostname=injail injail


Since mount_nullfs(8) will be used in the next part to mount jail(8) folders, it can be convenient to use the hack88 hack (introduced in 1.6.0) on our jail(8).
# iocage set hack88=1 injail

Then make sure the jail(8) is launched at boot. This is done in two steps. First, set the boot property of the jail(8) to on (this is not a jail(8) property per se but an iocage(8) specific property).
# iocage set boot=on injail

Then modify the /etc/rc.conf file.
Code:
iocage_enable="YES"
The last step is to launch the jail(8), attach to it and create the jailuser user using pw(1).
# iocage start injail
# iocage console injail
# pw user add jailuser -s /bin/tcsh -m
# passwd jailuser


If you plan to use jailme(), make sure that the username and user id match between host and jail(8) when creating this user (to know your user id, use id(1)).

3- Get the jail(8) "Firefox ready"

If you want to use Firefox, you have to install it.
# iocage console injail
# pkg install -y firefox


Firefox needs devel/dbus to run. So make sure this service is started at jail boot by editing the /etc/rc.conf file on the jail.
Code:
dbus_enable="YES"
Then, start the service.
# service dbus start

4- jailme or ssh(1)

Now, you have the choice between two solutions. The first one requires a perfect match between the jail and the host for both the username and the user id. We also have to set xhost(1) in order to accept connections from the jail(8) to the host X server. The second one requires to setup a ssh(1) server on the jail(8), to install xauth(1), to modify the host file of the jail(8) and use public key authentication (this is not compulsory but it is really convenient).

Two points worth mentioning before going on :
  • if your version of x11-servers/xorg-server is equal or greater than 1.17, you have to make sure that the Xorg server listen to tcp. Using startx(1), the command is % startx -- -listen tcp.
  • if you plan to use multiple jails to launch multiple Firefox instances, do not forget to add the --new-instance option each time you start Firefox to avoid strange behavior like starting Firefox on one jail and see it executes on a another one (see this for more details).

4-1 jailme

jailme() works as jexec(8) except that you do not need root credentials to use it. It will let you execute a command in the jail(8) as normal user provided that your username and your id are identical on both the host and the jail(8). The trick here is to execute an X11 application on the jail(8) and make it use the X server on the host. For this to happen, you have to modify the access to your X server with the xhost(1) command. Since our jail(8) as the IP address 10.0.0.1, allow only this address.
% xhost + inet:10.0.0.1

Add the following line to your ~/.xinitrc in order to allow the jail(8) when you login.
Code:
xhost + inet:10.0.0.1
If you do not use ~/.xinitrc in your setup, I am sure there is another way to make this happen. Of course, we need to install jailme() on the host.
# pkg install -y jailme.

jailme() takes as argument the jail id or jail name and the command you want to launch. The problem is that the jail id can change and the jail name set automaticaly by iocage(8) is rather long and cannot be changed (as far as I know). So make a small script named injail that allows you to directly issue injail [I]command[/I].
Code:
#! /bin/sh
jail_hostname=injail
jail_id=$(jls -h jid host.hostname | grep $jail_hostname | cut -w -f 1)
jailme $jail_id $@
That's it. You can try it.
% injail firefox

If you are not interested in the ssh(1) setup, you can skip the following paragraph and go to the one about sharing folders.

4-2 ssh(1) and X11 forwarding

With this solution, we use the well known X11 forwarding capabilities of ssh(1). For that, we need to configure the ssh(1) server on the jail. First attach to the jail(8).
# iocage console injail

Edit the /etc/ssh/sshd_config file and add the following lines.
Code:
X11Forwarding yes
X11UseLocalhost no

Since we do not use localhost, make sure that the jail(8) can resolve its hostname by adding the following line to /etc/hosts on the jail.
Code:
10.0.0.1  injail
The last step is to make sure that the ssh(1) server is started at jail boot adding the following line to /etc/rc.conf file.
Code:
sshd_enable="YES"
Then, start the ssh server.
# service sshd start.

We also need to install xauth(1) to do X11 forwarding.
# pkg install -y xauth

Then, come back to the host and configure de client side of ssh(1). It is quite boring to have to type password each time. So setup a public key authentication.
% ssh-keygen -t rsa -b 4096 -C "jailuser@example.com" -f ~/.ssh/injail
% ssh jailuser@10.0.0.1 mkdir .ssh
% cat ~/.ssh/injail.pub | ssh jailuser@10.0.0.1 'cat >> .ssh/authorized_keys'


We also have to tell the ssh(1) client to use this public key and to activate the X11 forwarding. Edit/create the ~/.ssh/config file.
Code:
Host injail
  Hostname 10.0.0.1
  Port 22
  User jailuser
  IdentityFile ~/.ssh/injail
  ForwardX11 yes
  ForwardX11Trusted yes
Check that everything works.
% ssh injail firefox

5- Sharing folders between the host and the jail(8) (optional)

It can be convenient to grab files you just downloaded on the jail(8) to use them on the host. nullfs(8) will be used to achieve this. First check the mount point of the jail.
# iocage get mountpoint injail

For those who already use iocage(8), you can see that this mount point is shorter than usual. This is due to the hack88 we set earlier. With this information, edit the /etc/fstab file.
Code:
$JAIL_MOUNT_POINT/root/home/jailuser /home/hostuser/whereyouwant nullfs  rw,late  0 0
$JAIL_MOUNT_POINT has to be replaced by the mount point you got previously. The late option is here to avoid problems such as this one Thread nullfs-via-etc-fstab-on-zfs-only-system.11454.

We can check if everything is set properly.
# mount /home/hostuser/whereyouwant
% ls /home/hostuser/whereyouwant


You can also choose not to mount the entire jailuser home but only one folder (I personaly mount a Documents subfolder of the jailuser home on the host). Anyway, now you have access to your files in the jail(8) from the host.

6 - Sharing sound devices (optional)

The internet is nice, but with sound it is even better and jails can share devices with the host including sound devices. This done by editing the devfs.rules(1). The default ones, including jails, can be found in the /etc/default/devfs.rules file. If the default settings are fine for classic jails, it is not enough for a jail(8) executing a browser. Therefore, non default devfs.rules(1) settings have to be created. Create/edit the /etc/devfs.rules file and add the following lines.
Code:
# Devices usually found in a desktop jail for sound.
#
[devfsrules_desktop_jail=5]
add include $devfsrules_hide_all
add include $devfsrules_unhide_basic
add include $devfsrules_unhide_login
add path zfs unhide
add path 'mixer*' unhide
add path 'dsp*' unhide
This is simply the default settings plus the two last lines that give the jail(8) access to sound devices. Set the new devfs.rules(1) to your jail(8).
# iocage set devfs_ruleset=5 injail

And restart the jail(8) (not using the soft restart)
# iocage stop injail
# iocage start injail


You should be able to play sound using your jailed browser now.

Afterthoughts

I have tried the different steps of this tutorial thoroughly in order to make sure that if you follow them this should work for you as well. However, each system is different and may be this tutorial will not work right away for you. Please, do not hesitate to post if you are facing problems following this tutorial.

There is for sure smarter ways to achieve what I just described. I would really like to know those smarter ways so I can improve this tutorial and my FreeBSD knowledge. So do not hesitate to question my setup. We could also image a script that take a snapshot of the jail(8) before starting the browser and rollback to that snapshot when you exit so you always start with a clean system.

Concerning browsers, it would be nice to change "Firefox ready" by "Browser ready", so if you try this with other browsers, do not hesitate to come here and tell me. I will modify this tutorial accordingly.

Why only browsers ? Because it is the only peace of software I jailed so far. I can imagine that other software would need additional/different devices shared between the host and the jail(8). A word processor for instance would not need sound but could need a printer device (/dev/ulpt0 for instance). Therefore, do not hesitate to share your devfs.rules(1) for other software.

Finally, this is my first HowTo. So if some parts are not clear enough for you or you think I skipped an important step and therefore this tutorial need some clarifications, I would happily add them to this tutorial.

EDITS :

2015-09-07 :
  • sections have been rearranged : optional steps have been put at the end ;
  • multiple Firefox instances issue added : comment added on the --new-instance option.
  • new Xorg version issue added : need to start Xorg(1) server with -listen tcp option starting from version 1.17 ;
  • $DISPLAY variable not strictly needed : if you attach your jail(8) using iocage(8), you need to set it. Otherwise, you can skip this step. The initial text that just followed the jailme() script in the jailme section was :
You can now use it to access to a shell on the jail(8) and set the $DISPLAY variable so the jail(8) uses the host X server. % injail /bin/tcsh. From there, edit the ~/.cshrc file by adding the following line
Code:
setenv  DISPLAY 10.0.0.254:0.0
Now the jail(8) will use the X server of the host. Note that those settings assume you use tcsh(1) or csh(1). If you decide to use another shell, you should set your variable in the corresponding setting file. Note also that you can set this variable system wide by editing the /etc/csh.cshrc file.
 
Last edited by a moderator:
(In case some further steps need to be added)
 
jailme() takes as argument the jail id and the command you want to launch. The problem is that the jail id can change. So make a small script named injail that allows you to directly issue injail command.
Not quite true (anymore):
usage: jailme jid|jailname command [...]
Works well for me :)
 
Not quite true (anymore):

Works well for me :)

Well, I did not mention it because iocage(8) set automatically a rather long jail name, too long to be remembered, and it is not possible to change it (I suppose it is used by iocage(8) somehow and should not be change anyway). But you are right, it is worth mentioning and I corrected the tutorial.
 
Last edited by a moderator:
Really? I'm currently using cbsd, but could imagine switching to iocage in the future, so this surprises me a bit.

You should not take my word for it ;). But last time I checked, the jail name was not accessible with the get/set command. But iocage(8) uses tags which are quite similar to name in a sense.
 
Quick question: I'd like to have multiple firefox jails, maybe one on the main system. Unfortunately, when the main system firefox is already running, a jailme $JID firefox runs another main system instance :( Any know how to fix this?
 
The --new-instance option should do the trick. If not, try with the --no-remote option. For more details, read this and firefox --help.
 
Thank you :)

But now I have a new problem, that I already had in the past every now and then: sometimes, without any apparent reason, trying to execute a graphical program in a jail results in a "cannot open display" error.
Right now for example. I just started the computer, wanted to execute a new firefox instance from a jail via jailme 2 firefox --new-instance, yielding a "Error: cannot open display: :0.0". Experimenting with other DISPLAY variables (setting it to $MAIN_IP:0.0 for example) doesn't change this.
xhost lists the corresponding IP address, so it should work, and actually it did work before.
As an alternative, I tried the ssh method: ssh -Y 192.168.0.101 firefox --new-instance yields an error stating that Firefox is already running, but unresponsive. Inside the jail, there is no Firefox process running, only on the main system.

Any Ideas what I'm overlooking?
 
If you followed the tutorial, could you please connect to the jail and check the value of your $DISPLAY variable.
% jailme you_jail_id /bin/tcsh
And once in the jail (this is my output.).
Code:
% echo $DISPLAY
10.0.0.254:0.0
If you did not follow the tutorial, which part did you skip/adapt ?
 
I adapted the IP to be directly deployed on my re0 interface.
Code:
re0: flags=8843<UP,BROADCAST,RUNNING,SIMPLEX,MULTICAST> metric 0 mtu 1500
    options=8209b<RXCSUM,TXCSUM,VLAN_MTU,VLAN_HWTAGGING,VLAN_HWCSUM,WOL_MAGIC,LINKSTATE>
    ether 74:d0:2b:9d:4c:46
    inet 192.168.0.12 netmask 0xffffff00 broadcast 192.168.0.255
    inet 192.168.0.102 netmask 0xffffffff broadcast 192.168.0.102
    inet 192.168.0.101 netmask 0xffffffff broadcast 192.168.0.101
    inet 192.168.0.103 netmask 0xffffffff broadcast 192.168.0.103
    nd6 options=29<PERFORMNUD,IFDISABLED,AUTO_LINKLOCAL>
    media: Ethernet autoselect (1000baseT <full-duplex>)
    status: active
That's once my Main IP + three Jails. Therefore, no pf is running. pinging the jails and logging in from the host via ssh works.

xhost looks like this:
Code:
yggdrasil@midgard:~ % xhost
access control enabled, only authorized clients can connect
INET:192.168.0.103
INET:192.168.0.102
INET:192.168.0.101
INET6:localhost
INET:localhost

I followed the iocage part, adapting to my interface/IPs, skipped the shared folder part.

Inside the 10.2 jail:
Code:
yggdrasil@banking:/ % echo $DISPLAY
192.168.0.102:0.0
yggdrasil@banking:/ % midori
midori - Cannot open display:
…
yggdrasil@banking:/ % echo $DISPLAY
192.168.0.12:0.0
yggdrasil@banking:/ % midori
midori - Cannot open display:
…
yggdrasil@banking:/ % echo $DISPLAY
:0.0
yggdrasil@banking:/ % midori
midori - Cannot open display:
…
yggdrasil@banking:/ % echo $DISPLAY
:0
yggdrasil@banking:/ % midori
midori - Cannot open display:

Since it worked before with these jails and xhost settings, I'm thoroughly confused...
 
Last edited by a moderator:
pinging the jails and logging in from the host via ssh works.
Does it work the other way around (ping the host from the jails - you have to allow raw socket for that). That's just a shot in the dark (and also because of the 0xffffffff netmask).
 
Yep, that works.
Before I followed your writeup (before my recent reinstall), I had a simple banking jail set up, that just worked. No need to even modify the DISPLAY variable, only adding to the xhost, that was it. Then jailme, or sudo jexec would just work. Until it wouldn't suddenly for no discernible reason. Now, even a full restart of the system doesn't reset whatever is going wrong.
 
Update:
It seems this may be caused by the xorg-server version in the Latest package set. I thought to remember that after switching from Quarterly to Latest it still worked, but that seems to be wrong. In a VM it worked (Quarterly, xorg-server 1.14), but after switching to Latest (xorg-server 1.17), it stopped working.
 
No need to even modify the DISPLAY variable
Apparently, you are right. Thank you for pointing this out. I will check if it does not break anything to remove this step and modify the tutorial accordingly.
but after switching to Latest (xorg-server 1.17), it stopped working.
As far as I understood, Xorg went from -listen tcp as default option to -nolisten tcp. So you have to make sure that Xorg starts with the first option again. It worked for me (I ran into this problem today).
 
Thank you very much, now it works again as promised :)

In case someone else is using good old xdm: you have to edit /usr/local/lib/X11/xdm/Xservers and simply add the -listen tcp to the X server command specified there.
 
I don't get why you put the jail on 10.0.0
In other words, why do you need a jail network?
Doesn't that isolates the jail from the 192.168.0 subnetwork on your home network, and make 10.0.0 unreachable from both the host and your home network?
So if you ssh into the jail from your host or another computer on your network or the Internet, what IP do you use?
Also, yggdrasil adds 192.168.0.10x on his host network adapter, how does he ssh into the jail from his host or another computer or the Internet, with what IP? When I use his technique, I can only get into the host, not any of the other IP's I have set up.
 
Hi,
I don't get why you put the jail on 10.0.0
I just picked that one but I could have chosen some other address range as long as they are non routable addresses. In fact, the first time I read a blog about jails sharing one public IP, the network used was this one. Since then, I use it myself.

In other words, why do you need a jail network?
The jails are only used by the host. They do not provide services to the home network. I do not see the need for them to appear on my home network. You can chose to do otherwise like yggdrasil did. It is up to you.

Doesn't that isolates the jail from the 192.168.0 subnetwork on your home network
If by isolate you mean that they are not visible from the home network, that's precisely what I want for the reasons explained just before.

and make 10.0.0 unreachable from both the host and your home network?
Not from the host. The host can still reach the jails (as explained in the HowTo) but that's true for the home network.

So if you ssh into the jail from your host (...)what IP do you use?
Like explained in the HowTo, I use the jail(8) IP.

So if you ssh into the jail from (...) another computer on your network or the Internet, what IP do you use?
In this case, I would use the IP address of the host and implement a rule in the host firewall to redirect the connection to the proper jail(8) (not described in the HowTo since it did not make sense to me in that case).

Your questions are more related to jail networking in general rather than the HowTo itself. If you have a general problem on jail networking you should search in the forum and if you do not find your answer, then post your question in the proper section.
 
Thank you hukadan. :)

I tried your tutorial with the following browsers:
  • opera -Works fine.
  • links - Works fine (both graphical and text mode).
  • qupzilla - Starts, but both address and search bars are not shown correctly. Browser's Interface responds, but I have problems writing and interacting with web pages.
I'm using sysutils/jailme to start them.
 
Just tried it on both the host system and the jailed one. It works, but stressing it a bit causes a core dump due to "illegal hardware instruction" (on both). But it is not a news for me, other QT5-related programs also had this problem on my system some time ago (in fact I use their QT4 version).
 
Back
Top