Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use mount namespace instead of chroot #62

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 3 additions & 11 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -16,30 +16,22 @@ install:
fi
rm -rf $(DESTDIR)
cp -aT $(CRAFT_STAGE)/base $(DESTDIR)
# ensure resolving works inside the chroot
cat /etc/resolv.conf > $(DESTDIR)/etc/resolv.conf
# copy-in launchpad's build archive
if grep -q ftpmaster.internal /etc/apt/sources.list; then \
cp /etc/apt/sources.list $(DESTDIR)/etc/apt/sources.list; \
cp /etc/apt/trusted.gpg $(DESTDIR)/etc/apt/ || true; \
cp -r /etc/apt/trusted.gpg.d $(DESTDIR)/etc/apt/ || true; \
fi
# since recently we're also missing some /dev files that might be
# useful during build - make sure they're there
[ -e $(DESTDIR)/dev/null ] || mknod -m 666 $(DESTDIR)/dev/null c 1 3
[ -e $(DESTDIR)/dev/zero ] || mknod -m 666 $(DESTDIR)/dev/zero c 1 5
[ -e $(DESTDIR)/dev/random ] || mknod -m 666 $(DESTDIR)/dev/random c 1 8
[ -e $(DESTDIR)/dev/urandom ] || \
mknod -m 666 $(DESTDIR)/dev/urandom c 1 9
# copy static files verbatim
/bin/cp -a static/* $(DESTDIR)
mkdir -p $(DESTDIR)/install-data
/bin/cp -r $(CRAFT_STAGE)/local-debs $(DESTDIR)/install-data/local-debs
# customize
set -eux; for f in ./hooks/[0-9]*.chroot; do \
base="$$(basename "$${f}")"; \
cp -a "$${f}" $(DESTDIR)/install-data/; \
chroot $(DESTDIR) "/install-data/$${base}"; \
./mount-ns.sh spawn $(DESTDIR) \
--ro-bind $$f "/install-data/$${base}" \
-- "/install-data/$${base}"; \
rm "$(DESTDIR)/install-data/$${base}"; \
done
rm -rf $(DESTDIR)/install-data
Expand Down
4 changes: 0 additions & 4 deletions hooks/001-extra-packages.chroot
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,6 @@ export DEBIAN_FRONTEND=noninteractive
rm -f /etc/apt/sources.list.d/proposed.list


# ensure we have /proc or systemd will fail
mount -t proc proc /proc
trap 'umount /proc' EXIT

# systemd postinst needs this
mkdir -p /var/log/journal

Expand Down
5 changes: 0 additions & 5 deletions hooks/999-clean-resolv-conf.chroot

This file was deleted.

125 changes: 125 additions & 0 deletions mount-ns.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
#!/bin/bash

set -eu

# FIXME: This should be replace by a mount of devtmpfs when
# it is supported in user namespaces
bind_dev() {
dev="${1}/dev"

for device in null zero full random urandom tty; do
touch "${dev}/${device}"
mount --bind "/dev/${device}" "${dev}/${device}"
done
}

# Because systemd debian package post-install script is not very
# robust, we have to create a symlink and bind mount resolv.conf
# there.
bind_resolv() {
sysroot="${1}"

resolv_real_path="${sysroot}/run/fake-resolv.conf"
create_symlink=yes
if [ -L "${sysroot}/etc/resolv.conf" ]; then
resolv="$(readlink "${sysroot}/etc/resolv.conf")"
if [ "${resolv}" = "../run/systemd/resolve/stub-resolv.conf" ]; then
resolv_real_path="${sysroot}/run/systemd/resolve/stub-resolv.conf"
create_symlink=no
fi
fi
mkdir -p "$(dirname "${resolv_real_path}")"
touch "${resolv_real_path}"
mount --bind -o ro /etc/resolv.conf "${resolv_real_path}"
if [ "${create_symlink}" = yes ]; then
ln -srf "${resolv_real_path}" "${sysroot}/etc/resolv.conf"
fi
}

if [ $# -lt 3 ]; then
echo "Expected at least 3 arguments" 1>&2
exit 1
fi

command="${1}"
sysroot="${2}"
shift 2

case "${command}" in
spawn)
# This is the first phase. This will re-spawn the script with
# `init` subcommand. There are limitations of what can be
# done within a LXD container. So in the first phase we mount
# a tmpfs filesytem which cannot always be done in a mount
# namespace. Because it is outside of the mount namespace,
# it has to be removed from manually.
# Then we spawn the second phase into a namespace,
# but without changing the root.

tmpdir="$(mktemp -d --tmpdir mount-ns.XXXXXXXXXX)"
cleanup() {
umount "${tmpdir}" || true
rm -rf "${tmpdir}"
}
mount -t tmpfs tmpfs "${tmpdir}"
mkdir -m 0755 -p "${tmpdir}/dev"
mkdir -m 1777 -p "${tmpdir}/tmp"
mkdir -m 0755 -p "${tmpdir}/run"
options=(
--bind "${tmpdir}/dev" /dev
--bind "${tmpdir}/tmp" /tmp
--bind "${tmpdir}/run" /run
)
trap cleanup EXIT
unshare --pid --fork --mount -- "${0}" init "${sysroot}" "${options[@]}" "${@}"
;;
init)
# This is the second phase. Here we are in a mount namespace,
# spawned from the `spawn` subcommand. But we still have the
# same root directory. So we can bind mount all we need in the
# sysroot. Then we can change the root to that sysroot.

mount -t proc proc "${sysroot}/proc"
while [ $# -gt 1 ]; do
case "${1}" in
--)
shift
break
;;
--bind|--ro-bind)
if [ -d "$2" ]; then
if ! [ -d "${sysroot}/$3" ]; then
mkdir -p "${sysroot}/$3"
fi
else
if ! [ -e "${sysroot}/$3" ]; then
dir="$(dirname "${sysroot}/$3")"
if ! [ -d "${dir}" ]; then
mkdir -p "${dir}"
fi
touch "${sysroot}/$3"
fi
fi
extra_args=()
case "$1" in
--ro-bind)
extra_args=("-o" "ro")
;;
esac
mount --bind "${extra_args[@]}" "$2" "${sysroot}/$3"
shift 3
;;
*)
break
;;
esac
done
bind_dev "${sysroot}"
bind_resolv "${sysroot}"
exec unshare --mount --root="${sysroot}" -- "${@}"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe it would be good to add comments on why we need to call unshare twice (from spawn, then from init) and why we use exec the second time.

;;
*)
echo "Unknown command" 1>&2
exit 1
;;
esac
Loading