Introduction and motivation
There are great articles ([1] and [2]) by patovm04 here on the forum explaining how to run Chrome and Brave in a Linux chroot environment (usually
These approaches work great. However, I am a big fan of FreeBSD's jails and it has always bothered me that these Linux compatibility layers were some kind of “sort-of” jail, but not real ones. By “real one” I mean a lightweight virtualization environment that makes use of FreeBSD's jail infrastructure (
I am hoping that the approach I am presenting here will illustrate the following benefits:
This tutorial was tested on FreeBSD-13.1-RELEASE.
Basic setup
The setup I am starting with is the following:
Enabling Linux support
Why we probably don't need
Normally, the service
So, besides the issue with the unbranded ELF executables, there is not much need for starting the service. Therefore, I omit it in this tutorial.
Loading the modules
Ensure that Linux support is activated:
To make it permanent, add the module in
E.g., my
Creating the jail
I like to put each of my jails into a separate ZFS dataset. This, for example, makes it easier to create snapshots or to transfer the jail to another computer. The zpool I am using for this is called
If you are using UFS instead of ZFS, a simple
Next, we'll need to prepare the system directories:
Now we can populate the jail with an Ubuntu root filesystem. For this, we'll use
Before we can actually start the jail, we need to specify what has to be mounted inside the jail. FreeBSD's jail mechanism can parse an
and put the following content into it:
Note that, with this configuration, the jail will share the host's
Next, we need to configure the jail. Open the file
You will probably need to adjust the following:
To start the jail at each boot, put the following into your
You can check if the jail is properly running by doing
which should give the following output:
Setting up the Ubuntu jail
Now that the Ubuntu jail is up and running, we can set up the Linux system. First, enter the jail:
Configure locales and timezone:
Install necessary packages (for running Google Chrome later on):
Install GPG key enabling the Google Chrome DEB repository:
Install Google Chrome:
Create a wrapper script for launching Chrome: Create a file
Make it executable:
Finally, add a non-privileged user that will be able to run Google Chrome (in my case this is
Add this point, you should be able to run Chrome. There will be no sound yet, though (see a later chapter to fix that). To run it, first change to our non-privileged user created above:
Then run Chrome:
When everything is allright, quit Chrome, leave the
Troubleshooting
You may need to allow the Ubuntu jail to connect to your Xorg server on the host. Do this by using the following command (on the host!):
To make this change permanent, place it, e.g. in
Allow non-root execution
You can now start Chrome from outside the jail using
Note that we use
Having to be root in order to launch Chrome is a little bit inconvenient. FreeBSD doesn't allow non-privileged jail-execution for good reasons, though. So we will use a different approach using
First, we'll create a launcher script on our host and call it
Make it executable:
Next, we allow a particular user on the host system to execute
and put a line into it having the following format:
The user (
After saving and closing
You can put this command inside a Desktop launcher, for example.
Enabling sound
Google Chrome on Linux relies on Pulseaudio, so we have to deal with it. In this guide I chose to run Pulseaudio as a system-wide service, which has pros and cons:
Pulseaudio aware applications can communicate with a Pulseaudio server either by TCP or a Unix-Domain-Socket (similar to Xorg). We configure this feature explicitly by editing
The second line is most important for us: It tells us how the Pulseaudio socket will be named (
Important: If you do not want remote connections over the network, delete the first line containing
Now that we have configured the Pulseaudio server, we need to enable it. The package
Next, enable the service in
Finally, start the service so the we do not have to reboot the machine:
We are almost there. The one thing left is to let Chrome in the Linux jail know which socket to use when talking to our Pulseaudio server. We could put this information directly into the
Note: If you would like to configure remote access, simply replace to line above with
where "Host-IP-address" is the addres of the machine running the actual Pulseaudio server.
Debugging sound
If there is no sound, ensure that the Pulseaudio connection is working: Enter the Linux jail and run
If everything works, you should see a list of available sources and sinks. If you get something like this:
then ensure the following:
References
There are great articles ([1] and [2]) by patovm04 here on the forum explaining how to run Chrome and Brave in a Linux chroot environment (usually
/compat/linux
or /compat/ubuntu
).These approaches work great. However, I am a big fan of FreeBSD's jails and it has always bothered me that these Linux compatibility layers were some kind of “sort-of” jail, but not real ones. By “real one” I mean a lightweight virtualization environment that makes use of FreeBSD's jail infrastructure (
jexec
, jls
, /etc/jail.conf
, etc.).I am hoping that the approach I am presenting here will illustrate the following benefits:
- Leverage standard jail infrastructure to uniform handling of Linux and FreeBSD jails.
- Create as many Linux jails as you want (not just one in
/compat/linux
, respectively/compat/ubuntu
).
This tutorial was tested on FreeBSD-13.1-RELEASE.
Basic setup
The setup I am starting with is the following:
- I am using ZFS, but do not rely on it in this tutorial. I have added remarks when appropriate on how to do the same thing on UFS.
- I am using “bare metal” jails, no management utility like
sysutils/bastille
orsysutils/iocage
. The use of them could possibly simplify some aspects of this tutorial, but I like the down-to-earth approach when using jails directly. - My jails are attached to a loopback device
lo1
and I usepf
to NAT to forward network traffic to my physical network. This way I can assign static IP addresses to my jails and keep using DHCP on my physical network. If you are interested in the details of this setup, just give me a call and I post them here. - I use Xorg, not Wayland, which I am unfamiliar with. If you know Wayland and a simple way to modify this tutorial to support both, please let me know!
Enabling Linux support
Why we probably don't need
/etc/rc.d/linux
or similarNormally, the service
/etc/rc.d/linux
takes care of everything that is needed to run Linux binaries. The service basically does the following:- Load the required kernel module(s).
- Configure how to handle unbranded ELF executables.
- Mount Linux system directories like
/dev
,/proc
, etc., under the path specified bycompat.linux.emul_path
.
rc.conf
. Mounting the Linux system directories will be dealt with by FreeBSD's Jail infractructure.So, besides the issue with the unbranded ELF executables, there is not much need for starting the service. Therefore, I omit it in this tutorial.
Loading the modules
Ensure that Linux support is activated:
Code:
# kldload linux64
To make it permanent, add the module in
/etc/rc.conf
to the kld_list
variable, but ensure that your other modules remain there:
Code:
kld_list="... linux64 ..."
E.g., my
kld_list
now looks like this:
Code:
kld_list="i915kms linux64"
Creating the jail
I like to put each of my jails into a separate ZFS dataset. This, for example, makes it easier to create snapshots or to transfer the jail to another computer. The zpool I am using for this is called
scratch
. You'll have to adjust that name to your particular configuration (e.g. zroot
or similar):
Code:
# zfs create scratch/jail/ubuntu
# zfs set mountpoint=/jail/ubuntu scratch/jail/ubuntu
mkdir -p /jail/ubuntu
will do.Next, we'll need to prepare the system directories:
Code:
# for DIR in /dev/fd /dev/shm /tmp /proc /sys; do mkdir -p /jail/ubuntu/${DIR}; done
Now we can populate the jail with an Ubuntu root filesystem. For this, we'll use
sysutils/debootstrap
:
Code:
# pkg install debootstrap
# debootstrap --arch=amd64 --no-check-gpg focal /jail/ubuntu
Before we can actually start the jail, we need to specify what has to be mounted inside the jail. FreeBSD's jail mechanism can parse an
fstab
-like file that does exactly that. The location of this file is irrelevant, but for simplicity, we put it right into the jail itself. So, create it using an editor:
Code:
# vi /jail/ubuntu/etc/fstab
Code:
devfs /jail/ubuntu/dev devfs rw 0 0
tmpfs /jail/ubuntu/dev/shm tmpfs rw,size=1g,mode=1777 0 0
fdescfs /jail/ubuntu/dev/fd fdescfs rw,linrdlnk 0 0
linprocfs /jail/ubuntu/proc linprocfs rw 0 0
linsysfs /jail/ubuntu/sys linsysfs rw 0 0
/tmp /jail/ubuntu/tmp nullfs rw 0 0
/tmp
directory. This makes it easier to share audio and X11 sockets with the jail, but, for security reasons, you might want a different configuration sometime later when everything is up and running. –Next, we need to configure the jail. Open the file
/etc/jail.conf
and append the following content:
Code:
ubuntu {
host.hostname="ubuntu.schattenwelt.org";
ip4.addr="lo1|10.10.0.5/24";
path="/jail/ubuntu";
allow.raw_sockets=1;
exec.start='/bin/true';
exec.stop='/bin/true';
persist;
mount.fstab="/jail/ubuntu/etc/fstab";
}
You will probably need to adjust the following:
- The domain name (mine is
schattenwelt.org
, but your's will be different). - The IP address. In my configuration I use a loopback device (
lo1
) and assign static IP address for each jail manually. If you wonder how I get internet access inside the jail: I use the packet filterpf
for that, redirecting network traffic from and to the jail to my physical network.
Code:
# service jail onestart ubuntu
To start the jail at each boot, put the following into your
/etc/rc.conf
:
Code:
jail_enable="YES"
You can check if the jail is properly running by doing
Code:
# jls
which should give the following output:
Code:
JID IP Address Hostname Path
...
4 10.10.0.5 ubuntu.schattenwelt.org /jail/ubuntu
Setting up the Ubuntu jail
Now that the Ubuntu jail is up and running, we can set up the Linux system. First, enter the jail:
Code:
# jexec ubuntu /bin/bash
Configure locales and timezone:
Code:
# dpkg-reconfigure locales
# dpkg-reconfigure tzdata
Install necessary packages (for running Google Chrome later on):
Code:
# printf "deb http://archive.ubuntu.com/ubuntu/ focal main restricted universe multiverse" > /etc/apt/sources.list
# apt update
# apt install curl gnupg pulseaudio fonts-symbola ttf-mscorefonts-installer
Install GPG key enabling the Google Chrome DEB repository:
Code:
# curl -s https://dl.google.com/linux/linux_signing_key.pub | apt-key --keyring /etc/apt/trusted.gpg.d/google-chrome-stable.gpg add -
Install Google Chrome:
Code:
# printf "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" > /etc/apt/sources.list.d/google-chrome.list
# apt update
# apt install google-chrome-stable
Create a wrapper script for launching Chrome: Create a file
/opt/google/chrome/chrome-wrapper
and add the following content:
Code:
#!/bin/bash
#
# Copyright (c) 2011 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
HERE="/opt/google/chrome"
export CHROME_VERSION_EXTRA="stable"
# We don't want bug-buddy intercepting our crashes. http://crbug.com/24120
export GNOME_DISABLE_CRASH_DIALOG=SET_BY_GOOGLE_CHROME
# Sanitize std{in,out,err} because they'll be shared with untrusted child
# processes (http://crbug.com/376567).
exec < /dev/null
exec > >(exec cat)
exec 2> >(exec cat >&2)
export DISPLAY=:0
export LIBGL_DRI3_DISABLE=1
"$HERE/chrome" --in-process-gpu --no-sandbox --no-zygote --test-type --enable-features=UseOzonePlatform --ozone-platform=x11 --v=0 "$@" || true
Make it executable:
Code:
# chmod +x /opt/google/chrome/chrome-wrapper
Finally, add a non-privileged user that will be able to run Google Chrome (in my case this is
hsebert
, myself):
Code:
# adduser hsebert
Add this point, you should be able to run Chrome. There will be no sound yet, though (see a later chapter to fix that). To run it, first change to our non-privileged user created above:
Code:
# su - hsebert
Then run Chrome:
Code:
$ /opt/google/chrome/chrome-wrapper
When everything is allright, quit Chrome, leave the
su
-environment and ultimatlely the jail:
Code:
$ exit
# exit
Troubleshooting
You may need to allow the Ubuntu jail to connect to your Xorg server on the host. Do this by using the following command (on the host!):
Code:
$ xhost +
~/.xinitrc
.Allow non-root execution
You can now start Chrome from outside the jail using
jexec
as root:
Code:
# jexec ubuntu /bin/bash -c "su -c /opt/google/chrome/chrome-wrapper - hsebert"
su
inside the jail to drop privileges and use our non-privileged user inside the jail (hsebert
).Having to be root in order to launch Chrome is a little bit inconvenient. FreeBSD doesn't allow non-privileged jail-execution for good reasons, though. So we will use a different approach using
security/sudo
:
Code:
# pkg install sudo
First, we'll create a launcher script on our host and call it
/usr/local/bin/linux-chrome
:
Code:
#!/bin/sh
if [ $# -lt 1 ] ; then
echo "usage: ${0} <user>"
exit 1
fi
if [ "${1}" = "root" ] ; then
echo "Cowardly refusing running Chrome as root"
exit 1
fi
jexec ubuntu /bin/bash -c "su -c /opt/google/chrome/chrome-wrapper - ${1}"
Make it executable:
Code:
# chmod +x /usr/local/bin/linux-chrome
Next, we allow a particular user on the host system to execute
linux-chrome
using sudo
, but without requiring a passwort for this particular command (this is important!). For this, create a dedicated sudoers
file as follows:
Code:
# visudo -f /usr/local/etc/sudoers.d/linux-chrome
Code:
<user> <host> = (root) NOPASSWD: <cmd>
The user (
<user>
) on my host system who is to be allowed to run linux-chrome
is hsebert
(same user name as inside the Linux jail, but this is not a necessity). My host name (<host>
) is thinkle
. The command to be run (<cmd>
)is /usr/local/bin/linux-chrome
(full path!). So, the resulting line looks like this:
Code:
hsebert thinkle = (root) NOPASSWD: /usr/local/bin/linux-chrome
After saving and closing
vi
, your regular user should be able to run Chrome as follows:
Code:
$ sudo linux-chrome $(whoami)
You can put this command inside a Desktop launcher, for example.
Enabling sound
Google Chrome on Linux relies on Pulseaudio, so we have to deal with it. In this guide I chose to run Pulseaudio as a system-wide service, which has pros and cons:
- Pro: It can be used by multiple jails at the same time, and even by Bhyve virtual machines, if remote access is configured (see below).
- Con: Even the Pulseaudio developers discourage the use as a system-wide service because of security issues, see here. Pick your poison. –
Code:
# pkg install pulseaudio
Pulseaudio aware applications can communicate with a Pulseaudio server either by TCP or a Unix-Domain-Socket (similar to Xorg). We configure this feature explicitly by editing
/usr/local/etc/pulse/system.pa
and add the following two lines:
Code:
load-module module-native-protocol-tcp auth-anonymous=1 auth-ip-acl=127.0.0.1;192.168.178.0/24
load-module module-native-protocol-unix auth-anonymous=1 socket=/tmp/pulse-native
/tmp/pulse-native
). We'll need that information later when we configure Pulseaudio in the Linux jail.Important: If you do not want remote connections over the network, delete the first line containing
module-native-protocol-tcp
. If remote connections are o.k. for you (e.g. because you would like to play audio in your Bhyve virtual machines), be sure to set the correct subnet mask (mine is in this case: 192.168.178.0/24).Now that we have configured the Pulseaudio server, we need to enable it. The package
audio/pulseaudio
does not come with an rc
script to start it up at boot time. Therefore, we have to create one: Create the file /usr/local/etc/rc.d/pulseaudio
and let it have the following content:
Code:
#!/bin/sh
# PROVIDE: pulseaudio
# REQUIRE: DAEMON FILESYSTEMS
# KEYWORD: nojail shutdown
. /etc/rc.subr
name="pulseaudio"
desc="Start the Pulseaudio server"
rcvar="pulseaudio_enable"
pulseaudio_bin="/usr/local/bin/${name}"
pulseaudio_pidfile="/var/run/pulse/pid"
start_cmd="${name}_start"
stop_cmd="${name}_stop"
load_rc_config "${name}"
pulseaudio_start()
{
${pulseaudio_bin} --system --disallow-module-loading &
}
pulseaudio_stop()
{
if [ -f "${pulseaudio_pidfile}" ]
then
kill $(cat "${pulseaudio_pidfile}")
fi
}
run_rc_command "$1"
Next, enable the service in
/etc/rc.conf
:
Code:
pulseaudio_enable="YES"
Finally, start the service so the we do not have to reboot the machine:
Code:
# service pulseaudio start
We are almost there. The one thing left is to let Chrome in the Linux jail know which socket to use when talking to our Pulseaudio server. We could put this information directly into the
chrome-wrapper
script introduced further up, but I think it's better to make it a system-wide default for our Linux jail. Therefore, create the file /jail/ubuntu/etc/profile.d/05-pulseaudio.sh
and add the following line:
Code:
export PULSE_SERVER=unix:/tmp/pulse-native
Note: If you would like to configure remote access, simply replace to line above with
Code:
export PULSE_SERVER="<Host-IP-address>"
Debugging sound
If there is no sound, ensure that the Pulseaudio connection is working: Enter the Linux jail and run
Code:
$ pactl list
If everything works, you should see a list of available sources and sinks. If you get something like this:
Code:
Connection failure: Connection refused
pa_context_connect() failed: Connection refused
- Is the
PULSE_SERVER
variable set correctly? - Is the socket
/tmp/pulse-native
available, i.e. is/tmp
properly mounted? - Is the Pulseaudio server running on the host:
ps -a | grep pulseaudio
? - Have you tried turning it off and on again?
References
Last edited: