Skip to content

Commit

Permalink
test: run dfuzzer against polkit's D-Bus interface
Browse files Browse the repository at this point in the history
Let's run dfuzzer to fuzz polkit's own D-Bus interface to catch issues
like [0][1][2] early.

The test implementation is a _bit_ convoluted, caused by the fact that
we have to rebuild polkit with Address and Undefined sanitizers to make
dfuzzer considerably more effective. This also involves running the just
built polkit in a lightweight container so we don't mess with the
already installed polkit (which could affect tests running after this
one that don't expect sanitized polkit). All this extra code can be
potentially dropped once we're able to do multiple Packit builds in one
PR (currently in an RFC stage).

Resolves: #515

[0] #506
[1] b709b69
[2] 5cce296
  • Loading branch information
mrc0mmand authored and jrybar-rh committed Nov 14, 2024
1 parent a8ac3f6 commit e01b6b9
Show file tree
Hide file tree
Showing 3 changed files with 421 additions and 0 deletions.
30 changes: 30 additions & 0 deletions test/integration/dfuzzer/main.fmf
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
summary: Run dfuzzer against polkit's D-Bus interface
test: ./test.sh
require:
- clang
- compiler-rt
- coreutils
- dbus
- dbus-devel
- dfuzzer
- expat-devel
- gcc-c++
- gdb
- gettext-devel
- git
- glib2-devel
- glibc-devel
- gobject-introspection-devel
- gtk-doc
- libasan
- libubsan
- llvm
- meson
- pam-devel
- pkgconfig(duktape)
- python3-dbusmock
- systemd
- systemd-container
- systemd-devel
- util-linux
duration: 30m
176 changes: 176 additions & 0 deletions test/integration/dfuzzer/test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
#!/bin/bash
# vi: set sw=4 ts=4 et tw=110:
# shellcheck disable=SC2016
#
# FIXME: this test can be _drastically_ simplified once we can run a dedicated sanitizer job, see
# https://github.com/packit/packit-service/issues/2610

set -eux
set -o pipefail

# shellcheck source=test/integration/util.sh
. "$(dirname "$0")/../util.sh"

export ASAN_OPTIONS=strict_string_checks=1:detect_stack_use_after_return=1:check_initialization_order=1:strict_init_order=1:detect_invalid_pointer_pairs=2:handle_ioctl=1:print_cmdline=1:disable_coredump=0:use_madv_dontdump=1
export UBSAN_OPTIONS=print_stacktrace=1:print_summary=1:halt_on_error=1
# FIXME: There' a bug in meson where it overrides UBSAN_OPTIONS when MSAN_OPTIONS is not set, see
# https://github.com/mesonbuild/meson/pull/13001. Drop this once v1.4.1 is widespread enough
export MSAN_OPTIONS=foo
export CC="${CC:-clang}"

# shellcheck disable=SC2317
at_exit() {
set +ex

# Let's do some cleanup and export logs if necessary

# Collect potential coredumps
coredumpctl_collect
container_destroy

if [[ -n "${TMT_TEST_DATA:-}" && -n "${BUILD_DIR:-}" ]]; then
cp -r "$BUILD_DIR/meson-logs" "$TMT_TEST_DATA/"
fi
}

trap at_exit EXIT

export BUILD_DIR="$PWD/build-san"

# Make sure the coredump collecting machinery is working
coredumpctl_init

: "=== Prepare polkit's source tree ==="
# The integration test suite runs without access to the source tree it was built from. If we need the source
# tree (most likely to rebuild polkit) we need to do a little dance to determine the correct references.
if [[ -n "${PACKIT_TARGET_URL:-}" ]]; then
# If we're running in Packit's context, use the set of provided environment variables to checkout the
# correct branch (and possibly rebase it on top of the latest source base branch so we always test the
# latest revision possible).
git clone "$PACKIT_TARGET_URL" polkit
cd polkit
git checkout "$PACKIT_TARGET_BRANCH"
# If we're invoked from a pull request context, rebase on top of the latest source base branch.
if [[ -n "${PACKIT_SOURCE_URL:-}" ]]; then
git remote add pr "${PACKIT_SOURCE_URL:?}"
git fetch pr "${PACKIT_SOURCE_BRANCH:?}"
git merge "pr/$PACKIT_SOURCE_BRANCH"
fi
git log --oneline -5
elif [[ -n "${POLKIT_TREE:-}" ]]; then
# Useful for quick local debugging when running this script directly, e.g. running
#
# # TMT_TEST_DATA=$PWD/logs POLKIT_TREE=$PWD test/integration/fuzz/sanitizers/test.sh
#
# from the polkit repo root.
cd "${POLKIT_TREE:?}"
else
# If we're running outside of Packit's context, pull the latest polkit upstream.
git clone https://github.com/polkit-org/polkit polkit
cd polkit
git log --oneline -5
fi

: "=== Build polkit with sanitizers ==="
MESON_OPTIONS=()

if [[ "$CC" == clang ]]; then
# See https://github.com/mesonbuild/meson/issues/764 for details
MESON_OPTIONS+=(-Db_lundef=false)
fi

rm -rf "$BUILD_DIR"
# FIXME:
# - drop -Wno-deprecated-declarations once it's not needed
# - generating introspection is currently FUBAR when building with clang + ASan,
# but we shouldn't need it here anyway (see https://github.com/mesonbuild/meson/issues/13211)
meson setup "$BUILD_DIR" \
--werror \
-Dintrospection=false \
-Dsession_tracking=logind \
-Dgettext=true \
-Dtests=true \
-Db_sanitize=address,undefined \
-Dc_args="-Wno-deprecated-declarations" \
-Dcpp_args="-Wno-deprecated-declarations" \
"${MESON_OPTIONS[@]}"
ninja -C "$BUILD_DIR"
meson test -C "$BUILD_DIR" --print-errorlogs

: "=== Run dfuzzer against polkit running under sanitizers ==="
container_prepare

# Install our custom-built polkit into the container's overlay
DESTDIR="$CONTAINER_OVERLAY" ninja -C "$BUILD_DIR" install
# Tweak the polkit.service to make it compatible with sanitizers
mkdir -p "$CONTAINER_OVERLAY/etc/systemd/system/polkit.service.d/"
cat >"$CONTAINER_OVERLAY/etc/systemd/system/polkit.service.d/sanitizer-env.conf" <<EOF
[Service]
# Pass ASAN_OPTIONS and UBSAN_OPTIONS to the polkit service in the container
Environment=ASAN_OPTIONS=$ASAN_OPTIONS
Environment=UBSAN_OPTIONS=$UBSAN_OPTIONS
# Get rid of any existing seccomp filters to allow sanitizers do their work
SystemCallFilter=
# Get rid of --no-debug (since sanitizers log their findings to stderr), and let polkit be as verbose as
# possible
ExecStart=
ExecStart=/usr/lib/polkit-1/polkitd --log-level=debug
EOF

check_journal_for_sanitizer_errors() {
if journalctl -q -D "/var/log/journal/${CONTAINER_MACHINE_ID:?}" --grep "SUMMARY:.+Sanitizer"; then
# Dump all messages recorded for the polkit.service, as that's usually where the stack trace ends
# up. If that's not the case, the full container journal is exported on test exit anyway, so we'll
# still have everything we need to debug the fail further.
journalctl -q -D "/var/log/journal/${CONTAINER_MACHINE_ID:?}" -o short-monotonic --no-hostname -u polkit.service --no-pager
exit 1
fi
}

run_and_check() {
local run=(container_run)
local ec=0

if [[ "$1" == "--unpriv" ]]; then
run=(container_run_user testuser)
shift
fi

# Run the passed command in the container
"${run[@]}" "$@" || ec=$?
# Check for potential stack traces from sanitizers
check_journal_for_sanitizer_errors
# Check if polkit is still running
"${run[@]}" systemctl status --full --no-pager polkit.service

return $ec
}

# Start the container and wait until it's fully booted up
container_start
container_run pkexec --version
container_run systemctl start polkit.service
container_run systemctl --no-pager status polkit.service
# Make _extra_ sure we're running the sanitized polkit with the correct environment
#
# Note: the check is not particularly nice, as libasan can be linked either statically or dynamically, so we
# can't just check ldd's output. Another option is using nm/objdump to check for ASan-specific functions, but
# that's also error prone. Instead, let's call each binary with ASan's "help" option, which produces output
# only if the target binary is built with (hopefully working) ASan.
container_run bash -xec 'ASAN_OPTIONS=help=1 /proc/$(systemctl show -P MainPID polkit.service)/exe -h 2>&1 >/dev/null | grep -q AddressSanitizer'
container_run systemctl show -p Environment polkit.service | grep -q ASAN_OPTIONS

# Now we should have a container ready for our shenanigans

# Fuzz polkit's own interface
run_and_check dfuzzer -v -n org.freedesktop.PolicyKit1
run_and_check --unpriv dfuzzer -v -n org.freedesktop.PolicyKit1

# Shut down the container and check for any sanitizer errors, since some of the errors can be detected only
# after we start shutting things down.
container_stop
check_journal_for_sanitizer_errors
# Also, check if polit didn't fail during the lifetime of the container
(! journalctl -q -D "/var/log/journal/$CONTAINER_MACHINE_ID" _PID=1 --grep "polkit.service.*Failed with result")

exit 0
Loading

0 comments on commit e01b6b9

Please sign in to comment.