Portable Packer is a developer and packager friendly tool for, you guessed it, packaging. It is designed to operate based on modes specified by the packager. Within a few lines, you can create a completely new sandboxed package, potentially free from known escape exploits.

Why a new tool? You may ask. Hold the comments before I explain.

How packaging works for now

Let’s look how a packager create a new sandboxed portable app on Arch Linux. Take firefox-developer-edition-portable created by me as an example. It’s PKGBUILD looks like this before:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
# Maintainer: Kimiblock Moe

pkgname=firefox-developer-edition-portable
pkgver=146.0b9
pkgrel=1
epoch=1
pkgdesc="Firefox sandboxed by portable (en_US locale)"
arch=('x86_64')
url="https://github.com/Kraftland/portable"
license=('GPL-3.0-or-later')
groups=()
provides=(firefox-developer-edition)
options=(!debug !strip)
conflicts=("firefox-developer-edition")

optdepends=()

makedepends+=(firefox-developer-edition coreutils)

checkdepends=()

source=(
portable-config
firefox.sh
firefox.desktop
)


md5sums=('1ce4e3c8db9bb3f86bbb0aaf5fd583bf'
'7b9045a876cb70cfce509dcef69d6561'
'7c0b0bae889e55058c366fc47d9edd25')

function prepare() {
pacman -Ql firefox-developer-edition >file.list
}

function package() {
depends=("portable")
depends+=(alsa-lib at-spi2-core bash cairo dbus ffmpeg4.4 fontconfig freetype2 gcc-libs gdk-pixbuf2 glib2 glibc gtk3 hicolor-icon-theme libpulse
libx11 libxcb libxcomposite libxdamage libxext libxfixes libxrandr libxss libxt mime-types nspr nss pango ttf-font)
while IFS= read -r line; do
file="$(echo "$line" | awk '{print $2}')"
if [[ -d ${file} ]]; then
echo "Omitting Directory"
else
if [[ -L "${file}" ]]; then
mkdir -p "$(dirname "${pkgdir}/${file}")"
ln -vsf "$(readlink -f "${file}")" "${pkgdir}/${file}"
else
install -vDm755 "${file}" "${pkgdir}/${file}"
fi
fi
done < file.list
rm -f "${pkgdir}/usr/share/applications"/*
rm -f "${pkgdir}/usr/bin"/*
install -Dm644 portable-config \
"${pkgdir}/usr/lib/portable/info/org.mozilla.firefox_developer_edition/config"
install -Dm755 "firefox.sh" \
"${pkgdir}/usr/bin/firefox-developer-edition-portable"
install -Dm644 "firefox.desktop" "${pkgdir}/usr/share/applications/org.mozilla.firefox_developer_edition.desktop"
echo '''[Desktop Entry]
Type=Application
Name=Firefox Developer Edition
GenericName=Stub for MPRIS
Icon=firefox-developer-edition
TryExec=portable
Exec=env _portableConfig="org.mozilla.firefox_developer_edition" portable -- %u
Terminal=false
NoDisplay=true''' >"${pkgdir}/usr/share/applications/firefox-developer-edition.desktop"
}

Looks pretty straight forward, isn’t it. Let’s focus on the prepare() and package(). You first query all of the files owned by the original package via pacman -Ql, add some dependencies and install them via a while loop. For finalizing, the portable config is then installed, and the .desktop file is replaced.

Of course it works. All packages from the repository is packaged like this.

But, this is not we want. And it has several downsides as some of you might’ve already realized.

Great, what’s the problem?

Duplicated lines of packaging code

As Portable packages continue to grow in figure, we have tens of duplicated copies in the repo. Not only makes the whole repository messy, but it also becomes a pain when we have to change something in all packages. Yuck!

Simplify for Elegance

Please recall the PKGBUILD example linked above. And look at the heading: Simplify. Now the hassle of manually hand writing packaging commands is gone. What’s above is the same as this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function package() {
depends=("portable")
depends+=(alsa-lib at-spi2-core bash cairo dbus ffmpeg4.4 fontconfig freetype2 gcc-libs gdk-pixbuf2 glib2 glibc gtk3 hicolor-icon-theme libpulse
libx11 libxcb libxcomposite libxdamage libxext libxfixes libxrandr libxss libxt mime-types nspr nss pango ttf-font)

export pkgdir
export pkgname
export srcdir
portable-packer \
-v \
--distro arch \
--mode copy firefox-developer-edition \
--hash true \
--config "${srcdir}/portable-config" \
--desktop-file "${srcdir}/firefox.desktop"
}

Clearly this approach is much more elegant than before, and it handles heavy lifting by itself. Which is the design goal of Portable.

Let’s explain this line-by-line: The first 2 lines is exactly identical, as dependencies are still set by the packager. The difference begins at line 3.

  1. We export pkgdir, pkgname and srcdir in the shell. This is done to provide packer with some information so you don’t have to include it manually (those are not exposed to child processes).
  2. The Packer is called with arguments:
  • -v to enable verbose logging
  • --distro arch to use Arch Linux specific code
  • --mode copy firefox-developer-edition instructs Packer to enter copy mode, and get files from the firefox-developer-edition alpm package.
  • --hash true currently has no effect, but will be relevant in following Portable releases (stay tuned!).
  • --config "${srcdir}/portable-config" specifies the configuration location for Portable.
  • --desktop-file "${srcdir}/firefox.desktop" tells Packer to use that desktop file directly.

Much simpler huh. You might be able to understand it ten times easier than the previous approach.

With packer and it’s unified mode of operations, packaging becomes mostly abstracted to packer options. For most packages that copy files from the original one, you just specify dependencies, and call packer with a fre lines of options. This makes the whole packaging pain easier, and you won’t have to dig through docs to find out required operations.

Cheesy!

If you are into, or interested in Aviation like me. The Swiss cheese model may not be unheard of. Don’t worry as I’ll explain:

The Swiss Cheese model

The Portable sandbox is like a stack of cheese slices. Every slice is a defence measure built to mitigate threat. Holes in the cheese slices represents weakness and error. Although we probably can’t eliminate all weaknesses, multiple layers of defences can be built to mitigate the threat, and prevent if from penetrating the security system (Portable).

Like all security defences, human is usually the weakest point of defence. There are too many of ways to break the sandbox: you may forget to remove D-Bus services making it instantly escape when anyone try to activate that interface, even unintentionally; there may be surplus binaries under /usr/bin; the .desktop file may not be removed beforehand and much more…

In a nutshell, a lot of things may go wrong. And many escapes are being found constantly. You might have no issues today, but next version there’s a hole you might forget to plug.

Packer is there to serve as an additional layer of security cheese. Therefore eliminating some portions of human error, preventing some escapes to materialize in advance.


Footnote

  1. The cheese model illustration is borrowed from Wikipedia author BenAveling, distributed under Creative Commons Attribution-Share Alike 4.0 International license. The model was originally formally propounded by James T. Reason of the University of Manchester
  2. This is just a small part of the 11.0 release.